Puffin-Backed Derived Data
열린 테이블 포맷은 파생 데이터 생명주기를 공짜로 제공한다
핵심 개념
Iceberg의 Puffin은 원래 테이블 통계(distinct-value 추정용 Theta sketch, 파티션 프루닝용 bloom filter)와 deletion vector를 담으려고 도입된 플랫 바이너리 컨테이너다. 그런데 스펙이 의도적으로 확장 가능하게 설계되어 있다.
각 blob은 다음을 갖는다.
- 불투명한 type 문자열
- Iceberg field-ID 집합
- 임의의 property map
- 바이트 페이로드
그리고 Puffin 파일 하나는 스냅샷의 statistics-file summary property 하나로 바인딩된다.
이 조합이 만드는 결과가 핵심이다: 스냅샷을 읽는 엔진은 footer를 검사해 자기가 아는 타입의 blob만 선택적으로 읽고, 모르는 타입은 에러 없이 무시한다. blob마다 독립 압축되고 byte-range 요청으로 개별 주소 지정이 가능해서, 전체 다운로드가 비현실적인 대용량에도 쓸 수 있다.
왜 이 슬롯이 중요한가
compute-disaggregated 엔진(Snowflake가 대중화하고 lakehouse가 일반화한 설계)에서 파생 상태를 두는 것은 까다롭다. 유지해야 할 네 가지 속성이 있다.
| 속성 | 의미 |
|---|---|
| stateless executor | 언제든 종료·교체 가능해야 한다. 로컬 디스크 전용 상태는 탄력적 스케일링과 양립 불가 |
| single source of truth | 파생 상태(인덱스, 통계)가 계산 기준이 된 데이터 버전과 일관되어야 한다 |
| 멀티엔진 상호운용 | 여러 엔진이 같은 데이터를 읽는데, 파생 산출물이 이를 깨뜨리면 안 된다 |
| 새 생명주기 없음 | 생성·리프레시·삭제가 테이블 포맷의 트랜잭션 커밋·시간여행·GC를 재사용해야 한다 |
파생 구조는 (a) 기저 데이터에서 재계산 가능하거나 (b) 테이블 포맷 자체에 커밋되어야 한다. Puffin은 (b)의 표준화된 자리다.
대표 사례 — ANN 벡터 인덱스
십억 규모 벡터 검색을 lakehouse 엔진에 붙이는 관습적 방법들 — 별도 벡터 DB, executor의 사설 인덱스 파일시스템, 쿼리마다 재구축 — 은 위 네 속성 중 최소 하나를 위반한다.
Puffin에 DiskANN/Vamana 그래프를 넣으면 다음이 구현 비용 없이 따라온다.
- 원자성 — 인덱스 커밋이 Iceberg 스냅샷 커밋
- 시간여행 — 인덱스 버전 = 스냅샷 버전.
AS OF쿼리가 자동으로 맞는 인덱스 버전을 읽는다 - 동시성 중재 — 동시 인덱스 빌드를 동시 데이터 쓰기와 같은 optimistic concurrency가 처리
- orphan GC — 옛 Puffin 파일은 참조가 사라지면 테이블 포맷의 기존 orphan-file 정리가 회수
- incremental refresh — manifest diff가 두 스냅샷 사이의 추가·제거·불변 데이터 파일을 정확히 반환하므로 영향받은 샤드만 리프레시
구조적 재사용
인덱스 빌드는 Parquet 대신 Puffin 파일을 만드는 INSERT-SELECT다. 기존 fragment dispatch·S3 I/O·cache-aware 스케줄링·REST 커밋을 그대로 쓴다. 리프레시된 Puffin의 커밋은 metadata-only — 데이터 파일은 그대로이고 summary가 새 경로를 가리킬 뿐이다.
실제 구현(Teradata의 FlockDB, DuckDB 기반 MPP)에서 신규 코드 약 8,000줄 중 대부분이 그래프 빌더였고 PuffinWriter는 약 200줄, 테이블 포맷 통합은 수백 줄이었다. 통합 비용이 알고리즘 비용의 잡음 수준이라는 것이 이 패턴의 논증이다.
얻는 것 (10억 벡터, 768차원, executor 4대)
| 인덱스 없음 | Centroid (coord.) | DiskANN (분산) | |
|---|---|---|---|
| 스캔 파일 | 10,000 | ~400 | ~50 |
| S3 읽기 | 300 GB | 12 GB | 1.5 GB |
| warm 레이턴시 | 15–25s | 2–4s | 50–200ms |
| Recall@100 | 1.00 | 0.85–0.95 | 0.95–0.99 |
치러야 할 대가
- 업데이트 granularity가 스냅샷 — 벡터가 Iceberg 커밋과 독립적으로 검색 가능해질 수 없다. 스트리밍 신선도 요구에서는 커밋 주기가 인덱스 신선도의 상한이고, 분당 수천 커밋은 REST 카탈로그를 포화시킨다
- stage barrier가 레이턴시 하한을 정한다 — 분산 probe의 barrier 2개가 각 5~10ms. sub-10ms P50 애플리케이션에는 과하고 단일 단계 로컬 인덱스가 맞다
- 독립 샤드 설계의 recall 손실 — oversampling과 exact rerank로 대부분 회복되지만 형식적 하한 보장은 없다
- 인덱스가 데이터만큼 크거나 더 클 수 있다 — 그래프 기반 ANN의 표준적 트레이드오프
패턴은 ANN 전용이 아니다
테이블 데이터에서 파생되고, 스냅샷과 함께 버전되며, 일부 엔진만 읽으면 되는 것은 무엇이든 Puffin 후보다.
- 학습된 인덱스·cardinality 모델 — 컬럼별로 학습된 분포 모델을 blob으로 저장하고 그것을 쓸 줄 아는 옵티마이저가 소비
- materialized derived column — 계산된 집계, 파생 임베딩, 비정규화 조인 컬럼을 blob에 두고 가상 컬럼으로 질의. 스냅샷 바인딩이 기저 데이터와의 일관성을 보장한다
- 엔진 간 가속 공유 — 한 엔진이 만든 인덱스를 다른 엔진이 소비. blob 포맷 합의가 전제 조건이라 커뮤니티 표준 논의가 필요하다 (
ann-routing-v1,ann-vamana-graph-v1,ann-centroid-index-v1이 제안된 후보)
패턴은 Delta Lake의 deletion vector·statistics file과 Hudi의 metadata table에도 깔끔히 전이된다. 같은 아키텍처 슬롯을 차지하기 때문이다.
넓게 보면 Puffin이 테이블 통계용을 넘어 스냅샷 시맨틱에 묶인 범용 append-only key-value 스토어로 쓰이기 시작하는 중이다.
연관 개념
- Catalog-Managed Tables
- Multimodal Search
- Object Storage Evolution
- Query Optimization
- Data Sketches
- Incremental Materialization
- DuckDB
- RAG
Source: Puffin-Backed Vector Indexes