CDC와 binlog 기반 동기화
폴링 방식의 한계에서 시작한다
서비스 데이터를 검색 엔진, 분석 DW, 캐시, 다른 마이크로서비스에 실시간으로 전달해야 하는 상황을 생각해보자. 가장 단순한 방법은 주기적으로 SELECT ... WHERE updated_at > :last_run으로 변경된 행을 뽑는 폴링(polling) 이다.
폴링의 문제점:
DELETE된 행은 조회 자체가 불가능해 추적할 수 없다.updated_at컬럼이 없거나 애플리케이션이 갱신하지 않으면 변경을 놓친다.- 폴링 간격마다 데이터베이스에 전체 범위 스캔 부하가 생긴다.
- 이벤트 순서(INSERT → UPDATE → DELETE)가 보장되지 않는다.
CDC(Change Data Capture) 는 데이터베이스의 변경 이벤트를 원본에서 직접 캡처해 스트림으로 전달하는 패턴이다. MySQL에서는 binlog가 그 역할을 한다.
binlog CDC의 원리
MySQL 복제 프로토콜은 레플리카가 소스에서 binlog 이벤트를 스트리밍받는 표준 메커니즘을 제공한다. CDC 도구는 이 메커니즘을 이용해 레플리카처럼 위장(pseudo-replica) 하여 binlog 이벤트를 가로챈다.
동작 흐름:
- CDC 도구가 MySQL에
REGISTER SLAVE명령으로 레플리케이션 슬레이브로 등록한다. - 소스는 CDC 도구를 일반 레플리카처럼 취급하고 binlog 이벤트를 스트리밍한다.
- CDC 도구가
Table_map+Write/Update/Delete_rows이벤트를 파싱해 변경 이벤트 메시지로 변환한다. - 메시지를 Kafka 등 목적지로 발행한다.
요구 조건: binlog는 반드시 ROW 포맷이어야 한다. STATEMENT 포맷은 실제 행 데이터가 아니라 SQL 텍스트를 기록하므로 before/after 이미지를 파싱할 수 없다.
Debezium: 가장 널리 쓰이는 MySQL CDC 도구
Debezium은 Red Hat이 오픈소스로 공개한 CDC 플랫폼이다. Kafka Connect 위에서 동작하며, MySQL 커넥터가 binlog를 읽어 Kafka 토픽에 변경 이벤트를 발행한다.
두 단계 동작: 초기 스냅샷 + 스트리밍
처음 실행 시 Debezium은 초기 스냅샷(initial snapshot) 을 수행해 현재 데이터 전체를 Kafka에 발행한다.
- 글로벌 읽기 잠금 획득 — 스냅샷 시작 시점의 binlog 위치(파일명+오프셋 또는 GTID)를 기록한다.
REPEATABLE READ트랜잭션 시작 후 잠금 해제 — InnoDB MVCC 덕분에 일관된 스냅샷이 유지된다.- 각 테이블의 전체 행을 읽어
op: "r"이벤트로 Kafka에 발행한다. - 스냅샷이 끝나면 기록해둔 binlog 위치부터 실시간 스트리밍을 시작한다.
이후 재시작 시에는 Kafka Connect가 저장해둔 오프셋(binlog 위치 또는 GTID)에서 스트리밍을 바로 재개한다.
스키마 히스토리 토픽
ALTER TABLE 같은 DDL 이벤트는 별도 Kafka 토픽(schema history topic)에 기록된다. 커넥터가 재시작되면 이 히스토리를 재생해 과거 특정 binlog 위치의 테이블 구조를 재구성하고, 그 시점의 이벤트를 정확히 파싱한다.
변경 이벤트 메시지 형식
{
"before": { "id": 101, "status": "pending" },
"after": { "id": 101, "status": "done" },
"source": {
"db": "orders", "table": "order",
"gtid": "3E11FA47-...:142",
"file": "binlog.000023", "pos": 104912
},
"op": "u",
"ts_ms": 1718000000000
}op 필드: c(INSERT), u(UPDATE), d(DELETE), r(스냅샷 읽기).
before 필드는 UPDATE/DELETE에서 변경 전 상태를 담는다. binlog_row_image = FULL(기본값)일 때 전체 컬럼이 채워진다.
Maxwell: 가볍고 단순한 대안
Maxwell은 Zendesk가 오픈소스로 공개한 CDC 도구다. Kafka Connect 없이 단독 JVM 프로세스로 동작하며 Kafka·RabbitMQ·Redis에 직접 JSON을 전달한다.
| 항목 | Debezium | Maxwell |
|---|---|---|
| 운영 방식 | Kafka Connect 위에서 동작 | 독립 JVM 프로세스 |
| 초기 스냅샷 | 지원 (snapshot mode 설정) | 지원 (--bootstrapper) |
| 스키마 저장 | Kafka 토픽 (schema history) | MySQL 내부 테이블 (maxwell.columns) |
| 출력 포맷 | Envelope 구조 (before/after) | 단순 JSON |
| 상태 저장 | Kafka Connect offset topic | Maxwell DB (maxwell.positions) |
| 적합한 상황 | 대규모, 엔터프라이즈 | 단순 단일 파이프라인 |
CDC 파이프라인 아키텍처
운영 시 주의사항
binlog 보존 기간 연장: CDC 도구가 오프라인이던 사이에 binlog가 만료되면 재시작 시 위치를 잃어 전체 스냅샷을 다시 해야 한다. CDC 도구의 최대 예상 다운타임보다 넉넉하게 설정한다.
-- 14일 보존 예시
SET PERSIST binlog_expire_logs_seconds = 1209600;DDL 주의: ALTER TABLE로 컬럼을 삭제·타입 변경하면 binlog 이벤트 파싱 방식이 달라진다. Debezium의 schema history가 이를 처리하지만, 스키마 변경 전 CDC 파이프라인이 현재 위치까지 모두 소비 완료된 상태인지 확인하는 것이 안전하다.
server_id 충돌: CDC 도구는 MySQL에 레플리카로 등록할 때 고유한 server_id를 사용한다. 기존 레플리카 또는 다른 CDC 인스턴스와 겹치면 기존 연결이 끊어진다. 클러스터 내 모든 MySQL 인스턴스와 CDC 도구에 고유한 server_id를 할당한다.
binlog_row_image 설정: MINIMAL로 줄이면 binlog 크기는 줄지만 before 이미지에 일부 컬럼이 누락된다. CDC 도구가 정확한 변경 전 상태를 필요로 한다면 기본값 FULL을 유지한다.
References
- https://debezium.io/documentation/reference/stable/connectors/mysql.html
- https://nordsecurity.com/blog/decoupling-maxwell-cdc-mysql
- https://medium.com/data-science/understanding-change-data-capture-cdc-in-mysql-and-postgresql-binlog-vs-wal-logical-decoding-ac76adb0861f
- https://www.baeldung.com/ops/mysql-maxwell-change-capture
- https://hevodata.com/learn/mysql-cdc/
- https://docs.confluent.io/cloud/current/connectors/cc-mysql-source-cdc-debezium.html