CDC가 데이터 플랫폼을 바꾸는 방식: CDC-based Incremental Replication

변경 로그를 병합하는 것에서 출발해, 정합성을 스스로 검증하는 복제 방식에 이르기까지
Jess Jang's avatar
Mar 03, 2026
CDC가 데이터 플랫폼을 바꾸는 방식: CDC-based Incremental Replication
Contents
1. 전수 적재 vs 증분 복제: 데이터 복제 방식을 재설계한 이유전통적인 전수 적재(Full Load) 방식의 구조적 한계CDC 기반 증분 복제(Incremental Replication): '전수 스캔'에서 '로그 병합'으로의 전환2. 아키텍처 설계: 변경 데이터가 흐르는 파이프라인의 구조2.1. 기술 스택과 데이터 흐름 (End-to-End)2.2. 왜 Apache Iceberg인가?2.3. 복제 프로세스: 초기 적재와 증분 병합2.4. 운영의 진화: 매시간(Hourly) 배치로의 전환과 과제3. 전략의 선택: PK 방식 vs 전체 로우 해시 방식3.1 두 가지 접근 방식: '현재의 스냅샷'인가, '상태의 기록'인가3.2 운영 환경에서의 차이: 정합성과 멱등성의 실전 시나리오3.3 비교 요약3.4 선택의 이유: "틀린 데이터보다 에러가 낫다"3.5. 트레이드오프(Trade-off): '완벽'이 아닌 '최선'의 선택4. 핵심 메커니즘: CDC Merge Logic의 정교함4.1 데이터 로드와 관점의 분리 (Read & Build)4.2 해시 기반 중복 제거 (Keep Latest)4.3 최종 병합과 원자성 보장 (Merge)4.4 데이터 품질 검증: 신뢰를 확정 짓는 마지막 단계5. 도입 후 변화: 비즈니스가 체감하는 혁신5.1. 성능 및 비용 최적화: 장애 대응 가용 시간 확보5.2. 데이터 신속성(Data Freshness): 시간 단위 분석 체계 구축5.3. 신뢰도 향상: 데이터 불일치 이슈의 해결6. 결론 및 로드맵: 미래를 향한 데이터 플랫폼6.1. 전수 적재에서 증분 복제로의 전환6.2. 향후 과제: 매시간(Hourly)을 넘어 완전한 실시간(Real-time)으로Conclusion: Faster, Consistent, Scalable

이전 글에서 우리는 Debezium과 Apache Flink를 활용해 신뢰할 수 있는 실시간 CDC 파이프라인을 구축했습니다. 하지만 파이프라인을 구축했다고 해서 모든 문제가 해결된 것은 아닙니다. S3에 저장된 Parquet 파일들은 발생한 순서대로 나열된 변경 이벤트들의 집합일 뿐, 그 자체로는 분석가가 즉시 쿼리하여 활용할 수 있는 상태가 아니기 때문입니다.

우리의 핵심 과제는 "이 파편화된 이벤트들을 어떻게 분석가가 즉시 쿼리할 수 있는 '복제 테이블(Replicated Table)'로 만들 것인가?"였습니다. 이 글에서 Replicated Table은 CDC 로그를 병합해 만든 최신 상태(Current-state) 분석용 테이블을 의미합니다. 이를 해결하기 위해 기존 전수 적재(Full Load) 방식의 한계를 분석하고, CDC 기반의 증분 복제 시스템을 설계한 과정을 공유합니다.

1. 전수 적재 vs 증분 복제: 데이터 복제 방식을 재설계한 이유

데이터 플랫폼의 복제 방식은 데이터 규모와 비즈니스 요구사항에 따라 변화합니다. 저희는 기존 전수 적재(Full Load) 방식이 가진 구조적 한계를 해결하기 위해 복제 프로세스를 근본적으로 재설계했습니다.

전통적인 전수 적재(Full Load) 방식의 구조적 한계

기존 전수 적재(Full Load): 매일 테이블 전체를 스캔하여 복제

그동안 저희는 Sqoop을 활용해 매일 서비스 DB의 데이터를 전수 적재해 왔습니다. 이 방식은 구현이 단순하지만, 데이터 규모가 커짐에 따라 운영상 다음과 같은 제약이 발생했습니다.

  • 24시간의 데이터 지연(Latency): 일 단위 배치 실행 구조로 인해 분석 환경과 실제 서비스 데이터 사이에 최대 24시간의 시차가 발생합니다. 이는 실시간 지표 모니터링이나 기민한 의사결정을 저해하는 요소가 되었습니다.

  • 원본 DB 부하(Full Table Scan): 변경된 데이터의 양과 상관없이 매번 테이블 전체를 읽어야 하므로 서비스 DB에 불필요한 I/O 부하를 지속적으로 줍니다. 데이터 양이 늘어날수록 복제 시간은 비례하여 길어지는 구조적 한계가 있었습니다.

  • 데이터 정합성 오류(Data Inconsistency): 전수 적재의 부하를 줄이기 위해 특정 컬럼(created_at 등)을 기준으로 최근 데이터만 가져오는 방식을 병행하기도 합니다. 하지만 이 경우, 과거에 생성된 데이터가 원천 DB에서 업데이트되었을 때 이를 감지하지 못해 데이터 플랫폼에 반영되지 않는 정합성 오류가 발생합니다.

결국 기존 방식은 단순하지만 무겁고(Heavy), 느리며(Slow), 일관성이 깨질 위험(Inconsistent)이 상존하는 구조였습니다.

CDC 기반 증분 복제(Incremental Replication): '전수 스캔'에서 '로그 병합'으로의 전환

이전 단계의 개선을 통해 Debezium과 Flink 조합으로 원천 DB의 모든 변경 이벤트를 누락 없이 신뢰성 있게 캡처할 수 있게 되었습니다. 이렇게 확보한 실시간 변경 로그를 활용해 기존 전수 적재(Full Load) 방식의 구조적 문제를 해결하는 것이 바로 CDC 기반 증분 복제(Incremental Replication)입니다. 이는 원천 DB를 직접 쿼리하여 스캔하는 대신, DB의 변경 로그(binlog)를 구독하여 복제 테이블(Replicated Table)에 반영하는 방식입니다.

