데이터 과학자라면 필수! Delta Lake(델타 레이크) 알아보기

데이터 분석이나 머신러닝 프로젝트를 시작할 때, 대부분 “데이터 레이크(Data Lake)“에 데이터를 저장하는 것으로 시작합니다. 보통 클라우드 저장소(S3, Azure Blob 등)에 CSV나 Parquet 파일을 잔뜩 쌓아두는 방식이죠.

처음에는 유연하고 저렴해 보입니다. 하지만 데이터 규모가 커지면 현실은 곧 엉망진창이 됩니다.

  • 데이터 덮어쓰기 충돌: 파이프라인이 동시에 돌다가 서로 데이터를 덮어써 버립니다.
  • 파일 불일치: 스키마(열 구조)가 다른 파일들이 섞여 에러가 납니다.
  • 데이터 품질 저하: 잘못된 데이터가 들어와도 아무도 모릅니다.
  • 버전 관리 불가: “이 모델 학습할 때 썼던 데이터가 뭐였지?”라고 물으면 아무도 대답을 못 합니다.

우리는 저장소를 가졌지만, 데이터의 신뢰성(Reliability) 은 갖지 못한 것입니다.

이 문제를 해결하기 위해 등장한 것이 바로 델타 레이크(Delta Lake) 입니다. 오늘은 델타 레이크가 무엇인지 알아보고, 데이터 과학자가 반드시 익혀야 할 7가지 핵심 작업을 코드를 통해 직접 실습해 보겠습니다.

1. 델타 레이크(Delta Lake)란 무엇인가요?

델타 레이크는 기존의 Parquet 파일 위에 트랜잭션 로그(Transaction Log) 라는 기능을 덧입힌 오픈 소스 스토리지 레이어입니다.

쉽게 비유하자면, 도서관의 책(데이터)이 아무렇게나 꽂혀 있는 상태가 기존의 데이터 레이크라면, 델타 레이크는 “누가, 언제, 어떤 책을 꽂았고 뺐는지 기록하는 사서와 대출 장부” 가 있는 시스템입니다.

핵심 비밀: _delta_log

델타 레이크 테이블을 만들면 데이터 폴더 안에 숨겨진 폴더인 /_delta_log/가 생성됩니다. 이곳에는 모든 변경 사항(추가, 삭제, 수정)을 기록한 JSON 파일들이 저장됩니다. 델타 레이크는 이 로그를 보고 데이터의 무결성을 보장합니다.

2. 실습 준비: 설치 및 설정

실습을 위해서는 Python과 Spark 환경이 필요합니다. 로컬 환경(내 컴퓨터)에서 따라 하실 수 있도록 설치부터 알려드립니다.

1) 라이브러리 설치

터미널(또는 CMD)에서 아래 명령어를 입력하여 필요한 패키지를 설치합니다.

pip install pyspark delta-spark
2) Spark 세션 시작하기 (파이썬 코드)

일반적인 Spark 설정에 델타 레이크 설정을 추가해야 합니다. 아래 코드를 실행하여 환경을 구축하세요.

from pyspark.sql import SparkSession
from delta import *

# 델타 레이크 설정
builder = SparkSession.builder.appName("NaverShoppingDelta") \
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
    .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog")

spark = configure_spark_with_delta_pip(builder).getOrCreate()

print("쇼핑 데이터 분석 환경 준비 완료!")

3. 실습 데이터셋: 쇼핑 검색 결과

쇼핑에서 흔히 볼 수 있는 노트북, 운동화, 생필품 데이터를 만들어 봅시다.

# 쇼핑 상품 데이터 생성
products = [
    ("P1001", "삼성전자 갤럭시북4 프로", "노트북", 1850000, "삼성전자"),
    ("P1002", "나이키 에어포스 1 '07", "신발", 139000, "나이키"),
    ("P1003", "코멧 아기 물티슈 100매", "생활용품", 15000, "쿠팡"),
]

columns = ["product_id", "product_name", "category", "price", "brand"]

df = spark.createDataFrame(products, columns)
df.show(truncate=False)

출력 결과:

