[Iceberg] Apache Iceberg 테이블 유지 관리 작업과 자동화 설계

1. 이 글의 목적

Apache Iceberg 테이블은 데이터를 쓸 때마다 새 스냅샷을 만든다. 이전 상태를 그대로 보관하기 때문에 타임 트래블과 롤백이 가능하지만, 아무 관리 없이 오래 운영하면 스냅샷과 작은 파일이 계속 늘어난다.

이 글에서는 Iceberg 테이블을 운영할 때 챙겨야 할 다섯 가지 작업을 정리한다. 각 작업이 무엇을 정리하는지, 어떤 순서로 실행할지, 적재 파이프라인과 어떻게 분리할지를 차례로 살펴본다.

예시는 Apache Iceberg 1.11의 Spark 프로시저를 기준으로 작성했다. 카탈로그는 lakehouse, 네임스페이스는 analytics, 테이블은 sales_fact라는 예시 이름을 사용한다. 실제 적용 전에는 사용하는 Iceberg와 Spark 버전에서 프로시저와 옵션을 다시 확인해야 한다.


2. Iceberg 테이블은 왜 정리가 필요할까?

Iceberg의 스냅샷은 특정 시점에 유효한 데이터 파일과 삭제 파일의 목록을 가리킨다. 데이터를 바꿀 때 기존 파일을 바로 덮어쓰지 않고 새 스냅샷을 추가하므로, 읽는 쪽에서는 일관된 테이블 상태를 볼 수 있다.

대신 커밋이 반복될수록 관리 대상도 늘어난다.

  • 스냅샷: 커밋마다 하나씩 늘어난다. 너무 많아지면 메타데이터가 커지고 쿼리 계획에도 부담이 생긴다.
  • 작은 데이터 파일: 짧은 주기의 배치나 스트리밍 적재에서 자주 생긴다. 파일을 여는 비용과 메타데이터 조회량을 키운다.
  • 매니페스트: 데이터 파일 정보를 묶어 관리한다. 쓰기 순서와 조회 조건이 어긋나면 파일을 찾는 데 더 많은 매니페스트를 읽게 된다.
  • 오펀 파일: 작업 실패나 잘못된 수동 업로드 때문에 어떤 테이블 메타데이터에서도 참조하지 않는 파일이 남을 수 있다.
  • 이전 메타데이터 JSON: 테이블이 변경될 때마다 새 파일이 만들어진다. 커밋이 잦은 테이블에서는 빠르게 늘어난다.

“보존 기간만 설정하면 Iceberg가 알아서 파일까지 지워 주지 않을까?”

그렇지 않다. history.expire.* 속성은 스냅샷 만료 기준을 정한다. 실제 만료와 파일 삭제는 expire_snapshots 같은 유지 관리 작업을 실행해야 일어난다.


3. 테이블 유지 관리 작업

Iceberg 공식 문서는 스냅샷 만료, 이전 메타데이터 정리, 오펀 파일 삭제를 권장 유지 관리로 분류한다. 데이터 파일 압축과 매니페스트 재작성은 테이블 상태에 따라 선택한다.

이 글에서는 다섯 작업을 의존 관계 순서로 소개한다. 파일을 다시 쓰는 작업(데이터 파일 압축, 매니페스트 재작성)이 이전 스냅샷을 만들고, 스냅샷 만료가 보존 기간이 지난 스냅샷과 그 스냅샷만 참조하던 파일을 실제로 지우며, 어떤 메타데이터도 참조하지 않는 파일은 마지막에 오펀 파일 삭제가 걷어낸다.

작업 해결하려는 문제 실행 방법 주의할 점
데이터 파일 압축 작은 데이터 파일 누적 프로시저 호출 대상 파티션과 동시 실행 수를 제한한다
매니페스트 재작성 조회 조건과 맞지 않는 매니페스트 구성 프로시저 호출 모든 테이블에 주기적으로 실행할 필요는 없다
스냅샷 만료 오래된 스냅샷과 더 이상 쓰지 않는 파일 프로시저 호출 타임 트래블 범위가 줄어든다
이전 메타데이터 정리 오래된 메타데이터 JSON 누적 테이블 속성 이미 추적에서 빠진 파일은 따로 정리해야 한다
오펀 파일 삭제 어떤 메타데이터에서도 참조하지 않는 파일 프로시저 호출 진행 중인 쓰기 파일을 지우지 않도록 보존 시간을 둔다

