Skip to main content
이 문서는 레이크하우스 아키텍처 섹션의 일부입니다.

심화: Delta Lake 내부 구조와 동시성 제어

이 섹션에서는 Principal SA 수준에서 알아야 할 Delta Lake의 내부 동작 원리, 동시성 제어 메커니즘, 대규모 테이블에서의 성능 이슈와 대응 방안을 다룹니다.

트랜잭션 로그 내부 구조 — 액션(Action) 상세

Delta Log의 각 JSON 커밋 파일은 하나 이상의 액션(Action) 으로 구성됩니다. 이 액션들이 Delta Lake의 모든 기능을 가능하게 하는 핵심 메커니즘입니다.

파일 통계(Stats)와 Data Skipping

Add 액션에 포함된 stats 필드는 각 Parquet 파일의 min/max 값, null 개수, 레코드 수 를 담고 있습니다. 이 통계를 활용하여 쿼리 시 불필요한 파일을 건너뛰는 것이 Data Skipping 입니다.
⚠️ Gotcha: 기본적으로 통계는 처음 32개 컬럼 에 대해서만 수집됩니다. 와이드 테이블(수백 개 컬럼)에서 33번째 이후 컬럼으로 필터링하면 Data Skipping이 동작하지 않습니다. delta.dataSkippingNumIndexedCols 속성으로 조정할 수 있지만, 너무 크게 설정하면 커밋 시 오버헤드가 증가합니다.
⚠️ Gotcha: Stats는 STRING 컬럼의 처음 32자 만 인덱싱합니다. 긴 문자열(URL, JSON 등)에 대한 Data Skipping은 효과가 제한적입니다. 이런 경우 Z-ORDER나 Liquid Clustering을 활용하세요.

쓰기 충돌 해결 (Write Conflict Resolution)

Delta Lake는 낙관적 동시성 제어(Optimistic Concurrency Control, OCC) 를 사용합니다. 이는 “충돌이 드물 것”이라고 낙관적으로 가정하고, 충돌이 실제로 발생했을 때만 처리하는 방식입니다.

동작 원리

  1. 트랜잭션 시작 시 현재 테이블 버전(예: v5)을 읽습니다
  2. 데이터 변환 처리를 수행합니다
  3. 커밋 시, _delta_log/00000000000000000006.json 파일을 원자적으로(atomically) 생성합니다
  4. 다른 Writer가 이미 v6을 커밋했다면, 충돌이 발생합니다
  5. 충돌 발생 시, 새로 커밋된 로그를 읽어 논리적 충돌 이 있는지 확인합니다

ConflictException 발생 조건

모든 동시 쓰기가 충돌하는 것은 아닙니다. Delta Lake는 실제로 같은 파일/파티션에 영향을 주는 경우 에만 충돌로 판단합니다.

재시도 패턴

⚠️ Gotcha — OPTIMIZE와 동시 쓰기: OPTIMIZE(또는 Auto Compaction)가 실행 중일 때 다른 Writer가 커밋하면 ConcurrentAppendException이 발생할 수 있습니다. 프로덕션에서는 OPTIMIZE를 쓰기 워크로드가 적은 시간대 에 스케줄링하거나, 파티션 단위로 나누어 실행하세요.
⚠️ Gotcha — Structured Streaming + Batch 쓰기: 같은 테이블에 Streaming과 Batch 작업이 동시에 실행되면 충돌이 빈번합니다. Streaming은 foreachBatch + merge 패턴을 사용하고, Batch 작업은 다른 파티션에 쓰도록 분리하는 것이 좋습니다.

격리 수준 (Isolation Levels)

Delta Lake는 두 가지 격리 수준을 제공합니다. 기본값은 WriteSerializable이며, 대부분의 워크로드에 적합합니다.

WriteSerializable (기본값)

  • 동시 쓰기 작업들이 어떤 직렬 순서로 실행한 것과 동일한 결과 를 보장합니다
  • 단, 읽기(read set)와 쓰기(write set) 사이의 직렬화는 보장하지 않습니다
  • 즉, Writer A가 “조건 X에 해당하는 행이 없음”을 확인하고 INSERT를 수행하는 동안, Writer B가 조건 X에 해당하는 행을 INSERT할 수 있습니다 (Phantom Read 가능)

Serializable

  • 모든 작업이 완전히 직렬적으로 실행된 것처럼 보장합니다
  • Phantom Read를 방지합니다
  • 동시성이 크게 감소하므로, 정말 필요한 경우에만 사용하세요
⚠️ 실무 가이드: 99%의 데이터 엔지니어링 워크로드에서는 기본값인 WriteSerializable이 적합합니다. Serializable은 금융 원장, 재고 관리 등 비즈니스 로직이 read-then-write 패턴에 의존하는 경우 에만 사용하세요. 불필요하게 Serializable로 설정하면 동시 처리량이 크게 떨어집니다.

트랜잭션 로그 스케일링 — 대규모 테이블에서의 성능

수십만 건 이상의 커밋이 쌓인 대규모 테이블에서는 트랜잭션 로그 자체가 성능 병목이 될 수 있습니다.

체크포인트(Checkpoint)

Delta Lake는 기본적으로 10번의 커밋마다 체크포인트 파일(Parquet 형식)을 생성합니다. 체크포인트는 해당 시점까지의 모든 액션을 하나의 Parquet 파일로 압축한 것으로, 테이블의 현재 상태를 빠르게 복원할 수 있게 합니다.
  • 테이블을 읽을 때: 가장 최근 체크포인트 + 이후 JSON 파일들 만 읽으면 됩니다
  • 체크포인트 주기는 delta.checkpointInterval 속성으로 조정할 수 있습니다 (기본값: 10)

대규모 테이블의 성능 이슈와 대응

⚠️ Gotcha — VACUUM과 타임 트래블: VACUUM은 오래된 Parquet 파일을 물리적으로 삭제합니다. VACUUM 실행 후에는 보관 기간 이전 버전으로의 타임 트래블이 불가능 합니다. 규정 준수 요건(데이터 감사, 7년 보관 등)이 있다면 VACUUM 보관 기간을 신중하게 설정하세요.
⚠️ Gotcha — 체크포인트 주기 변경: 체크포인트 주기를 너무 크게 늘리면(예: 100) 테이블 첫 읽기 시 100개의 JSON 파일을 파싱해야 하므로 성능이 저하됩니다. 반대로 너무 작게 줄이면(예: 1) 매 커밋마다 체크포인트를 생성하여 쓰기 오버헤드가 증가합니다. 기본값 10이 대부분의 경우 최적입니다.

프로덕션 규모별 가이드

🆕 Predictive Optimization (GA): Databricks가 테이블 사용 패턴을 분석하여 OPTIMIZE, VACUUM, ANALYZE를 자동으로 실행하는 기능입니다. 대규모 테이블 운영의 부담을 크게 줄여줍니다. Unity Catalog Managed Table에서 사용 가능합니다.

정리

다음 문서에서는 Delta Lake 테이블을 체계적으로 구성하는 설계 패턴인 Medallion 아키텍처 를 살펴보겠습니다.

참고 링크