LabHub
Get started
배우기 러닝패스 코스

Lakehouse Table Format — Understanding Apache Iceberg Through Its Metadata

Clean up 30 small commits — tag, compact, expire, then orphans, in that order

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

작은 묶음 30개를 커밋 30번으로 넣어 스트리밍 적재가 만드는 작은 파일과 스냅샷 더미를 재현한 뒤, 되돌아갈 시점에 태그를 걸고, rewrite_data_files 로 압축하고, expire_snapshots 로 옛 스냅샷과 그 파일을 지우고, remove_orphan_files 로 아무도 가리키지 않는 파일을 치운다. 단계마다 파일 수와 스냅샷 수가 어떻게 바뀌는지 기록한다.

왜 중요한가

Iceberg 는 아무것도 스스로 지우지 않는다. 커밋마다 새 파일과 새 metadata 가 쌓이고, 압축해도 옛 파일은 옛 스냅샷이 가리키고 있어 그대로 남는다. 그래서 정리를 안 한 표는 읽기가 느려지고(작은 파일·매니페스트가 많아서) 저장소는 계속 커진다. 정리는 세 가지 다른 일이다. 압축은 작은 파일을 큰 파일로 다시 써서 지금 스냅샷을 빠르게 한다. 만료는 오래된 스냅샷과, 그것들만 가리키던 파일을 지운다 — 타임트래블로 갈 수 있는 과거가 그만큼 줄어든다. 고아 정리는 어떤 스냅샷도 가리킨 적 없는 파일(실패한 잡의 흔적)을 지운다 — 지금 쓰이는 중인 파일까지 지우지 않게 충분한 시간 여유를 둔다. 셋 다 되돌릴 수 없다. 그래서 순서가 중요하다. 되돌아가야 할 시점이 있다면 만료 전에 태그를 걸어야 하고, 고아 정리의 시간 여유는 줄이지 않는다.

단계

  1. /root/ice/mnt/trickle.py(앱 ice-mnt-trickle)로 lake.mnt.ordersPARTITIONED BY (days(order_ts))·format-version 2 로 만들고 /data/ice/batches/batch-001.csv 부터 batch-030.csv 까지를 묶음마다 한 커밋씩 넣으세요.
  2. /root/ice/mnt/before.py 로 지금 스냅샷 수·데이터 파일 수·평균 파일 크기를 /root/ice/mnt/out/before.json 에 쓰세요.
  3. /root/ice/mnt/tag.py(앱 ice-mnt-tag)로 열 번째 커밋 스냅샷에 태그 batch10RETAIN 30 DAYS 로 붙이세요.
  4. /root/ice/mnt/compact.py(앱 ice-mnt-compact)로 rewrite_data_files 를 부르고 결과를 /root/ice/mnt/out/compact.json 에 쓰세요.
  5. /root/ice/mnt/expire.py(앱 ice-mnt-expire)로 지금보다 오래된 스냅샷을 retain_last => 1 로 만료하세요.
  6. /root/ice/warehouse/mnt/orders/data 에 오래된 고아(stray-old.parquet, 수정 시각 나흘 전)와 새 파일(stray-new.parquet)을 두고, /root/ice/mnt/orphans.py(앱 ice-mnt-orphans)로 remove_orphan_files 를 먼저 dry_run 으로 돌려 /root/ice/mnt/out/orphans_dry.txt 에 적은 뒤 실제로 돌리세요.
  7. /root/ice/mnt/after.py 로 정리 뒤 스냅샷 수·데이터 파일 수·디스크의 Parquet 파일 수를 /root/ice/mnt/out/after.json 에 쓰세요.
  8. /root/ice/mnt/report.md## 압축 ## 만료와 태그 ## 고아 파일 세 절을 쓰세요.

참고

작은 묶음 30개, 커밋 30번

/root/ice/mnt/trickle.py 를 앱 이름 ice-mnt-trickle 로 만들어 lake.mnt.orders(열 여섯 개, PARTITIONED BY (days(order_ts)), 'format-version' = '2')를 만들고 /data/ice/batches/batch-001.csv 부터 batch-030.csv 까지 차례로 하나씩 append() 하세요.

묶음 하나가 커밋 하나, 스냅샷 하나이고, 날짜 파티션마다 작은 파일이 하나씩 생깁니다. 채점기는 30번째 커밋 직후의 metadata 에서 스냅샷 30개가 모두 그 묶음의 행 수를 더한 append 인지 봅니다.

정리 전의 숫자

/root/ice/mnt/before.py(pyiceberg)로 스냅샷 수·데이터 파일 수(total-data-files)·평균 파일 크기(total-files-size ÷ 파일 수, 정수 나눗셈)를 /root/ice/mnt/out/before.json{"snapshots", "data_files", "avg_file_bytes"} 로 쓰세요.

