반응형
Ⅰ. 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로 미러링
- 커밋 발생: Flink/Spark가 Hudi 테이블에 새 커밋(.hoodie/2026...commit)을 남김.
- SourceClient 읽기: HudiSourceClient가 마지막 동기화 스냅샷 ID 이후의 신규 커밋·신규 파일 목록·삭제 파일 목록을 추출.
- InternalTable 변환: Hudi의 fileGroup·logFile·baseFile 모델 → 포맷 중립 file/row/stat 모델로 매핑.
- TargetClient 쓰기:
- IcebergTargetClient → metadata/v3.metadata.json + 새 manifest list + manifest 파일 작성.
- DeltaTargetClient → _delta_log/00000000000000000003.json 추가(add/remove action).
- (옵션) 카탈로그 등록: Glue·Unity·Hive에 신규 metadata 위치 업데이트(외부 책임).
- 다음 라운드: 마지막 동기화 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만큼 뒤처짐. 정확히 같은 시점 보장 어려움. |
반응형
'빅데이터(Big Data)' 카테고리의 다른 글
| 실시간 업데이트가 가능한 데이터 레이크하우스의 표준, Apache Hudi 분석 (0) | 2026.05.13 |
|---|---|
| 데이터 레이크의 표준 페더레이션 SQL 쿼리 엔진, Trino 분석 (0) | 2026.05.12 |
| 모던 데이터 스택의 공용어, Apache Arrow에 대해 분석 (0) | 2026.05.11 |
| 진정한 실시간 스트림 처리의 표준, Apache Flink에 대해 알아보겠습니다. (1) | 2026.05.06 |
| 실시간 이벤트 스트리밍의 심장, Apache Kafka에 대해 알아보겠습니다. (0) | 2026.05.02 |