핵심 컨셉: Current Replicated Table = Previous Replicated Table + Latest CDC logs

CDC 기반 증분 복제(Incremental Replication): 변경 로그만 병합하여 복제 테이블을 갱신

실제 동작은 Spark 엔진이 S3에 저장된 CDC 레코드를 읽어와 기존 테이블에 Insert, Update, Delete 연산을 적용하는 방식으로 이루어집니다. 이러한 로그 기반 증분 복제 방식을 통해 얻을 수 있는 기대 효과는 다음과 같습니다.

  • 데이터 수집 효율성 및 원천 DB 부하 감소: 원천 DB에 대해 Full Table Scan을 수행하지 않습니다. 오직 변경된 데이터(Deltas)만 처리하기 때문에 수집 시간이 단축되며 서비스 DB에 주는 I/O 부하가 거의 없습니다.

  • 컴퓨팅 리소스 최적화: 매번 테이블 전체를 다시 적재하는 대신 변경분만 읽어 병합하므로, 데이터 처리에 드는 컴퓨팅 리소스 소모가 크게 줄어듭니다.

  • 강력한 데이터 정합성 확보: binlog의 이벤트 발생 순서를 보장하여 원천 DB의 최종 상태를 정확하게 재현합니다. 과거 데이터의 수정이나 삭제 내역도 누락 없이 반영하므로 데이터 불일치 위험을 제거할 수 있습니다.

  • 유연한 확장성: 복제에 소요되는 시간이 전체 테이블 크기가 아니라 발생한 변경량(Throughput)에 비례합니다. 따라서 데이터 규모가 커지더라도 안정적인 처리 성능을 유지할 수 있습니다.

결과적으로 CDC 기반 증분 복제는 데이터를 더 효율적이고 정확하게 데이터 플랫폼으로 적재할 수 있는 최선의 선택이었습니다.

2. 아키텍처 설계: 변경 데이터가 흐르는 파이프라인의 구조

새롭게 설계한 복제 시스템은 원천 DB의 변경 사항을 캡처하는 것부터 최종 분석용 테이블에 반영하기까지 전 과정을 자동화하고 정합성을 유지하도록 설계되었습니다. 전체 아키텍처는 실시간으로 데이터를 추출해 저장하는 CDC 파이프라인과, 저장된 로그를 분석용 테이블로 병합하는 복제 엔진(Replication Engine)으로 구성됩니다.

End-to-End 아키텍처: Debezium → Kafka → Flink → S3 → Spark(EMR)

2.1. 기술 스택과 데이터 흐름 (End-to-End)

데이터가 원천 DB에서 분석 계층까지 흐르는 과정은 신뢰성과 확장성을 고려하여 다음과 같은 기술 스택으로 구성했습니다.

  1. Debezium (추출): 원천 DB의 binlog를 실시간으로 모니터링하여 삽입, 수정, 삭제 이벤트를 캡처합니다. 원천 DB에 직접적인 쿼리 부하를 주지 않고 로그를 읽어 서비스 영향을 최소화했습니다.

  2. Kafka (중계): Debezium에서 추출된 대량의 이벤트를 순서대로 수집하고 전달하는 완충 지대 역할을 합니다. 추출과 처리 단계 사이의 결합도를 낮춰 시스템의 안정성을 확보합니다.

  3. Flink (정제): Kafka의 데이터를 실시간으로 읽어 분석에 적합한 형태로 가공합니다. 이 단계에서 스키마 변환 및 데이터 타입 일치화 작업을 수행합니다.

  4. S3 (저장): 정제된 이벤트 데이터는 Parquet 포맷으로 변환되어 S3 '변경 로그(Change Logs)' 저장소에 적재됩니다.

  5. Spark on EMR (병합): S3에 쌓인 변경 로그를 읽어와 최종 테이블에 반영하는 핵심 엔진입니다. Airflow의 스케줄링에 따라 EMR 클러스터에서 실행되며, 대규모 데이터를 효율적으로 병합(Merge)합니다.

  6. Airflow (스케줄링): 전체 복제 프로세스의 워크플로우를 관리합니다. 매시간(Hourly) 단위로 Spark 잡을 트리거하여 데이터의 최신성을 유지합니다.

2.2. 왜 Apache Iceberg인가?

CDC 데이터를 효율적으로 병합하고 관리하기 위해 저희는 데이터 레이크 테이블 포맷으로 Apache Iceberg를 선택했습니다. Iceberg는 다음과 같은 기능을 통해 복제 시스템의 완성도를 높여줍니다.

  • ACID 트랜잭션 지원: 데이터 병합 과정에서 발생할 수 있는 읽기/쓰기 충돌을 방지하고 데이터 원자성을 보장합니다.

  • 효율적인 Upsert 연산: Iceberg의 MERGE INTO 구문을 활용하면 변경 사항만 효율적으로 반영할 수 있습니다.

  • 스키마 및 파티션 진화: 테이블을 다시 생성하지 않고도 컬럼을 추가하거나 파티션 전략을 변경할 수 있어 운영 유연성이 뛰어납니다.

  • 스냅샷 관리와 타임 트래블: 모든 커밋은 스냅샷으로 관리되므로, 필요 시 특정 시점으로 데이터를 되돌리거나 이전 스냅샷을 정리하여 저장 공간을 효율적으로 관리할 수 있습니다.

이번 설계에서는 낮은 지연 시간의 실시간 upsert보다, 배치 단위의 원자성과 재처리 안정성, 그리고 검증 가능성을 우선했기 때문에 Iceberg의 스냅샷 기반 모델이 더 적합하다고 판단했습니다.

2.3. 복제 프로세스: 초기 적재와 증분 병합