지금 스냅샷의 요약에는 표 전체의 누적 값(total-*)이 들어 있어 매니페스트를 다 읽지 않고도 파일 수와 크기를 알 수 있습니다. Parquet 파일 하나가 수 KB 라면 읽을 때 파일을 여는 비용이 데이터를 읽는 비용보다 커집니다.

지우기 전에 이름부터

/root/ice/mnt/tag.py 를 앱 이름 ice-mnt-tag 로 만들어 lake.mnt.orders.snapshotscommitted_at 순으로 읽고 열 번째 스냅샷에 CREATE TAG batch10 AS OF VERSION <ID> RETAIN 30 DAYS 를 거세요.

만료는 older_than 보다 오래된 스냅샷을 지우지만, 태그나 브랜치가 가리키는 스냅샷은 그 이름표의 보존 기간이 남아 있는 동안 지우지 않습니다. 그래서 태그는 만료보다 먼저 걸어야 합니다. 채점기는 태그가 30번째 커밋 직후 metadata 의 열 번째 스냅샷을 가리키는지 봅니다.

압축 — 작은 파일을 다시 쓴다

/root/ice/mnt/compact.py 를 앱 이름 ice-mnt-compact 로 만들어 CALL lake.system.rewrite_data_files(table => 'lake.mnt.orders', options => map('min-input-files', '2')) 를 부르고, 결과 행의 rewritten_data_files_count·added_data_files_count/root/ice/mnt/out/compact.json{"rewritten", "added"} 로 쓰세요.

압축은 같은 파티션의 작은 파일을 읽어 큰 파일로 다시 쓰고 'replace' 스냅샷 하나로 커밋합니다. 행은 하나도 바뀌지 않습니다. 옛 작은 파일은 목록에서만 빠지고 디스크에는 남습니다 — 옛 스냅샷(과 태그)이 아직 가리키니까요. 채점기는 replace 커밋의 요약과 여러분의 두 값을 견줍니다.

만료 — 옛 스냅샷과 그 파일이 지워진다

/root/ice/mnt/expire.py 를 앱 이름 ice-mnt-expire 로 만들어 지금 시각을 읽고 CALL lake.system.expire_snapshots(table => 'lake.mnt.orders', older_than => TIMESTAMP '<지금>', retain_last => 1) 을 부르세요.

만료는 스냅샷을 metadata 에서 지우고, 남은 스냅샷 어느 것도 가리키지 않는 파일을 디스크에서 지웁니다. 지금 main 과 태그 batch10 의 스냅샷만 남고, 태그가 가리키는 열 번째 스냅샷의 작은 파일들은 지워지지 않습니다. 채점기는 남은 스냅샷과 디스크의 파일을 봅니다.

고아 정리 — 시간 여유를 두고

/root/ice/warehouse/mnt/orders/data 에 기존 데이터 파일 하나를 복사해 stray-old.parquet(수정 시각 touch -d '4 days ago')와 stray-new.parquet(지금)을 만드세요. 그다음 /root/ice/mnt/orphans.py 를 앱 이름 ice-mnt-orphans 로 만들어 remove_orphan_files(table => 'lake.mnt.orders', dry_run => true) 결과의 orphan_file_location 을 한 줄에 하나씩 /root/ice/mnt/out/orphans_dry.txt 에 쓰고, 이어서 dry_run 없이 한 번 더 부르세요.

두 파일 모두 어느 스냅샷도 가리키지 않는 고아입니다. 그런데 방금 생긴 파일은 지금 누군가 쓰는 중인 커밋의 파일일 수도 있습니다. 그래서 older_than 보다 새 파일은 건드리지 않습니다(기본 사흘). 채점기는 dry_run 목록에 오래된 것만 있었는지, 실제로 오래된 것만 지워졌는지 봅니다.

정리 뒤의 숫자

/root/ice/mnt/after.py(pyiceberg)로 남은 스냅샷 수·지금 스냅샷의 데이터 파일 수·/root/ice/warehouse/mnt/orders/data 아래 Parquet 파일 수를 /root/ice/mnt/out/after.json{"snapshots", "data_files", "files_on_disk"} 로 쓰세요.

디스크의 파일 수는 지금 스냅샷의 파일 수보다 많습니다. 태그 batch10 이 지키는 작은 파일들과, 고아 정리가 일부러 남긴 새 파일이 있기 때문입니다. 그 차이를 설명할 수 있으면 정리 작업을 이해한 것입니다.

정리 작업을 운영 절차로

/root/ice/mnt/report.md## 압축 ## 만료와 태그 ## 고아 파일 세 절을 쓰세요. 첫 절에는 압축 전 데이터 파일 수(2단계)와 압축 뒤 지금 스냅샷의 데이터 파일 수(7단계)를 숫자로 넣으세요.

이 셋을 매일 도는 작업으로 만든다면 어떤 순서·주기·기준(older_than, retain_last, 고아 시간 여유)으로 두겠습니까. 스트리밍으로 1분마다 커밋하는 표라면 무엇이 달라지는지도 적어 보세요.