1727 words
9 minutes
[DE Design Pattern]04-2. Overwriting 패턴들

Overwriting 패턴#

01. Fast Metadata Cleaner#

Pattern Overview#

  • 데이터셋을 하나의 거대한 테이블이 아닌, 시간 단위로 분할된 여러 테이블의 집합으로 바라봄.
  • 테이블 집합에 view를 적용해 하나처럼 보이게함 재처리가 필요하면 해당 파티션 테이블만 TRUNCATE 또는 DROP
  • TRUNCATE TABLEDELETE FROM (조건 없는)과 의미적으로 동일하지만, 테이블 스캔을 하지 않는 메타데이터 연산이라 훨씬 빠르게 동작

멱등성 그래뉼러리티(Idempotency Granularity)#

  • 파티션 단위가 곧 멱등성의 최소 단위.

TRUNCATE vs DROP#

# === TRUNCATE 기반 워크플로우 ===
def truncate_based_idempotency(db, execution_date, data):
table_name = f"visits_week_{execution_date.isocalendar()[1]}"
# 1. 테이블 없으면 생성
db.execute(f"CREATE TABLE IF NOT EXISTS {table_name} (LIKE visits_template)")
# 2. 뷰에 포함
db.execute(f"CREATE OR REPLACE VIEW visits AS SELECT * FROM {table_name} UNION ALL ...")
# 3. TRUNCATE — 메타데이터 연산, 매우 빠름
db.execute(f"TRUNCATE TABLE {table_name}")
# 4. 데이터 적재
db.execute(f"INSERT INTO {table_name} SELECT * FROM {data}")
# === DROP 기반 워크플로우 ===
def drop_based_idempotency(db, execution_date, data):
table_name = f"visits_week_{execution_date.isocalendar()[1]}"
# 1. 뷰에서 먼저 제거 (소비자 에러 방지)
db.execute(f"-- remove {table_name} from view")
# 2. DROP — 테이블 자체를 삭제
db.execute(f"DROP TABLE IF EXISTS {table_name}")
# 3. 새로 생성
db.execute(f"CREATE TABLE {table_name} (LIKE visits_template)")
# 4. 뷰에 다시 포함
db.execute(f"CREATE OR REPLACE VIEW visits AS SELECT * FROM {table_name} UNION ALL ...")
# 5. 데이터 적재
db.execute(f"INSERT INTO {table_name} SELECT * FROM {data}")
  • DROP 방식에서 뷰에서 먼저 제거하는 이유는, 소비자가 뷰를 조회하는 도중 테이블이 DROP되면 에러가 발생하기 때문입니다.

Full Load에 적용#

  • Full 일 경우매번 테이블을 재생성하면 됨.
# Full Load용 단순화된 Fast Metadata Cleaner
def full_load_fast_clean(db, data):
# DROP → CREATE → LOAD (매 실행마다)
db.execute("DROP TABLE IF EXISTS devices")
db.execute("CREATE TABLE devices (type TEXT, full_name TEXT, version TEXT)")
db.execute(f"COPY devices FROM '{data}'")

주의#

메타데이터 한계 — 여러 파이프라인에서 이 패턴을 쓰면 한계에 빠르게 도달. freezing 스텝을 따로 두어서, 변경이 없는 오래된 주간 테이블을 월간/연간 테이블로 병합 가능

Schema Evolution — 새 optional 필드가 추가되면, 이미 존재하는 파티션 테이블들의 스키마 별도 업데이트 필요. required 필드 추가는 재처리 시 자동으로 반영

  • 메타데이터 연산은 항상 테이블 전체에 적용

Concept

  • Fast Metadata Cleaner : DELETE 대신 TRUNCATE/DROP 같은 메타데이터 연산으로 빠르게 테이블을 정리하여 멱등성을 확보하는 패턴
  • 멱등성 그래뉼러리티(Idempotency Granularity) : 멱등성이 적용되는 최소 단위. 주간 테이블이면 1주, 일간이면 1일이 백필 단위가 됨
  • Single Data Exposition Abstraction : 물리적으로 분할된 여러 테이블을 뷰(view)로 묶어 소비자에게 하나의 데이터셋처럼 제공하는 구조
  • TRUNCATE vs DELETE : TRUNCATE는 테이블 스캔 없이 메타데이터만 조작하여 전체 삭제. DELETE는 조건별 row 스캔 후 삭제하는 데이터 연산
  • Freezing : 더 이상 변경되지 않는 파티션 테이블들을 더 큰 단위로 합쳐 파티션 수를 줄이는 기법
  • Partition Pruning : 뷰 아래 다수의 테이블이 있을 때, 쿼리 엔진이 불필요한 파티션을 건너뛰는 최적화 기법
  • Apache Airflow BranchPythonOperator : 실행 날짜에 따라 테이블 생성 브랜치와 데이터 로딩 브랜치를 분기하는 오케스트레이션 패턴

