본문 바로가기
빅데이터(Big Data)

Iceberg·Hudi·Delta 분열을 끝낸 메타데이터 변환기, Apache XTable

by forward error correction Circle 2026. 6. 29.
반응형

Ⅰ. XTable 기술이란?

레이크하우스를 1년쯤 굴리면 거의 모든 팀이 같은 결정에 부딪힙니다. "우리는 Iceberg로 갈까, Hudi로 갈까, Delta로 갈까?" 한 포맷을 고르면 어떤 엔진이 손해 봅니다. 현실은 다음과 같습니다.

엔진 선호  포맷 현실적인 결과
Snowflake / BigQuery / Trino Apache Iceberg 1급 분석·쿼리 엔진은 거의 Iceberg로 수렴
Databricks / Spark Photon Delta Lake 1급 ETL·ML 파이프라인은 Delta가 최상
Flink / 실시간 CDC Apache Hudi 1급 스트리밍 UPSERT·인덱스는 Hudi가 가장 성숙
같은 데이터를 모두에서 3중 복제 / dual-write 스토리지 3배, 일관성 깨짐, 새벽 호출

그래서 팀들은 같은 데이터를 3개 포맷으로 중복 작성하거나, Spark Job으로 매번 재변환합니다. 결국 비용은 3배, 동기화 lag은 분 단위, "Iceberg 테이블에는 보이는데 Delta 테이블에는 아직 안 보여요" 같은 장애가 분기마다 나옵니다.

 

기술 정의

