Iceberg 매니페스트와 매니페스트 리스트
Iceberg 매니페스트와 매니페스트 리스트
Iceberg에서 매니페스트(manifest)는 스냅샷을 구성하는 데이터·delete 파일 목록을 담는 불변(immutable) Avro 파일이에요. 각 파일의 파티션 데이터 튜플, 메트릭, 추적 정보도 함께 보관하죠. 매니페스트가 스냅샷에 걸쳐 재사용되면서 천천히 변하는 메타데이터를 다시 쓰지 않도록 해 주고, 이를 실제로 묶는 게 매니페스트 리스트(manifest list)예요.
본문
매니페스트 (Manifests)
하나 이상의 매니페스트 파일이 특정 시점의 테이블 파일 전체를 추적하는 스냅샷을 저장해요. 매니페스트는 각 테이블 스냅샷에 대해 매니페스트 리스트가 추적해요. 매니페스트 자체도 유효한 Iceberg 데이터 파일이라, 유효한 Iceberg 포맷·스키마·컬럼 프로젝션을 따라야 해요.
매니페스트는 데이터 파일이거나 delete 파일 하나만 저장할 수 있어요. 둘 다 담지는 못하죠 — 잡 계획 중 delete 파일이 담긴 매니페스트를 먼저 스캔하기 때문이에요. 데이터 매니페스트인지 delete 매니페스트인지는 매니페스트 메타데이터에 저장돼요.
매니페스트는 단일 파티션 스펙에 대한 파일을 저장해요. 테이블의 파티션 스펙이 바뀌면, 옛 파일은 옛 매니페스트에 남고 새 파일은 새 매니페스트에 쓰여져요. 매니페스트 파일의 스키마가 그 파티션 스펙에 기반하기 때문에 이렇게 해야 해요. 각 매니페스트의 파티션 스펙은 테이블 데이터 행에 대한 술어를, 잡 계획에서 매니페스트에서 파일을 선택할 때 쓰는 파티션 값에 대한 술어로 변환하는 데도 사용돼요.
매니페스트 파일은 Avro 파일의 키-값 메타데이터에 파티션 스펙과 다른 메타데이터를 속성으로 저장해야 해요:
| v1 | v2 and v3 | 키 | 값 |
|---|---|---|---|
| required | required | schema |
매니페스트가 쓰일 당시 테이블 스키마의 JSON 표현 |
| optional | required | schema-id |
매니페스트를 쓰는 데 사용된 스키마의 ID (문자열) |
| required | required | partition-spec |
매니페스트를 쓰는 데 사용된 파티션 스펙의 파티션 필드 배열만의 JSON 표현 |
| optional | required | partition-spec-id |
매니페스트를 쓰는 데 사용된 파티션 스펙의 ID (문자열) |
| optional | required | format-version |
매니페스트의 테이블 포맷 버전 번호 (문자열) |
| required | content |
매니페스트가 추적하는 콘텐츠 파일의 타입: "data" 또는 "deletes" |
콘텐츠 파일 유일성 (Content File Uniqueness)
스냅샷 안에서 각 콘텐츠 파일은 모든 매니페스트에 걸쳐 최대 하나의 live 매니페스트 항목에만 참조되어야 해요. 그렇지 않으면 스냅샷의 동작이 정의되지 않아요. 라이터는 같은 스냅샷에서 같은 콘텐츠 파일에 대해 여러 매니페스트 항목(예: 같은 파일에 대해 ADDED와 DELETED 둘 다)을 만들면 안 돼요. 커밋 시점에 유일성을 검증할 의무는 없어요.
매니페스트 항목 필드
매니페스트 파일의 스키마는 manifest_entry struct로 정의되며 다음 필드로 구성돼요:
| v1 | v2 and v3 | 필드 id, 이름 | 타입 | 설명 |
|---|---|---|---|---|
| required | required | 0 status |
의미 있는 값 0: EXISTING 1: ADDED 2: DELETED인 int |
추가·삭제를 추적. 삭제는 정보 제공용일 뿐 스캔에 쓰이지 않아요. |
| required | optional | 1 snapshot_id |
long |
파일이 추가된 스냅샷 id (status가 2면 삭제된 id). null이면 상속. |
| optional | 3 sequence_number |
long |
파일의 데이터 시퀀스 번호. null이고 status가 1(added)이면 상속. | |
| optional | 4 file_sequence_number |
long |
파일이 추가된 시기를 나타내는 파일 시퀀스 번호. null이고 status가 1(added)이면 상속. | |
| required | required | 2 data_file |
data_file struct |
파일 경로, 파티션 튜플, 메트릭 등. |
파일이 데이터셋에 추가되면 그 매니페스트 항목은 파일이 추가된 스냅샷 id를 저장하고 status를 1(added)로 설정해야 해요. 파일이 교체·삭제되면 매니페스트 항목은 파일이 삭제된 스냅샷 id와 status 2(deleted)를 저장해요. 파일은 삭제된 스냅샷과 그보다 오래된 스냅샷이 가비지 컬렉션된 뒤 파일 시스템에서 삭제될 수 있어요 [1].
Iceberg v2는 항목에 데이터·파일 시퀀스 번호를 추가하고 스냅샷 id를 옵션으로 만들었어요. 이 값들은 null일 때 매니페스트 메타데이터(매니페스트 리스트에 저장됨)에서 상속돼요. sequence_number는 데이터 시퀀스 번호를 뜻하며 파일이 데이터셋에 추가된 뒤 절대 바뀌면 안 돼요. 데이터 시퀀스 번호는 파일 콘텐츠의 상대적 나이를 나타내고, 데이터 파일에 어떤 delete 파일을 적용할지 계획하는 데 쓰여요. file_sequence_number는 파일을 추가한 스냅샷의 시퀀스 번호를 뜻하며 커밋 시 부여된 뒤 역시 변하지 않아야 해요. 데이터·파일 시퀀스 번호는 항목 status가 1(added)일 때만 상속되고, status가 0(existing)이나 2(deleted)면 항목이 두 시퀀스 번호를 모두 명시해야 해요.
참고:
- 파일을 마지막에 "live" 데이터로 담은 스냅샷이 가비지 컬렉션될 때 데이터 파일을 삭제하는 것도 가능해요. 다만 감지가 어렵고 여러 스냅샷의 diff를 찾아야 해요. 스냅샷에서 삭제된 파일을 추적하고 그 스냅샷이 만료될 때 삭제하는 편이 더 쉬워요. 삭제된 파일을 테이블에 다시 추가하는 것은 권장하지 않아요 — 증분 삭제가 테이블 스냅샷을 깨뜨릴 수 있는 엣지 케이스가 생길 수 있거든요.
- 매니페스트 리스트 파일은 v2에서 필수예요. 상속할
sequence_number와snapshot_id가 항상 쓰이도록요.
매니페스트 리스트 (Manifest Lists)
스냅샷은 테이블 메타데이터에 임베드되지만, 스냅샷에 대한 매니페스트 목록은 별도의 매니페스트 리스트 파일에 저장돼요. 새 스냅샷을 만들려면 매니페스트 목록이 항상 바뀌므로, 매니페스트 리스트는 스냅샷 커밋을 시도할 때마다 새로 쓰여져요. 매니페스트 리스트를 쓸 때, 그 리스트가 추적하는 새 매니페스트 파일 전부에는 스냅샷의 (낙관적) 시퀀스 번호가 기록돼요.
매니페스트 리스트는 테이블 스캔을 계획할 때 스냅샷의 매니페스트를 전부 스캔하지 않도록 해 주는 요약 메타데이터를 포함해요. 추가·기존·삭제된 파일 수와, 매니페스트를 쓰는 데 사용된 파티션 스펙의 각 필드에 대한 값 요약이 여기 포함되죠. 매니페스트 리스트도 유효한 Iceberg 데이터 파일이에요.
매니페스트 리스트 파일은 manifest_file struct를 저장하며 다음 필드를 가져요:
| v1 | v2 and v3 | 필드 id, 이름 | 타입 | 설명 |
|---|---|---|---|---|
| required | required | 500 manifest_path |
string |
매니페스트 파일 위치 |
| required | required | 501 manifest_length |
long |
매니페스트 파일의 바이트 길이 |
| required | required | 502 partition_spec_id |
int |
매니페스트를 쓰는 데 사용된 파티션 스펙의 id. 테이블 메타데이터 partition-specs에 있어야 해요. |
| required | required | 517 content |
의미 있는 값 0: data, 1: deletes인 int. 매니페스트가 추적하는 파일 타입. |
|
| required | required | 515 sequence_number |
long |
|
| required | required | 516 min_sequence_number |
long |
|
| required | required | required | 503 added_snapshot_id |
long |
| optional | required | required | 504 added_files_count |
int |
| optional | required | required | 505 existing_files_count |
int |
| optional | required | required | 506 deleted_files_count |
int |
| optional | required | required | 512 added_rows_count |
long |
| optional | required | required | 513 existing_rows_count |
long |
| optional | required | required | 514 deleted_rows_count |
long |
| optional | optional | optional | 507 partitions |
list<508: field_summary> |
| optional | optional | optional | 519 key_metadata |
binary |
| optional | 520 first_row_id |
long |
field_summary는 다음 필드를 가진 struct예요:
| v1 | v2 and v3 | 필드 id, 이름 | 타입 | 설명 |
|---|---|---|---|---|
| required | required | 509 contains_null |
boolean |
매니페스트가 해당 필드에 대해 null 값인 파티션을 최소 하나 포함하는지 |
| optional | optional | 518 contains_nan |
boolean |
매니페스트가 해당 필드에 대해 NaN 값인 파티션을 최소 하나 포함하는지 |
| optional | optional | 510 lower_bound |
bytes [1] |
파티션 필드의 non-null·non-NaN 값에 대한 하한. 전부 null·NaN이면 null. |
| optional | optional | 511 upper_bound |
bytes [1] |
파티션 필드의 non-null·non-NaN 값에 대한 상한. 전부 null·NaN이면 null. |
참고:
- 하한·상한은 Appendix D의 단일 객체 직렬화를 사용해 바이트로 직렬화돼요. 값을 인코딩하는 데 쓰는 타입은 파티션 필드 데이터의 타입이에요.
- 파티션 필드 값에 -0.0이 있으면
lower_bound는 +0.0이면 안 되고, +0.0이 있으면upper_bound는 -0.0이면 안 돼요.