S3에 적재된 변경 로그들을 분석가가 쿼리할 수 있는 테이블 형태로 구현하기 위해, 복제 엔진을 두 단계로 운영합니다.

  • 초기 적재 (Initial Load): 분석용 테이블의 기초를 만드는 최초 1회성 작업입니다. 원천 테이블의 생성 시점과 CDC 로그 보유 상태에 따라 두 가지 전략을 취합니다.

    • 신규 테이블 (CDC Only): 테이블 생성 시점부터 모든 변경 이력이 CDC 로그에 보존되어 있다면, 별도의 DB 스캔 없이 CDC 로그만으로 복제 테이블(Replicated Table)을 구축합니다.

    • 기존 테이블 (Full Load Table 기반): CDC 로그가 전체 이력을 담고 있지 않은 기존 테이블은 레거시 파이프라인인 전수 적재 테이블(Full Load Table)의 특정 시점 데이터를 초기 데이터셋으로 활용합니다.

  • 증분 병합 (Incremental Merge): 초기 적재 이후 발생하는 변경분만 골라내어 기존 테이블을 최신화하는 작업입니다. S3에 쌓인 CDC 이벤트를 읽어와 복제 테이블에 병합(Merge)하여 최종 상태를 갱신합니다. 이때 어떤 기준으로 병합할 것인지에 대한 전략적 선택(PK 방식 vs 전체 로우 해시 방식)은 3장에서, 실제 데이터를 로드하고 중복을 제거하며 Iceberg 테이블에 커밋하는 상세 메커니즘은 4장에서 다룹니다.

2.4. 운영의 진화: 매시간(Hourly) 배치로의 전환과 과제

초기 시스템 설계의 목표는 기존 전수 적재(Full Load) 기반의 데일리 파이프라인을 안정적으로 대체하는 것이었습니다. 하지만 시스템 구축 과정에서 실시간 비즈니스 지표 모니터링과 당일 데이터에 대한 즉각적인 분석 요구사항이 강력하게 제기되었습니다.

이에 따라 운영 방식을 개선하여, 일 단위 복제를 넘어 매시간(Hourly) 배치로 동작하도록 구현했습니다. 하지만 배치의 주기가 짧아진다는 것은 운영 난이도의 비약적인 상승을 의미합니다. 기존 데일리 배치 대비 작업 횟수가 24배로 늘어난 만큼, 시스템의 견고함과 자동화가 필수적이었습니다. 이를 해결하기 위해 저희는 다음과 같은 장치들을 도입했습니다.

  • 지수 백오프 기반 리트라이 (Exponential Backoff Retry): 일시적인 네트워크 장애나 리소스 부족으로 작업이 실패할 경우, 자동으로 재시도 간격을 늘려가며 복구를 시도합니다.

  • 적응형 워커 스케일링 (Adaptive Worker Scaling): 처리해야 할 테이블 수와 시스템 리소스 상태에 따라 워커(Worker) 수를 동적으로 조절하여 메모리 부족 문제를 방지합니다.

  • 실시간 모니터링 및 Slack 알림: 작업 진행 상황, 성공/실패 여부, 리트라이 현황을 Slack으로 실시간 공유하여 즉각적인 대응 체계를 구축했습니다.

  • 강력한 데이터 검증: 매 실행 직후 기본 키(PK) 유일성 검사와 CDC 커밋 일치 여부를 자동으로 검증하여 복제 테이블(Replicated Table)의 정합성을 확인합니다.

결과적으로 이러한 설계를 통해 늘어난 작업량 속에서도 데이터 유실 없이 안정적인 매시간 배치 운영이 가능해졌습니다.

3. 전략의 선택: PK 방식 vs 전체 로우 해시 방식

CDC 데이터를 복제 테이블(Replicated Table)에 병합(Merge)하는 전략을 설계할 때 가장 보편적인 방식은 기본 키(Primary Key, 이하 PK)를 기준으로 데이터를 덮어쓰는 것입니다. 하지만 저희는 데이터 정합성을 보장하고 운영 신뢰도를 높이기 위해, 모든 컬럼 값을 결합해 해싱한 전체 로우 해시(Full-row Hash)를 연산 기준으로 삼았습니다.

이 선택의 핵심 차이는 "최종 상태(State)만 남길 것인가" 아니면 "모든 상태의 변화(Change)를 추적할 것인가"에 있습니다.

3.1 두 가지 접근 방식: '현재의 스냅샷'인가, '상태의 기록'인가

PK 방식: 최종 상태 중심

PK 방식은 원천 데이터베이스의 현재 상태를 복제 테이블(Replicated Table)에 그대로 재현하는 것을 목표로 합니다. 이는 구현이 직관적이고 컴퓨팅 리소스 소모가 적어, Airbyte나 Fivetran 같은 대부분의 현대적 데이터 도구들이 채택하고 있는 가장 보편적이고 경제적인 방식입니다. 시스템은 "지금 원천 DB의 상태가 무엇인가?"라는 본질적인 질문에 집중하며, 데이터 이력보다 최신 스냅샷의 효율적인 유지가 중요한 대부분의 비즈니스 환경에서 최선의 선택지로 작동합니다.

MERGE INTO target_table AS t
USING cdc_changes AS s
ON t.id = s.id  -- PK로 매칭
WHEN MATCHED AND s.op = 'DELETE' THEN DELETE
WHEN MATCHED AND s.op = 'UPDATE' THEN UPDATE SET ...
WHEN NOT MATCHED AND s.op = 'INSERT' THEN INSERT ...

같은 PK를 가진 레코드가 들어오면 최신 값으로 업데이트하고, 삭제 요청이 오면 레코드를 제거합니다.

전체 로우 해시 방식: 상태 변화 추적

전체 로우 해시 방식은 레코드의 각 상태를 명시적으로 식별하고 추적합니다. 모든 컬럼 값을 결합해 만든 해시값(_row_hash)을 로우의 고유한 지문으로 사용하며, 데이터가 단 하나라도 바뀌면 해시가 달라져 전혀 다른 상태로 인식합니다.

