Blog
deltalakeiceberglakehousedatabricks

Delta Lake 완벽 가이드 — Delta Table 개념부터 Iceberg 비교까지

Delta Lake의 핵심 개념과 아키텍처, Delta Table 생성 및 활용법, 그리고 Apache Iceberg와의 상세 비교를 통해 Lakehouse 테이블 포맷 선택 기준을 제시합니다.

Data Dynamics2026년 4월 14일22 min read

도서관 책장을 상상해 보세요. 책만 잔뜩 쌓아두면 원하는 책을 찾기 어렵고, 누군가 책을 반납하다 실수로 이상한 위치에 꽂아도 알 방법이 없습니다. Data Lake 도 마찬가지입니다 — 파일만 쌓아둔 상태에서는 "어제 데이터를 되돌릴 수 있을까?", "두 사람이 동시에 써도 괜찮을까?" 같은 질문에 답하기 어렵습니다. Delta Lake 는 그 Data Lake 위에 사서(Transaction Log)를 두어 모든 변경을 기록하고, 과거로 되돌아갈 수 있게 해줍니다.

이 글에서 배우는 것

  • Delta Lake 가 Data Lake 의 어떤 문제를 해결하는지
  • Transaction Log, Checkpoint, ACID 트랜잭션의 동작 원리
  • MERGE, Schema Evolution, Time Travel 등 Delta Table 의 핵심 기능
  • Apache Iceberg 와의 메타데이터 구조·스키마 진화·파티셔닝 전략 차이
  • 두 포맷 중 어느 것을 선택해야 하는지 실무 판단 기준

이 글에서는 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 아키텍처에서의 역할

"유연하지만 불안정한" Data Lake 와 "안정적이지만 유연하지 못한" Data Warehouse 의 장점만 합친 것이 Lakehouse 입니다. Delta Lake 는 그 아키텍처의 스토리지 계층에서 신뢰성을 책임집니다.

Loading diagram…

핵심 특징

특징설명
ACID 트랜잭션모든 읽기/쓰기 작업에 대해 원자성, 일관성, 격리성, 내구성 보장
스키마 강제(Schema Enforcement)쓰기 시 스키마 검증으로 잘못된 데이터 유입 방지
스키마 진화(Schema Evolution)테이블 스키마를 안전하게 변경 가능
Time Travel과거 시점의 데이터를 조회하거나 롤백 가능
Unified Batch & Streaming배치와 스트리밍 처리를 동일한 테이블에서 수행
DML 지원UPDATE, DELETE, MERGE 연산 지원

2. Delta Lake 아키텍처

Parquet 파일 + Transaction Log

Delta Table 을 뜯어보면 생각보다 단순합니다. 데이터 파일은 우리가 아는 Parquet 그대로이고, _delta_log/ 디렉토리가 추가되어 모든 변경을 JSON 으로 기록하는 구조입니다.

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 는 Git 커밋 히스토리와 비슷합니다. 각 JSON 파일이 하나의 커밋이고, 그 안에 "어떤 파일을 추가했고, 어떤 파일을 삭제했는지"를 기록합니다.

