Apache Iceberg 백서 — 차세대 Lakehouse 테이블 포맷의 구조와 도입 전략
Apache Iceberg의 메타데이터 구조, 운영 모델, 멀티-엔진 호환성, 도입 전략을 데이터 아키텍트와 플랫폼 리더의 관점에서 정리한 백서. Hive·Delta Lake·Hudi와의 비교부터 마이그레이션 패턴, 운영 자동화, 2026년 전망까지 종합적으로 다룹니다.
본 백서가 다루는 범위
Apache Iceberg를 "왜 도입해야 하는가"와 "어떻게 운영해야 하는가"의 두 축으로 정리합니다. 데이터 아키텍트·플랫폼 리드·CDO가 멀티-엔진 Lakehouse 환경에서 테이블 포맷을 선택·도입할 때 참조할 수 있도록, 사양(spec) 설명에 머무르지 않고 운영 자동화, 카탈로그 토폴로지, 마이그레이션 패턴까지 실무 관점에서 다룹니다.
1. Executive Summary
1.1 한 문장 요약
Apache Iceberg는 개체 스토리지 위에 데이터베이스 수준의 트랜잭션과 진화(evolution) 의미론을 부여하는 개방형 테이블 포맷이며, 2026년 현재 멀티-엔진 Lakehouse의 사실상 표준으로 자리잡고 있다.
1.2 핵심 결론
- Lakehouse가 "단일 엔진의 ACID 레이어"에서 "멀티-엔진의 공유 테이블"로 이동하면서, Iceberg의 엔진 중립적 사양과 REST Catalog 표준의 가치가 결정적으로 부각됐다.
- Iceberg는 "스토리지 포맷"이 아니라 "메타데이터 구조" 다. Parquet/ORC/Avro 같은 파일 포맷 위에 스냅샷 기반 격리, 숨겨진 파티셔닝, 스키마·파티션 진화, 시간 여행, 브랜치/태그를 얹는 사양이다.
- **단일 엔진 환경(예: Databricks-only)**에서는 Delta Lake가 여전히 합리적 선택이지만, 2개 이상 엔진을 동시에 사용하거나 장기 보존·법적 정정·실험 격리가 중요한 워크로드라면 Iceberg가 명확한 우위에 있다.
- 운영 비용은 무시할 수 없다. 컴팩션, 스냅샷 만료, 고아 파일 정리, 카탈로그 운영을 자동화하지 않으면 Iceberg는 빠르게 "메타데이터 늪"이 된다. 이 백서는 운영 자동화의 표준 패턴을 함께 제시한다.
- Iceberg V3와 REST Catalog 표준의 정착으로, 향후 3년 내에 **카탈로그 계층이 Lakehouse의 진정한 통제점(control plane)**이 될 것이다.
1.3 누구에게 어떤 결정에 도움이 되는가
| 독자 | 본 백서에서 얻을 수 있는 것 |
|---|---|
| 데이터 플랫폼 아키텍트 | 메타데이터 계층 구조, 카탈로그 토폴로지, 엔진 호환성 매트릭스 |
| 데이터 엔지니어링 리드 | 운영 자동화 패턴, 컴팩션·정리 작업 표준, 모니터링 지표 |
| CDO / 데이터 임원 | 도입 의사결정 트리, 마이그레이션 위험·기간 추정, 비용 구조 |
| ML / 분석 리드 | 시간 여행, 브랜치/태그를 활용한 실험·재현성 패턴 |
2. 배경: 왜 또 새로운 테이블 포맷인가
2.1 Hive 시대의 구조적 한계
2010년대 중반까지 Hadoop·Spark 생태계의 사실상 표준은 Hive Metastore(HMS) + 디렉터리 파티셔닝이었다. 이 방식은 단순했지만, 규모가 커지면서 구조적 한계가 누적됐다.
- 디렉터리 = 파티션 = Query Predicate.
WHERE event_date = '2026-05-20'같은 Predicate가 디렉터리 경로(/event_date=2026-05-20/)와 정확히 일치해야 Partition Pruning이 동작했다. 컬럼 이름이 바뀌거나, 사용자가WHERE ts > ...처럼 표현식을 쓰면 Pruning이 무력화됐다. - 파티션 진화 불가. "일별 파티션을 시간별로 바꾸자"는 의사결정이 곧 테이블 재작성(rewrite)을 의미했다. 운영 중인 페타바이트급 테이블에서는 사실상 불가능했다.
- 스키마 진화는 외부 규약. 컬럼 추가/이름변경은 Hive가 어느 정도 지원했지만, 컬럼 ID가 없는 위치 기반 매핑 때문에 컬럼 재배치/이름변경이 위험했다.
- 원자성 부재. 디렉터리 이동(rename)으로 "원자적 publish"를 흉내냈으나, S3 같은 객체 스토리지에서는 rename이 비원자 복사로 구현되어 부분 가시성·중복 문제가 발생했다.
- 읽기-쓰기 동시성 부재. 동일 파티션을 다른 잡이 쓰는 동안 읽으면, 부분 파일이 보이거나 ListBucket 결과가 일관되지 않았다.
2.2 클라우드·객체 스토리지로의 이동이 만든 결정타
S3·ADLS·GCS는 HDFS와 두 가지 결정적인 차이를 만들었다.
- List는 비싸고 일관성이 약하다. Hive는
s3 list를 수만 번 호출해 파일을 식별하는 구조였는데, S3 list는 prefix가 수십만 키를 가지면 분 단위 비용이 발생한다. - Rename은 사실상 copy + delete. HDFS의 디렉터리 rename은 메타데이터 연산이었지만, S3에서는 객체 단위 복사다. 잡 중간 실패 시 "절반만 쓰인" 상태가 보이게 된다.
요약하면, Hive는 "파일 시스템이 강한 일관성과 빠른 rename을 제공한다"는 전제로 설계됐고, 클라우드는 그 전제를 무너뜨렸다.
2.3 새 포맷들의 등장 — Iceberg, Delta Lake, Hudi
이 시기에 세 개의 포맷이 거의 동시에 등장했다.
| 포맷 | 시작 시점·주체 | 설계 출발점 |
|---|---|---|
| Apache Iceberg | 2017, Netflix | "디렉터리 list 없이도 어떤 파일이 테이블의 일부인지 알 수 있어야 한다" |
| Delta Lake | 2017, Databricks | "Spark의 트랜잭션 로그를 외부로 분리하여 ACID를 부여한다" |
| Apache Hudi | 2016, Uber | "스트리밍·upsert 워크로드에 최적화된 증분 처리" |
세 포맷 모두 메타데이터 계층으로 문제를 해결한다는 공통점이 있지만, 그 메타데이터의 구조와 운영 모델이 다르고, 그것이 곧 사용 가능한 워크로드의 차이를 만든다.
2.4 Iceberg가 받은 평가의 변화
- 2018~2020: "Netflix 내부 프로젝트". 사용 사례는 흥미롭지만 도입 사례 제한적.
- 2020~2022: Apache Top-Level 승격(2020), AWS Athena/EMR, Snowflake, Trino, Flink가 차례로 지원 추가.
- 2023~2024: REST Catalog 사양 v1이 합의되고, Snowflake Polaris·Databricks Unity가 Iceberg를 직접 다루기 시작하면서 "멀티-엔진의 공유 포맷" 역할이 정착.
- 2025~2026: Iceberg V3(Variant type, Geospatial, default values, deletion vectors)가 도입되며 사양이 더 풍부해지고, REST Catalog가 사실상 카탈로그 표준으로 굳어짐.
3. Iceberg 아키텍처 심층
3.1 세 계층 모델
Iceberg는 어떤 엔진에서 읽더라도 동일한 답을 보장하기 위해, 테이블 상태를 다음 세 계층으로 분리한다.
이 분리가 가져오는 결과는 명확하다.
- 읽기 = 디렉터리 list가 없다. 항상
metadata.json → manifest list → manifest → data file의 트리만 따라간다. 따라서 list 비용·일관성 문제에서 자유롭다. - 쓰기 = 새 metadata.json을 만들고 카탈로그에서 포인터를 원자적으로 바꾸는 것. 옛 데이터 파일과 옛 metadata.json은 그대로 남아 있어, 다른 리더는 깨지지 않는다.
- 데이터 파일 자체에는 "테이블이 어떤 모양이어야 한다"는 정보가 없다. 컬럼 이름·타입·파티션 등 모든 정보는 metadata 계층에 있다. 따라서 스키마·파티션 진화를 데이터 재작성 없이 수행할 수 있다.
3.2 metadata.json의 실제 내용
대략 다음과 같은 구조다(V2 기준, 단순화).
{
"format-version": 2,
"table-uuid": "5f8a...e9",
"location": "s3://bucket/warehouse/db/events",
"last-updated-ms": 1747804800000,
"last-column-id": 12,
"schemas": [
{
"schema-id": 0,
"fields": [
{ "id": 1, "name": "event_id", "required": true, "type": "long" },
{ "id": 2, "name": "user_id", "required": false, "type": "long" },
{ "id": 3, "name": "event_ts", "required": true, "type": "timestamptz" },
{ "id": 4, "name": "event_type", "required": true, "type": "string" }
]
}
],
"current-schema-id": 0,
"partition-specs": [
{
"spec-id": 0,
"fields": [
{ "name": "event_day", "source-id": 3, "transform": "day", "field-id": 1000 }
]
}
],
"default-spec-id": 0,
"sort-orders": [ ... ],
"current-snapshot-id": 8123412345678901234,
"snapshots": [
{
"snapshot-id": 8123412345678901234,
"timestamp-ms": 1747804790000,
"summary": {
"operation": "append",
"added-data-files": "3",
"added-records": "1450000"
},
"manifest-list": "s3://.../snap-8123-1-abc.avro",
"schema-id": 0
}
],
"refs": {
"main": { "snapshot-id": 8123..., "type": "branch" },
"wap-2026-05-20": { "snapshot-id": 8123..., "type": "tag" }
}
}핵심 관찰:
fields안의id가 컬럼의 진짜 식별자다. 이름이 바뀌어도 ID는 그대로 유지된다.partition-specs는 배열이다. 즉 한 테이블이 시간에 따라 다른 파티션 사양을 가질 수 있다.snapshots는 누적된다. 만료(expire) 전까지는 시간 여행을 위해 모두 보존된다.refs는 Git 같은 브랜치/태그 참조다.main외에 사용자가 정의한 브랜치/태그도 모두 동등하게 다뤄진다.
3.3 Manifest list / Manifest의 역할 분담
쿼리 시 파일을 빠르게 Pruning하기 위해, 데이터 파일에 대한 정보는 두 단계로 모인다.
Query Pruning 흐름 (WHERE event_day = '2026-05-20' AND user_id = 42)
- 카탈로그에서 현재 metadata.json 위치를 얻는다.
- metadata.json → 현재 스냅샷의 manifest list를 읽는다.
- manifest list의 각 row(=manifest 파일)에 대해, 요약된 파티션 범위로 1차 Pruning.
event_day = '2026-05-20'을 포함하지 않는 manifest는 건너뛴다. - 살아남은 manifest만 읽는다. 그 안의 각 data file의
user_idlower/upper bound로 2차 Pruning. - 남은 데이터 파일만 실제로 연다.
이 구조의 효과:
- 수십만 개의 데이터 파일을 가진 테이블에서도 쿼리당 수십~수백 개의 파일만 검토한다.
- 디렉터리 list가 필요 없어, S3 list 비용·일관성에 의존하지 않는다.
- 통계(lower/upper/null)가 manifest에 응축되어 있어, Parquet footer를 모두 열지 않아도 된다.
3.4 Hidden Partitioning(숨겨진 파티셔닝)
Hive 시대 가장 큰 페인포인트 중 하나가 "파티션 표현식과 Query Predicate가 일치해야 한다" 였다. Iceberg는 이를 메타데이터에 변환(transform)을 등록하는 방식으로 해결한다.
-- DDL: event_ts를 일 단위로 파티셔닝 (단, 데이터에는 event_ts만 저장)
CREATE TABLE events (
event_id BIGINT,
user_id BIGINT,
event_ts TIMESTAMP,
event_type STRING
)
USING iceberg
PARTITIONED BY (days(event_ts));
-- 쿼리: 파티션 컬럼을 알 필요가 없다
SELECT count(*)
FROM events
WHERE event_ts >= TIMESTAMP '2026-05-20 00:00:00'
AND event_ts < TIMESTAMP '2026-05-21 00:00:00';- 데이터에는
event_ts만 저장되고, 파티션 키event_day는 메타데이터에만 존재한다. - Query Predicate가
event_ts에 걸려도, 엔진은 partition spec의 transform(days(event_ts))을 이해해 자동으로 Partition Pruning을 수행한다. - 사용자가 별도로
event_day = ...같은 조건을 적을 필요가 없다. "파티션 키"가 사용자에게서 숨겨진다.
지원되는 transform: identity, bucket(N, col), truncate(W, col), year, month, day, hour, void.
3.5 Schema Evolution
Iceberg는 컬럼에 영구 ID를 부여하기 때문에, 다음 변경을 데이터 재작성 없이 수행한다.
| 연산 | 안전성 | 비고 |
|---|---|---|
| 컬럼 추가 | 안전 | 기존 row는 NULL 또는 default |
| 컬럼 삭제 | 안전 | ID가 메타데이터에서만 사라짐, 데이터 파일은 그대로 |
| 컬럼 이름변경 | 안전 | ID 유지, 이름만 metadata.json에서 갱신 |
| 컬럼 재배치 | 안전 | 메타데이터의 필드 순서만 변경 |
| 타입 확장 | 일부 안전 | int → long, float → double, decimal precision 증가 등 |
| 타입 축소 | 불가 | long → int 같은 손실 변환은 금지 |
| nullable → required | 조건부 | 모든 기존 행이 not null임을 검증해야 함 |
이는 Parquet의 위치 기반 매핑이 아닌, Iceberg의 field-id 기반 매핑이 가능하게 한다. 데이터 파일에 컬럼 ID가 함께 기록되므로, 메타데이터에서 이름이 바뀌어도 일관된 컬럼을 찾을 수 있다.
3.6 Partition Evolution
Iceberg에서 파티션 사양은 시간에 따라 추가될 수 있다. 예를 들어 "처음에는 일별, 트래픽이 늘면서 시간별로" 같은 운영 패턴이 가능하다.
-- 처음: 일별 파티션
ALTER TABLE events SET TBLPROPERTIES (...);
-- partition spec id = 0 : (day(event_ts))
-- 운영 중에 시간별로 변경
ALTER TABLE events
REPLACE PARTITION FIELD event_ts WITH hours(event_ts);
-- partition spec id = 1 : (hour(event_ts))운영상 주의점:
- 과거 데이터는 옛 partition spec으로 남아 있고, 새 쓰기만 새 spec을 따른다.
- 따라서 동일 테이블 안에 두 가지 partition spec이 공존한다. 쿼리는 두 spec 모두에 대해 정확하게 동작하지만, Pruning 효율은 spec별로 다르다.
- 필요하면
rewrite_data_files로 옛 데이터를 새 spec에 맞춰 재작성할 수 있다(데이터 이동 비용 발생).
3.7 V1 vs V2 vs V3 — 사양의 진화
| 항목 | V1 | V2 | V3 (2025~) |
|---|---|---|---|
| 표준화 시점 | 2018~ | 2021~ | 2025+ |
| Row-level Delete | 불가 (CoW만) | Position / Equality delete file | Deletion vectors (Puffin) |
| 시퀀스 번호 | 없음 | 있음 (정합성 위해 필수) | 유지 |
| 컬럼 default value | 없음 | 없음 | 있음 |
| Variant 타입 | 없음 | 없음 | 있음 (반구조화) |
| Geospatial 타입 | 없음 | 없음 | 있음 |
| Row Lineage | 없음 | 없음 | 있음 (CDC 친화) |
V2가 만든 변화 — Row-level Delete:
- V1에서는 한 행을 지우려면 그 행이 속한 파일 전체를 다시 써야 했다(Copy-on-Write, CoW).
- V2는 두 종류의 delete file을 도입해 "옛 파일 + 삭제 표시"로 표현할 수 있게 했다(Merge-on-Read, MoR).
- Position delete:
(파일경로, 행 위치)형태. CDC·MERGE 워크로드에 유리. - Equality delete:
(컬럼=값)형태. 키 기반 삭제에 유리.
- Position delete:
- 읽을 때 엔진은 delete를 메모리에서 적용한다. 자주 컴팩션해 주지 않으면 읽기가 느려진다.
V3가 만든 변화:
- Deletion Vectors(Puffin 포맷) — V2의 position delete를 더 효율적으로 표현. Roaring bitmap을 사용해 메모리·디스크 사용량이 줄어든다.
- Variant 타입 — JSON 같은 반구조화 데이터를 일관된 인코딩으로 저장하고, 엔진들이 같은 방식으로 해석한다.
- Row Lineage — 각 행에 안정적인 ID를 부여해 CDC·머신러닝 재현성에 활용.
3.8 Copy-on-Write vs Merge-on-Read
V2 이후 가장 중요한 운영 결정은 테이블 단위로 CoW와 MoR 중 무엇을 쓸지이다.
| 측면 | Copy-on-Write (CoW) | Merge-on-Read (MoR) |
|---|---|---|
| UPDATE/DELETE 시 동작 | 영향받는 파일을 새로 씀 | 옛 파일 유지 + delete file 추가 |
| 쓰기 비용 | 높음 (대형 파일 재작성) | 낮음 (작은 delete 파일만 쓰기) |
| 읽기 비용 | 낮음 (delete 적용 없음) | 높음 (delete를 메모리 적용) |
| 압축·정렬 상태 | 즉시 유지 | 점진적으로 악화, 컴팩션 필요 |
| 적합 워크로드 | 분석 위주, 정정이 드문 테이블 | CDC, GDPR 정정, 빈번한 upsert |
운영 권고:
- 분석 중심 테이블(예: 일별 집계, 별표 스키마 팩트)는 CoW 권장.
- CDC·MERGE 패턴(예: 실시간 사용자 상태 테이블)은 MoR + 주기적 컴팩션 권장.
- 테이블 속성으로 명시적으로 지정한다:
ALTER TABLE events SET TBLPROPERTIES ( 'write.delete.mode'='merge-on-read', 'write.update.mode'='merge-on-read', 'write.merge.mode' ='merge-on-read' );
3.9 Time Travel, Branch, Tag
Iceberg의 스냅샷 모델은 자연스럽게 Git 같은 데이터 버전 관리로 확장된다.
-- 시간 여행 (특정 시각)
SELECT * FROM events FOR SYSTEM_TIME AS OF '2026-05-20 09:00:00';
-- 스냅샷 ID로 직접 지정
SELECT * FROM events VERSION AS OF 8123412345678901234;
-- 브랜치 생성 (Write-Audit-Publish 패턴)
ALTER TABLE events CREATE BRANCH `wap-2026-05-20`;
-- 새 브랜치에서 변경 후 검증
INSERT INTO events.`wap-2026-05-20` SELECT ...;
-- 검증 통과 시 main으로 fast-forward
ALTER TABLE events FAST FORWARD `main` TO `wap-2026-05-20`;
-- 태그 (영구 보존 지점)
ALTER TABLE events CREATE TAG `q1-2026-close`
AS OF VERSION 8123412345678901234
RETAIN 365 DAYS;활용 패턴:
- Write-Audit-Publish (WAP) — 새 데이터를 일단 브랜치에 쓰고, 품질 검증(예: dbt test, Great Expectations)이 통과한 뒤에 main으로 병합. 검증 실패 시 브랜치만 폐기하면 끝이라, "잘못된 데이터가 잠시라도 main에 노출되는" 위험을 막는다.
- ML 실험 격리 — 모델 학습용 스냅샷을 태그로 고정(예:
model-v3-train). 6개월 뒤에도 동일한 데이터로 재학습 가능. - 법적 정정·감사 — 정정 직전 상태를 태그로 보존, 감사인에게 "정정 전·후"를 모두 보여줄 수 있다.
4. 운영 관점의 Iceberg
4.1 유지보수 작업 표준 셋
Iceberg를 운영한다는 것은 곧 다음 네 가지 유지보수 작업을 자동화한다는 의미다.
| 작업 | 무엇을 하는가 | 빈도 권장 |
|---|---|---|
rewrite_data_files | 작은 파일을 큰 파일로 합치고, 정렬 순서를 적용 | 일~주 단위 |
rewrite_manifests | manifest를 재구성해 Pruning 효율 회복 | 주~월 단위 |
expire_snapshots | 오래된 스냅샷과 그것만 참조하던 파일을 정리 | 일 단위 |
remove_orphan_files | 어떤 메타데이터도 참조하지 않는 데이터·메타 파일 정리 | 주~월 단위 |
이 네 작업을 빠뜨리면 어떤 일이 일어나는가:
- 작은 파일 폭증 — 스트리밍 잡이 10초마다 커밋하면 하루 8,640개 파일. 한 달이면 26만 개. 쿼리당 manifest를 수천 개씩 읽어야 한다.
- 메타데이터 폭증 — 스냅샷이 수만 개 쌓이면 metadata.json 자체가 수십 MB가 되고, 모든 쓰기가 그것을 통째로 다시 쓰기 때문에 커밋이 느려진다.
- 저장 비용 폭증 — expire가 안 되면, 한 행을 1억 번 업데이트한 테이블의 실제 저장량은 원본의 수십 배가 된다.
4.2 컴팩션 설계
-- Spark SQL: 기본 컴팩션
CALL system.rewrite_data_files(
table => 'db.events',
options => map(
'min-input-files', '5',
'target-file-size-bytes','536870912', -- 512 MiB
'rewrite-all', 'false'
)
);
-- 정렬 추가
CALL system.rewrite_data_files(
table => 'db.events',
strategy => 'sort',
sort_order => 'event_ts ASC, user_id ASC'
);설계 원칙:
- target-file-size = 256~1024 MiB 사이. 너무 작으면 list/open 비용, 너무 크면 셔플·메모리 부담.
- 정렬 키는 가장 자주 Pruning에 쓰이는 컬럼으로. lower/upper bound 효율이 극대화된다.
- MoR 테이블은 delete file 적용까지 한 번에 처리되므로, 정기 컴팩션이 쿼리 성능 유지의 핵심.
- 잡 크기 제어 — 한 번에 모든 파일을 다시 쓰지 말고, 파티션 또는 시간 범위 기반으로 점진 컴팩션.
where옵션으로 범위를 좁힌다.
4.3 스냅샷 만료와 정리
-- 7일 이전 스냅샷 만료, 최소 5개는 보존
CALL system.expire_snapshots(
table => 'db.events',
older_than => TIMESTAMP '2026-05-13 00:00:00',
retain_last => 5
);
-- 어떤 스냅샷도 참조하지 않는 고아 파일 정리 (3일 이상 묵은 것)
CALL system.remove_orphan_files(
table => 'db.events',
older_than => TIMESTAMP '2026-05-17 00:00:00'
);운영 권고:
older_than은 보수적으로 — 진행 중인 장기 잡(예: 6시간짜리 백필)이 옛 스냅샷을 참조하고 있을 수 있다.remove_orphan_files는 신중히 — 잘못 호출하면 다른 잡이 동시에 쓴 파일을 지울 수 있다. 드라이런(dry_run => true)으로 먼저 검증한 뒤 실행한다.- 시간 여행 SLA를 결정 — "30일 이전 시점은 복원하지 않는다"는 정책을 명시하면, 만료 기준이 명확해진다.
4.4 카탈로그 선택
Iceberg는 카탈로그를 추상화하지만, 실제로 어떤 카탈로그를 쓸지가 운영 모델을 좌우한다.
| 카탈로그 | 적합 시나리오 | 한계 |
|---|---|---|
| Hive Metastore (HMS) | 기존 Hive 자산 위에서 점진 도입 | 멀티-엔진 환경에서 일관성·권한 모델 약함 |
| AWS Glue | AWS 단일 클라우드, Athena/EMR/Redshift 통합 | AWS 외부에서는 사용 불편 |
| REST Catalog | 멀티-엔진·멀티-클라우드, 표준 사양 기반 | 자체 호스팅·운영 부담, 백엔드 구현체 선택 필요 |
| Project Nessie | Git 같은 데이터 버저닝(브랜치/머지) | 권한·SaaS 옵션 제한 |
| Snowflake Polaris | Snowflake 중심 환경의 멀티-엔진 공유 | Snowflake 종속도 일부 잔존 |
| Databricks Unity Catalog | Databricks 중심 환경, UC가 Iceberg를 일급으로 다룸 | UC 외부 엔진은 별도 REST 어댑터 필요 |
권장 패턴: 신규 멀티-엔진 환경이라면 REST Catalog 사양을 따르는 백엔드(예: Apache Polaris, Tabular OSS, Lakekeeper, Apache Gravitino, Unity Catalog OSS)를 두고, Spark·Trino·Flink·BigQuery·Snowflake 모두 그것을 통해 접근한다.
4.5 쓰기 모드와 분배 튜닝
ALTER TABLE events SET TBLPROPERTIES (
'write.distribution-mode' = 'hash', -- 'none' | 'hash' | 'range'
'write.target-file-size-bytes' = '536870912', -- 512 MiB
'write.parquet.compression-codec' = 'zstd',
'write.parquet.row-group-size-bytes' = '134217728',
'commit.retry.num-retries' = '8',
'commit.retry.min-wait-ms' = '500'
);핵심 파라미터:
write.distribution-modenone— 입력 분포 그대로. 작은 파일이 많이 생긴다.hash— 파티션 컬럼 해시로 분배. 일반적 권장. 파티션당 파일 수가 일정해진다.range— 정렬된 분포. 시간순 로그 같은 워크로드에 유리.
commit.retry.*— 다중 작성자 환경에서 낙관적 잠금(optimistic concurrency control) 실패 시 재시도 정책. 충돌이 잦으면 늘려두는 것이 안전.
4.6 모니터링 지표
Iceberg 테이블이 건강한지 보려면 다음 지표를 정기적으로 봐야 한다.
| 지표 | 의미 | 적신호 |
|---|---|---|
| 파티션당 평균 파일 수 | 컴팩션 효과 | 100개 초과 |
| 평균 파일 크기 | 컴팩션·쓰기 분배 | 32 MiB 미만 |
| 스냅샷 누적 수 | 만료 정책 작동 여부 | 1,000 초과 |
| metadata.json 크기 | 메타 폭증 신호 | 8 MiB 초과 |
| manifest 평균 크기·수 | Pruning 효율 | manifest 수 5,000 초과 |
| 평균 커밋 지연 | 카탈로그·동시성 문제 | p95가 5 s 초과 |
| delete file/data file 비율 | MoR 압축 필요 | 5% 초과 시 컴팩션 권장 |
이 값을 주기적으로 카탈로그·메타데이터에서 추출해 대시보드에 올리는 것이 운영의 표준이다. Iceberg의 시스템 테이블(db.events.files, db.events.snapshots, db.events.manifests)을 그대로 사용할 수 있다.
-- 파일 통계
SELECT
partition,
count(*) AS file_count,
avg(file_size_in_bytes) AS avg_size,
sum(file_size_in_bytes) AS total_size
FROM db.events.files
GROUP BY partition
ORDER BY file_count DESC;
-- 스냅샷 누적
SELECT count(*) FROM db.events.snapshots;4.7 운영 자동화의 기본 형태
성숙한 Iceberg 운영팀이 갖춰야 할 자동화 잡 세트:
- 일별 컴팩션 잡 — 어제 파티션에 대해
rewrite_data_files(작은 파일이 임계치를 넘는 파티션만). - 일별 만료 잡 — N일 이전 스냅샷 만료, 일정 개수는 항상 보존.
- 주별 manifest 재작성 잡 —
rewrite_manifests. - 월별 고아 정리 잡 —
remove_orphan_files(드라이런 → 검증 → 실행). - 테이블 건강도 리포트 잡 — 위 모니터링 지표를 추출해 대시보드·알림으로 송출.
이 잡들은 모두 멱등적이어야 하며, 실패해도 안전하게 재시도할 수 있어야 한다. 대형 환경에서는 이 자동화를 별도의 "테이블 관리 서비스"로 분리해 카탈로그와 함께 운영하는 것이 표준화되어 있다.
5. 엔진 호환성
5.1 핵심 엔진별 지원 현황 (2026년 시점)
| 엔진 | 읽기 | 쓰기 | DML | 시간여행 | 브랜치/태그 | V2 (MoR) | V3 | REST Catalog |
|---|---|---|---|---|---|---|---|---|
| Apache Spark | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 진행중 | ✓ |
| Trino | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 부분 | ✓ |
| Apache Flink | ✓ | ✓ (스트리밍) | 일부 | ✓ | 일부 | ✓ | 진행중 | ✓ |
| Snowflake | ✓ | ✓ | ✓ | ✓ | 제한 | ✓ | 진행중 | ✓ (Polaris) |
| Databricks (Unity) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 진행중 | ✓ (UC OSS) |
| BigQuery | ✓ | 일부 | 일부 | ✓ | 일부 | ✓ | 진행중 | ✓ (BigLake) |
| AWS Athena | ✓ | ✓ | ✓ | ✓ | 일부 | ✓ | 진행중 | ✓ (Glue) |
| ClickHouse | ✓ | 실험적 | ✗ | ✓ | ✗ | 부분 | ✗ | ✓ |
| DuckDB | ✓ | 실험적 | ✗ | ✓ | ✗ | 부분 | ✗ | ✓ |
| PyIceberg | ✓ | ✓ | 일부 | ✓ | ✓ | ✓ | 진행중 | ✓ |
위 표는 2026년 5월 기준 일반적 지원 수준 요약이며, 실제 채택 시에는 해당 엔진의 최신 릴리스 노트와 Iceberg 사양 호환 표를 함께 확인할 것을 권한다.
5.2 엔진별 권장 역할
- Spark — 백필·대형 ETL·테이블 유지보수의 표준.
system.*프로시저가 가장 완성도 높음. - Trino — 인터랙티브 분석·BI 백엔드. 짧은 쿼리에 강점, MoR 적용도 안정적.
- Flink — 스트리밍 적재. exactly-once 커밋과 V2 delete write에 강점.
- Snowflake / Databricks — 사내 사용자 셀프 서비스·BI. 카탈로그를 통한 공유 테이블 패턴.
- BigQuery / Athena — 보고서·임시 분석. 별도 인프라 운영 없이 쿼리만 필요할 때.
- PyIceberg — 가벼운 ETL, ML 학습 파이프라인, 로컬·노트북에서의 검증.
5.3 REST Catalog의 의미
REST Catalog 표준이 정착한 2024~2025년 이후, **"엔진과 카탈로그를 분리해 자유롭게 조합한다"**가 현실이 됐다.
이 의미는 결정적이다.
- 엔진 종속을 끊는다. 한 엔진에서 다른 엔진으로 옮길 때 데이터 마이그레이션이 필요 없다.
- 권한·감사·정책을 한 곳에 모은다. 카탈로그가 진정한 통제점(control plane)이 된다.
- 새 엔진의 도입 비용이 낮아진다. REST 사양을 구현한 모든 엔진은 즉시 같은 테이블을 다룰 수 있다.
6. 다른 포맷과의 비교
6.1 Iceberg vs Delta Lake vs Hudi — 핵심 차이
| 항목 | Iceberg | Delta Lake | Hudi |
|---|---|---|---|
| 출발점 | 멀티-엔진, 메타데이터 중심 | Spark·Databricks 중심, 트랜잭션 로그 | 스트리밍 upsert·증분 처리 |
| 메타데이터 모델 | 스냅샷 + manifest 트리 | 트랜잭션 로그(JSON) + 체크포인트 | 타임라인(.hoodie) + 메타 |
| 카탈로그 추상화 | 일급 (REST 사양) | 부수적 (Unity가 점차 채움) | 외부 의존 |
| Hidden Partitioning | ✓ | 제한적 (Generated Column) | 일부 |
| Partition Evolution | ✓ | 제한적 | 일부 |
| Schema Evolution | 안전(ID 기반) | 안전(이름 기반) | 안전 |
| Row-level Delete | V2 delete files / V3 vectors | Deletion Vectors | Soft delete + 컴팩션 |
| 시간여행 | ✓ | ✓ | ✓ |
| 브랜치/태그 | ✓ (Git 같은 의미) | 제한적 (Time travel만) | 제한적 |
| 멀티-엔진 성숙도 | 가장 높음 | Databricks 중심, 외부 엔진 향상 중 | Spark/Flink 중심 |
| 스트리밍 워크로드 | 가능, 점점 강해짐 | 가능 | 가장 성숙 |
| 표준 사양 공개 | 사양 v3까지 공개·합의 | 사양 공개되나 Databricks 주도 | 공개 |
6.2 의사결정 트리
6.3 Delta-Iceberg 호환 옵션
2024년 이후 두 진영 모두 상호운용을 추구하면서 다음 옵션들이 등장했다.
- Delta UniForm — Delta 테이블 옆에 Iceberg 메타데이터를 함께 생성해, Iceberg 리더가 Delta 테이블을 읽을 수 있게 한다. 단방향(Delta → Iceberg 읽기).
- Apache XTable (구 OneTable) — 세 포맷(Iceberg/Delta/Hudi) 사이의 메타데이터를 변환한다. 데이터는 공유하고, 메타데이터만 각 포맷으로 표현.
- Iceberg REST를 Delta가 노출 — Unity Catalog OSS가 Iceberg REST 사양으로 Delta 테이블을 노출하는 방향으로 가고 있다.
호환 옵션은 편리하지만, **"네이티브 사양 그대로"**가 항상 가장 안정적이다. 호환 모드의 알려진 한계(예: V2 delete 호환 미지원 구간)는 도입 전에 명확히 검토해야 한다.
7. 도입 전략과 마이그레이션 패턴
7.1 어떤 워크로드부터 도입할 것인가
도입 우선순위 권고:
- 장기 보존·법적 정정 대상 데이터 — 시간여행과 브랜치/태그 가치가 즉시 발현.
- 멀티-엔진 공유가 필요한 핵심 팩트 테이블 — 한 번 표준화하면 사내 분석 인프라 전체에 파급.
- 새로 시작하는 도메인의 마트 — 가장 적은 리스크로 운영 경험 축적.
- 기존 Hive 핵심 테이블 — 가장 큰 이득이 있지만 마이그레이션 부담도 크다. 위 1~3에서 운영 노하우를 확보한 뒤 진입.
7.2 Hive → Iceberg 마이그레이션 옵션
세 가지 표준 패턴이 있다.
(a) migrate — 제자리 교체
CALL system.migrate('hive_db.events');
-- Hive 테이블의 metadata를 Iceberg metadata로 교체.
-- 데이터 파일은 그대로 유지. 가장 빠른 옵션.- 장점: 데이터 이동 없음, 분 단위로 끝남.
- 단점: 옛 Hive 디렉터리 구조(파티션 키 표현)가 그대로 남아 hidden partitioning의 이점을 일부 못 받음. 옛 파일에 컬럼 ID가 부여되며, 일부 엔진은 첫 쿼리에서 추가 비용 발생.
(b) snapshot — 그림자 테이블
CALL system.snapshot('hive_db.events', 'iceberg_db.events_v2');
-- Hive 테이블을 그대로 두고, 같은 데이터 파일을 참조하는 Iceberg 테이블을 생성.
-- 검증·비교 기간 동안 양쪽에 쓰기를 병행할 수 있다.- 장점: 운영 중 안전한 비교·롤백 가능.
- 단점: 두 메타데이터를 동시에 유지해야 하는 기간이 생김.
(c) CTAS — 풀 재작성
CREATE TABLE iceberg_db.events
USING iceberg
PARTITIONED BY (days(event_ts))
TBLPROPERTIES ('write.distribution-mode'='hash')
AS SELECT * FROM hive_db.events;- 장점: 새 파티션·정렬·압축 코덱·파일 크기 정책을 처음부터 적용. 가장 깨끗한 상태.
- 단점: 데이터를 다시 쓴다. 페타바이트급은 시간·비용이 크다.
권장: 핵심 테이블·장기 운영 테이블은 (c), 단기 비교가 필요한 경우 (b), 빠른 도입이 우선이면 (a).
7.3 Delta → Iceberg
옵션:
- UniForm 사용 — Delta는 그대로 두고 Iceberg 메타데이터를 추가 생성. 읽기만 필요할 때 가장 저렴.
- XTable로 양방향 메타데이터 변환 — 데이터는 공유, 메타데이터는 양쪽 포맷 모두로 노출.
- CTAS로 풀 재작성 — Iceberg 네이티브 운영을 시작할 준비가 됐다면 권장.
운영 권고: Delta 위에서 운영 중인 핵심 워크로드는 즉시 옮기지 말고, 새 도메인이나 그림자 테이블로 Iceberg 운영 경험을 6~12개월 쌓은 뒤 단계적으로 이전한다.
7.4 단계별 도입 체크리스트
0단계 — 사전 평가 (2~4주)
- 사내 엔진·카탈로그·스토리지 현황 인벤토리
- 후보 워크로드 3개 선정 (도입 우선순위 기준에 따름)
- 카탈로그 선택 결정 (REST / Glue / Unity / Polaris 등)
- 운영 자동화 잡 계획 수립
1단계 — PoC (4~8주)
- 후보 테이블 1개에 대해
snapshot또는 CTAS로 Iceberg 버전 생성 - 두 엔진(예: Spark + Trino)으로 동일 결과 검증
- 컴팩션·만료·고아 정리 잡 실행, 모니터링 지표 수집
- WAP 패턴, 시간여행 사례 1건 실제 적용
2단계 — 운영 자동화 (4~8주)
- 위 §4.7의 자동화 잡 5종 전사 표준 정의 및 배포
- 카탈로그·권한·감사 모델 정착
- 모니터링 대시보드·알림 임계값 합의
- 운영 Runbook 문서화 (실패 시나리오 포함)
3단계 — 확장 (3~6개월)
- 도입 우선순위에 따라 핵심 테이블 마이그레이션
- 사내 데이터 카탈로그·BI·ML 파이프라인을 새 카탈로그에 연결
- Iceberg V3 도입 여부 결정 (변동성·성숙도 평가)
7.5 마이그레이션 시 흔한 실수
- 카탈로그 결정 미루기 — "일단 데이터부터 옮기자"는 접근은 곧 카탈로그가 운영의 병목이 된다. 1순위로 결정한다.
- 운영 자동화 늦추기 — PoC에서 잘 돌던 것이 6개월 후 메타데이터 폭증으로 멈춘다. 운영 자동화는 PoC와 동시에 만든다.
- CoW vs MoR 일괄 결정 — 테이블별 워크로드 특성을 무시하고 한 가지로 통일하면, CDC 테이블이 느려지거나 분석 테이블이 정렬을 잃는다. 테이블 단위로 결정한다.
- 시간여행 SLA 미정의 — "언제까지의 데이터를 복원할 수 있는가"를 정하지 않으면 만료 정책이 보수적이 되어 비용이 계속 증가한다.
- 컴팩션 잡의 셔플 폭주 — 한 잡이 너무 큰 범위를 컴팩션하면 클러스터를 마비시킨다. 범위·시간을 잘게 쪼개고, 잡 단위 자원 한도를 명시한다.
8. 2026년 시점 전망
8.1 사양 진화
- V3의 보편화 — Variant·Geospatial·Deletion Vectors가 2026년 후반부터 주요 엔진에서 GA로 자리잡을 것으로 보인다. CDC·실시간 분석 워크로드에서 V3의 이득이 가장 크다.
- Row Lineage의 부상 — 행 단위 안정 ID는 CDC·feature store·재현 가능한 ML 학습에 직접적 의미가 있다. 이는 데이터 거버넌스·계보(lineage) 도구와 결합되며 새로운 운영 패턴을 만들 것이다.
- Materialized View의 표준화 — Iceberg 위의 MV·집계 캐시가 사양 수준에서 합의되면서, 분석 워크로드 비용 구조가 다시 바뀔 가능성이 크다.
8.2 카탈로그 진영의 합종연횡
- Polaris(공개)·Unity OSS·Lakekeeper·Apache Gravitino 등이 REST 사양을 공통 표준으로 두고 경쟁한다. 표준 사양이 있기 때문에, 어떤 백엔드를 쓰든 사용자는 동일한 인터페이스를 본다.
- 상용 서비스 vs OSS의 균형 — 카탈로그를 SaaS로 쓸지, 직접 운영할지가 향후 3년의 핵심 결정이 된다. 권한·감사·메타데이터 관리의 깊이가 비용을 좌우한다.
8.3 엔진 측의 변화
- AI/ML 워크로드와의 통합 — Iceberg의 브랜치/태그·시간여행은 학습 데이터 재현, 모델·데이터 버전의 동기화에 활용된다. 2026년 이후 feature store·MLOps 도구가 Iceberg를 일급으로 다루는 흐름이 강해진다.
- OLAP 엔진의 직접 지원 확대 — ClickHouse·StarRocks·DuckDB 등 OLAP 엔진의 Iceberg 직접 쓰기 지원이 빠르게 성숙 중이다.
8.4 데이터 거버넌스의 통제점 이동
Iceberg·REST Catalog 조합은 거버넌스의 통제점을 "엔진"에서 "카탈로그"로 이동시킨다. 데이터 마스킹·행 단위 필터·감사 로그가 카탈로그 레벨에서 결정되면, 어떤 엔진에서 접근해도 동일한 정책이 적용된다. 이는 멀티-엔진 환경의 컴플라이언스를 사실상 처음으로 일관되게 만든다.
9. 결론과 권고
9.1 핵심 메시지
- Iceberg는 단순한 테이블 포맷이 아니라, 개체 스토리지 위의 데이터베이스 의미론을 제공하는 메타데이터 사양이다.
- 그 가치는 단일 엔진 환경에서는 부분적으로만 드러나며, 멀티-엔진·장기 운영·정정·실험 격리 워크로드에서 결정적이다.
- 2026년 현재 REST Catalog 표준 + V3 사양의 조합으로, Iceberg는 사실상 멀티-엔진 Lakehouse의 표준 포맷이 됐다.
- 그러나 운영 자동화 없이 Iceberg를 도입하면 곧 메타데이터 운영 비용이 도입 효과를 상쇄한다. 컴팩션·만료·고아 정리·모니터링은 사양 이해와 동등하게 중요하다.
9.2 도입 권고
| 시나리오 | 권고 |
|---|---|
| 신규 멀티-엔진 데이터 플랫폼 구축 | Iceberg + REST Catalog를 처음부터 표준으로 채택 |
| 기존 Databricks 단일 환경 + 외부 엔진 도입 검토 | Delta UniForm 또는 점진적 Iceberg 도입 |
| Hive 기반 레거시 마이그레이션 | 우선순위 테이블부터 단계적 도입, CTAS 권장 |
| 실시간 CDC·upsert 중심 | Iceberg V2/V3 (MoR) 또는 Hudi와 비교 검토 |
| ML·실험 재현성 중요 | Iceberg 브랜치/태그·시간여행 활용 |
9.3 Data Dynamics의 지원 영역
본 백서를 작성한 Data Dynamics는 다음 영역에서 Iceberg 도입을 지원합니다.
- Lakehouse 아키텍처 설계 및 검토 — 카탈로그·엔진·스토리지 토폴로지 권고
- 마이그레이션 실행 — Hive·Delta로부터의 단계적 이전, PoC부터 운영 자동화까지
- 운영 자동화 표준화 — 컴팩션·만료·정리 작업의 멱등 잡 설계, 모니터링 대시보드
- 카탈로그 운영 — REST Catalog 백엔드(Polaris, Unity OSS, Lakekeeper) 선정·운영
- 엔진 통합 — Spark·Trino·Flink·Snowflake·Databricks·BigQuery 간 일관성 검증
도입 전 진단·기술 워크숍이 필요한 경우 문의로 연락 주시면, 사내 환경에 맞춘 도입 로드맵을 함께 정리해 드립니다.
10. 참고 자료
- Apache Iceberg 공식 사양 — iceberg.apache.org/spec
- Apache Iceberg 문서 — iceberg.apache.org/docs
- Iceberg REST Catalog 사양 — Apache Iceberg 저장소 내
open-api/rest-catalog-open-api.yaml - Apache Polaris — polaris.apache.org
- Apache XTable — xtable.apache.org
- PyIceberg — py.iceberg.apache.org
- Delta Lake 사양 — delta.io/protocol
- Apache Hudi 문서 — hudi.apache.org
- Netflix Tech Blog — Iceberg 도입 사례
- AWS·Snowflake·Databricks 각 사의 Iceberg 통합 안내서
- 본 사이트 관련 글: Delta Lake 완벽 가이드 (Delta·Iceberg 비교 중심)
본 백서는 2026년 5월 기준 정보를 바탕으로 작성되었습니다. Iceberg 사양과 엔진 호환 상태는 빠르게 진화하므로, 도입 시점의 최신 릴리스 노트와 사양 호환 표를 함께 확인할 것을 권합니다.