Delta Lake 핵심: transaction log, time travel, optimize/vacuum
Delta Lake의 위치
Databricks가 2019년 오픈소스로 공개한 Delta Lake는 Apache Spark 생태계에서 가장 널리 채택된 테이블 포맷이다. 현재 Linux Foundation Delta Lake 프로젝트로 운영되며, Delta Lake 3.x(2024~2025)에서는 Spark 없이도 Python/Rust 클라이언트(delta-rs)로 읽고 쓸 수 있다.
이 장에서는 Delta Lake 내부를 움직이는 핵심 요소인 Transaction Log의 구조와 그것이 가능하게 하는 Time Travel, OPTIMIZE, VACUUM을 차례로 살펴본다.
1. _delta_log/: 모든 변경의 기록
Delta 테이블 루트 디렉토리 안에는 _delta_log/ 폴더가 있다. 이 폴더 안에 모든 변경 이력이 쌓인다.
s3://my-table/
├── _delta_log/
│ ├── 00000000000000000000.json # 버전 0: 테이블 생성
│ ├── 00000000000000000001.json # 버전 1: 첫 쓰기
│ ├── ...
│ ├── 00000000000000000009.json # 버전 9
│ ├── 00000000000000000010.checkpoint.parquet # 버전 10 체크포인트
│ ├── 00000000000000000010.json # 버전 10 커밋
│ └── _last_checkpoint # 최신 체크포인트 버전 기록
├── part-00001-abc123.parquet
└── part-00002-def456.parquet파일 이름은 20자리 0-padded 버전 번호다. 버전은 0부터 시작하고, 커밋마다 1씩 증가한다. 이 번호가 테이블의 버전이자 Time Travel의 기준이 된다.
2. JSON 커밋 파일의 Action 구조
각 JSON 파일은 하나 이상의 action 객체를 줄(Line-delimited JSON)로 담는다.
operation (WRITE/UPDATE/
DELETE/MERGE/OPTIMIZE)
operationParameters
operationMetrics
partitionValues
size (bytes)
modificationTime
dataChange (bool)
stats (min/max/null counts)
deletionTimestamp
dataChange (bool)
partitionValues
size
schemaString
partitionColumns
configuration (테이블 속성)
minWriterVersion
기능 플래그 목록
버전 1의 JSON 커밋 파일 예시:
{"commitInfo": {"timestamp": 1750000000000, "operation": "WRITE", "operationParameters": {"mode": "Append"}, "operationMetrics": {"numFiles": "2", "numOutputRows": "50000"}}}
{"add": {"path": "part-00001-abc.parquet", "size": 1048576, "partitionValues": {}, "modificationTime": 1750000000000, "dataChange": true, "stats": "{\"numRecords\":25000,\"minValues\":{\"id\":1},\"maxValues\":{\"id\":25000},\"nullCount\":{\"id\":0}}"}}
{"add": {"path": "part-00002-def.parquet", "size": 1048576, "partitionValues": {}, "modificationTime": 1750000000000, "dataChange": true}}add의 stats 필드 안에 컬럼별 min/max/null count가 JSON 문자열로 들어있다. 이 통계가 Delta Lake의 data skipping 기반이다. 쿼리 엔진은 파일을 실제로 열기 전에 stats를 보고 건너뛸 수 있는 파일을 결정한다.
DELETE/UPDATE/MERGE의 내부 표현
Delta Lake에서 행 수준 변경은 기본적으로:
- 변경 대상 행이 포함된 파일을 새로 쓴다(기존 파일은 수정하지 않음).
- 기존 파일을
removeaction으로 논리적으로 삭제 표시한다. - 새 파일을
addaction으로 추가한다.
Delta Lake 3.x부터는 Deletion Vector(DV) 를 사용해 삭제된 행 번호를 별도 비트맵으로 기록, 파일 재작성 없이 소프트 삭제가 가능하다.
3. Checkpoint 파일: 오래된 JSON 읽기 방지
버전이 수백, 수천 개가 되면 처음부터 JSON 파일을 모두 읽는 것은 비효율적이다. Delta Lake는 기본적으로 10번 커밋마다 checkpoint를 생성한다(delta.checkpointInterval 속성으로 조정 가능).
Checkpoint는 Parquet 파일로, 그 시점까지의 모든 활성 add action을 담는다. 취소된 파일(add 후 remove된 파일)은 checkpoint에 포함되지 않는다.
JSON 파일들
(checkpoint에 포함됨)
활성 파일 전체 상태
JSON 파일들
(최신 변경)
= checkpoint + v11~v13
_last_checkpoint 파일이 최신 checkpoint 버전 번호를 담음 → 리더는 여기서 시작점을 파악한다
Log Replay 순서
_last_checkpoint파일을 읽어 최신 checkpoint 버전 확인.- 해당 버전의
.checkpoint.parquet를 읽어 활성 파일 목록 적재. - 그 이후의 JSON 파일들을 순서대로 읽어
add/remove를 적용. - 결과가 현재 테이블의 활성 파일 목록.
4. ACID와 Optimistic Concurrency Control
두 Spark job이 동시에 같은 Delta 테이블에 쓰려 할 때:
- Job A와 Job B 모두 현재 버전(v5)을 읽는다.
- 둘 다 v6 번호의 JSON 파일을 쓰려 한다.
- 먼저 쓴 쪽(A)이 v6 파일을 성공적으로 생성한다.
- B가 v6 파일을 쓰려 하면 이미 존재하므로 실패 → B는 v6의 내용을 읽고 충돌 여부를 판단해 재시도한다.
| 충돌 유형 | 결과 |
|---|---|
| 서로 다른 파티션/파일에 append | 충돌 없음, 둘 다 성공 |
| 같은 파티션을 overwrite | 한쪽 실패, retry |
| update/delete가 겹치는 행 | 한쪽 실패, retry 또는 예외 |
5. Time Travel: 버전과 타임스탬프로 과거 조회
Delta Lake의 time travel은 transaction log의 버전 번호 또는 commitInfo.timestamp 필드를 이용한다.
-- Spark SQL: 버전으로 조회
SELECT * FROM orders VERSION AS OF 5;
-- 타임스탬프로 조회 (해당 시각 직전의 최신 버전)
SELECT * FROM orders TIMESTAMP AS OF '2026-06-25 00:00:00';
-- Python API
df = spark.read.format("delta").option("versionAsOf", 5).load("s3://my-table/")
-- 전체 이력 조회
from delta.tables import DeltaTable
dt = DeltaTable.forPath(spark, "s3://my-table/")
dt.history().show()history()로 확인할 수 있는 버전 이력의 예시:
| version | timestamp | operation | numFiles | numOutputRows |
|---|---|---|---|---|
| 10 | 2026-06-30 | OPTIMIZE | 5 | 500000 |
| 9 | 2026-06-29 | MERGE | 8 | 320000 |
| 8 | 2026-06-28 | WRITE | 3 | 120000 |
주의: time travel이 가능하려면 해당 버전에서 참조하는 실제 Parquet 파일이 스토리지에 존재해야 한다. VACUUM이 해당 파일을 삭제했다면 time travel은 실패한다.
6. OPTIMIZE: 작은 파일 합치기
스트리밍 ingestion이나 잦은 소량 쓰기로 생긴 작은 파일들은 쿼리 성능을 떨어뜨린다. OPTIMIZE 명령은 이 파일들을 더 큰 파일로 합친다.
-- 전체 테이블 OPTIMIZE
OPTIMIZE orders;
-- 특정 파티션만 OPTIMIZE
OPTIMIZE orders WHERE order_date = '2026-06-30';OPTIMIZE는 내부적으로:
- 타깃 파티션의 작은 파일들을 병합해서 목표 크기(기본 1 GB) 파일을 생성한다.
- 기존 파일들에
removeaction을, 새 파일들에addaction을 커밋한다. - 진행 중인 읽기는 이전 스냅샷을 보므로 안전하게 병렬 실행 가능하다.
Z-Order: 멀티컬럼 데이터 정렬
OPTIMIZE orders ZORDER BY (customer_id, order_date);Z-Order는 Z-curve(Space-Filling Curve) 알고리즘으로 지정된 컬럼들을 다차원 공간에서 물리적으로 가까운 순서로 정렬해 파일에 쓴다. customer_id = 10001로 필터링하면 해당 고객의 데이터가 가능한 한 적은 파일에 몰려 있어, data skipping 효과가 극대화된다.
단, Z-Order는 OPTIMIZE를 실행할 때마다 전체 파일을 재작성한다. 클러스터링 컬럼을 바꾸려면 전체 테이블을 다시 OPTIMIZE해야 한다.
Liquid Clustering (Delta 3.1+)
Liquid Clustering은 Z-Order의 한계를 해결하기 위해 2023년 도입된 대안이다.
-- 테이블 생성 시 클러스터링 키 지정
CREATE TABLE orders (id LONG, customer_id LONG, order_date DATE)
CLUSTER BY (customer_id, order_date);
-- OPTIMIZE는 이미 클러스터링된 파일은 건드리지 않음 (증분 방식)
OPTIMIZE orders;| 항목 | Z-Order | Liquid Clustering |
|---|---|---|
| 실행 방식 | 전체 파일 재작성 | 증분 (미클러스터링 파일만) |
| 클러스터링 키 변경 | 전체 재실행 필요 | 키 변경 후 점진적 반영 |
| 파티셔닝과의 관계 | 파티셔닝과 함께 사용 | 파티셔닝 대체 가능 |
| 도입 시기 | Delta Lake 초기 | Delta Lake 3.1 (2023) |
7. VACUUM: 물리적 파일 삭제
Transaction log에서 remove된 파일은 논리적으로만 삭제된 상태다. 실제 Parquet 파일은 스토리지에 남아 Time Travel에 활용된다.
VACUUM은 이 물리적 파일을 스토리지에서 실제로 삭제한다.
-- 기본: 7일(168시간)보다 오래된 파일 삭제
VACUUM orders;
-- 보존 기간 명시
VACUUM orders RETAIN 336 HOURS; -- 14일
-- 삭제할 파일 목록만 확인 (dry run)
VACUUM orders DRY RUN;보존 기간은 테이블 속성으로도 설정한다:
ALTER TABLE orders
SET TBLPROPERTIES ('delta.deletedFileRetentionDuration' = 'interval 14 days');VACUUM과 Time Travel의 트레이드오프
| 보존 기간 | Time Travel 가능 범위 | 스토리지 비용 |
|---|---|---|
| 7일 (기본) | 최대 7일 전까지 | 기본 |
| 30일 | 최대 30일 전까지 | 증가 |
| VACUUM 없음 | 제한 없음 | 무제한 증가 |
VACUUM 이전 버전의 파일이 삭제된 뒤 그 버전으로 time travel을 시도하면 FileNotFoundException으로 실패한다. transaction log 항목은 남아있지만 실제 파일이 없기 때문이다.
VACUUM LITE (Delta Lake 3.3.0, 2025)
기존 VACUUM(VACUUM FULL)은 스토리지의 전체 파일 목록을 LIST API로 조회해 로그에서 참조되지 않는 파일을 찾는 방식이었다. 파일이 많을수록 LIST 비용이 크고 느렸다.
2025년 1월 Delta Lake 3.3.0에서 도입된 VACUUM LITE는 transaction log를 직접 읽어 remove된 파일 중 보존 기간이 지난 것만 삭제한다. LIST API 호출을 최소화해 대규모 테이블에서 VACUUM 실행 시간을 크게 줄인다.
VACUUM orders LITE;8. Deletion Vector: 행 수준 소프트 삭제 (Delta 3.x)
기존 Delta Lake에서 UPDATE/DELETE는 대상 파일 전체를 재작성했다. Deletion Vector(DV) 는 삭제된 행 번호를 별도 비트맵 파일에 기록해, Parquet 파일을 건드리지 않는 소프트 삭제를 가능하게 한다.
-- DV 활성화
ALTER TABLE orders
SET TBLPROPERTIES ('delta.enableDeletionVectors' = 'true');
-- 이후의 DELETE는 파일 재작성 없이 DV만 업데이트
DELETE FROM orders WHERE order_status = 'cancelled' AND order_date < '2026-01-01';DV를 사용하면 소량 삭제의 쓰기 비용이 크게 줄어든다. VACUUM이나 OPTIMIZE가 실행될 때 DV가 적용된 파일들은 실제로 물리적 정리가 된다.
마무리: Delta Lake 운영 핵심 포인트
- Log 크기 관리: 커밋이 많을수록 JSON 파일이 쌓인다. checkpoint 주기가 너무 느리다면
delta.checkpointInterval을 낮춘다. - OPTIMIZE 주기: 스트리밍 테이블은 최소 1일 1회 OPTIMIZE를 권장한다. Databricks 환경에서는 Auto Optimize(Optimized Writes + Auto Compaction)를 활성화하면 자동으로 처리된다.
- VACUUM 주기와 보존 기간: 7일 기본값은 대부분의 워크로드에서 적합하다. 규정 준수나 장기 time travel이 필요하다면 보존 기간을 늘리되 스토리지 비용 증가를 모니터링한다.
- VACUUM LITE 활용: Delta Lake 3.3+에서 대규모 테이블의 VACUUM 부하를 줄이기 위해
VACUUM LITE를 고려한다. - Time Travel은 파일이 있어야 작동한다: VACUUM 이전 버전의 파일이 삭제된 뒤에는 time travel이 실패한다. 이 관계를 운영 팀이 명확히 이해하고 있어야 한다.
References
- Delta Lake, Protocol Specification, https://github.com/delta-io/delta/blob/master/PROTOCOL.md
- Databricks Engineering Blog, "Diving into Delta Lake: Unpacking the Transaction Log," https://www.databricks.com/blog/2019/08/21/diving-into-delta-lake-unpacking-the-transaction-log.html
- Delta Lake, "Delta Lake Time Travel," https://delta.io/blog/2023-02-01-delta-lake-time-travel/
- Delta Lake, "Liquid Clustering," https://delta.io/blog/liquid-clustering/
- Databricks Documentation, VACUUM Command, https://docs.databricks.com/aws/en/sql/language-manual/delta-vacuum
- Databricksters, "Your Storage Bill Is Too High. Here Are 3 Levels of VACUUM to Fix It," https://www.databricksters.com/p/your-storage-bill-is-too-high-here
- Medium (Nidhin), "Inside the Delta Lake Transaction Log: The Invisible Engine That Makes Lakehouse Architecture Reliable," https://medium.com/@nidhin.dwh/inside-the-delta-lake-transaction-log-the-invisible-engine-that-makes-lakehouse-architecture-5478dd41d17e
- Denny Lee, "Computing Delta Lake State Quickly with Checkpoint Files," https://dennyglee.com/2024/01/09/computing-delta-lake-state-quickly-with-checkpoint-files/
- Medium (Christian Hansen), "Important Changes Coming to Delta Lake Time Travel (Databricks, December 2025)," https://medium.com/@cralle/important-changes-coming-to-delta-lake-time-travel-databricks-december-2025-644b6fd03d9e
- Conduktor, "Delta Lake Transaction Log: How It Works," https://www.conduktor.io/glossary/delta-lake-transaction-log-how-it-works