02. Data Overwrite#

Pattern Overview#

  • 오브젝트 스토어에 이벤트 타임 파티션으로 방문 데이터를 저장
  • 메타데이터 레이어가 없어서 TRUNCATE/DROP 불가능 -> 백필할 때마다 중복 레코드가 생성

핵심 아이디어#

메타데이터 연산을 쓸 수 없으면, 데이터 레이어에서 직접 덮어쓰기. 기존 파일/레코드를 물리적으로 교체

세 가지 구현 방법#

방법 1: 프레임워크의 save mode 활용

# Apache Spark — overwrite 모드
input_data = spark.read.json("s3://input/visits/date=2024-01-15")
# mode('overwrite')가 기존 데이터를 삭제 후 새로 쓴다
input_data.write.mode('overwrite').parquet("s3://output/visits/date=2024-01-15")

Spark의 mode('overwrite')는 대상 경로의 기존 파일을 모두 제거한 뒤 새 데이터를 작성.

방법 2: SQL DELETE + INSERT 조합

# 전통적인 SQL 방식
def overwrite_with_delete_insert(db, target_date, staging_table):
# 1단계: 해당 날짜 데이터 삭제
db.execute(f"DELETE FROM visits WHERE event_date = '{target_date}'")
# 2단계: 새 데이터 삽입
db.execute(f"""
INSERT INTO visits
SELECT * FROM {staging_table}
WHERE event_date = '{target_date}'
""")
  • 특정 조건에 맞는 row만 지우고 재적재

방법 3: INSERT OVERWRITE (단일 명령)

# Databricks, Snowflake 등에서 지원
# DELETE + INSERT를 하나의 명령으로 수행
db.execute("""
INSERT OVERWRITE INTO devices
SELECT * FROM devices_staging
""")

INSERT OVERWRITE는 테이블 전체를 대상으로 한 번에 교체

Delta Lake의 선택적 덮어쓰기#

Delta Lake에서는 replaceWhere 옵션으로 파티션 단위 선택적 덮어쓰기가 가능합니다.

# Delta Lake — 특정 파티션만 덮어쓰기
(input_data.write
.format("delta")
.mode("overwrite")
.option("replaceWhere", "event_date = '2024-01-15'")
.save("s3://output/visits"))

전체 테이블이 아닌 조건에 맞는 파티션의 파일만 교체

Time Travel과 VACUUM의 관계#

# 덮어쓰기 후에도 이전 버전 조회 가능 (Delta Lake)
spark.read.format("delta").option("versionAsOf", 3).load("s3://output/visits")
# 오래된 파일을 실제로 삭제하려면 VACUUM 실행
from delta.tables import DeltaTable
dt = DeltaTable.forPath(spark, "s3://output/visits")
dt.vacuum(retentionHours=168) # 7일 이전 파일 삭제
  • VACUUM 없이는 디스크 사용량이 계속 증가하지만, VACUUM을 실행하면 해당 버전 이전으로의 time travel이 불가능.
  • 여전히 비용 vs 복구 가능성의 트레이드오프 존재

Fast Metadata Cleaner vs Data Overwrite 비교#

기준Fast Metadata CleanerData Overwrite
연산 레벨메타데이터 (논리적)데이터 (물리적)
속도빠름 (스캔 없음)데이터 크기에 비례
적용 환경DW, RDBMS, Lakehouse오브젝트 스토어 포함 모든 환경
파티셔닝 필요필수 (멱등성 단위)권장 (성능 최적화)
VACUUM 필요불필요필요할 수 있음

Concept

  • Data Overwrite : 메타데이터 레이어 없이 데이터 파일 자체를 물리적으로 교체하여 멱등성을 확보하는 패턴
  • Save Mode / Write Mode : Spark(overwrite), Flink(write mode) 등 프레임워크가 제공하는 쓰기 동작 설정. 기존 데이터 처리 방식을 결정
  • INSERT OVERWRITE : 테이블 전체를 한 번에 교체하는 SQL 명령. DELETE+INSERT의 단축 형태이나 조건부 선택 불가
  • replaceWhere : Delta Lake에서 특정 조건에 맞는 파티션만 선택적으로 덮어쓰는 옵션
  • VACUUM : time travel을 지원하는 데이터 스토어에서, 더 이상 필요 없는 이전 버전 파일을 물리적으로 삭제하여 스토리지를 회수하는 작업
  • Time Travel : 데이터의 과거 버전을 조회/복원할 수 있는 기능. Delta Lake, Iceberg, BigQuery 등에서 지원

[DE Design Pattern]04-2. Overwriting 패턴들
https://yjinheon.netlify.app/posts/02de/de-design-pattern/04-idempotency/04-02-data_overwrite/
Author
Datamind
Published at
2025-02-28
License
CC BY-NC-SA 4.0