# 전체 로우 해시 생성 예시
{id: 1, name: "Alice", age: 25} → _row_hash: "abc123"
{id: 1, name: "Alice", age: 30} → _row_hash: "def456" # 다른 상태로 취급

MERGE INTO target_table AS t
USING cdc_changes AS s
ON t.row_hash = s.row_hash  -- 상태 해시로 매칭
WHEN MATCHED AND s.op = 'D' THEN DELETE
WHEN NOT MATCHED AND s.op = 'I' THEN INSERT ...

이 방식은 "원천 DB가 어떤 상태에서 어떤 상태로 변화했는가?"를 기록합니다. 모든 UPDATE 이벤트를 '이전 상태의 삭제(Delete) + 새로운 상태의 삽입(Insert)'으로 분해하여 처리하며, 병합 시에는 PK가 아닌 해시값을 기준으로 정확히 일치하는 상태를 찾아 연산합니다.

3.2 운영 환경에서의 차이: 정합성과 멱등성의 실전 시나리오

실제 운영 환경에서의 장애 상황을 가정한 시나리오를 통해, 두 방식이 복제 테이블(Replicated Table)의 무결성에 어떠한 차이를 만드는지 살펴보겠습니다.

3.2.1 데이터 무결성 검증: '신뢰'가 아닌 '확정'의 영역

Debezium과 Flink 기반의 파이프라인은 높은 신뢰성을 제공하지만, 카프카 리밸런싱이나 네트워크 지연 등 분산 환경의 변수를 고려할 때 '누락 없음'을 맹신하는 것은 위험합니다. 따라서 저희는 "데이터가 어긋났을 때 시스템이 이를 즉각 발견할 수 있는가?"라는 질문에 집중했습니다.

[시나리오] 예기치 못한 인프라 이슈로 11:05 업데이트 이벤트가 유실된 경우

10:00 INSERT {id:1, age:25}  ✅
11:05 UPDATE {id:1, age:30}  ⚠️ (분산 환경의 일시적 변수로 인한 유실 가정)
12:10 UPDATE {id:1, age:35}  ✅
13:15 UPDATE {id:1, age:40}  ✅

PK 방식: 조용한 실패 (Silent Failure)

적용된 이벤트:
10:00 INSERT {id:1, age:25}
12:10 UPDATE {id:1, age:35}
13:15 UPDATE {id:1, age:40}

최종 결과: {id: 1, age: 40}

PK 방식에서는 유실된 11:05 이벤트를 제외한 나머지 기록들만 복제 테이블(Replicated Table)에 반영됩니다. 그 결과는 다음과 같습니다.

  • 누락 감지 불가: 최종 결과값이 {id: 1, age: 40}으로 원천 DB와 일치합니다. PK 중복이 없고 값이 정확해 보이기 때문에 시스템은 경고 없이 데이터 검증을 통과시킵니다.

  • 시점별 데이터 불일치: 11:07에 데이터를 조회할 경우 실제 DB는 30세이지만, 복제 테이블(Replicated Table)은 여전히 25세로 노출됩니다. 다음 이벤트가 처리되는 12:10 전까지 플랫폼은 잘못된 데이터를 제공하게 됩니다.

  • 상태 히스토리 손실: 실제 상태 변화는 25 → 30 → 35 → 40이었으나, 기록에는 25 → 35 → 40으로 남습니다. '30세'라는 상태가 존재했다는 사실 자체가 히스토리에서 영원히 사라집니다.

PK 방식은 "지금의 값"을 맞추는 데 최적화되어 있으나, 중간 과정에서 발생한 데이터 유실을 시스템적으로 잡아낼 수 없다는 치명적인 한계가 있습니다.

전체 로우 해시 방식: 명시적 실패 (Explicit Failure)

1. INSERT age=25
  - INSERT {age: 25, hash: "aaa"}
2. [누락] UPDATE age=30:
   - DELETE {age: 25, hash: "aaa"}
   - INSERT {age: 30, hash: "bbb"}
3. UPDATE age=35:
   - DELETE {age: 30, hash: "bbb"} ← 대상이 없음! 무시
   - INSERT {age: 35, hash: "ccc"}
4. UPDATE age=40:
   - DELETE {age: 35, hash: "ccc"}
   - INSERT {age: 40, hash: "ddd"}

최종 테이블:
{id: 1, age: 25, hash: "aaa"}  ← 삭제되지 않고 남음!
{id: 1, age: 40, hash: "ddd"}

전체 로우 해시 방식은 모든 업데이트를 '이전 상태 삭제 + 새로운 상태 삽입'의 쌍으로 처리하는 방어적 설계를 취합니다. 동일한 유실 시나리오에서 시스템이 어떻게 자가 검증을 수행하는지 살펴보겠습니다.

Merge 이후 즉시 수행되는 PK 유일성 검증은 동일한 id: 1이 두 건 존재하는 것을 탐지하고, 에러를 발생시켜 작업을 중단합니다. 이는 데이터가 틀린 상태로 방치되는 것을 막고, 운영자에게 "데이터 무결성이 입증되지 않았음"을 알리는 강력한 신호가 됩니다. 단, 이 메커니즘은 누락된 이벤트 이후에 동일 PK에 대한 후속 이벤트가 존재할 때 작동하며, 후속 이벤트가 없는 경우의 한계는 3.5절에서 별도로 다룹니다.

정리: 두 방식의 결정적 차이

구분

PK 방식

전체 로우 해시 방식

누락 발생 시

검증 통과, 조용히 틀린 데이터 적재

Merge 후 PK 중복 검증 실패로 작업 중단

데이터 상태

최종 값은 맞으나 중간 히스토리 손실

데이터 오염 즉시 감지 및 복구 가능

운영 알림

알림 없음 (운영자 인지 불가)

즉시 알림 (Fail-fast)

