Delta Lake 완벽 가이드 — Delta Table 개념부터 Iceberg 비교까지
Delta Lake의 핵심 개념과 아키텍처, Delta Table 생성 및 활용법, 그리고 Apache Iceberg와의 상세 비교를 통해 Lakehouse 테이블 포맷 선택 기준을 제시합니다.
이 글에서는 Delta Lake의 개념과 아키텍처, Delta Table의 생성 및 핵심 기능, 그리고 Apache Iceberg와의 비교를 다룹니다. Lakehouse 아키텍처에서 테이블 포맷을 선택할 때 참고할 수 있는 실무 가이드입니다.
1. Delta Lake란?
오픈소스 스토리지 레이어
Delta Lake는 기존 Data Lake 위에 신뢰성(Reliability)을 추가하는 오픈소스 스토리지 레이어입니다. Apache Spark와 함께 사용하도록 설계되었으며, 기존 Parquet 파일 기반의 Data Lake가 가진 한계를 해결합니다.
기존 Data Lake의 문제점:
- 데이터 파일의 부분 쓰기(Partial Write)로 인한 데이터 손상
- 동시 읽기/쓰기 시 일관성 보장 불가
- 스키마 변경 관리의 어려움
- 과거 데이터 상태로의 복원 불가
Delta Lake는 이러한 문제를 Transaction Log 기반의 ACID 트랜잭션으로 해결합니다.
Lakehouse 아키텍처에서의 역할
Lakehouse는 Data Lake의 유연성과 Data Warehouse의 안정성을 결합한 아키텍처입니다. Delta Lake는 이 아키텍처의 스토리지 계층으로서 핵심 역할을 담당합니다.
┌─────────────────────────────────────────┐
│ BI / ML / Analytics │
├─────────────────────────────────────────┤
│ Query Engine (Spark) │
├─────────────────────────────────────────┤
│ Delta Lake (Storage Layer) │ ← ACID, Schema, Time Travel
├─────────────────────────────────────────┤
│ Object Storage (S3, ADLS, GCS) │
└─────────────────────────────────────────┘
핵심 특징
| 특징 | 설명 |
|---|---|
| ACID 트랜잭션 | 모든 읽기/쓰기 작업에 대해 원자성, 일관성, 격리성, 내구성 보장 |
| 스키마 강제(Schema Enforcement) | 쓰기 시 스키마 검증으로 잘못된 데이터 유입 방지 |
| 스키마 진화(Schema Evolution) | 테이블 스키마를 안전하게 변경 가능 |
| Time Travel | 과거 시점의 데이터를 조회하거나 롤백 가능 |
| Unified Batch & Streaming | 배치와 스트리밍 처리를 동일한 테이블에서 수행 |
| DML 지원 | UPDATE, DELETE, MERGE 연산 지원 |
2. Delta Lake 아키텍처
Parquet 파일 + Transaction Log
Delta Table은 물리적으로 두 가지 요소로 구성됩니다.
my_table/
├── _delta_log/ # Transaction Log 디렉토리
│ ├── 00000000000000000000.json # 버전 0
│ ├── 00000000000000000001.json # 버전 1
│ ├── 00000000000000000002.json # 버전 2
│ ├── ...
│ └── 00000000000000000010.checkpoint.parquet # 체크포인트
├── part-00000-...snappy.parquet # 데이터 파일
├── part-00001-...snappy.parquet
└── part-00002-...snappy.parquet
- 데이터 파일: 표준 Apache Parquet 형식으로 저장
- Transaction Log (
_delta_log/): 테이블에 대한 모든 변경 사항을 순서대로 기록
Transaction Log 동작 원리
Transaction Log는 JSON 파일의 순서 배열입니다. 각 파일은 하나의 커밋(Commit)을 나타내며, 다음과 같은 액션(Action)을 포함합니다.
{
"add": {
"path": "part-00000-abc123.snappy.parquet",
"size": 1024000,
"partitionValues": {"date": "2026-04-14"},
"modificationTime": 1713052800000,
"dataChange": true
}
}주요 액션 유형:
| 액션 | 설명 |
|---|---|
add | 새 데이터 파일 추가 |
remove | 기존 데이터 파일 논리적 삭제 |
metaData | 테이블 스키마, 파티션 컬럼 등 메타데이터 변경 |
protocol | 읽기/쓰기 프로토콜 버전 지정 |
txn | 애플리케이션 수준 트랜잭션 식별자 |
Optimistic Concurrency Control: 여러 작업이 동시에 같은 테이블을 수정할 때, Delta Lake는 낙관적 동시성 제어를 사용합니다. 충돌이 감지되면 자동으로 재시도합니다.
Checkpoint 메커니즘
매 10개 커밋마다 Delta Lake는 체크포인트(Checkpoint) 파일을 생성합니다. 체크포인트는 해당 시점까지의 모든 액션을 하나의 Parquet 파일로 통합하여, 테이블 상태를 빠르게 복원할 수 있게 합니다.
# 체크포인트 없이 버전 100을 읽으려면?
→ JSON 파일 100개를 순차적으로 읽어야 함
# 체크포인트가 있으면?
→ 버전 100 체크포인트 1개 + 이후 JSON 파일만 읽으면 됨
3. Delta Table이란?
Delta Table의 정의와 구조
Delta Table은 Delta Lake 포맷으로 저장된 테이블입니다. 일반 Parquet 테이블과 달리 _delta_log 디렉토리가 존재하며, 이를 통해 ACID 트랜잭션과 버전 관리가 가능합니다.
# 일반 Parquet 읽기 vs Delta Table 읽기
# Parquet
df = spark.read.parquet("/data/my_table")
# Delta Table
df = spark.read.format("delta").load("/data/my_table")Managed Table vs External Table
| 구분 | Managed Table | External Table |
|---|---|---|
| 데이터 위치 | Metastore가 관리하는 기본 경로 | 사용자가 지정한 경로 |
| DROP 시 동작 | 메타데이터 + 데이터 파일 모두 삭제 | 메타데이터만 삭제, 데이터 파일 유지 |
| 사용 시나리오 | ETL 중간 결과, 임시 테이블 | 여러 시스템에서 공유하는 데이터 |
-- Managed Table 생성
CREATE TABLE managed_events (
id BIGINT,
event_type STRING,
event_time TIMESTAMP
) USING DELTA;
-- External Table 생성
CREATE TABLE external_events (
id BIGINT,
event_type STRING,
event_time TIMESTAMP
) USING DELTA
LOCATION 's3://my-bucket/events/';Delta Table 생성 및 기본 조작
SQL로 생성 및 조작:
-- 테이블 생성
CREATE TABLE users (
user_id BIGINT,
name STRING,
email STRING,
created_at TIMESTAMP
) USING DELTA
PARTITIONED BY (created_at);
-- 데이터 삽입
INSERT INTO users VALUES
(1, '홍길동', 'hong@example.com', '2026-04-14T10:00:00'),
(2, '김철수', 'kim@example.com', '2026-04-14T11:00:00');
-- 데이터 수정
UPDATE users SET email = 'hong_new@example.com' WHERE user_id = 1;
-- 데이터 삭제
DELETE FROM users WHERE user_id = 2;PySpark로 생성 및 조작:
from delta.tables import DeltaTable
from pyspark.sql.types import StructType, StructField, LongType, StringType, TimestampType
# DataFrame으로 Delta Table 생성
schema = StructType([
StructField("user_id", LongType(), False),
StructField("name", StringType(), True),
StructField("email", StringType(), True),
StructField("created_at", TimestampType(), True)
])
data = [(1, "홍길동", "hong@example.com", "2026-04-14T10:00:00")]
df = spark.createDataFrame(data, schema)
# Delta 포맷으로 저장
df.write.format("delta").mode("overwrite").save("/data/users")
# Delta Table 객체로 읽기
delta_table = DeltaTable.forPath(spark, "/data/users")
delta_table.toDF().show()4. Delta Table 핵심 기능
MERGE (Upsert) 처리
MERGE는 Delta Table의 가장 강력한 기능 중 하나로, INSERT + UPDATE + DELETE를 단일 트랜잭션으로 처리합니다.
MERGE INTO target_table AS t
USING source_table AS s
ON t.id = s.id
WHEN MATCHED AND s.is_deleted = true THEN DELETE
WHEN MATCHED THEN UPDATE SET
t.name = s.name,
t.email = s.email,
t.updated_at = current_timestamp()
WHEN NOT MATCHED THEN INSERT (id, name, email, updated_at)
VALUES (s.id, s.name, s.email, current_timestamp());# PySpark MERGE
from delta.tables import DeltaTable
delta_table = DeltaTable.forPath(spark, "/data/users")
(delta_table.alias("t")
.merge(source_df.alias("s"), "t.id = s.id")
.whenMatchedUpdate(set={
"name": "s.name",
"email": "s.email"
})
.whenNotMatchedInsert(values={
"id": "s.id",
"name": "s.name",
"email": "s.email"
})
.execute()
)Schema Evolution
Delta Lake는 기존 데이터를 유지하면서 스키마를 안전하게 변경할 수 있습니다.
-- 컬럼 추가
ALTER TABLE users ADD COLUMNS (phone STRING, address STRING);
-- 컬럼 이름 변경 (Column Mapping 활성화 필요)
ALTER TABLE users SET TBLPROPERTIES ('delta.columnMapping.mode' = 'name');
ALTER TABLE users RENAME COLUMN phone TO phone_number;# mergeSchema 옵션으로 자동 스키마 진화
new_data.write \
.format("delta") \
.mode("append") \
.option("mergeSchema", "true") \
.save("/data/users")주의: 스키마 진화 시 기존 데이터의 새 컬럼 값은
null로 채워집니다. 타입 변경(예: STRING → INT)은 지원되지 않으며, 별도의 마이그레이션이 필요합니다.
Time Travel과 버전 관리
Delta Table의 모든 변경은 버전으로 기록되며, 과거 시점의 데이터를 조회하거나 롤백할 수 있습니다.
-- 특정 버전의 데이터 조회
SELECT * FROM users VERSION AS OF 3;
-- 특정 시간 기준 조회
SELECT * FROM users TIMESTAMP AS OF '2026-04-14T10:00:00';
-- 변경 이력 확인
DESCRIBE HISTORY users;
-- 이전 버전으로 롤백
RESTORE TABLE users TO VERSION AS OF 3;# PySpark Time Travel
df_v3 = spark.read.format("delta").option("versionAsOf", 3).load("/data/users")
df_ts = spark.read.format("delta").option("timestampAsOf", "2026-04-14T10:00:00").load("/data/users")VACUUM과 OPTIMIZE
시간이 지나면 삭제된 파일과 작은 파일들이 축적됩니다. VACUUM과 OPTIMIZE로 정리합니다.
-- VACUUM: 더 이상 참조되지 않는 오래된 파일 삭제 (기본 7일 보존)
VACUUM users;
-- 보존 기간 지정 (24시간)
VACUUM users RETAIN 24 HOURS;
-- OPTIMIZE: 작은 파일들을 병합하여 읽기 성능 향상
OPTIMIZE users;
-- Z-ORDER: 특정 컬럼 기준으로 데이터 배치를 최적화
OPTIMIZE users ZORDER BY (user_id);주의:
VACUUM으로 삭제된 파일은 복구할 수 없으므로, Time Travel 보존 기간보다 짧은 값을 사용하지 마세요.
5. Delta Table vs Iceberg Table 비교
개요 비교
| 항목 | Delta Lake | Apache Iceberg |
|---|---|---|
| 개발 주체 | Databricks (Linux Foundation) | Netflix → Apache Foundation |
| 최초 공개 | 2019 | 2018 |
| 라이선스 | Apache 2.0 | Apache 2.0 |
| 기반 파일 포맷 | Parquet | Parquet, ORC, Avro |
| 메타데이터 저장 | JSON + Parquet (Transaction Log) | Manifest 파일 (Avro + JSON) |
| 기본 엔진 | Apache Spark | 엔진 독립적 |
| ACID 트랜잭션 | O | O |
| Time Travel | O | O |
| Schema Evolution | O | O |
| 파티션 진화 | X (재작성 필요) | O (Hidden Partitioning) |
메타데이터 관리 방식 차이
Delta Lake — Transaction Log 방식:
_delta_log/
├── 00000000000000000000.json ← 각 커밋이 하나의 JSON 파일
├── 00000000000000000001.json
├── ...
└── 00000000000000000010.checkpoint.parquet ← 10건마다 체크포인트
- JSON 파일에
add,remove등의 액션을 순차 기록 - 체크포인트로 빠른 상태 복원
- 단순하고 직관적인 구조
Apache Iceberg — Metadata Tree 방식:
metadata/
├── v1.metadata.json ← 메타데이터 파일 (스냅샷 목록)
├── snap-123456.avro ← 스냅샷 → Manifest List
├── manifest-abc.avro ← Manifest → 데이터 파일 목록
└── manifest-def.avro
- 3계층 구조: Metadata File → Manifest List → Manifest File
- 각 Manifest에 파티션 통계 및 컬럼 수준 메트릭 포함
- 대규모 테이블에서 파일 프루닝(File Pruning) 성능이 우수
스키마 진화 방식 차이
| 작업 | Delta Lake | Apache Iceberg |
|---|---|---|
| 컬럼 추가 | ALTER TABLE ADD COLUMNS | ALTER TABLE ADD COLUMNS |
| 컬럼 삭제 | Column Mapping 모드 필요 | 기본 지원 |
| 컬럼 이름 변경 | Column Mapping 모드 필요 | 기본 지원 (ID 기반) |
| 컬럼 순서 변경 | Column Mapping 모드 필요 | 기본 지원 |
| 타입 확장 (int→long) | 미지원 | 지원 |
| 중첩 스키마 진화 | 부분 지원 | 완전 지원 |
Iceberg는 컬럼을 이름이 아닌 고유 ID로 추적하므로, 이름 변경이나 순서 변경이 자유롭습니다. Delta Lake는 Column Mapping 모드를 활성화해야 유사한 기능을 사용할 수 있습니다.
파티셔닝 전략 차이
Delta Lake — 명시적 파티셔닝:
-- 파티션 컬럼을 명시적으로 지정
CREATE TABLE events (...) USING DELTA PARTITIONED BY (event_date);
-- 파티션 변경 시 테이블 전체 재작성 필요Apache Iceberg — Hidden Partitioning:
-- 파티션 변환 함수 사용 가능
CREATE TABLE events (...) USING ICEBERG
PARTITIONED BY (days(event_timestamp));
-- 파티션 전략 변경이 자유로움 (기존 데이터 유지)
ALTER TABLE events ADD PARTITION FIELD months(event_timestamp);Iceberg의 Hidden Partitioning은 다음과 같은 장점이 있습니다:
- 쿼리 시 파티션 컬럼을 직접 지정할 필요 없음
- 파티션 전략 변경 시 기존 데이터를 재작성하지 않음
- 시간 기반 파티셔닝에서
year,month,day,hour변환 함수 제공
에코시스템 및 엔진 호환성
| 엔진 | Delta Lake | Apache Iceberg |
|---|---|---|
| Apache Spark | 네이티브 지원 | 지원 |
| Trino/Presto | 커넥터 지원 | 네이티브 지원 |
| Apache Flink | 제한적 지원 | 네이티브 지원 |
| Apache Hive | 제한적 지원 | 지원 |
| Dremio | 지원 | 네이티브 지원 |
| Snowflake | 지원 | 네이티브 지원 |
| AWS Athena | 지원 | 네이티브 지원 |
| Databricks | 네이티브 지원 | 지원 |
- Delta Lake는 Spark 및 Databricks 환경에서 가장 깊은 통합을 제공합니다.
- Iceberg는 엔진 독립적 설계로 더 넓은 생태계를 지원합니다.
성능 특성 비교
| 시나리오 | Delta Lake | Apache Iceberg |
|---|---|---|
| 소규모 테이블 (1TB 미만) | 빠른 읽기/쓰기 | 유사 |
| 대규모 테이블 (10TB 이상) | 체크포인트 의존적 | Manifest 프루닝으로 우수 |
| 빈번한 Upsert | MERGE 최적화 우수 | 지원하나 Delta 대비 느릴 수 있음 |
| 파티션 프루닝 | 파티션 컬럼 기반 | 컬럼 수준 메트릭으로 더 정밀 |
| 스트리밍 쓰기 | Spark Structured Streaming 최적 | Flink 스트리밍에 강점 |
| 동시 쓰기 처리 | Optimistic Concurrency | Optimistic Concurrency |
언제 어떤 테이블 포맷을 선택할 것인가?
Delta Lake를 선택해야 하는 경우:
- Databricks 또는 Spark 중심 환경
- 빈번한 MERGE/Upsert 작업이 핵심 워크로드
- Spark Structured Streaming 기반 실시간 파이프라인
- Databricks의 관리형 기능(Auto-Optimize, Predictive Optimization 등) 활용
Apache Iceberg를 선택해야 하는 경우:
- 다양한 쿼리 엔진(Trino, Flink, Spark 등)을 혼용하는 환경
- 파티션 전략이 자주 변경되는 대규모 테이블
- 스키마 진화가 빈번하고 복잡한 중첩 구조를 다루는 경우
- 특정 벤더에 종속되지 않는 오픈 표준을 선호하는 경우
6. 마무리
Delta Lake와 Apache Iceberg는 모두 Data Lake에 ACID 트랜잭션과 테이블 추상화를 제공하는 Lakehouse 테이블 포맷입니다. 두 프로젝트 모두 활발하게 발전하고 있으며, 최근에는 서로의 장점을 흡수하는 방향으로 진화하고 있습니다.
핵심 선택 기준은 다음과 같습니다:
- 엔진 환경: Spark/Databricks 중심이면 Delta Lake, 멀티 엔진이면 Iceberg
- 파티셔닝 유연성: 파티션 변경이 잦으면 Iceberg
- Upsert 빈도: MERGE가 핵심이면 Delta Lake
- 벤더 독립성: 특정 벤더 종속을 피하려면 Iceberg
어떤 포맷을 선택하든, 핵심은 데이터의 신뢰성과 관리 가능성을 확보하는 것입니다.
References
- Delta Lake Documentation
- Apache Iceberg Documentation
- Delta Lake GitHub
- Apache Iceberg GitHub
- Databricks — Delta Lake vs Iceberg
— Data Dynamics 엔지니어링 팀