3.1 데이터 파일 압축: rewrite_data_files

작은 파일이 많으면 쿼리가 같은 양의 데이터를 읽더라도 더 많은 파일을 열어야 한다. 매니페스트에 기록할 항목도 늘어나므로 쿼리 계획과 실행이 함께 느려질 수 있다.

CALL lakehouse.system.rewrite_data_files(
  table => 'analytics.sales_fact',
  options => map(
    'min-input-files', '5',
    'target-file-size-bytes', '536870912',
    'partial-progress.enabled', 'true',
    'partial-progress.max-commits', '5',
    'max-concurrent-file-group-rewrites', '2'
  ),
  where => 'sale_date >= DATE ''2026-07-01'''
)

위 수치는 정답이 아니라 출발점이다. files 메타데이터 테이블에서 실제 파일 크기 분포를 확인하고, 실행 엔진의 메모리와 작업 시간을 함께 보면서 조정해야 한다.

재작성 방식은 다음처럼 고른다.

방식 쓰기 결과 잘 맞는 경우
binpack 작은 파일을 목표 크기에 맞춰 합친다 파일 수를 줄이는 것이 주목적일 때
sort 지정한 순서로 정렬하면서 다시 쓴다 범위 조건과 정렬 순서가 맞을 때
sort와 zorder(...) 여러 컬럼을 기준으로 가까운 값을 모은다 여러 컬럼 조합으로 자주 필터링할 때

큰 테이블 전체를 한 번에 다시 쓰면 실행 시간과 충돌 가능성이 커진다. 최근 변경된 파티션처럼 범위를 좁히고, 부분 커밋과 동시 실행 수 제한을 함께 쓰는 편이 안정적이다.

작은 파일은 쓰기 단계에서도 줄일 수 있다. write.distribution-mode=hash는 같은 파티션의 행을 모아 쓰는 데 도움이 된다. 셔플 비용은 늘지만 이후 압축할 파일 수를 줄일 수 있으므로, 적재 시간과 유지 관리 비용을 함께 보고 결정한다.

3.2 매니페스트 재작성: rewrite_manifests

Iceberg는 매니페스트의 파티션 통계를 보고 읽지 않아도 되는 데이터 파일을 건너뛴다. 쓰기 순서가 조회 조건과 잘 맞으면 커밋 과정에서 만들어진 매니페스트만으로도 충분하다.

반대로 여러 시점과 파티션의 파일이 뒤섞여 들어오면, 하나의 조회가 많은 매니페스트를 확인해야 할 수 있다. 이때 매니페스트를 다시 묶는다.

CALL lakehouse.system.rewrite_manifests(
  table => 'analytics.sales_fact'
)

매니페스트 재작성은 작은 데이터 파일을 합치는 작업이 아니다. 데이터 파일은 그대로 두고 파일 정보를 묶는 방식을 바꾼다. 쿼리 계획 시간이 길어졌거나 쓰기 순서와 조회 조건이 어긋난 테이블을 골라 실행하는 편이 낫다.

3.3 스냅샷 만료: expire_snapshots

오래된 스냅샷을 테이블 메타데이터에서 제거한다. 만료된 스냅샷에서만 참조하던 데이터 파일과 삭제 파일, 매니페스트도 함께 정리할 수 있다. 3.1의 압축이 대체한 이전 파일도, 그 파일을 참조하던 압축 전 스냅샷이 만료되어야 실제로 지워진다.

CALL lakehouse.system.expire_snapshots(
  table => 'analytics.sales_fact'
)

older_than과 retain_last를 생략하면 테이블에 설정한 만료 속성을 사용한다.

속성 역할
history.expire.max-snapshot-age-ms 이 시간보다 오래된 스냅샷을 만료 후보로 본다
history.expire.min-snapshots-to-keep 나이와 관계없이 브랜치마다 남길 최소 스냅샷 수를 정한다
history.expire.max-ref-age-ms main을 제외한 브랜치와 태그의 최대 보존 시간을 정한다

