Iceberg 파티셔닝: 파티션 스펙과 파티션 트랜스폼
Iceberg 파티셔닝: 파티션 스펙과 파티션 트랜스폼
Iceberg에서 파티셔닝은 테이블 설정이에요. 데이터 파일은 파티션 값의 튜플과 함께 매니페스트에 저장되고, 스캔에서 필터 술어에 매칭될 수 없는 파일을 걸러내는 데 이 파티션 값이 쓰여요. 핵심은 파티셔닝이 바뀌어도 쿼리는 변하지 않는다는 점 — 파티션 필터가 항상 데이터 값의 컬럼 술어에서 자동으로 유도되거든요.
본문
파티션 스펙 (Partition Spec)
테이블은 레코드에서 파티션 값의 튜플을 만들어내는 방법을 정의하는 **파티션 스펙(partition spec)**으로 설정돼요. 파티션 스펙은 다음과 같은 요소로 이루어진 필드 목록이에요:
- 테이블 스키마에서 온 소스 컬럼 id 또는 소스 컬럼 id 목록
- 파티션 필드를 식별하고 파티션 스펙 안에서 유일한 파티션 필드 id (v2 테이블 메타데이터에서는 모든 파티션 스펙에 걸쳐 유일해요)
- 소스 컬럼에 적용되어 파티션 값을 만들어내는 트랜스폼(transform)
- 파티션 이름
id로 선택되는 소스 컬럼은 기본 타입(primitive type)이어야 하고, map이나 list에 포함될 수 없지만 struct 안에는 중첩될 수 있어요.
파티션 스펙은 테이블 데이터에서 파티션 값으로의 트랜스폼을 담아요. 이건 데이터 값을 변환하는 것뿐 아니라 술어를 파티션 술어로 변환하는 데도 쓰여요. 컬럼 술어에서 파티션 술어를 유도하면 논리적 쿼리와 물리적 스토리지가 분리돼요. 파티셔닝이 바뀌어도 올바른 파티션 필터가 컬럼 술어에서 항상 유도되니, 사용자가 논리적 술어와 파티션 술어를 둘 다 제공할 필요가 없어져요.
두 파티션 스펙이 필드 수가 같고, 대응하는 각 필드의 소스 컬럼 id·트랜스폼 정의·파티션 이름이 같다면 서로 동등한 것으로 간주돼요. 라이터는 테이블에 이미 호환되는 파티션 스펙이 있으면 새 스펙을 만들면 안 되고, 기존 스펙에 동등한 필드가 있으면 파티션 필드 id를 재사용해야 해요.
파티션 트랜스폼 (Partition Transforms)
| 트랜스폼 이름 | 설명 | 소스 타입 | 결과 타입 |
|---|---|---|---|
identity |
소스 값 그대로 | geometry·geography를 제외한 모든 기본 타입 |
소스 타입 |
bucket[N] |
값의 해시를 N으로 mod |
int, long, decimal, date, time, timestamp, timestamptz, timestamp_ns, timestamptz_ns, string, uuid, fixed, binary |
int |
truncate[W] |
너비 W로 잘라낸 값 |
int, long, decimal, string, binary |
소스 타입 |
year |
날짜·타임스탬프의 연도 추출 (1970년부터의 연도) | date, timestamp, timestamptz, timestamp_ns, timestamptz_ns |
int |
month |
날짜·타임스탬프의 월 추출 (1970-01-01부터의 월) | date, timestamp, timestamptz, timestamp_ns, timestamptz_ns |
int |
day |
날짜·타임스탬프의 일 추출 (1970-01-01부터의 일) | date, timestamp, timestamptz, timestamp_ns, timestamptz_ns |
date [1] |
hour |
타임스탬프의 시 추출 (1970-01-01 00:00:00부터의 시) | timestamp, timestamptz, timestamp_ns, timestamptz_ns |
int |
void |
항상 null 생성 |
Any | 소스 타입 또는 int |
모든 트랜스폼은 null 입력 값에 대해 null을 반환해야 해요. void 트랜스폼은 v1 테이블에서 기존 파티션 필드의 트랜스폼을 대체해 그 필드를 사실상 제거하는 데 쓸 수 있어요.
참고:
- 리더는
day트랜스폼에 대해int값도 받아들여야 하며, 각 정수를1970-01-01부터의 일 수로 표현된date로 해석해요.
버킷 트랜스폼 (Bucket Transform)
버킷 파티션 트랜스폼은 소스 값의 32비트 해시를 사용해요. 구현은 32비트 Murmur3 해시, x86 변형, 시드 0이에요. 트랜스폼은 버킷 수 N [1]으로 파라미터화되고, 해시 mod N이 양수가 되도록 먼저 해시 값의 부호 비트를 버려요. 의사코드는 다음과 같아요:
def bucket_N(x) = (murmur3_x86_32_hash(x) & Integer.MAX_VALUE) % N
참고:
- 테이블이 커지면서 버킷 수를 바꾸는 것은 파티션 스펙을 진화시켜 가능해요.
트렁케이트 트랜스폼 (Truncate Transform)
| 타입 | 설정 | 트렁케이트 명세 | 예시 |
|---|---|---|---|
int |
W, width |
v - (v % W) 나머지는 양수여야 함 [1] |
W=10: 1 → 0, -1 → -10 |
long |
W, width |
v - (v % W) 나머지는 양수여야 함 [1] |
W=10: 1 → 0, -1 → -10 |
decimal |
W, width (scale 제외) |
scaled_W = decimal(W, scale(v)) v - (v % scaled_W) [1, 2] |
W=50, s=2: 10.65 → 10.50 |
string |
L, length |
길이 L의 부분문자열: v.substring(0, L) [3] |
L=3: iceberg → ice |
binary |
L, length |
길이 L의 부분배열: v.subarray(0, L) [4] |
L=3: \x01\x02\x03\x04\x05 → \x01\x02\x03 |
참고:
- 나머지
v % W는 양수여야 해요.%가 음수를 만들 수 있는 언어에서는 올바른 트렁케이트 함수는v - (((v % W) + W) % W)예요. - decimal 값을 트렁케이트하는 너비
W는 추가적(그리고 잠재적으로 충돌하는) 파라미터를 피하기 위해 decimal 컬럼의 scale을 적용해요. - 문자열은
L보다 많은 코드 포인트가 없는 유효한 UTF-8 문자열로 잘라져요. - 문자열과 달리 binary 값은 가정된 인코딩이 없어
L바이트로 잘려요.
파티션 진화 (Partition Evolution)
테이블 파티셔닝은 파티션 스펙 필드를 추가·제거·이름 변경·재정렬해 진화시킬 수 있어요. 파티션 스펙을 바꾸면 고유한 spec ID로 식별되는 새 스펙이 만들어지고, 테이블의 파티션 스펙 목록에 추가되며 기본 스펙으로 설정될 수 있어요.
스펙을 진화할 때 파티션 필드 id는 바뀌지 않아야 해요. 파티션 필드 id가 매니페스트 파일에서 파티션 튜플 필드 id로 쓰이기 때문이에요.
v2에서는 파티션 필드 id가 각 파티션 필드에 명시적으로 추적돼요. 새 id는 테이블 메타데이터의 마지막으로 부여된 파티션 id를 기준으로 부여돼요. v1에서는 파티션 필드 id를 추적하지 않았고, 참조 구현에서 1000부터 순차적으로 부여했어요. 이 방식은 여러 스펙의 매니페스트 파일을 기반으로 메타데이터 테이블을 읽을 때 문제를 일으켰죠. 옛 버전과의 호환을 위해 v1 테이블의 파티션 진화에는 다음 규칙이 권장돼요:
- 파티션 필드를 재정렬하지 않기
- 파티션 필드를 제거하지 않고, 대신 필드의 트랜스폼을
void트랜스폼으로 교체하기 - 파티션 필드는 이전 파티션 스펙의 끝에만 추가하기