운영 체크리스트: metadata growth, compaction 주기, rollback 전략
포맷이 관리해주지 않는 것들
Iceberg, Delta Lake, Hudi는 ACID 트랜잭션과 snapshot isolation을 제공한다. 하지만 snapshot이 쌓이고, 파일이 작아지고, compaction이 밀리는 것은 포맷 스스로 막아주지 않는다. 운영자가 주기적으로 개입하지 않으면 테이블 성능과 비용이 서서히 나빠지고, 결정적인 순간에 rollback이 불가능한 상황이 온다.
이 장은 Lakehouse table format 운영의 세 가지 축인 metadata growth 제어, compaction 주기 설계, rollback 절차와 검증을 다룬다. 포맷마다 용어와 명령이 다르지만, 문제의 구조와 판단 기준은 같다.
1. Metadata growth: 왜 쌓이고 언제 문제가 되는가
메타데이터 누적 구조
각 포맷은 쓰기가 일어날 때마다 메타데이터를 추가한다.
- Iceberg: 매 커밋마다 새 snapshot, manifest list, manifest file이 생긴다.
- Delta Lake: 매 트랜잭션마다
_delta_log/디렉터리에 JSON 파일이 순차적으로 추가된다. 기본값으로 100번 커밋마다 Parquet checkpoint 파일이 생성된다. - Hudi:
.hoodie/아래 active timeline에 instant 파일이 쌓이고, 일정 임계값에서 archive로 이동된다.
메타데이터는 읽기에도 영향을 준다. query planning 시 Iceberg는 manifest file 목록을 스캔하고, Delta는 transaction log를 파싱하고, Hudi는 timeline을 탐색한다. 메타데이터가 클수록 planning time이 늘어나고 메모리 pressure도 높아진다.
커밋마다 생성
snapshot당 1개
파티션당 여러 개
정리 필요
트랜잭션마다
100커밋마다 자동
정리 필요
.hoodie/ 아래
임계값 초과 시
1.0+ 자동 compacted
문제 징후와 임계값
| 포맷 | 지표 | 경고 임계값 |
|---|---|---|
| Iceberg | snapshot 수 | > 1,000 |
| Iceberg | manifest 수 | > 500 |
| Iceberg | 평균 파일 크기 | < 64 MB |
| Delta Lake | _delta_log 크기 / 데이터 크기 비율 | > 20% |
| Delta Lake | checkpoint 이전 JSON 파일 수 | > 500 |
| Hudi | active timeline instant 수 | > 1,000 |
Iceberg: snapshot expiration
현재 snapshot 상태를 조회하고, 오래된 것을 제거한다.
-- 14일 이전 snapshot 제거 (최소 10개 유지)
CALL catalog.system.expire_snapshots(
table => 'my_db.my_table',
older_than => CAST(current_timestamp - interval '14' day AS timestamp),
retain_last => 10
);테이블 속성으로 retention 정책을 고정할 수 있다.
ALTER TABLE my_table SET TBLPROPERTIES (
'history.expire.max-snapshot-age-ms' = '1209600000',
'history.expire.min-snapshots-to-keep' = '10'
);주의: snapshot이 삭제되면 해당 시점으로 rollback하거나 time travel query를 수행할 수 없다. retention을 너무 짧게 설정하면 운영 사고 시 복구 선택지가 사라진다. 14일 이상 유지하고 최소 10개 보존을 권장한다.
manifest rewrite도 주기적으로 실행한다. delete file이 많이 쌓이거나 manifest가 500개를 넘은 경우에 효과적이다.
CALL catalog.system.rewrite_manifests('my_db.my_table');Delta Lake: VACUUM
_delta_log는 VACUUM을 실행하지 않으면 계속 성장한다. 기본 retention은 7일이다.
-- dry-run으로 삭제 대상 확인 후
VACUUM my_table RETAIN 7 DAYS DRY RUN;
-- 실제 삭제
VACUUM my_table RETAIN 7 DAYS;Delta Lake 3.3.0부터 VACUUM LITE 모드가 추가됐다. 전체 디렉터리 스캔 대신 transaction log를 활용해 더 빠르게 동작한다.
VACUUM my_table RETAIN 7 DAYS USING LITE;logRetentionDuration(기본 30일)은 DESCRIBE HISTORY로 볼 수 있는 과거 버전 범위를 결정한다. rollback 가능 시점과 직결되므로 함부로 줄이지 않는다.
Hudi: timeline 관리
Hudi 1.0(2025년 1월)은 LSM(Log-Structured Merge) 기반 timeline 아키텍처를 도입했다. 과거에는 archival 배치마다 소형 파일이 생성되어 timeline 디렉터리 자체가 파편화됐지만, LSM 구조는 compacted 트리 레이아웃으로 수백만 개의 instant도 예측 가능한 성능으로 다룬다.
자동 archival 활성화:
hoodie.archive.automatic = true
hoodie.archive.automatic.aggressive = trueactive timeline instant가 1,000개를 넘으면 query planning 시간이 늘어나기 시작한다. 정기적인 archival 주기를 유지하는 것이 핵심이다.
2. Compaction: 언제, 얼마나 자주 해야 하는가
소형 파일 문제의 본질
스트리밍 write는 소형 파일을 지속적으로 생성한다. 소형 파일이 많아지면 두 가지 문제가 생긴다.
- read amplification: 쿼리가 수천 개의 파일을 열고 닫아야 해서 I/O overhead가 커진다.
- metadata overhead: 파일 목록 자체가 커지면 query planning 비용이 올라간다.
반대로 너무 자주 compaction을 돌리면 컴퓨트 비용이 낭비되고 writer와 자원을 경합한다. 스케줄 기반이 아닌 신호 기반으로 트리거하는 것이 핵심이다.
| 신호 | 의미 | 권장 임계값 |
|---|---|---|
| 평균 파일 크기 | 작을수록 read overhead 증가 | < 64 MB면 compaction 검토 |
| 소형 파일 비율 | 64 MB 미만 파일 비율 | > 30%면 compaction 필요 |
| 파티션당 파일 수 | 파티션이 잘게 나뉨 | > 10개면 compaction 검토 |
| MoR의 log file 수 | 읽기 시 merge overhead | > 10개면 즉각 compaction |
Iceberg: rewrite_data_files
-- 기본 실행 (binpack 전략)
CALL catalog.system.rewrite_data_files('my_db.my_table');
-- 특정 파티션만 (운영 부하 분산)
CALL catalog.system.rewrite_data_files(
table => 'my_db.my_table',
where => "date_col >= '2026-06-01'"
);
-- 옵션 지정
CALL catalog.system.rewrite_data_files(
table => 'my_db.my_table',
strategy => 'binpack',
options => map(
'min-input-files', '5',
'target-file-size-bytes', '268435456'
)
);Compaction이 느린 주요 원인은 파일 그룹당 병렬성 부족이다. max-concurrent-file-group-rewrites(기본 5)를 늘리거나 shuffle-partitions-per-file(기본 50)을 높여 조정한다.
Delta Lake: OPTIMIZE
-- 기본 최적화
OPTIMIZE my_table;
-- 특정 파티션만
OPTIMIZE my_table WHERE date >= '2026-06-01';
-- Z-Order 적용 (자주 필터링하는 컬럼 기준)
OPTIMIZE my_table ZORDER BY (user_id, event_type);Delta Lake 3.x부터 auto compaction을 설정하면 write 후 자동으로 소형 파일을 합친다.
spark.databricks.delta.autoCompact.enabled = true
spark.databricks.delta.autoCompact.minNumFiles = 10OPTIMIZE 결과는 numFilesAdded, numFilesRemoved, filesAdded.avg 같은 메트릭을 반환한다. 이 수치를 주기적으로 기록해 compaction 효과를 추적한다.
Hudi: async compaction
Hudi MoR 테이블은 base file과 delta log file로 구성된다. log file이 쌓일수록 읽기 시 merge overhead가 커지므로 compaction이 특히 중요하다.
프로덕션에서는 inline compaction(쓰기와 동기)보다 async compaction을 권장한다.
hoodie.compact.inline = false
hoodie.compact.async.enabled = true
hoodie.compact.inline.max.delta.commits = 20Compaction 전략은 LogFileSizeBasedCompactionStrategy(log file 크기 기준)와 LogFileNumBasedCompactionStrategy(log file 수 기준) 중 워크로드에 맞게 선택한다.
워크로드별 권장 compaction 주기:
| 워크로드 유형 | 권장 주기 |
|---|---|
| 스트리밍 + MoR | 매 2~4시간 |
| 고빈도 배치 쓰기 | 매일 |
| 저빈도 배치 쓰기 | 매주 |
| 모든 경우 | 신호 기반 자동 트리거 병행 |
3. Rollback: 포맷별 복구 절차와 검증
rollback이 필요한 상황
데이터 플랫폼에서 rollback이 필요한 전형적인 상황은 다음과 같다.
- 잘못된 transform 로직이 데이터를 오염시킨 경우
- 스키마 변경이 downstream을 깨뜨린 경우
- 배치 잡이 중복 데이터를 삽입한 경우
- 잘못된 DELETE 또는 UPDATE가 실행된 경우
rollback_to_snapshot
즉각 완료 (테이블 크기 무관)
되돌리기 가능
snapshot이 남아 있어야 함
orphan file 별도 정리 필요
RESTORE TABLE
즉각 완료
되돌리기 가능
logRetentionDuration 이내 버전만
복원 후 재복원도 가능
rollback_to_savepoint
즉각 완료
되돌리기 불가 (파괴적)
사전 savepoint 생성 필수
timeline도 삭제됨
Iceberg: rollback_to_snapshot
-- snapshot ID로 rollback
CALL catalog.system.rollback_to_snapshot(
'my_db.my_table',
9182737623456
);
-- 타임스탬프 기반 rollback
CALL catalog.system.rollback_to_timestamp(
'my_db.my_table',
TIMESTAMP '2026-06-30 14:30:00'
);Iceberg rollback은 메타데이터 포인터만 변경하므로 테이블 크기와 무관하게 즉각 완료된다. 단, 대상 snapshot이 아직 존재해야 한다. snapshot expiration이 공격적으로 설정된 환경에서는 rollback이 불가능할 수 있다.
rollback 후에는 해당 snapshot 이후에 쓰인 데이터 파일이 orphan으로 남는다. 별도로 orphan file cleanup을 실행해야 스토리지 비용이 정리된다.
Delta Lake: RESTORE TABLE
-- 버전 번호로 복원
RESTORE TABLE my_table TO VERSION AS OF 5;
-- 타임스탬프 기반 복원
RESTORE TABLE my_table TO TIMESTAMP AS OF '2026-06-30 14:30:00';Delta의 RESTORE 역시 메타데이터 기반이므로 즉각 완료된다. 복원 이후 버전 데이터는 DESCRIBE HISTORY에서 조회할 수 있으며, 필요하면 다시 RESTORE할 수 있다. logRetentionDuration(기본 30일) 내에 있는 버전만 복원 가능하다.
Hudi: savepoint rollback
Hudi의 savepoint rollback은 파괴적 연산이다. savepoint 이후의 데이터 파일과 timeline 파일을 삭제하며 되돌릴 수 없다.
-- 중요 배치 실행 전에 savepoint 생성
CALL create_savepoint_on_table(
table => 'my_db.my_table',
version => '20260630140000'
);
-- 필요 시 savepoint로 rollback (되돌릴 수 없음)
CALL rollback_to_savepoint(
table => 'my_db.my_table',
instant_time => '20260630140000'
);Hudi를 사용한다면 중요한 배치 작업 전후에 savepoint를 생성하는 것을 운영 표준으로 만들어야 한다. savepoint 없이는 rollback할 시점 자체가 없다.
rollback 후 검증 절차
rollback이 완료됐다고 바로 writer나 downstream을 재개하면 안 된다. 다음 검증을 순서대로 수행한다.
- 레코드 수 확인: 예상 시점의 레코드 수와 일치하는가?
- 스키마 확인: schema가 예상 버전인가?
- 샘플 데이터 검증: 핵심 레코드 몇 건을 직접 조회해 값이 맞는가?
- downstream 점검: rollback된 테이블을 읽는 pipeline, BI, feature store가 정상 동작하는가?
- orphan file 정리: Iceberg의 경우 dry-run으로 확인 후 제거한다.
-- Iceberg orphan file 확인 (dry-run)
CALL catalog.system.remove_orphan_files(
table => 'my_db.my_table',
older_than => CAST(current_timestamp - interval '1' day AS timestamp),
dry_run => true
);4. 운영 안티패턴
자주 반복되는 실수와 그 대안을 정리한다.
① 스케줄 기반 compaction 매주 고정 시각에 무조건 OPTIMIZE를 돌리는 방식은 필요 없는 compaction을 돌리거나, 급속히 쌓이는 테이블에는 늦게 반응한다. 파일 크기, 소형 파일 비율 같은 테이블 신호를 기반으로 트리거한다.
② 공격적인 snapshot/version 제거 rollback을 위한 time window를 너무 짧게 설정하면 사고 시 복구 선택지가 없어진다. 컴플라이언스와 운영 안전성을 함께 고려해 retention을 설정한다. Iceberg는 14일/최소 10개, Delta는 logRetentionDuration 30일이 보수적 기본값이다.
③ writer와 compaction 동시 실행 writer가 데이터를 쓰는 동안 같은 클러스터에서 compaction을 돌리면 자원을 경합한다. writer 트래픽이 낮은 시간대 또는 별도 클러스터에서 분리한다.
④ orphan file 방치 rollback, 실패한 write, 중단된 compaction 후에 데이터 파일이 메타데이터에 참조되지 않은 채 스토리지에 남는다. 월 단위로 orphan file cleanup을 dry-run으로 확인하고 필요하면 제거한다.
⑤ Hudi savepoint 없이 대형 배치 실행 Hudi에서 사고가 나면 savepoint 없이는 rollback할 시점이 없다. 중요한 배치 실행 전에 savepoint를 반드시 생성한다.
5. 운영 대시보드: 포맷별 핵심 지표
주기적으로 모니터링해야 할 지표를 정리한다.
| 지표 | Iceberg | Delta Lake | Hudi |
|---|---|---|---|
| 커밋/버전 수 | $snapshots COUNT | DESCRIBE HISTORY 행 수 | .hoodie/ instant 수 |
| 평균 파일 크기 | $files AVG | DESCRIBE DETAIL numFiles/sizeInBytes | metadata.file_index |
| 메타데이터 크기 비율 | $manifests 합계 / 데이터 크기 | _delta_log 크기 / 데이터 크기 | .hoodie/archived 크기 |
| Orphan file | remove_orphan_files dry-run | VACUUM DRY RUN | 파일 수 드리프트 |
| Compaction 경과 시간 | rewrite 마지막 실행 이후 | OPTIMIZE 마지막 실행 이후 | async compaction 마지막 실행 |
일일 점검 항목:
- snapshot/version 수가 임계값을 초과하는가?
- 평균 파일 크기가 64 MB 미만인가?
- 전날 compaction이 정상 완료됐는가?
- 실패한 작업이 있는가?
주간 점검 항목:
- 임계값 초과 테이블에 대한 compaction 실행
- orphan file 누적 추세 확인
- 메타데이터 증가율 검토
월간 점검 항목:
- orphan file cleanup (dry-run 후 실행)
- retention 정책 감사
- rollback 절차 drill (테스트 환경에서 실제 rollback 수행)
- 스토리지 용량 계획 검토
정리
- Metadata growth는 모든 포맷에서 자동으로 제어되지 않는다. Iceberg는 snapshot expiration과 manifest rewrite, Delta는 VACUUM, Hudi는 timeline archival을 주기적으로 실행해야 한다.
- Compaction은 스케줄이 아니라 파일 크기, 소형 파일 비율, log file 수 같은 테이블 신호를 기준으로 트리거한다. writer와 자원을 경합하지 않도록 별도 클러스터 또는 유휴 시간대에 분리한다.
- Rollback은 Iceberg와 Delta에서 메타데이터 기반으로 즉각 가능하지만, 대상 snapshot/version이 retention 기간 내에 남아 있어야 한다. Hudi의 savepoint rollback은 파괴적이므로 사전 savepoint 생성이 필수다.
- rollback 후에는 레코드 수, 스키마, 샘플 데이터, downstream 영향, orphan file까지 순서대로 검증한다.
- 운영 안티패턴의 공통점은 "포맷이 알아서 해준다"는 믿음이다. metadata 정리, compaction, rollback 준비는 모두 운영자가 명시적으로 설계하고 실행해야 한다.
References
- Apache Iceberg Docs, "Maintenance", https://iceberg.apache.org/docs/latest/maintenance/
- Apache Iceberg Docs, "Spark Procedures", https://iceberg.apache.org/docs/latest/spark-procedures/
- Delta Lake Blog, "Remove files with VACUUM", https://delta.io/blog/remove-files-delta-lake-vacuum-command/
- Databricks Docs, "Remove unused data with vacuum", https://docs.databricks.com/aws/en/delta/vacuum
- Delta Lake Blog, "Rollback a Delta Lake table", https://delta.io/blog/2022-10-03-rollback-delta-lake-restore/
- Apache Hudi Docs, "Compaction", https://hudi.apache.org/docs/compaction/
- Apache Hudi Blog, "LSM Timeline", https://hudi.apache.org/blog/2025/05/29/lsm-timeline/
- Apache Hudi Docs, "Disaster Recovery", https://hudi.apache.org/docs/next/disaster_recovery/
- Dremio Blog, "Compaction in Apache Iceberg", https://www.dremio.com/blog/compaction-in-apache-iceberg-fine-tuning-your-iceberg-tables-data-files/
- Dremio Blog, "Dealing with Data Incidents using Rollback in Iceberg", https://www.dremio.com/blog/dealing-with-data-incidents-using-the-rollback-feature-in-apache-iceberg/
- IOMETE Blog, "The Iceberg Maintenance Runbook", https://iomete.com/resources/blog/iceberg-maintenance-runbook
- IOMETE Blog, "Why Is My Iceberg Compaction So Slow?", https://iomete.com/resources/blog/iceberg-compaction-slow
- AWS Prescriptive Guidance, "Monitoring Apache Iceberg workloads", https://docs.aws.amazon.com/prescriptive-guidance/latest/apache-iceberg-on-aws/monitoring.html
- Databricks Community, "How to Evaluate Delta Table Operational Health", https://community.databricks.com/t5/technical-blog/how-to-evaluate-delta-table-operational-health-using-delta-table/ba-p/117114
- Delta Lake Docs, "Delta table optimization and V-Order", https://docs.delta.io/latest/optimizations-oss.html