데이터 파이프라인에서 잘못된 데이터를 제공하는 것보다, 에러를 통해 문제를 알리는 것이 더 올바른 설계입니다. 특히 금융 데이터나 비즈니스 핵심 지표를 다루는 환경에서 이러한 '방어적 정합성'은 시스템 신뢰도를 결정짓는 가장 중요한 요소입니다.

3.2.2 여러 번 배치 처리 시 멱등성: '내용'이 기준이 되는 안정성

운영 환경에서는 네트워크 장애에 따른 재시도, 실패한 배치의 수동 재실행, 혹은 과거 데이터 재처리 등 동일한 CDC 배치를 다시 실행해야 하는 상황이 빈번하게 발생합니다. 이때 이미 반영된 데이터가 중복으로 적재되거나 데이터가 오염되는 것은 파이프라인의 주요 리스크입니다.

PK 방식: 중복 실행의 딜레마

PK 방식에서 같은 배치를 두 번 실행하면 두 가지 문제에 직면합니다.

  • 중복 작업 발생: 단순 INSERT라면 PK 충돌 에러가 발생하며, 이를 피하기 위해 UPSERT(Merge)를 사용하더라도 데이터가 이미 원천 DB와 일치하는 상태에서 불필요한 UPDATE 연산을 반복하게 됩니다.

  • 메타데이터 오버헤드: Apache Iceberg와 같은 테이블 포맷에서는 데이터 값의 변화가 없더라도 UPDATE 명령이 수행되면 새로운 데이터 파일이 생성되고 스냅샷 기록이 남습니다. 이는 메타데이터에 불필요한 변경 이력을 쌓아 시스템 효율을 저하시킵니다.

전체 로우 해시 방식: 내용 기준 멱등성(Content-based Idempotency)

전체 로우 해시 방식은 MERGE의 매칭 조건을 (파티션, rowhash)로 설정하여 이 문제를 해결합니다. 해시는 로우의 '내용' 그 자체를 의미하므로, 시스템은 "해당 내용의 로우가 이미 존재하는가?"를 기준으로 연산 여부를 판단합니다.

  1. INSERT 재실행: 재실행 시 이미 복제 테이블(Replicated Table)에 동일한 해시를 가진 로우가 존재하므로 MATCHED 상태가 됩니다. 하지만 로직상 INSERT는 NOT MATCHED일 때만 수행되도록 설계되어 있어 아무런 동작 없이 자동으로 스킵됩니다.

  2. DELETE 재실행: 첫 실행 시 해당 해시를 가진 로우를 삭제했다면, 재실행 시에는 대상 해시가 테이블에 존재하지 않아 NOT MATCHED가 됩니다. 삭제 연산(op='D')은 삽입 조건이 아니므로 변화 없이 종료됩니다.

  3. UPDATE 재실행 (DELETE + INSERT 쌍): 업데이트는 '이전 상태 삭제'와 '새 상태 삽입'으로 분해됩니다. 재실행 시 지워야 할 이전 상태는 이미 없고, 넣어야 할 새 상태는 이미 존재하므로 두 연산 모두 자동으로 스킵됩니다.

이 메커니즘의 가장 큰 이점은 운영 안정성입니다. 실수로 배치를 중복 실행하거나 장애 복구를 위해 과거 구간 데이터를 다시 투입해도 데이터 중복이나 오염을 걱정할 필요가 없습니다. 또한 실제 데이터 변화가 없다면 Iceberg 스냅샷에도 불필요한 기록이 남지 않아 메타데이터를 효율적으로 유지할 수 있습니다. 결과적으로 "이미 반영된 내용인가?"를 해시로 구분해내는 방식은 재처리에 대한 운영 부담을 낮추고, 엔지니어가 데이터 품질 관리에 더욱 집중할 수 있는 기반이 됩니다.

3.3 비교 요약

지금까지 살펴본 두 가지 시나리오를 통해 PK 방식과 전체 로우 해시 방식의 핵심 차이를 정리하면 다음과 같습니다.

관점

PK 방식

전체 로우 해시 방식

철학

현재 상태(Snapshot) 중심

상태 변화(Change) 추적

매칭 기준

PK 존재 여부

내용(hash) 존재 여부

이벤트 누락 시

감지 불가 (조용한 실패)

Merge 후 PK 중복 검증에서 에러 발생

중복 실행 시

불필요한 업데이트/기록 발생

내용 기준 자동 스킵 (멱등성)

운영 신뢰성

낮음

높음 (Fail-fast & Safe-retry)

복잡도/비용

낮음(경제적)

높음

3.4 선택의 이유: "틀린 데이터보다 에러가 낫다"

구현 난이도와 리소스 비용을 감수하면서도 전체 로우 해시 방식을 선택한 이유는 명확합니다. 데이터 시스템 설계의 핵심 원칙인 "틀린 데이터를 제공하는 것보다 에러를 발생시켜 문제를 알리는 것이 낫다"를 실천하기 위함입니다.

특히 금융 데이터나 핵심 비즈니스 지표를 다루는 환경에서 '조용한 실패'는 플랫폼 전체의 신뢰도를 저하시킵니다. 전체 로우 해시 방식은 문제가 생겼을 때 Merge 직후의 PK 중복 검증을 통해 에러를 발생시키고(Explicit Failure), 정상 상황에서는 어떤 재시도에도 동일한 결과를 보장함으로써 데이터 플랫폼의 정합성을 유지합니다.

3.5. 트레이드오프(Trade-off): '완벽'이 아닌 '최선'의 선택