+----------+-----------------------+--------+-------+--------+
|product_id|product_name           |category|price  |brand   |
+----------+-----------------------+--------+-------+--------+
|P1001     |삼성전자 갤럭시북4 프로  |노트북   |1850000|삼성전자|
|P1002     |나이키 에어포스 1 '07    |신발     |139000 |나이키  |
|P1003     |코멧 아기 물티슈 100매   |생활용품  |15000  |쿠팡    |
+----------+-----------------------+--------+-------+--------+

작업 #1: 델타 테이블 생성 (Create)

기존에는 df.write.csv() 등을 썼겠지만, 이제는 delta 포맷을 사용합니다. 이것만으로도 데이터 보호가 시작됩니다. 또한 가장 기본이 되는 작업입니다. 데이터를 저장할 때 format("delta")를 지정합니다. 이제부터 모든 변경 사항이 기록됩니다.

# 데이터를 저장할 경로
data_path = "./datalake/shopping_products"

# 1. 델타 포맷으로 저장 (기존 데이터 덮어쓰기 모드)
df.write.format("delta").mode("overwrite").save(data_path)

# 2. SQL 사용을 위한 테이블 등록
spark.sql(f"""
    CREATE TABLE IF NOT EXISTS shopping_products
    USING DELTA
    LOCATION '{data_path}'
""")

print("상품 데이터 델타 테이블 생성 완료")

작업 #2: 테이블 상태 확인 (Describe Detail)

데이터 엔지니어라면 내 데이터가 “어떤 포맷으로, 얼마나 크게, 어디에” 저장되어 있는지 파악해야 합니다.

spark.sql("DESCRIBE DETAIL shopping_products").show(truncate=False)

이 정보가 중요한 이유:

  • format: delta (확인 필수)
  • numFiles: 파일 개수가 너무 많으면 성능이 떨어지므로 나중에 최적화(Optimize)가 필요함을 알 수 있습니다.

작업 #3: 시간 여행 (Time Travel) – 가격 오류 사고 복구

상황: MD의 실수로 185만 원짜리 ‘갤럭시북’ 가격이 0 하나가 빠져 18만 5천 원으로 업데이트되는 사고가 발생했습니다! 😱

이때 델타 레이크는 **사고 발생 전(버전 0)**으로 데이터를 되돌릴 수 있습니다.

변경 이력(History) 확인하기
spark.sql("DESCRIBE HISTORY shopping_products").show(truncate=False)

이 표를 보면 version 0, version 1 처럼 데이터가 변할 때마다 버전이 기록된 것을 볼 수 있습니다.

# 버전 0 (최초 생성 시점, 사고 치기 전)의 데이터 불러오기
df_old = spark.read.format("delta").option("versionAsOf", 0).load(data_path)
df_old.show()
과거 데이터 조회 및 복구
# 사고 치기 전인 버전 0 데이터 조회
df_safe = spark.read.format("delta").option("versionAsOf", 0).load(data_path)
df_safe.show()

이것이 시간 여행(Time Travel)입니다. 데이터 분석가에게는 데이터를 복구할 수 있는 ‘타임머신’이 생긴 셈입니다.

작업 #4: 배치와 스트리밍의 통합 (Unified Read)

쇼핑몰에는 두 가지 데이터 흐름이 있습니다.

  1. 배치: 어제 하루 동안 팔린 상품 목록 (정산용)
  2. 스트리밍: 지금 이 순간 실시간으로 클릭 되고 있는 상품 목록 (추천용)

델타 레이크는 이 두 가지를 하나의 테이블에서 처리합니다.

# 배치로 읽기 (일반적인 분석)
df_items = spark.read.table("shopping_products")

# 스트리밍으로 읽기 (실시간 모니터링)
# df_stream = spark.readStream.format("delta").load(data_path)

코드가 거의 동일합니다. 덕분에 데이터 파이프라인 구조가 아주 단순해집니다.

작업 #5: 파티셔닝 (Partitioning) – 검색 속도 최적화

쇼핑엔 상품이 수억 개가 있습니다. 누군가 “노트북 카테고리 상품만 보여줘”라고 했는데, 기저귀나 라면 데이터까지 뒤지면 너무 느리겠죠?