만료된 스냅샷으로는 더 이상 타임 트래블하거나 롤백할 수 없다. 백필, 감사, 장애 복구에 필요한 기간을 먼저 정하고 그보다 넉넉하게 보존해야 한다.

브랜치나 태그가 참조하는 스냅샷은 해당 참조의 보존 정책이 끝나기 전까지 지워지지 않는다. 스냅샷 개수만 보고 예상 삭제량을 계산하면 실제 결과와 다를 수 있는 이유다.

3.4 이전 메타데이터 정리: delete-after-commit

Iceberg는 테이블이 바뀔 때마다 새로운 메타데이터 JSON을 만든다. 커밋이 잦은 테이블은 이전 버전을 몇 개까지 추적할지 정하고, 한도를 넘은 파일을 커밋 시점에 정리할 수 있다.

ALTER TABLE lakehouse.analytics.sales_fact SET TBLPROPERTIES (
  'write.metadata.delete-after-commit.enabled' = 'true',
  'write.metadata.previous-versions-max' = '20'
)

이 설정은 최신 메타데이터 파일의 metadata-log가 추적하는 이전 파일에만 적용된다. 이미 추적에서 빠져 오펀이 된 메타데이터 파일까지 소급해 지우지는 않는다. 그런 파일은 오펀 파일 삭제 작업에서 찾아야 한다.

스냅샷 만료와도 역할이 다르다. 이 설정은 이전 메타데이터 JSON의 개수를 관리하고, expire_snapshots는 더 이상 보존하지 않을 스냅샷과 관련 파일을 정리한다.

3.5 오펀 파일 삭제: remove_orphan_files

작업 실패나 수동 파일 처리 뒤에는 테이블이 참조하지 않는 파일이 남을 수 있다. remove_orphan_files는 스토리지의 파일 목록과 Iceberg 메타데이터를 비교해 이런 파일을 찾는다.

먼저 dry_run으로 삭제 후보만 확인한다.

CALL lakehouse.system.remove_orphan_files(
  table => 'analytics.sales_fact',
  dry_run => true
)

공식 Spark 프로시저의 기본 older_than은 실행 시점에서 3일 전이다. 이 간격은 진행 중인 작업이 아직 커밋하지 않은 정상 파일을 오펀으로 잘못 판단하지 않게 하는 안전장치다.

실제 환경에서는 가장 오래 걸리는 쓰기 작업보다 충분히 긴 보존 시간을 잡는다. 장시간 백필이나 지연 커밋이 있다면 3일보다 더 넉넉하게 두는 편이 안전하다.

기본 검색 범위는 테이블의 루트 위치다. write.data.path나 write.metadata.path를 루트 밖의 별도 경로로 바꿨다면 한 번의 호출로 모든 경로가 정리되는지 확인해야 한다.


4. 실행 순서는 고정 규칙보다 의존 관계로 정한다

다섯 가지 작업을 매번 한 묶음으로 실행할 필요는 없다. 이전 메타데이터 정리는 커밋 때 적용되는 테이블 속성이고, 나머지 네 작업도 목적과 주기가 서로 다르다.

한 번의 유지 관리 실행에서 여러 작업이 함께 필요하다면 다음 흐름이 이해하기 쉽다.

상태 점검
├─ 작은 파일이 많음        → 데이터 파일 압축
├─ 매니페스트 구성이 나쁨 → 매니페스트 재작성
├─ 보존 기간이 지난 스냅샷 → 스냅샷 만료
└─ 참조되지 않는 파일      → 오펀 파일 삭제

오펀 파일 삭제는 다른 작업과 겹치지 않는 시간에 따로 실행하는 편이 안전하다. 같은 시간대에 파일을 쓰거나 재작성하는 작업이 있으면 아직 커밋되지 않은 파일을 오펀으로 잘못 볼 가능성이 있기 때문이다.

데이터 파일 압축 뒤에 스냅샷 만료를 실행할 수는 있지만, 압축 전 파일이 바로 지워진다고 보면 안 된다. 압축은 새 스냅샷을 만들고, 이전 파일은 압축 전 스냅샷이 만료될 때까지 남는다. 보존 기간이 지나지 않았거나 브랜치와 태그가 이전 스냅샷을 가리키고 있으면 파일도 계속 유지된다.