전체 로우 해시 방식이 데이터 정합성 측면에서 강력한 장점을 가지는 것은 분명하지만, 기술적 선택에는 대가가 따릅니다. 저희는 시스템 도입 시 다음과 같은 비용과 한계를 검토했습니다.

  • 구현 및 운영 복잡성 증가: PK 방식의 단순한 덮어쓰기 로직과 달리, 전체 로우 해시 방식은 여러 단계의 추가 로직을 수반합니다. UPDATE 이벤트를 before/after로 분해하는 뷰 분리, 모든 컬럼을 결합한 해시 생성, 윈도우 함수를 활용한 배치 내 중복 제거, 그리고 해시 기준의 Merge 연산까지 — 단순 UPSERT 한 줄이면 끝나는 작업이 네 단계의 파이프라인으로 확장됩니다.

  • 저장 공간 및 처리 리소스: 모든 로우에 해시 컬럼(_row_hash)이 추가되며, Merge 시 파티션 내 전체 로우의 해시를 비교해야 하므로 shuffle 비용이 증가합니다. 여기에 중복 제거를 위한 윈도우 함수의 메모리 점유까지 더해져, 동일한 데이터 볼륨이라도 EMR 클러스터의 리소스 사용량이 높아지며 이는 테이블 수가 늘어날수록 누적됩니다.

  • 여전히 존재하는 사각지대: 전체 로우 해시의 Fail-fast는 만능이 아닙니다. 누락된 이벤트 이후 동일 PK에 대한 후속 변경이 있어야 PK 중복으로 감지되므로, 원천 DB에서 삭제된 로우의 이벤트가 유실되고 이후 변경이 없다면 유령 데이터가 잔존합니다. 이를 해소하기 위해 원천 DB와 복제 테이블을 주기적으로 대조하는 Reconciliation 프로세스를 준비하고 있습니다.

이러한 트레이드오프에도 불구하고 이 방식을 고수한 이유는 데이터 신뢰 때문입니다. 인프라 비용은 수치화할 수 있지만, 정합성 결여로 무너진 데이터에 대한 신뢰는 회복하기 어렵습니다. 비즈니스의 핵심 의사결정이 흐르는 플랫폼에서 '조용한 실패'를 방치하지 않고, 문제가 생겼을 때 Merge 후 PK 중복 검증으로 감지(Fail-fast)하며, 어떤 재시도에도 흔들리지 않는 멱등성을 보장하는 것이 엔지니어링 측면에서 더 가치 있는 선택이라고 판단했습니다.

4. 핵심 메커니즘: CDC Merge Logic의 정교함

실제 코드로 전체 로우 해시 전략을 구현하기 위해서는 Spark 엔진이 CDC 이벤트를 해석하여 Iceberg 테이블에 병합하는 과정을 정교하게 정의해야 합니다. 시나리오 데이터를 바탕으로 그 첫 번째 단계를 살펴보겠습니다.

4.1 데이터 로드와 관점의 분리 (Read & Build)

가장 먼저 S3에 저장된 복제 테이블(Replicated Table)의 기존 데이터와 새로 유입된 CDC 로그를 읽어 들입니다. 이 단계의 핵심은 UPDATE 이벤트를 단순한 '수정'이 아닌 '상태의 교체'로 정의하는 것입니다.

CDC 로그를 After View(Insert)와 Before View(Delete)로 분리

이를 위해 유입된 CDC 로그를 두 가지 뷰(after view, before view)로 분리하여 처리합니다.

  • After View (Insert/Update): 새롭게 생성되거나 변경된 이후의 최신 상태입니다. 시스템은 여기에 연산 코드 I(Insert)를 부여합니다.

  • Before View (Delete/Update): 삭제되거나 변경되기 전의 과거 상태입니다. 시스템은 여기에 연산 코드 D(Delete)를 부여합니다.

이 과정에서 모든 레코드는 모든 컬럼 값을 결합한 고유한 rowhash를 부여받습니다. 해시 함수로는 Spark 네이티브 md5()를 사용했습니다. 이 파이프라인에서 해시의 역할은 로우 동일성 판별이며, 암호학적 충돌 내성이 요구되는 컨텍스트가 아니므로 연산 효율과 저장 공간(128비트)을 우선했습니다. 이 해시값은 해당 레코드가 가진 특정 시점의 데이터를 대변하는 독립적인 '상태 지문' 역할을 하게 됩니다.

4.2 해시 기반 중복 제거 (Keep Latest)

한 번의 배치 주기 안에서도 특정 데이터는 여러 번 변할 수 있습니다 (예: 10시 5분에 수정 후 10시 8분에 또 수정). 동일한 내용(_row_hash)이 중복으로 처리되는 것을 막고, 가장 최신 상태만 남기기 위해 중복 제거(Deduplication) 과정을 거칩니다.

_row_hash 기준 중복 제거: 동일 상태에 대해 최신 이벤트만 선별

rowhash와 binlog timestamp를 기준으로 정렬하여, 각 상태 지문에 대해 가장 마지막에 발생한 이벤트만 선별해 latest changes 세트를 만듭니다. 이 과정은 앞서 설명한 내용 기준 멱등성을 확보하는 물리적 기반이 됩니다.

4.3 최종 병합과 원자성 보장 (Merge)

이제 선별된 최신 변경 사항들을 기존 복제 테이블(Replicated Table)에 병합합니다. rowhash를 기준으로 매칭하여, 삭제(D) 연산은 테이블에서 해당 지문을 가진 로우를 제거하고, 삽입(I) 연산은 새로운 지문을 추가합니다.

_row_hash 기반 최종 병합: Iceberg ACID 트랜잭션 하에 원자적 커밋

이 과정은 Apache Iceberg의 ACID 트랜잭션 하에 실행됩니다. 수만 건의 삭제와 삽입이 일어나더라도, 모든 연산이 완벽히 성공했을 때만 새로운 스냅샷이 생성됩니다. 만약 중간에 장애가 발생하면 Iceberg는 이전 상태를 유지하여 데이터 오염을 방지합니다.

4.4 데이터 품질 검증: 신뢰를 확정 짓는 마지막 단계

적재가 완료되었다고 해서 파이프라인이 종료된 것은 아닙니다. 시스템이 스스로 결과물을 검증하는 데이터 품질 체크(Data Quality Checks) 단계가 이어집니다.

4.4.1 Primary Key 유일성 검증

