레이크하우스 표 형식 — Apache Iceberg 를 metadata 로 이해한다
Iceberg 는 아무것도 스스로 지우지 않는다 — 압축, 만료, 고아 정리
한 줄 요약
Iceberg 는 커밋마다 파일과 metadata 를 더하기만 하므로, 작은 파일은 압축(rewrite_data_files)으로 합치고, 옛 스냅샷과 그것들만 쓰던 파일은 만료(expire_snapshots)로 지우고, 아무 스냅샷도 가리킨 적 없는 파일은 고아 정리(remove_orphan_files)로 치운다. 셋 다 되돌릴 수 없으니 순서와 여유가 정답이다.
왜 정리가 필요한가
스트리밍 적재가 5분마다 커밋하는 표를 떠올려 보자. 하루면 커밋 288번, 파티션마다 작은 파일 수백 개, 스냅샷 288개, metadata.json 288개가 쌓인다. Maintenance 문서가 말하듯 작은 파일은 매니페스트에 적을 것을 늘리고 쿼리마다 파일을 여는 비용을 키운다. 그리고 이 중 어느 것도 저절로 사라지지 않는다.
압축을 해도 공간은 줄지 않는다는 점이 처음에는 이상하게 느껴진다. 압축은 작은 파일을 큰 파일로 다시 쓰는 새 커밋이고, 옛 작은 파일은 옛 스냅샷이 여전히 가리키고 있어 남는다. 타임트래블을 가능하게 한 바로 그 성질이다. 공간이 돌아오는 것은 옛 스냅샷을 만료한 뒤다.
어떻게 동작하나 — 세 가지 다른 일
| 작업 | 하는 일 | 되돌릴 수 있나 |
|---|---|---|
| rewrite_data_files | 작은 파일을 읽어 큰 파일로 다시 쓰고 replace 스냅샷으로 커밋 | 커밋이라 롤백 가능 |
| expire_snapshots | 기준보다 오래된 스냅샷을 metadata 에서 빼고 그것들만 쓰던 파일을 지움 | 불가 |
| remove_orphan_files | 어떤 metadata 도 가리키지 않는 파일을 표 위치에서 찾아 지움 | 불가 |
압축. rewrite_data_files의 기본 전략은 binpack 이고 sort(정렬하며 다시 쓰기)도 있다. 목표 크기 target-file-size-bytes 는 표 속성 write.target-file-size-bytes(기본 512MB)를 따르고, 목표의 75% 보다 작은 파일은 다시 쓸 후보가 된다. min-input-files(기본 5)는 파일이 그만큼 모인 묶음을 다른 조건과 상관없이 다시 쓰게 한다. 결과는 스펙이 말하는 replace 스냅샷 — 표의 자료는 그대로이고 파일만 바뀐다.
만료. expire_snapshots는 older_than(기본 5일 전)보다 오래된 스냅샷을 지우되 retain_last(기본 1)개는 남긴다. 문서는 두 가지를 분명히 한다. 만료는 아직 살아 있는 스냅샷이 쓰는 파일은 절대 지우지 않고, 브랜치나 태그가 가리키는 스냅샷도 지우지 않는다. 그래서 되돌아가야 할 시점에 태그를 걸어 두면 그 스냅샷과 파일은 만료를 견딘다. 반대로 만료 뒤에는 그 스냅샷으로 타임트래블할 수 없다.
고아 정리. 잡이 실패하거나 커밋 경쟁에 지면 데이터 파일이 어느 스냅샷에도 들어가지 못한 채 남는다. remove_orphan_files는 표 위치의 파일을 나열해 metadata 가 가리키지 않는 것을 지운다. 여기에 함정이 있다. 지금 쓰이고 있는 파일도 커밋 전까지는 고아처럼 보인다. Maintenance 문서는 쓰기가 끝나는 데 걸리는 시간보다 짧은 간격으로 고아를 지우면 진행 중인 파일을 지워 표가 깨질 수 있다고 경고하고, 기본 간격을 3일로 둔다. 실습 이미지의 Spark 프로시저는 여기서 한 걸음 더 나가 24시간보다 짧은 간격을 아예 거절한다. 먼저 dry_run => true 로 무엇이 지워질지 보는 것이 습관이 되어야 한다.
metadata 파일도 쌓인다. 표 속성 write.metadata.delete-after-commit.enabled(기본 false)를 켜면 write.metadata.previous-versions-max(기본 100)개만 남기고 가장 오래된 metadata 파일을 커밋마다 지운다.
현장에서 만나는 모습
순서를 거꾸로 했다. 월말 마감 스냅샷으로 돌아갈 일이 있다는 것을 만료를 돌린 다음 날 알게 된다. 되돌릴 방법은 없다. 마감·배포 같은 시점에는 만료 전에 태그를 거는 것을 절차에 넣는다.
고아 정리가 멀쩡한 적재를 망쳤다. 누군가 '어제 것까지만 남기자' 며 older_than 을 한 시간 전으로 줄였고, 마침 오래 걸리던 적재 잡의 파일이 지워졌다. 그 잡이 커밋에 성공하면 표는 없는 파일을 가리킨다. 간격은 가장 오래 걸리는 쓰기보다 넉넉하게 둔다.
정리 작업이 표를 흔든다. 압축도 커밋이라 적재와 경쟁한다. 적재가 적은 시간에 돌리고, 실패하면 다음 주기에 다시 하면 된다.
실무에서 진짜 중요한 것
- 압축은 지금을 빠르게, 만료는 공간을 돌려준다. 압축만으로는 공간이 줄지 않는다.
- 만료 전에 태그. 태그·브랜치가 가리키는 스냅샷은 만료가 지우지 않는다.
- 고아 정리의 시간 여유를 줄이지 않는다. 기본 3일, 먼저 dry_run.
- 셋을 정해진 순서로 주기적으로 돌린다. 태그 → 압축 → 만료 → 고아 정리.
다음 실습에서 할 것
작은 묶음 30개를 커밋 30번으로 넣어 작은 파일과 스냅샷 더미를 만들고 정리 전 숫자를 기록한다. 열 번째 커밋에 태그를 건 뒤 rewrite_data_files 로 압축하고, expire_snapshots 로 지금 스냅샷만 남기되 태그가 지킨 스냅샷과 파일이 살아남는 것을 확인한다. 오래된 고아와 방금 만든 파일을 하나씩 두고 remove_orphan_files 를 dry_run 부터 돌려 오래된 것만 지워지는 것을 보고, 정리 뒤 디스크의 파일 수가 지금 스냅샷의 파일 수보다 많은 이유를 설명한다.