실시간 UPDATE 동기화에 두 개의 레코드가 필요한 이유는? Apache SeaTunnel에서 전체 체인 분석
Source: Dev.to
소개
실시간 데이터 플랫폼—실시간 웨어하우스, 데이터 레이크 인제스트, 그리고 분산 데이터 복제—에서 CDC(Change Data Capture)는 현대 파이프라인을 구축하기 위한 표준 기능이 되었습니다. 데이터를 StarRocks, Doris, ClickHouse, Iceberg, Paimon, 혹은 Hudi에 로드하든, 데이터베이스 간 동기화를 하든, CDC가 핵심 기반입니다.
자주 간과되는 일반적인 CDC 질문은 다음과 같습니다:
- MySQL CDC UPDATE 이벤트가 두 개의 레코드—하나 BEFORE와 하나 AFTER—를 출력해야 하는 이유는?
- 왜 최종 새로운 값만 출력하지 못하는가?
- AFTER만 있으면 시스템이 여전히 올바르게 동기화되지 않을까?
겉보기엔 가능해 보이지만, 일관성, 멱등성, 재생 가능성, 기본키 처리, 데이터 레이크 병합 의미론, 그리고 순서가 뒤섞인 복구 상황을 고려하면 UPDATE를 BEFORE + AFTER로 나누는 것은 “포맷 선택”이 아니라 CDC 정확성에 필수적인 요소입니다.
MySQL Binlog에서 UPDATE의 실제 구조
MySQL은 UPDATE를 단일 레코드로 기록하지 않습니다. 행 기반 binlog 포맷에서 이벤트는 다음과 같이 보입니다:
update_rows_event {
before_image: {id: 1, price: 100}
after_image: {id: 1, price: 200}
}
다음 구문을 실행하면:
UPDATE t SET price = 200 WHERE id = 1;
binlog는 기존 값(BEFORE)과 새로운 값(AFTER) 모두를 기록합니다. 이 쌍은 상태 전이를 완전하게 설명하여 올바른 재생, 롤백, 트랜잭션 검증을 가능하게 합니다.
CDC가 UPDATE를 단일 레코드로 표현할 수 없는 이유
시나리오 1 – 실제 변화 감지
UPDATE t SET price = 200 WHERE id = 1;
원래 가격이 이미 200이었다면, AFTER‑only 이벤트({id:1, price:200})는 하위 시스템에 데이터가 실제로 변했는지 알 방법을 제공하지 못합니다. 이는 다음을 초래합니다:
- 데이터 레이크에 불필요한 쓰기(비용이 많이 드는 Iceberg 병합)
- 잘못된 메트릭 재계산
- 낭비되는 컴퓨팅 자원
시나리오 2 – 기본키 업데이트
UPDATE user SET id = 2 WHERE id = 1;
AFTER‑only 이벤트({id:2})는 이전 기본키 값을 포함하지 않으므로, 하위 시스템은 원래 행을 삭제할 수 없습니다. 결과는 다음과 같습니다:
- 중복 레코드 발생
- 고유키 위반
- 데이터베이스 간 복제 손상
시나리오 3 – 기본키가 없는 경우(모호한 행)
| name | score |
|---|---|
| A | 100 |
| A | 200 |
UPDATE t SET score = 300 WHERE name = 'A';
AFTER‑only는 두 개의 동일한 행(A, 300)을 생성합니다. BEFORE가 없으면 시스템은 각 업데이트가 원래 어느 행을 가리키는지 판단할 수 없습니다.
시나리오 4 – 정확히 한 번 처리 보장(멱등성)
CDC 파이프라인은 다음과 같은 이유로 이벤트를 재전송할 수 있습니다:
- 분산 복구
- 네트워크 재시도
- 체크포인트 재생
- 컨슈머 재시작
AFTER‑only 이벤트는 중복과 구분할 수 없어 멱등성 보장을 깨뜨립니다.
시나리오 5 – 순서가 뒤섞인 Binlog 이벤트
MySQL 다중 스레드 복제는 다음과 같은 상황을 만들 수 있습니다:
- 스레드 1: 100 → 120
- 스레드 2: 120 → 200
AFTER = 200이 AFTER = 120보다 먼저 도착하면, BEFORE 이미지가 없으므로 120이 200을 덮어써야 함을 알 수 없습니다.
시나리오 6 – 데이터 레이크 삭제 요구사항
데이터 레이크 업데이트 의미론은 일반적으로 다음과 같이 수행됩니다:
DELETE old_row
INSERT new_row
DELETE는 정확한 BEFORE 이미지(WHERE id=1 AND price=100)와 일치해야 합니다. BEFORE가 없으면 DELETE가 불가능해져, 규제가 엄격한 금융 환경에서 치명적인 데이터 불일치가 발생합니다.
SeaTunnel의 CDC 아키텍처
SeaTunnel은 Debezium의 로그 파싱을 기반으로 네 가지 RowKind를 정의합니다:
INSERTDELETEUPDATE_BEFOREUPDATE_AFTER
따라서 MySQL‑CDC 소스는 두 개의 긴밀히 연결된 이벤트를 방출합니다:
UPDATE_BEFORE → old row (DELETE)
UPDATE_AFTER → new row (INSERT)
처리 흐름
MySQL Binlog (ROW)
|
UpdateRowsEvent (before, after)
|
SeaTunnel MySQL‑CDC Parser
|-------------------|
UPDATE_BEFORE UPDATE_AFTER
(old row) (new row)
하위 싱크는 RowKind에 따라 연산을 결정합니다. 이 모델은 다음을 보장합니다:
- 분산 환경에서의 재생 가능성
- 복구 가능성
- 순서 보존
- 데이터 레이크 병합 의미론과의 호환성
같은 패턴이 OLAP 데이터베이스(Doris, StarRocks), 데이터 레이크(Iceberg, Paimon, Hudi), 그리고 메시징 시스템(Kafka)에도 적용됩니다. BEFORE를 생략하면 전체 파이프라인이 실패하게 됩니다.
데이터 레이크와 웨어하우스가 BEFORE를 소비하는 방식
Iceberg, Paimon, Hudi는 ACID 의미론을 지원하지만, UPDATE는 복합 연산입니다:
UPDATE event
|
------------------------------
| |
DELETE old_row INSERT new_row
DELETE 단계는 정확한 BEFORE 이미지와 일치해야 하며, 그렇지 않으면 UPDATE를 적용할 수 없어 데이터 불일치가 발생합니다.
실제 생산 환경 사례 연구
사례 1 – 중복 고객 레코드(기본키 업데이트)
한 게임 회사는 AFTER 이벤트만 캡처했습니다. 사용자가 복합 기본키(전화번호)를 업데이트했을 때, 하위 시스템은 기존 키를 삭제하지 못해 중복 고객 레코드가 생성되었습니다.
사례 2 – Iceberg 병합 실패
한 금융 기관은 AFTER‑only CDC로 데이터를 Iceberg에 적재했습니다. DELETE 연산이 기존 레코드와 매치되지 않아 대량의 불일치 데이터가 발생했습니다. 파이프라인을 BEFORE 이미지를 포함하도록 재구성해 정확성을 회복했습니다.
최종 정리
- UPDATE는 이전 상태 → 새로운 상태로의 전이이며, 정확한 CDC를 위해 두 이미지가 모두 필요합니다.
- BEFORE + AFTER는 다음을 가능하게 합니다:
- 실제 변화 감지
- 기본키 업데이트의 올바른 처리
- 모호함 없는 행 식별
- 정확히 한 번 처리(멱등성)
- 순서가 뒤섞인 복구
- 데이터 레이크에서의 적절한 delete/insert 의미론
SeaTunnel(및 유사 CDC 프레임워크)은 UPDATE_BEFORE와 UPDATE_AFTER를 쌍으로 방출하여 재생 가능성, 복구 가능성, 그리고 하위 시스템과의 호환성을 보장합니다.
BEFORE 이미지를 이해하고 보존하는 것은 데이터베이스, 데이터 웨어하우스, 데이터 레이크 전반에 걸쳐 신뢰할 수 있고 일관된 실시간 파이프라인을 구축하는 데 필수적입니다.