따라서 실행 순서보다 먼저 정해야 할 것은 다음 세 가지다.

  1. 타임 트래블과 롤백을 며칠까지 허용할지 정한다.
  2. 쓰기 작업과 유지 관리 작업이 겹치지 않게 실행 시간을 나눈다.
  3. 테이블 상태를 보고 필요한 작업만 고른다.

5. 자동화는 점검과 실행을 나눈다

적재가 끝날 때마다 모든 유지 관리 작업을 실행하면 계산 자원만 낭비한다. 파일 재작성과 스냅샷 만료가 잦아지면 커밋 충돌 가능성도 커지고, 필요 이상으로 타임 트래블 범위를 줄일 수 있다.

자동화는 가벼운 점검과 실제 실행을 나누는 방식이 다루기 쉽다.

5.1 점검 단계

REST Catalog의 loadTable이나 Iceberg 메타데이터 테이블을 조회해 현재 상태를 모은다. 이 단계에서는 Spark 작업을 띄우지 않거나, 최소한의 메타데이터 조회만 수행한다.

확인할 정보 결정할 작업
데이터 파일 개수와 크기 분포 데이터 파일 압축
매니페스트 개수와 크기 매니페스트 재작성
스냅샷 개수와 가장 오래된 시각 스냅샷 만료
마지막 유지 관리 시각 장기간 미실행 테이블 보완

현재 스냅샷의 summary에는 추가되거나 삭제된 파일 수와 전체 파일 수 같은 값이 들어 있다. 최근 커밋의 변화량을 빠르게 보는 데는 유용하지만, 개별 파일의 크기 분포까지 알려 주지는 않는다.

그래서 summary만으로 작은 파일을 정확히 셀 수는 없다. 매번 적은 수의 파일을 추가하는 테이블은 임계치를 한 번도 넘지 않으면서 작은 파일이 오래 누적될 수 있다. 이런 테이블은 files 메타데이터 테이블을 주기적으로 확인하거나, 마지막 압축 후 일정 시간이 지나면 다시 점검하는 보조 조건이 필요하다.

5.2 실행 단계

점검 결과에 따라 필요한 프로시저만 실행한다. 실행 단계에서는 왜 이 작업을 골랐는지 다시 계산하지 않고, 대상 테이블과 작업 목록을 입력으로 받는다.

점검 결과
└─ analytics.sales_fact
   ├─ rewrite_data_files: 실행
   ├─ rewrite_manifests: 건너뜀
   ├─ expire_snapshots: 실행
   └─ remove_orphan_files: 별도 시간대에 실행

판단 기준과 프로시저 실행 코드를 분리하면 기준을 바꿀 때 실행 코드를 건드리지 않아도 된다. 실패 원인도 상태 점검 문제인지, 실제 재작성 문제인지 나눠서 볼 수 있다.


6. 정리

  • Iceberg 테이블은 커밋할 때마다 스냅샷과 메타데이터를 남긴다. 짧은 주기의 적재는 작은 파일과 매니페스트도 빠르게 늘린다.
  • 유지 관리 작업은 데이터 파일 압축, 매니페스트 재작성, 스냅샷 만료, 이전 메타데이터 정리, 오펀 파일 삭제로 나눌 수 있다. 재작성이 이전 스냅샷을 만들고, 만료가 이전 파일을 지우고, 오펀 파일 삭제가 남은 파일을 걷어내는 의존 관계 순서다.
  • 스냅샷 보존 속성은 기준을 정할 뿐이다. 실제 정리는 프로시저나 유지 관리 작업을 실행해야 일어난다.
  • 압축한 파일이 곧바로 삭제되는 것은 아니다. 이전 스냅샷의 보존 기간이 끝나고 다른 브랜치나 태그에서도 참조하지 않아야 정리된다.
  • 자동화는 상태를 확인하는 단계와 실제 프로시저를 실행하는 단계를 나누고, 필요한 작업만 골라 실행한다.

공식 문서는 Apache Iceberg Maintenance, Spark Procedures, Table Configuration에서 확인할 수 있다.

Leave a comment