Data Engineering
데이터 엔지니어링 시리즈 #10: 데이터 레이크 vs 웨어하우스 - 레이크하우스 아키텍처
Data Engineering Series(3 / 12)
데이터 엔지니어링 시리즈 #10: 데이터 레이크 vs 웨어하우스 - 레이크하우스 아키텍처
대상 독자: 충분한 경험을 가진 백엔드/풀스택 엔지니어로, PostgreSQL ACID에 익숙하지만 데이터 레이크/웨어하우스는 처음인 분
이 편에서 다루는 것
"S3에 Parquet 올려두면 되는 거 아닌가요?" 라는 질문에서 시작합니다. 왜 Delta Lake 같은 테이블 포맷이 필요한지, 그리고 레이크하우스가 무엇인지 배웁니다.
데이터 저장소의 진화
세대별 변화
데이터 웨어하우스 (Data Warehouse)
특징
PostgreSQL과의 비교
| 특성 | PostgreSQL (OLTP) | BigQuery (DW) |
|---|---|---|
| 목적 | 트랜잭션 처리 | 분석 쿼리 |
| 스토리지 | Row-based | Column-based |
| 스케일 | 수직 확장 | 무한 수평 확장 |
| 비용 | 서버 비용 | 쿼리당 비용 |
| 쿼리 속도 | 단건 빠름 | 집계 빠름 |
데이터 레이크 (Data Lake)
특징
데이터 레이크의 문제점
레이크하우스 (Lakehouse)
두 세계의 통합
핵심 가치
| 특성 | 레이크 | 웨어하우스 | 레이크하우스 |
|---|---|---|---|
| 저장 비용 | 저렴 ✅ | 비쌈 | 저렴 ✅ |
| ACID | ❌ | ✅ | ✅ |
| 오픈 포맷 | ✅ | ❌ (벤더) | ✅ |
| ML 지원 | ✅ | 제한적 | ✅ |
| SQL 분석 | 제한적 | ✅ | ✅ |
Medallion Architecture (Bronze/Silver/Gold)
레이크하우스에서 데이터를 계층화하여 관리하는 표준 패턴입니다. Databricks가 제안하고 현재 업계 표준으로 자리잡았습니다.
출처: Databricks - Medallion Architecture, Armbrust et al., "Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores" (VLDB 2020)
세 레이어 구조
각 레이어의 역할
| Layer | 목적 | 데이터 특성 | 소비자 |
|---|---|---|---|
| Bronze | 원본 보존 | Raw, 스키마 유연 | 데이터 엔지니어 |
| Silver | 정제/통합 | Cleaned, 조인됨 | 데이터 분석가, DS |
| Gold | 비즈니스 집계 | Aggregated, 최적화 | BI, 경영진 |
코드 예시
# Bronze: 원본 그대로 저장
raw_events = spark.readStream \
.format("kafka") \
.option("kafka.bootstrap.servers", "kafka:9092") \
.option("subscribe", "user_events") \
.load()
raw_events.writeStream \
.format("delta") \
.option("checkpointLocation", "/checkpoints/bronze") \
.start("/delta/bronze/events")
# Silver: 정제 및 스키마 적용
bronze_df = spark.read.format("delta").load("/delta/bronze/events")
silver_df = bronze_df \
.select(from_json(col("value"), schema).alias("data")) \
.select("data.*") \
.filter(col("user_id").isNotNull()) \
.dropDuplicates(["event_id"])
silver_df.write.format("delta").mode("overwrite") \
.save("/delta/silver/events")
# Gold: 비즈니스 집계
silver_df = spark.read.format("delta").load("/delta/silver/events")
gold_df = silver_df \
.groupBy("date", "event_type") \
.agg(
count("*").alias("event_count"),
countDistinct("user_id").alias("unique_users")
)
gold_df.write.format("delta").mode("overwrite") \
.save("/delta/gold/daily_metrics")
왜 이 패턴인가?
| 문제 | Medallion 해결책 |
|---|---|
| 원본 데이터 유실 | Bronze에 원본 보존 |
| 스키마 변경 대응 | Bronze는 스키마 유연, Silver에서 검증 |
| 재처리 필요 | Bronze → Silver → Gold 순서대로 재실행 |
| 다양한 소비자 니즈 | 레이어별 최적화된 데이터 제공 |
Delta Lake 심층 분석
ACID 트랜잭션
Delta Lake의 방법: 트랜잭션 로그 (_delta_log/)
table/
├── _delta_log/
│ ├── 00000000000000000000.json # 첫 트랜잭션
│ ├── 00000000000000000001.json # 두 번째
│ └── 00000000000000000002.json # 세 번째
├── part-00000.parquet
├── part-00001.parquet
└── part-00002.parquet
Time Travel
# 특정 버전으로 읽기
df = spark.read.format("delta") \
.option("versionAsOf", 2) \
.load("/delta/users")
# 특정 시점으로 읽기
df = spark.read.format("delta") \
.option("timestampAsOf", "2024-01-01") \
.load("/delta/users")
# 히스토리 조회
from delta.tables import DeltaTable
dt = DeltaTable.forPath(spark, "/delta/users")
dt.history().show()
Schema Evolution
# 자동 스키마 병합
df_new.write.format("delta") \
.mode("append") \
.option("mergeSchema", "true") \
.save("/delta/users")
# 스키마 덮어쓰기 (주의!)
df_new.write.format("delta") \
.mode("overwrite") \
.option("overwriteSchema", "true") \
.save("/delta/users")
MERGE (Upsert)
from delta.tables import DeltaTable
# 타겟 테이블
target = DeltaTable.forPath(spark, "/delta/users")
# 소스 데이터 (업데이트할 데이터)
source = spark.read.parquet("/staging/users")
# MERGE 실행
target.alias("t").merge(
source.alias("s"),
"t.user_id = s.user_id"
).whenMatchedUpdate(
set={
"name": "s.name",
"email": "s.email",
"updated_at": "current_timestamp()"
}
).whenNotMatchedInsert(
values={
"user_id": "s.user_id",
"name": "s.name",
"email": "s.email",
"created_at": "current_timestamp()"
}
).execute()
Delta Lake vs Apache Iceberg
비교
| 특성 | Delta Lake | Apache Iceberg |
|---|---|---|
| 개발사 | Databricks | Netflix→Apache |
| Spark 지원 | 최고 | 좋음 |
| Flink 지원 | 제한적 | 좋음 |
| Trino 지원 | 좋음 | 좋음 |
| 파티셔닝 | 명시적 | Hidden (투명) |
| 채택율 | 높음 | 증가 중 |
선택 가이드
아키텍처 결정 가이드
언제 무엇을 선택하나?
정리
다음 편 예고
11편: 데이터 모델링에서는 분석용 모델링을 다룹니다:
- Star Schema vs Snowflake Schema
- Fact Table vs Dimension Table
- Slowly Changing Dimensions (SCD)
참고 자료
- Delta Lake Documentation
- Databricks Medallion Architecture
- Apache Iceberg Documentation
- Armbrust et al., "Delta Lake: High-Performance ACID Table Storage" (VLDB 2020)
- Databricks, "The Data Lakehouse" White Paper
- Martin Kleppmann, "Designing Data-Intensive Applications" - Chapter 3