적재 완료 직후, 복제 테이블(Replicated Table) 내에서 PK 중복이 발생하는지 전수 조사합니다. 앞서 3장에서 강조했듯, 중간 이벤트가 하나라도 누락되었다면 삭제되지 못한 과거 해시 레코드가 남아 PK 중복 에러를 일으키게 됩니다. 이 검증을 통과해야만 해당 배치는 비로소 '성공'으로 간주됩니다.

4.4.2 CDC Commit Consistency: 커밋 수치 기반의 정합성 검증

마지막으로 원천 DB에서 읽어온 CDC 이벤트 총합과 Iceberg가 실제로 커밋한 통계치를 대조하여 물리적 유실 여부를 확인합니다.

CDC 순 변동량과 Iceberg 커밋 변동량의 일치 여부 검증
  • CDC 측: 2건의 Insert와 1건의 Delete가 발생하여 순 변동량(cdc_net_changes)은 1입니다. (업데이트는 -1, +1로 상쇄됨)

  • Iceberg 측: 최종적으로 1건의 레코드가 추가(added-records)되어 순 변동량(iceberg_net_changes)은 1입니다.

  • 검증 로직: cdc_net_changes == iceberg_net_changes

이 두 수치가 정확히 일치할 때 시스템은 Validation Pass 판정을 내립니다. 만약 단 1건이라도 데이터가 유실되었다면 이 수치는 어긋나게 되고, 시스템은 즉시 에러를 반환하며 슬랙 알림을 통해 엔지니어에게 상황을 전파합니다. 다만 이 검증은 순 변동 건수의 일치 여부만 확인하므로, 건수는 동일하나 내용이 다른 경우까지 잡아내지는 못합니다. 이 한계는 4.4.1의 PK 유일성 검증과 조합하여 보완하되, 완전한 내용 수준의 검증은 향후 Reconciliation 프로세스에서 담당할 영역입니다.

4.4.3 실전 사례: TRUNCATE가 불러온 정합성 경고와 복구 기록

시스템 설계 단계에서 예상하지 못한 실전 변수는 언제나 존재합니다. 최근 신규 테이블 적재 과정에서 발생한 PK 중복 이슈와 그 해결 과정을 통해, 정합성 검증 체계가 왜 필요한지 다시 한번 확인할 수 있었습니다.

1. 문제 발생: 복제 테이블 내 PK 중복 감지

적재 완료 직후 슬랙(Slack)을 통해 VALIDATION WARNING 알림이 발생했습니다. 특정 테이블(payment_gateway_detail)에서 23건의 PK 중복 데이터가 발견된 것입니다.

실제 운영 알림: PK 중복 감지로 VALIDATION WARNING 발생

2. 원인 파악: DELETE가 아닌 TRUNCATE의 함정

스키마 변경 과정에서 원천 DB의 데이터를 초기화하기 위해 TRUNCATE 연산이 수행되었습니다. 대량의 데이터를 효율적으로 제거하기 위한 적절한 선택이었으나, CDC 파이프라인 관점에서는 다음과 같은 기술적 특징이 변수로 작용했습니다.

  • 로그 기록의 차이: 일반적인 DELETE는 로우 단위의 변경 로그를 생성하여 CDC가 이를 캡처할 수 있지만, TRUNCATE는 DDL 성격의 최소 로깅(Minimal Logging) 연산이기에 binlog에 개별 데이터 삭제 이력이 남지 않을 수 있습니다.

  • 상태 불일치: 결과적으로 원천 DB의 데이터는 모두 제거되었으나, 복제 테이블(Replicated Table)에는 과거 데이터가 삭제되지 않은 채 그대로 남게 되었습니다. 이후 새로운 데이터가 유입되면서 기존 해시 레코드와 충돌하여 PK 중복을 일으켰습니다.

3. 복구 및 2차 경고: Commit Consistency 에러

오염된 데이터를 정제하기 위해 데이터 플랫폼 내의 과거 CDC 데이터를 삭제하고 테이블을 재생성했습니다. 재생성 과정에서 11:30분 기준의 최신 데이터까지 모두 반영되었습니다.

이후 수행된 12:00 정기 배치에서 두 번째 지표인 CDC Commit Consistency 경고가 발생했습니다.

복구 후 CDC Commit Consistency 불일치 경고 발생
  • 발생 원인: 12:00 배치는 11:00~12:00 사이의 CDC 로그를 읽어 병합을 시도합니다. 하지만 이미 11:30분 재생성 시점에 해당 구간의 CDC 내용이 테이블에 선반영되어 있었기 때문에, 수집된 CDC 변동량(+5038)과 실제 Iceberg 커밋 변동량(+2831)이 일치하지 않게 된 것입니다.

  • 조치 결과: 해당 경고는 수동 복구 과정에 따른 일시적인 오버랩(Overlap)으로 판명되었습니다. 시스템이 데이터 유입과 처리량의 불일치를 정확히 감지하여 에러를 발생시킨 것이므로, 복구 상태를 확인한 후 정상 가동을 승인했습니다.

이 사례는 "데이터가 맞을 것이다"라는 신뢰 대신, 명시적 에러 발생(Explicit Failure) 설계가 운영자에게 얼마나 명확한 가시성을 제공하는지 보여줍니다. 시스템이 이상 징후를 즉각 감지하여 차단했기에, 데이터 오염을 확산시키지 않고 정확한 진단과 복구가 가능했습니다.

우리는 전체 로우 해시를 통해 데이터의 논리적 무결성을 지켰고, Commit Consistency 검증을 통해 물리적 유실을 차단했습니다. "데이터가 맞을 것이다"라는 추측 대신, 시스템이 스스로 이상 징후를 감지하고 알린다는 원칙이 데이터 플랫폼의 신뢰도를 결정짓는 핵심입니다.

5. 도입 후 변화: 비즈니스가 체감하는 혁신

단순히 기술 스택을 교체하는 것보다 중요한 것은 그 기술이 실제 비즈니스의 문제를 어떻게 해결했느냐입니다. 기존 전수 적재(Full Load) 방식에서 CDC 기반의 증분 복제(Incremental Replication)로 전환한 후, 데이터 플랫폼은 다음과 같은 근본적인 변화를 맞이했습니다.

