1727 words
9 minutes
[DE Design Pattern]04-2. Overwriting 패턴들
Overwriting 패턴
01. Fast Metadata Cleaner
Pattern Overview
- 데이터셋을 하나의 거대한 테이블이 아닌, 시간 단위로 분할된 여러 테이블의 집합으로 바라봄.
- 테이블 집합에 view를 적용해 하나처럼 보이게함 재처리가 필요하면 해당 파티션 테이블만
TRUNCATE또는DROP TRUNCATE TABLE은DELETE 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 Cleanerdef 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 DeltaTabledt = DeltaTable.forPath(spark, "s3://output/visits")dt.vacuum(retentionHours=168) # 7일 이전 파일 삭제- VACUUM 없이는 디스크 사용량이 계속 증가하지만, VACUUM을 실행하면 해당 버전 이전으로의 time travel이 불가능.
- 여전히 비용 vs 복구 가능성의 트레이드오프 존재
Fast Metadata Cleaner vs Data Overwrite 비교
| 기준 | Fast Metadata Cleaner | Data 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/