Lakehouse Table Format — Understanding Apache Iceberg Through Its Metadata
Create a table and walk from metadata.json down to its data files
한국어 원문으로 표시합니다.
목표
Spark 로 Iceberg 표를 만들고 하루치 주문을 두 번 커밋한 뒤, 카탈로그 → metadata.json → 매니페스트 리스트 → 매니페스트 → 데이터 파일로 이어지는 나무를 도구 없이 손으로 따라 내려간다. 매 층에서 찾은 값을 파일로 적고, 채점기가 그 값을 실제 메타데이터와 견준다.
왜 중요한가
Iceberg 표는 '디렉터리' 가 아니라 '파일 목록' 이다. 읽는 쪽은 디렉터리를 훑지 않고 metadata.json 이 가리키는 목록만 믿는다. 그래서 커밋은 파일을 옮기는 일이 아니라 새 목록을 써 두고 카탈로그의 포인터 한 칸을 바꾸는 일이고, 그 한 칸이 바뀌는 순간 읽는 사람 모두가 새 상태를 본다. 이 구조를 손으로 한 번 따라가 보면 뒤의 모든 기능이 같은 원리의 변주라는 것이 보인다. 타임트래블은 옛 스냅샷의 목록을 읽는 것이고, 롤백은 포인터를 옛 스냅샷으로 돌리는 것이며, 압축과 만료는 목록을 다시 쓰고 더는 아무도 가리키지 않는 파일을 지우는 것이다. 장애가 났을 때 가장 먼저 보는 것도 이 나무다. "왜 이 행이 안 보이나" 는 거의 언제나 "그 파일이 지금 스냅샷의 목록에 있나" 로 바뀐다.
단계
- /root/ice/meta/create.py(앱
ice-meta-create)로 네임스페이스lake.meta와 표lake.meta.orders를 만드세요. 열은order_id STRING, customer_id STRING, region STRING, amount INT, status STRING, order_ts TIMESTAMP, 속성은'format-version' = '2'입니다. - /root/ice/meta/load.py 가 날짜 하나를 인자로 받아
/data/ice/orders/<날짜>.csv를 표에 한 번 커밋하게 만들고,2026-03-01을 넣으세요. - 같은 스크립트로
2026-03-02를 넣어 스냅샷을 둘로 만드세요. - 카탈로그
/root/ice/catalog.db의iceberg_tables에서 이 표의metadata_location과previous_metadata_location을 읽어 /root/ice/meta/out/pointer.txt 에 두 줄로 쓰세요. - 현재 metadata.json 에서 스냅샷 목록을 뽑아 /root/ice/meta/out/snapshots.json 에 쓰세요.
- 현재 스냅샷의 매니페스트 리스트(Avro)를 풀어 매니페스트 목록을 /root/ice/meta/out/manifests.json 에 쓰세요.
- 그 매니페스트들을 풀어 지금 살아 있는 데이터 파일 목록을 /root/ice/meta/out/datafiles.json 에 쓰세요.
- /root/ice/meta/report.md 에
## 포인터## 스냅샷## 파일세 절을 쓰세요. 둘째 절에는 스냅샷 수를, 셋째 절에는 매니페스트 수와 데이터 파일 수를 넣으세요.
참고
- 스크립트는
cd /root/ice/meta && spark-submit create.py처럼 돌립니다. Spark 가 뜨는 데 15–20초가 걸립니다. ice-loc meta.orders는 카탈로그가 가리키는 현재 metadata.json 경로를 찍습니다.ice-avro <파일>은 Avro 를 JSON 한 줄씩으로 풉니다(jq로 거르세요).- 경로 앞의
file:은 Java 가,file:///는 파이썬이 붙입니다. 채점기는 둘 다 같은 경로로 봅니다. - 흔한 실수:
load.py를 같은 날짜로 두 번 돌려 스냅샷이 셋이 되는 것. 처음부터 다시 하려면spark-sql -e "DROP TABLE lake.meta.orders PURGE"뒤 1단계부터 하세요 — 적어 둔 값은 모두 새 표에서 다시 뽑아야 합니다. - 공식 문서: Table Spec — Overview · Spark Getting Started · JDBC Catalog
표 만들기 — 스냅샷 없는 첫 metadata
/root/ice/meta/create.py 를 앱 이름 ice-meta-create 로 만들어 lake.meta 네임스페이스와 lake.meta.orders 표(열 여섯 개, 'format-version' = '2')를 만들고 spark-submit 으로 돌리세요.
표를 만들면 metadata 파일(00000-….metadata.json)이 하나 생기고, 카탈로그에 그 경로가 한 줄 들어갑니다. 아직 커밋한 자료가 없으니 스냅샷은 없습니다. 채점기는 첫 metadata 파일에 스냅샷이 없는지, 형식 판과 열 이름·형이 맞는지 봅니다.
첫 커밋 — 스냅샷 하나
/root/ice/meta/load.py 를 앱 이름 ice-meta-load 로 만들어, 인자로 받은 날짜의 /data/ice/orders/<날짜>.csv 를 스키마를 주어 읽고 writeTo("lake.meta.orders").append() 로 넣게 하세요. spark-submit load.py 2026-03-01 로 돌리세요.
append 한 번이 커밋 한 번이고 커밋 한 번이 스냅샷 하나입니다. 채점기는 첫 스냅샷이 부모 없이 append 로 생겼는지, 요약(summary)의 added-records 가 그날 파일의 행 수와 같은지 봅니다.
둘째 커밋 — 부모를 가리키는 스냅샷
같은 스크립트로 spark-submit load.py 2026-03-02 를 돌려 스냅샷을 정확히 둘로 만드세요.
새 스냅샷은 앞 스냅샷을 부모(parent-snapshot-id)로 가리키고, 시퀀스 번호가 하나 늘어납니다. 첫 스냅샷의 파일은 다시 쓰이지 않고 새 파일 하나만 더해집니다. 같은 날짜를 두 번 넣었다면 표를 PURGE 로 지우고 1단계부터 다시 하세요.
카탈로그의 포인터 두 칸
/root/ice/catalog.db 의 iceberg_tables 에서 meta.orders 행의 metadata_location 과 previous_metadata_location 을 읽어 /root/ice/meta/out/pointer.txt 첫 줄과 둘째 줄에 쓰세요.
JDBC 카탈로그는 표마다 한 줄뿐입니다. 커밋은 '지금 값이 내가 읽은 값과 같을 때만 새 값으로 바꾼다' 는 조건부 UPDATE 이고, 바뀌기 전 값이 previous 칸에 남습니다. sqlite3 -separator 로 두 칸을 두 줄로 찍을 수 있습니다.
metadata.json 의 스냅샷 목록
현재 metadata.json(ice-loc meta.orders)에서 /root/ice/meta/out/snapshots.json 을 {"current_snapshot_id": 정수, "snapshots": [{"snapshot_id", "parent_snapshot_id", "sequence_number", "manifest_list"}, …]} 모양으로 만드세요.
metadata.json 의 키는 하이픈을 씁니다(current-snapshot-id, parent-snapshot-id). jq 에서는 .["snapshot-id"] 처럼 대괄호로 읽습니다. 스냅샷 ID 는 19자리 정수라 손으로 옮기면 틀리기 쉽습니다 — jq 로 그대로 옮기세요.
매니페스트 리스트 — 매니페스트들의 목록
현재 스냅샷의 manifest_list 파일을 ice-avro 로 풀어 /root/ice/meta/out/manifests.json 에 [{"manifest_path", "added_snapshot_id", "added_files_count", "existing_files_count"}, …] 배열로 쓰세요.
둘째 스냅샷의 매니페스트 리스트에는 첫 커밋이 만든 매니페스트가 그대로 다시 들어 있습니다. 새 커밋은 옛 매니페스트를 고쳐 쓰지 않고 가리키기만 합니다 — 그래서 커밋이 싸고, 옛 스냅샷이 그대로 남습니다. added_snapshot_id 로 어느 커밋이 만든 매니페스트인지 봅니다.
매니페스트 — 데이터 파일과 행 수
manifests.json 의 매니페스트들을 풀어 status 가 2(DELETED)가 아닌 항목의 데이터 파일을 /root/ice/meta/out/datafiles.json 에 [{"file_path", "record_count"}, …] 로 쓰세요.
매니페스트 한 줄(엔트리)은 status(0 EXISTING·1 ADDED·2 DELETED)와 data_file 구조체입니다. 읽는 엔진은 이 목록과 열 통계(하한·상한)만 보고 어느 파일을 열지 정합니다 — 디렉터리를 훑지 않습니다. 채점기는 목록이 지금 스냅샷의 살아 있는 파일과 정확히 같은지, 행 수 합이 표의 행 수와 같은지 봅니다.
나무를 한 장으로
/root/ice/meta/report.md 에 ## 포인터 ## 스냅샷 ## 파일 세 절을 쓰세요. 둘째 절에는 스냅샷 수를, 셋째 절에는 현재 스냅샷의 매니페스트 수와 데이터 파일 수를 숫자로 넣으세요.
누군가 '어제 넣은 자료가 안 보인다' 고 할 때 어느 층부터 확인할지 순서로 적어 보세요. 숫자는 여러분의 out/ 파일에서 옮깁니다.