{
  "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 메커니즘

커밋이 쌓일수록 JSON 파일을 처음부터 읽는 데 시간이 걸립니다. Delta Lake 는 매 10개 커밋마다 체크포인트 파일을 만들어 이 문제를 해결합니다. 마치 게임의 세이브 포인트처럼, 최신 체크포인트부터 읽으면 됩니다.

# 체크포인트 없이 버전 100을 읽으려면?
→ JSON 파일 100개를 순차적으로 읽어야 함

# 체크포인트가 있으면?
→ 버전 100 체크포인트 1개 + 이후 JSON 파일만 읽으면 됨

3. Delta Table이란?

Delta Table의 정의와 구조

Delta Table 은 _delta_log 디렉토리가 붙은 Parquet 테이블입니다. 이 디렉토리 하나의 유무가 일반 Parquet 테이블과 Delta Table 의 차이를 만들어 냅니다.

# 일반 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 TableExternal 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 입니다. CDC(Change Data Capture) 파이프라인에서 특히 유용합니다.

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과 버전 관리

"어제 실수로 DELETE 했는데, 되돌릴 수 있을까요?" — Delta Lake 가 있다면 가능합니다. 모든 변경이 버전으로 기록되므로, 특정 시점의 데이터를 조회하거나 롤백할 수 있습니다.

-- 특정 버전의 데이터 조회
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

Time Travel 이 편리한 반면, 오래된 버전 파일이 계속 쌓이면 저장소가 불필요하게 늘어납니다. 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 비교

개요 비교

둘 다 ACID 트랜잭션과 Time Travel 을 지원하는 Lakehouse 테이블 포맷이지만, 탄생 배경과 설계 철학이 다릅니다. 먼저 전체 그림을 파악해 봅시다.

항목Delta LakeApache Iceberg
개발 주체Databricks (Linux Foundation)Netflix → Apache Foundation
최초 공개20192018
라이선스Apache 2.0Apache 2.0
기반 파일 포맷ParquetParquet, ORC, Avro
메타데이터 저장JSON + Parquet (Transaction Log)Manifest 파일 (Avro + JSON)
기본 엔진Apache Spark엔진 독립적
ACID 트랜잭션OO
Time TravelOO
Schema EvolutionOO
파티션 진화X (재작성 필요)O (Hidden Partitioning)

메타데이터 관리 방식 차이

두 포맷의 가장 큰 차이는 메타데이터를 어떻게 저장하느냐에 있습니다. Delta Lake 는 단순한 JSON 로그 방식을, Iceberg 는 3계층 트리 방식을 선택했습니다.

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 LakeApache Iceberg
컬럼 추가ALTER TABLE ADD COLUMNSALTER 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 LakeApache Iceberg
Apache Spark네이티브 지원지원
Trino/Presto커넥터 지원네이티브 지원
Apache Flink제한적 지원네이티브 지원
Apache Hive제한적 지원지원
Dremio지원네이티브 지원
Snowflake지원네이티브 지원
AWS Athena지원네이티브 지원
Databricks네이티브 지원지원
  • Delta Lake는 Spark 및 Databricks 환경에서 가장 깊은 통합을 제공합니다.
  • Iceberg는 엔진 독립적 설계로 더 넓은 생태계를 지원합니다.

성능 특성 비교

시나리오Delta LakeApache Iceberg
소규모 테이블 (1TB 미만)빠른 읽기/쓰기유사
대규모 테이블 (10TB 이상)체크포인트 의존적Manifest 프루닝으로 우수
빈번한 UpsertMERGE 최적화 우수지원하나 Delta 대비 느릴 수 있음
파티션 프루닝파티션 컬럼 기반컬럼 수준 메트릭으로 더 정밀
스트리밍 쓰기Spark Structured Streaming 최적Flink 스트리밍에 강점
동시 쓰기 처리Optimistic ConcurrencyOptimistic 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

어떤 포맷을 선택하든, 핵심은 데이터의 신뢰성관리 가능성을 확보하는 것입니다.

마치며 — 핵심 요약

  • Delta Lake = Parquet + _delta_log/ 입니다. Transaction Log 하나가 ACID·Time Travel·Schema Evolution 을 모두 가능하게 합니다.
  • MERGE 는 Delta Lake 의 킬러 기능입니다. INSERT + UPDATE + DELETE 를 단일 트랜잭션으로 처리합니다.
  • Time Travel 은 강력하지만 VACUUM 관리도 함께 해야 합니다. 오래된 버전 파일을 방치하면 스토리지 비용이 늘어납니다.
  • Iceberg 는 스키마 진화와 Hidden Partitioning 이 강점입니다. 멀티 엔진 환경과 파티션 전략 변경이 잦은 경우에 유리합니다.
  • Delta Lake 는 Spark/Databricks 생태계에서, Iceberg 는 멀티 엔진 환경에서 빛을 발합니다.
  • 둘 중 어느 쪽도 명확한 우위는 없습니다 — 여러분의 워크로드와 엔진 스택에 맞는 것을 선택하세요.

처음 도입한다면 현재 쓰고 있는 쿼리 엔진을 기준으로 시작하는 것이 가장 현실적입니다.


References


— Data Dynamics 엔지니어링 팀