Apache XTable(Incubating)은 Iceberg·Hudi·Delta 사이의 메타데이터를 N방향으로 변환·동기화하는 자바 기반 OSS 라이브러리·CLI입니다(어떤 포맷이든 source가 될 수 있음, 다만 한 시점에는 한 포맷이 source-of-truth). 핵심 원칙은 "데이터 파일은 한 번만 쓴다"입니다. Parquet 데이터는 손대지 않고, 각 포맷이 요구하는 메타데이터(_delta_log/*.json, metadata.json + manifest, .hoodie/timeline)만 추가로 적어 놓아, 같은 디렉터리를 3개 포맷으로 동시에 노출합니다. 라이선스는 Apache 2.0, 2023년 11월 Onehouse·LinkedIn·Microsoft·Google이 공동 기증해 ASF Incubator에 들어갔습니다.

Ⅱ. XTable 기술 특징

특징 의미와 실전 효과
Metadata-Only 변환 Parquet 데이터 파일은 단 한 벌. 스토리지 비용 1배 그대로, 메타데이터 디렉터리만 작은 JSON·avro로 추가됨.
3-Way 변환 Iceberg ↔ Hudi ↔ Delta. 한 번 실행으로 1개 source → N개 target.
Incremental Sync 매번 풀 스캔이 아니라 마지막 동기화 이후의 커밋·스냅샷만 따라잡음. 큰 테이블에서 분 단위 동기화 가능.
Catalog-Agnostic Hive Metastore·Glue·Unity·Nessie·REST(Lakekeeper) 어느 카탈로그든 등록 가능. XTable 자체는 카탈로그를 강제하지 않음.
Internal Table 모델 포맷 중립 IR(Intermediate Representation). 새 포맷은 SourceClient/TargetClient 두 인터페이스만 구현하면 들어옴.
One-Way 동기화 권장 Source 포맷에만 쓰고, Target은 읽기 전용 미러로 사용. 양방향 쓰기는 충돌 위험.
Spark / Flink 통합 단발 CLI, Airflow Operator, Spark Job 모두 가능. RunSync 클래스만 부르면 됨.
Apache 2.0 / Incubating 벤더 중립. Onehouse·LinkedIn·Microsoft·Google·Walmart·Cloudera 등이 공동 기여 중.

Ⅲ. XTable 기술 동작방식

구성 요소 — 4개의 부품

컴포넌트 역할
SourceClient 원본 포맷(예: Hudi)의 최신 스냅샷·커밋 타임라인·스키마·파티션 스펙·파일 목록·통계를 읽어 들이는 어댑터.
InternalTable (Canonical IR) 포맷 중립 표현. 데이터 파일 경로, 행 수·min·max 통계, 파티션 값, 스키마 트리, 스냅샷 ID 등이 정규화돼 들어 있음.
TargetClient 타깃 포맷(예: Iceberg)의 메타데이터 파일을 작성. Iceberg는 metadata.json + manifest list + manifest, Delta는 _delta_log/*.json, Hudi는 .hoodie/타임라인.
RunSync (Runner) 단발(one-shot) 또는 스케줄 모드로 위 3개를 묶어 돌리는 오케스트레이터. CLI 진입점.

 

데이터 흐름 — Hudi 원본을 Iceberg·Delta로 미러링

  1. 커밋 발생: Flink/Spark가 Hudi 테이블에 새 커밋(.hoodie/2026...commit)을 남김.
  2. SourceClient 읽기: HudiSourceClient가 마지막 동기화 스냅샷 ID 이후의 신규 커밋·신규 파일 목록·삭제 파일 목록을 추출.
  3. InternalTable 변환: Hudi의 fileGroup·logFile·baseFile 모델 → 포맷 중립 file/row/stat 모델로 매핑.
  4. TargetClient 쓰기:
    • IcebergTargetClient → metadata/v3.metadata.json + 새 manifest list + manifest 파일 작성.
    • DeltaTargetClient → _delta_log/00000000000000000003.json 추가(add/remove action).
  5. (옵션) 카탈로그 등록: Glue·Unity·Hive에 신규 metadata 위치 업데이트(외부 책임).
  6. 다음 라운드: 마지막 동기화 ID를 체크포인트(_xtable/lastSyncedCheckpoint.json)에 기록 → Incremental Sync.

Ⅴ. XTable 기술 구성 및 흐름도

ⅰ. 전체 아키텍처

                  쓰기 (Source-of-Truth)
        +-------------------------------+
        |   Flink / Spark / Kafka CDC   |
        +---------------+---------------+
                        |
                        v  Hudi 커밋
        +-------------------------------+
        |  s3://bucket/orders/          |
        |    ├─ part-001.parquet  (1)   |  <-- 데이터 파일은 1벌
        |    ├─ part-002.parquet  (1)   |
        |    ├─ .hoodie/         (Hudi) |  <-- 원본 메타데이터
        |    ├─ metadata/      (Iceberg)|  <-- XTable이 추가
        |    └─ _delta_log/    (Delta)  |  <-- XTable이 추가
        +---------------+---------------+
                        ^
                        |  메타데이터 동기화
        +-------------------------------+
        |        Apache XTable          |
        |  SourceClient(Hudi)           |
        |    -> InternalTable (IR)      |
        |       -> TargetClient(Iceberg)|
        |       -> TargetClient(Delta)  |
        +---------------+---------------+
                        |
        +---------------+---------------+
        |             읽기 측           |
        |  Snowflake / Trino  → Iceberg |
        |  Databricks / Photon → Delta  |
        |  Flink / Presto-Hudi → Hudi   |
        +-------------------------------+

 

ⅱ. 단계별 처리 흐름

단계 동작  실제 산출물
1. Discover SourceClient가 마지막 동기화 체크포인트 이후의 신규 커밋 발견 신규 commit timestamp, 변경 파일 목록
2. Extract 스키마·파티션 스펙·파일 통계 추출 InternalTable 객체
3. Transform 파티션 표현 변환(Hudi 디렉터리 ↔ Iceberg PartitionSpec ↔ Delta partition values) 포맷별 파티션·스키마 매핑
4. Write TargetClient가 메타데이터 파일 atomic write metadata.json, _delta_log/*.json
5. Checkpoint 마지막 동기화 ID 기록 → 다음 Incremental에서 사용 _xtable/lastSyncedCheckpoint.json
6. Catalog Sync (옵션) Glue·Unity·Hive에 새 메타데이터 위치 등록 카탈로그 entry update

Ⅴ. XTable 기술 설치 방법

ⅰ. 사전 요구 사항

  • Java 11 또는 17 (LTS 권장)
  • Maven 3.8+ (소스 빌드 시)
  • 객체 스토리지 자격 증명(예: AWS_ACCESS_KEY_ID, GCS, ABFS)
  • 대상 포맷의 카탈로그 접근 권한(선택)

ⅱ. 소스 빌드

git clone https://github.com/apache/incubator-xtable.git
cd incubator-xtable

# 전체 빌드(테스트 스킵하면 5~10분)
mvn clean package -DskipTests

# 결과물(CLI 실행 가능 jar)
ls xtable-utilities/target/xtable-utilities-*-bundled.jar

 

ⅲ. Docker로 빠르게 시도

# 작업 디렉터리에 config.yaml 준비 후
docker run --rm -v $(pwd):/work -w /work \
  -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
  eclipse-temurin:17-jre \
  java -jar /work/xtable-utilities-bundled.jar \
       --datasetConfig /work/config.yaml

. XTable 기술 사용 방법

ⅰ. 예제 1 — Hudi 원본을 Iceberg·Delta로 미러

# config.yaml
sourceFormat: HUDI
targetFormats:
  - ICEBERG
  - DELTA
datasets:
  - tableBasePath: s3://lakehouse-prod/orders/
    tableName: orders
    partitionSpec: order_date:VALUE
    namespace: sales
java -jar xtable-utilities-bundled.jar \
     --datasetConfig config.yaml

실행 후 s3://lakehouse-prod/orders/ 안에 metadata/(Iceberg)와 _delta_log/(Delta)가 생기고, 같은 Parquet을 Snowflake·Databricks가 동시에 읽을 수 있게 됩니다.

 

ⅱ. 예제 2 — Iceberg 원본을 Delta로 단방향 미러

sourceFormat: ICEBERG
targetFormats:
  - DELTA
datasets:
  - tableBasePath: s3://lakehouse-prod/events/
    tableName: events
    namespace: analytics

※ 운영 시 고려사항

주의 포인트 어떻게 해야 하는가
쓰기는 한 포맷만 Source-of-truth를 한 포맷으로 못 박기. 두 포맷에서 동시에 INSERT하면 둘 다 자기 메타데이터만 갱신해 데이터 정합성이 깨짐.
Sync 주기 vs SLA 기본 단발 실행. 분 단위 SLA가 필요하면 Airflow/cron으로 1분 간격, 그 미만은 별도 데몬 모드 필요.
카탈로그 등록은 별개 XTable은 메타데이터 파일을 만들 뿐, Glue/Unity 같은 외부 카탈로그에 자동 등록하지 않음. REGISTER TABLE 또는 카탈로그 동기화 스크립트가 필요.
Snapshot ID는 1:1 아님 Iceberg snapshotId, Delta version, Hudi instant time은 의미가 다름. Time travel을 cross-format으로 하면 결과가 달라질 수 있음. 동일 시점 비교는 timestamp로만.
파티션 표현 차이 Iceberg의 days(ts)·bucket(16, id) 같은 hidden partition은 Delta에 1:1 표현이 없음. Identity 파티션부터 시작 권장.
Hudi MoR(Merge-on-Read) Hudi MoR의 log 파일은 Iceberg·Delta에 매핑이 어려움. Compaction 후 base 파일만 노출되도록 정책 정리 필요.
스키마 evolution 차이 Iceberg는 컬럼 ID 기반, Delta·Hudi는 이름 기반. 컬럼 rename은 한쪽에서만 안전. add/drop 위주로 운영.
Vacuum/Cleaner 충돌 Source 포맷의 Vacuum/Cleaner가 파일을 지우면 mirror 포맷의 manifest는 깨진 참조를 갖게 됨. 보존 기간을 source ≥ mirror로 맞추기.
대용량 첫 동기화 테이블이 수십 TB·수백만 파일이면 첫 sync에 수 시간. Spark on K8s/EMR로 메모리 충분히(드라이버 8GB+) 잡아 돌리기.

트러블슈팅 

증상 원인 해결
Iceberg 미러에서 "missing manifest" 에러 Source(Hudi)의 Cleaner가 파일을 지웠는데 mirror manifest는 그 파일을 아직 참조 Hudi hoodie.cleaner.commits.retained를 키우고, sync 주기를 cleaner 주기보다 짧게.
Snowflake에서 row count 불일치 최신 커밋 후 Sync가 아직 안 돔, 또는 카탈로그가 옛 metadata.json 가리킴 Sync 후 ALTER ICEBERG TABLE ... REFRESH 호출. Glue·Unity의 metadata pointer 갱신 자동화.
Delta 미러에서 partition mismatch Iceberg Hidden Partition(days(ts)) → Delta는 컬럼 partition만 지원 파생 컬럼(date_col)을 명시 컬럼으로 두고 Identity partition으로 변경.
Sync가 OOM으로 죽음 첫 풀 sync에서 manifest list를 메모리에 모음 -Xmx8g 이상, 가능하면 Spark Job으로 분산 실행.
Delta time travel 결과가 Iceberg와 다름 버전·snapshot ID가 1:1 매핑 안 됨 timestamp 기반(VERSION AS OF TIMESTAMP)으로만 비교.
"Schema not compatible" on Sync Source에서 컬럼 rename 후 mirror가 못 따라감 컬럼 rename 대신 add 새 컬럼 → backfill → drop 옛 컬럼 패턴 사용.
S3 throttling(503) 대량 manifest read 동시성 폭증 prefix 분산, S3 retry policy 늘리기, sync 동시성 옵션 낮추기.
Lakekeeper/REST 카탈로그에 안 보임 XTable이 메타데이터만 쓰고 카탈로그 등록은 별개 REST 카탈로그에 register-table 호출하는 후처리 스크립트 또는 Spark CALL system.register_table 실행.

Ⅶ. XTable 자주 쓰는 명령어

ⅰ. 기본 CLI 패턴

# 단발 sync
java -jar xtable-utilities-bundled.jar --datasetConfig config.yaml

# 카탈로그 설정까지 같이
java -jar xtable-utilities-bundled.jar \
     --datasetConfig config.yaml \
     --hadoopConfig core-site.xml \
     --icebergCatalogConfig iceberg-catalog.yaml

 

ⅱ. 사례 1 — Airflow에서 1분 주기 sync

from airflow import DAG
from airflow.providers.docker.operators.docker import DockerOperator
from datetime import datetime, timedelta

with DAG(
    "xtable_orders_sync",
    start_date=datetime(2026, 5, 1),
    schedule_interval=timedelta(minutes=1),
    catchup=False,
    max_active_runs=1,
) as dag:
    DockerOperator(
        task_id="sync",
        image="onehouse/xtable:0.2.0-incubating",
        command="java -jar /opt/xtable.jar --datasetConfig /cfg/orders.yaml",
        mounts=[{"source":"/airflow/cfg","target":"/cfg","type":"bind"}],
        environment={"AWS_ACCESS_KEY_ID":"{{ var.value.aws_key }}",
                     "AWS_SECRET_ACCESS_KEY":"{{ var.value.aws_secret }}"},
    )

ⅲ. 사례 2 — Spark 코드에서 직접 호출

// Scala / Spark
import org.apache.xtable.conversion.ConversionController
import org.apache.xtable.model.sync.SyncMode

val controller = new ConversionController(hadoopConf)
controller.sync(datasetConfig, SyncMode.INCREMENTAL)

ⅳ. 사례 3 — Snowflake에서 미러 Iceberg 등록

CREATE EXTERNAL VOLUME lakehouse_vol
  STORAGE_LOCATIONS = (( NAME='s3', STORAGE_PROVIDER='S3',
    STORAGE_BASE_URL='s3://lakehouse-prod/' ));

CREATE OR REPLACE ICEBERG TABLE orders
  EXTERNAL_VOLUME = 'lakehouse_vol'
  CATALOG = 'SNOWFLAKE'
  METADATA_FILE_PATH = 'orders/metadata/v3.metadata.json';

ALTER ICEBERG TABLE orders REFRESH;

ⅴ. 사례 4 — 헬스체크 / 검증

# 마지막 sync 체크포인트 확인
aws s3 cp s3://lakehouse-prod/orders/_xtable/lastSyncedCheckpoint.json -

# Iceberg metadata 최신본 확인
aws s3 ls s3://lakehouse-prod/orders/metadata/ | tail -5

# Delta 최신 버전 확인
aws s3 ls s3://lakehouse-prod/orders/_delta_log/ | tail -5

# 행 수 cross-check (Trino-Iceberg vs Spark-Delta)
trino  --execute "SELECT count(*) FROM iceberg.sales.orders"
spark-sql --packages io.delta:delta-spark_2.12:3.2.0 \
  -e "SELECT count(*) FROM delta.\`s3://lakehouse-prod/orders/\`"

 

Ⅷ. XTable 기술 활용방안

ⅰ. 대표 활용 시나리오

시나리오 XTable이 푸는 문제
BI(Snowflake) ↔ ML(Databricks) 데이터팀은 Delta로 쓰고, 분석팀은 Iceberg로 보게 함. 데이터 복제·ETL 파이프라인 0.
실시간 CDC(Hudi) → 분석(Iceberg) Flink CDC가 Hudi로 1분 내 UPSERT, XTable이 Iceberg metadata 동기화 → Trino/Snowflake 즉시 조회.
벤더 마이그레이션 "Delta에서 Iceberg로 옮기자" 결정 시 데이터 재작성 없이 metadata만 발급해 점진적 전환.
멀티 클라우드 AWS=Iceberg, Azure=Delta(Fabric), GCP=Hudi 같은 정책 차이를 한 데이터셋으로 흡수.
데이터 메시 / 데이터 셰어링 소비자별로 선호 포맷이 다른 사내 도메인을 한 셰어링 면(공용 버킷)으로 묶기.

ⅱ. 대안 기술 비교

대안 접근 방식 XTable과의 차이
Databricks UniForm Delta가 자기 자신 + Iceberg + Hudi metadata를 자동 생성 Delta 진영 한정·Databricks 종속. XTable은 source가 어떤 포맷이든 OK, OSS·벤더 중립.
delta-rs / iceberg-rust 한 포맷의 라이브러리 구현체 단일 포맷용. cross-format sync는 직접 구현해야 함.
Dual-write (직접 구현) 파이프라인이 두 포맷에 동시에 write 데이터 2배·일관성 깨짐 위험. XTable은 데이터는 1벌만, 메타데이터만 다중.
Snowflake Iceberg / BigQuery BLMS 엔진이 외부 Iceberg를 native로 읽음 읽기 전용, 한 포맷 한정. XTable은 미러 포맷 자체를 만들어 줌.
Iceberg ↔ Delta 1회성 마이그레이션 CTAS로 한 번 옮기고 끝 계속 동기화 안 됨·중복 데이터. XTable은 incremental·재사용.

 

※ 언제 쓰면 안 되는가

상황 이유
한 포맷·한 엔진만 쓴다 미러를 만들 이유 없음. 운영 부담만 늘어남.
두 포맷에서 모두 쓰기 필요 XTable은 one-way가 안전. 양쪽 쓰기는 충돌 위험.
초당 단위 SLA Sync는 보통 분 단위. 마이크로배치보다 짧으면 lag 누적.
Hudi MoR + 활발한 log file log 파일은 cross-format 매핑이 어려움. compaction 후로 미루는 게 낫음.
강한 트랜잭션 격리 필요 미러 포맷은 source 커밋과 sync lag만큼 뒤처짐. 정확히 같은 시점 보장 어려움.

 

반응형