데이터 엔지니어 · AI 엔지니어를 위한 핵심 요약
Delta Lake는 데이터 레이크 위에 ACID 트랜잭션, 스키마 관리, 데이터 버전 관리(타임 트래블) 기능을 제공하는 오픈 소스 스토리지 레이어입니다. Parquet, ORC 같은 오픈 포맷을 기반으로 하며, Apache Spark, Trino, Flink 등과 통합되어 기존 데이터 파이프라인을 신뢰성 있는 Lakehouse 아키텍처로 전환하는 핵심 기술입니다.
다중 작업 쓰기 시 원자성(Atomicity) 보장. 읽기와 쓰기 충돌 방지 및 직렬화 격리 수준 제공. 데이터 일관성과 신뢰성을 보장합니다.
자동 스키마 추론, 스키마 진화(Schema Evolution)로 백필링 없이 새 컬럼 추가 가능. 스키마 강화(Schema Enforcement)로 데이터 품질을 유지합니다.
트랜잭션 로그(Delta Log)를 통해 데이터의 모든 변경 이력을 보존. 특정 시점 버전(Version) 또는 타임스탬프로 데이터를 쿼리하거나 오퍼레이션을 롤백할 수 있습니다.
기존 데이터 레이크(RAW S3/GCS + Parquet)는 유연하지만 데이터 품질, 일관성, 업데이트/삭제 작업에서 한계가 있었습니다. Delta Lake는 이를 해결하며 데이터 웨어하우스 수준의 관리 기능을 제공합니다.
| 기능 / 대안 | RAW Parquet/ORC | 전통적 데이터 웨어하우스 | Delta Lake |
|---|---|---|---|
| ACID 트랜잭션 | ❌ 없음 | ✅ 제공 | ✅ 제공 (멀티 클러스터) |
| 업데이트/머지/삭제 | ❌ Overwrite만 가능 | ✅ 제공 | ✅ 제공 (MERGE, UPDATE, DELETE) |
| 스키마 관리 | ❌ 수동 관리 | ✅ 엄격한 스키마 | ✅ 진화(Evolution) & 강화(Enforcement) |
| 데이터 버전 관리 | ❌ 없음 | ⚠️ 제한적 (백업) | ✅ 타임 트래블 (Version, Timestamp) |
| 오픈 포맷 | ✅ Parquet/ORC | ❌ 벤더 종속 | ✅ Parquet 기반 |
| 비용 | 💰 낮음 (스토리지) | 💰 높음 (라이선스+컴퓨트) | 💰 낮음 (스토리지+오픈소스) |
단일 소스의 진실(Single Source of Truth): ETL, 스트리밍, ML 훈련, BI 리포트가 모두 동일한 Delta 테이블을 안전하게 참조할 수 있습니다. 데이터 레이크의 유연성과 데이터 웨어하우스의 신뢰성을 결합한 Lakehouse 아키텍처의 실현체입니다.
# PySpark 셸 또는 노트북에서
# Delta Lake 패키지 지정 (Spark 3.x 이상)
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("DeltaQuickstart") \
.config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
.config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
.getOrCreate()
# 또는 spark-submit 시 패키지 추가
# --packages io.delta:delta-core_2.12:3.1.0
# 샘플 데이터 생성
data = [(1, "Alice", "Seoul", 1000),
(2, "Bob", "Busan", 2000),
(3, "Charlie", "Incheon", 1500)]
columns = ["id", "name", "city", "amount"]
df = spark.createDataFrame(data, columns)
# Delta 형식으로 저장 (디렉토리 생성)
delta_path = "s3a://my-bucket/delta/demo_table" # 또는 "file:///tmp/delta_demo"
df.write.format("delta").mode("overwrite").save(delta_path)
print(f"Delta 테이블 생성 완료: {delta_path}")
# 읽기
df_read = spark.read.format("delta").load(delta_path)
df_read.show()
# 업데이트 (SQL)
from delta.tables import DeltaTable
delta_table = DeltaTable.forPath(spark, delta_path)
# 조건부 업데이트
delta_table.update(
condition = "city = 'Seoul'",
set = { "amount": "amount + 100" }
)
# 머지(Upsert) 작업
new_data = spark.createDataFrame([(4, "David", "Daegu", 3000), (1, "Alice", "Seoul", 1200)], columns)
delta_table.alias("target").merge(
new_data.alias("source"),
"target.id = source.id"
).whenMatchedUpdateAll() \
.whenNotMatchedInsertAll() \
.execute()
# 타임 트래블 (이전 버전 조회)
df_history = spark.read.format("delta").option("versionAsOf", 0).load(delta_path)
print("초기 버전 데이터:")
df_history.show()
# 테이블 히스토리 확인
delta_table.history().show(truncate=False)
# 상세 정보
spark.sql(f"DESCRIBE DETAIL delta.`{delta_path}`").show(truncate=False)
# 오래된 파일 정리 (7일 이전 파일 삭제, dry-run 먼저 실행 권장)
delta_table.vacuum(retentionHours=168) # 7일
# delta_table.vacuum(retentionHours=168, dryRun=True) # 실제 삭제 전 확인
| 설정 | 기본값 | 설명 | 실무 권장값 |
|---|---|---|---|
delta.logRetentionDuration |
30 days | 트랜잭션 로그 보관 기간. 타임 트래블 가능 기간 결정. | 30-90 days (규제 요구사항에 따라) |
delta.deletedFileRetentionDuration |
1 week | VACUUM 명령 실행 시 삭제되지 않을 파일의 최소 보존 기간. | 7 days (VACUUM 주기보다 길게) |
delta.autoOptimize.autoCompact |
false | 작성 후 자동으로 소규모 파일을 컴팩션. | true (스트리밍/소규모 쓰기 많을 때) |
delta.autoOptimize.optimizeWrite |
false | 쓰기 시 자동으로 파일 크기 최적화. | true (대부분의 경우 권장) |
spark.databricks.delta.schema.autoMerge.enabled |
false | 스키마 자동 진화 허용 (스키마 병합). | true (ELT/스키마 변경 많을 때) |
delta.appendOnly |
false | 테이블을 추가 전용 모드로 설정 (업데이트/삭제 방지). | 로그/이벤트 데이터 테이블에 적용 |
# 테이블 생성 시 설정 지정
df.write.format("delta") \
.option("delta.autoOptimize.optimizeWrite", "true") \
.option("delta.logRetentionDuration", "interval 60 days") \
.save("/path/to/table")
# 기존 테이블 설정 변경 (Spark SQL)
spark.sql("""
ALTER TABLE my_delta_table
SET TBLPROPERTIES (
'delta.deletedFileRetentionDuration' = 'interval 15 days',
'delta.autoOptimize.autoCompact' = 'true'
)
""")
증상: 많은 수의 소규모 Parquet 파일로 인해 메타데이터 처리 오버헤드 증가, 쿼리 성능 저하.
해결:
# 1. OPTIMIZE 명령으로 파일 컴팩션
spark.sql("OPTIMIZE delta.`/path/to/table`")
# 2. Z-Ordering으로 데이터 클러스터링 (자주 함께 필터링하는 컬럼 기준)
spark.sql("OPTIMIZE delta.`/path/to/table` ZORDER BY (date, user_id)")
# 3. 쓰기 시 자동 최적화 활성화 (테이블 속성 설정)
spark.sql("""
ALTER TABLE my_table SET TBLPROPERTIES (
'delta.autoOptimize.autoCompact' = 'true',
'delta.autoOptimize.optimizeWrite' = 'true'
)
""")
증상: VACUUM 실행 후 특정 시점으로의 타임 트래블 실패 또는 데이터 복구 불가.
원인: VACUUM은 보존 기간(deletedFileRetentionDuration) 이전의 데이터 파일을 물리적으로 삭제합니다.
해결:
dryRun=True로 삭제될 파일 확인.# 안전한 VACUUM 실행
delta_table.vacuum(retentionHours=168, dryRun=True) # 먼저 확인
delta_table.vacuum(retentionHours=168) # 실제 실행
문제: 새 컬럼이 추가된 데이터를 기존 Delta 테이블에 쓰려고 할 때 스키마 불일치 오류.
선택:
# 스키마 진화 활성화하여 쓰기
new_df.write.format("delta") \
.mode("append") \
.option("mergeSchema", "true") \ # 스키마 진화 활성화
.save("/path/to/table")
# 또는 테이블 수준에서 자동 병합 활성화
spark.sql("""
ALTER TABLE my_table SET TBLPROPERTIES (
'spark.databricks.delta.schema.autoMerge.enabled' = 'true'
)
""")
증상: 여러 Spark 잡이나 사용자가 동시에 같은 테이블에 쓸 때 ConcurrentAppendException 또는 ConcurrentUpdateException 발생.
해결:
spark.databricks.delta.optimisticConcurrencyControl.enabled 설정.