데이터를 카테고리별로 폴더를 나눠서 저장(파티셔닝) 합니다.

# 'category' 기준으로 폴더를 나눠서 저장
df.write.format("delta") \
    .mode("overwrite") \
    .option("overwriteSchema", "true") \
    .partitionBy("category") \
    .save(data_path)

결과: category=노트북/, category=신발/ 처럼 물리적인 폴더가 분리됩니다. 쿼리 시 WHERE category = '노트북'을 쓰면, 스파크는 다른 폴더는 쳐다보지도 않고 노트북 폴더만 읽어 처리 속도가 비약적으로 상승합니다.

작업 #6: 스키마 진화 (Schema Evolution)

서비스를 하다 보면 데이터 구조가 바뀝니다. 예를 들어 상품에 “리뷰 평점(rating)” 정보를 추가해야 한다고 칩시다. 일반적인 DB나 파일 시스템에선 아주 골치 아픈 일입니다.

하지만 델타는 자연스럽게 구조를 진화(Evolution)시킵니다.

from pyspark.sql.functions import lit

# 1. 새로운 컬럼 'rating' 추가 (기본값 0.0)
df_new = df.withColumn("rating", lit(0.0))

# 2. overwriteSchema 옵션으로 스키마 변경 허용
df_new.write.format("delta") \
    .mode("overwrite") \
    .option("overwriteSchema", "true") \
    .save(data_path)

print("평점 컬럼이 추가되었습니다.")

데이터 파이프라인을 중단하지 않고도 새로운 비즈니스 요구사항(컬럼 추가)을 반영할 수 있습니다.

작업 #7: MERGE (Upsert) – 데이터 동기화의 끝판왕

이커머스 데이터 분석의 꽃입니다. “판매자 시스템에서 최신 엑셀 파일이 왔습니다. 기존 상품은 가격/재고를 수정하고, 신상품은 추가해주세요.”

Upsert(Update + Insert) 작업을 델타는 MERGE 명령어로 완벽하게 수행합니다.

from delta.tables import DeltaTable

delta_table = DeltaTable.forPath(spark, data_path)

# 업데이트할 데이터 (갤럭시북 가격 인하, 아이패드 신규 추가)
updates = spark.createDataFrame([
    ("P1001", "삼성전자 갤럭시북4 프로", "노트북", 1600000, "삼성전자", 4.8), # 가격 할인
    ("P1004", "애플 아이패드 에어 6", "태블릿", 990000, "애플", 0.0),      # 신상품
], df_new.columns)

# MERGE 실행
delta_table.alias("original").merge(
    updates.alias("updates"),
    "original.product_id = updates.product_id" # 상품 ID가 같으면?
).whenMatchedUpdateAll() \
 .whenNotMatchedInsertAll() \
 .execute()

# 결과 확인
delta_table.toDF().orderBy("product_id").show(truncate=False)

실행 결과:

  1. P1001(갤럭시북): 가격이 185만 원 -> 160만 원으로 수정됨 (Update)
  2. P1004(아이패드): 목록에 없었으므로 새로 추가됨 (Insert)
  3. 나머지: 변화 없음

마치며: 데이터 호수(Lake)를 맑게 유지하세요

쇼핑 제품 데이터를 통해 델타 레이크의 핵심 7가지 기술을 배웠습니다.

  1. Create: 트랜잭션이 보장되는 테이블 생성
  2. Describe: 데이터 저장소의 물리적 상태 점검
  3. History: 사고 발생 시 과거로 롤백 (Time Travel)
  4. Read: 배치와 스트리밍의 경계 없는 조회
  5. Partition: 카테고리별 분리 저장으로 속도 향상
  6. Schema: 유연한 컬럼 추가 (스키마 진화)
  7. Merge: 복잡한 데이터 갱신을 한 번에 (Upsert)

더 이상 깨지기 쉬운 CSV 파일 때문에 고민하지 마세요. 델타 레이크를 도입하는 순간, 데이터 파이프라인은 ‘쇼핑몰의 장바구니’처럼 견고하고 유연해질 것이라고 생각합니다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다