5.1. 성능 및 비용 최적화: 장애 대응 가용 시간 확보

전체 파이프라인에서 Sqoop 기반 전수 적재(Full Load)가 차지하는 처리 시간 비중

기존 Sqoop 기반의 전수 적재(Full Load) 방식에서 가장 큰 문제는 5시간에 달하는 처리 시간이 전체 파이프라인의 핵심 경로(Critical Path)를 점유하고 있었다는 점입니다.

  • 연쇄 지연 해소: 전수 적재에 5시간 이상 소요되면서 이를 기반으로 실행되는 데이터 마트 적재와 BI 대시보드 업데이트는 상시적인 지연 리스크를 안고 있었습니다.

  • 장애 복구 탄력성: 과거에는 대용량 테이블에서 장애가 발생하면 수 시간의 재작업이 필요해 당일 의사결정 시점을 넘기기 일쑤였습니다.

  • 처리 시간 95% 단축: CDC 기반 증분 복제 도입 후, 적재 시간은 15분 이내로 단축되었습니다. 이제는 장애 발생 시에도 15분 내외의 재처리만으로 즉시 정상화가 가능하며, 마감 시한 내에 안정적으로 데이터를 공급할 수 있는 운영 가용 시간을 확보했습니다.

5.2. 데이터 신속성(Data Freshness): 시간 단위 분석 체계 구축

비즈니스 의사결정의 속도는 데이터의 신선도에 정비례합니다.

  • 지연 시간(Lag) 단축: 기존에는 전날 데이터를 다음 날 아침에 확인하는 24시간의 지연이 존재했으나, 이제는 최대 1.5시간 이내의 최신 데이터를 복제 테이블(Replicated Table)에서 조회할 수 있습니다.

  • 기민한 대응: 당일 마케팅 캠페인 성과를 실시간에 가깝게 분석하거나 이상 징후를 조기에 발견하는 등 비즈니스 대응 속도가 향상되었습니다.

5.3. 신뢰도 향상: 데이터 불일치 이슈의 해결

데이터 엔지니어와 분석가 사이의 가장 큰 갈등 원인인 '데이터 불일치' 문제를 시스템적으로 해결했습니다.

  • 컴플라이언스 리스크 제거: 특히 금융권에서는 RBI(Reserve Bank of India) 등 규제 기관 제출용 레포트의 정합성이 중요합니다. 과거에는 소스 DB의 과거 데이터 수정 시 이를 감지하지 못해 분석가들이 수동으로 수치를 보정하는 작업이 빈번했습니다.

  • 수치 기반의 정합성 검증: 이번에 도입한 전체 로우 해시와 Commit Consistency 검증 체계는 사람이 아닌 시스템이 정합성을 입증하게 만듭니다.

  • 분석 본연의 가치 집중: 분석가들은 이제 데이터의 정확성을 의심하는 대신, 데이터가 주는 인사이트를 도출하는 본연의 업무에만 집중할 수 있게 되었습니다.

6. 결론 및 로드맵: 미래를 향한 데이터 플랫폼

이번 CDC 기반 증분 복제(Incremental Replication) 프로젝트는 단순히 기술을 교체하는 작업이 아니었습니다. 데이터의 정합성을 시스템 스스로 검증하게 만들고 운영 신뢰도를 높여, 비즈니스가 데이터를 의심하는 대신 활용하는 데 집중하게 만든 근본적인 체질 개선이었습니다.

6.1. 전수 적재에서 증분 복제로의 전환

저희는 현재 기존 Sqoop 기반의 전수 적재(Full Load) 파이프라인을 점진적으로 폐쇄하고, 모든 복제 프로세스를 CDC 기반의 복제 테이블(Replicated Table) 아키텍처로 전환하는 과정을 밟고 있습니다.

  • 구조적 단순화: 서로 다른 두 가지 적재 방식을 유지하며 분산되던 운영 리소스를 하나의 파이프라인으로 집중하여 효율성을 높이고 있습니다.

  • 리스크 제거: 잠재적인 데이터 불일치 위험을 안고 있던 레거시 방식을 제거함으로써, 플랫폼 전체의 데이터 신뢰도를 상향 평준화하고 있습니다.

6.2. 향후 과제: 매시간(Hourly)을 넘어 완전한 실시간(Real-time)으로

현재의 '시간 단위(Hourly)' 배치는 비즈니스 요구사항을 충족하고 있으나, 저희의 비전은 여기서 멈추지 않습니다.

  • 스트리밍 기반 동기화(Streaming-based Sync): 배치의 개념을 지우고, 원천 DB의 변화가 발생하는 즉시 복제 테이블(Replicated Table)에 반영되는 완전한 스트리밍 기반 동기화를 준비하고 있습니다.

  • 지연 시간 제로(Near-zero Latency): 현재의 지연마저 최소화하여, 데이터 플랫폼이 비즈니스 실시간성을 완벽히 확보하는 환경을 구축하고자 합니다.

Conclusion: Faster, Consistent, Scalable

이번 프로젝트의 성과를 요약하자면, "신뢰할 수 있는 데이터를 가장 빠르게 제공하는 확장성 있는 기반을 마련한 것"입니다.

 Our New CDC Pipeline is enabling:
   Faster,
   More Efficient,
   And more Accurate decision-making.

데이터 엔지니어링의 본질은 결국 '비즈니스의 가속'에 있습니다. 저희가 '전체 로우 해시'라는 복잡한 경로를 택하고, 단 1건의 데이터 불일치도 즉각 감지하고 차단하는 시스템을 설계한 이유는 비즈니스가 신뢰할 수 있는 데이터 위에서 달릴 수 있게 하기 위함이었습니다.

앞으로도 저희는 "틀린 데이터보다는 에러가 낫다"는 원칙을 고수하며, 더 빠르고 더 정확한 데이터를 제공하기 위한 여정을 계속할 것입니다.

Share article