한 줄 요약: CDC (Change Data Capture) 는 MySQL binlog 를 Kafka·ClickHouse·Elasticsearch 등 외부 시스템으로 스트리밍하는 아키텍처임. 타겟이 원본 DB 에 재질의하지 않고 Row 전체 상태를 재구성해야 하므로
binlog_format=ROW,binlog_row_image=FULL이 사실상 필수이며, GTID 는 도구에 따라 권장 또는 필수임.
개요
CDC 는 DB 의 변경 이벤트를 실시간 스트림으로 내보내 검색 엔진·DW·메시지 큐와 동기화하는 패턴임. MySQL 에서는 이미 레플리케이션용으로 쓰이는 binlog 를 그대로 재활용 함 — 레플리카 대신 Debezium 같은 커넥터가 binlog 를 읽고 이벤트를 가공해 전달함.
binlog 가 재료이므로 binlog 에 무엇이 실리는지 가 설계의 출발점이 됨. 이 글은 대표적인 CDC 도구가 MySQL 쪽에 요구하는 설정과, 그 요구의 근본 원인이 되는 binlog_row_image 동작을 실측으로 확인하는 범위를 다룸.
| 설정 | 필수값 | 이유 |
|---|---|---|
log_bin | ON | binlog 생성 자체의 전제. MySQL 8.0+ 기본값 ON |
binlog_format | ROW | 타겟은 SQL 이 아닌 Row 변경 이벤트를 소비함 |
binlog_row_image | FULL | 변경되지 않은 컬럼까지 필요 (아래 결론·테스트 참조) |
server_id | 클러스터 내 유일 | CDC 커넥터도 레플리카처럼 binlog 을 구독하므로 기존 노드와 ID 충돌 시 끊김 |
binlog_expire_logs_seconds | 커넥터 다운타임 SLA 보다 길게 | 커넥터 정지 사이 binlog 이 만료되면 재시작 시 스냅샷부터 다시 떠야 함 |
gtid_mode | ON (ClickHouse 필수, 그 외 권장) | 커넥터가 위치를 GTID 로 정확히 추적 |
enforce_gtid_consistency | ON (GTID 병행) | GTID 무결성 보장 — GTID 모드와 세트로 움직임 |
결론
왜 이 설정들이 필요한가
| 설정 | 설계 이유 |
|---|---|
ROW | STATEMENT/MIXED 는 비결정적 함수(NOW(), UUID()) 때문에 타겟에서 재실행이 어긋남. CDC 는 “최종 값” 자체를 소비해야 함 |
FULL | 타겟이 원본 DB 에 재접속 없이 한 이벤트로 Row 전체 상태를 재구성 해야 함. MINIMAL 은 PK + 변경 컬럼만 실려 스냅샷이 불완전함 |
GTID | 커넥터 재시작·소스 장애 승격 시 위치를 파일+오프셋이 아닌 논리 식별자 로 이어받음 |
주요 도구별 요구 매트릭스
| 도구 | binlog_format | binlog_row_image | GTID |
|---|---|---|---|
| Debezium MySQL Connector | ROW 필수 | FULL 필수 | 권장 |
| ClickHouse MaterializedMySQL | 공식 미명시 | 공식 미명시 | 필수 |
ClickHouse “공식 미명시” 의 뜻 — v24.3 LTS docs 의 Settings on MySQL-server Side mandatory 목록에
binlog_format/binlog_row_image가 안 들어감. 다만 엔진이 binlog 의 row 이벤트를 직접 파싱하고ReplacingMergeTree머지에 Row 전체 스냅샷이 필요하므로 운영상 ROW + FULL 이 아니면 동작이 깨짐. 즉 docs 가 강제하지 않을 뿐 구현이 강제함.
운영 시 체크
- 복제 전용 클러스터에 CDC 를 얹을 때
binlog_row_image부터 확인. 복제 용량 절감을 위해MINIMAL로 쓰던 환경에 CDC 를 붙이면 타겟에 불완전 레코드가 실림. - GTID 전환은 사전 합의.
enforce_gtid_consistency=ON은CREATE TABLE ... SELECT, 트랜잭셔널-비트랜잭셔널 혼합 같은 일부 쿼리를 거부함. 온라인 전환 절차(OFF → OFF_PERMISSIVE → ON_PERMISSIVE → ON) 가 필요함. binlog_expire_logs_seconds가 커넥터 가용성 SLA 보다 긴지 확인. MySQL 기본값은 30일(2,592,000초)이지만 디스크 압박으로 줄여놓은 환경이 흔함. 커넥터가 그보다 오래 멈춰 있으면 만료된 binlog 를 못 찾고 GTID gap 으로 끊겨 결국 스냅샷부터 다시 떠야 함. Debezium 예시 설정은864000(10일).server_id충돌 점검. 기존 레플리카·다른 CDC 커넥터·툴(pt-online-schema-change등)이 쓰는 ID 와 겹치면 소스가 binlog dump 연결 한쪽을 잘라버림. 커넥터마다 다른 값을 부여하고, MySQL 측server_id와도 분리.- 테이블에 PK 가 없으면 타겟에서 UPSERT·DELETE 매칭이 깨짐. MySQL 8.0.30+ 에서는
sql_generate_invisible_primary_key=ON(GIPK) 으로 자동 부여 가능.
도구별 공식 요구사항
Debezium MySQL Connector
공식 “Setting up MySQL” 항목이 server-id (cluster 내 유일), binlog_format=ROW, binlog_row_image=FULL, binlog_expire_logs_seconds=864000 네 줄을 표로 명시함. GTID 는 별도 섹션 “Enabling GTIDs” 에서 “Though not required for a Debezium MySQL connector, using GTIDs simplifies replication” 이라고 표현 — 즉 강제는 아니지만 소스 장애 승격 시 커넥터가 바로 이어받기 위해 운영에서는 거의 켜고 씀.
ClickHouse MaterializedMySQL
공식 문서(v24.3 LTS 기준)의 Settings on MySQL-server Side 가 mandatory 로 명시하는 항목은 gtid_mode=on, enforce_gtid_consistency=on, default_authentication_plugin=mysql_native_password 세 가지임. binlog 를 row 이벤트로 직접 소비하므로 binlog_format=ROW + binlog_row_image=FULL 도 사실상 강제 — ReplacingMergeTree 가 머지 시 Row 최신 전체 스냅샷을 요구해 MINIMAL 이면 미변경 컬럼이 NULL 로 합쳐져 데이터가 유실됨. ClickHouse 24.3 이후 LTS 부터 MaterializedMySQL 페이지가 공식 docs 에서 빠졌고 신규 도입 권장 경로는 ClickPipes/PeerDB 로 옮겨갔으므로, 본 글이 인용하는 mandatory 설정 근거는 v24.3 태그의 GitHub 영구 링크를 사용함 (참고 섹션).
binlog_row_image 가 CDC 에서 결정적인 이유
MINIMAL 에서는 Before 이미지에 PK 만, After 이미지에 변경된 컬럼만 실림. 일반 복제는 레플리카가 Before-PK 로 타겟 행을 찾고 After 로 패치하면 되므로 문제가 없으나, CDC 타겟은 “Row 최신 스냅샷” 을 생산해야 함.
FULL 로 실리는 UPDATE 이벤트:
Before: {id=1, name='홍길동', email='hong@...', bio='...', point=30}
After: {id=1, name='홍길동', email='hong@...', bio='...', point=50}
→ 타겟은 이벤트 하나로 "id=1 의 현재 전체 상태" 를 확정 가능
MINIMAL 로 실리는 동일 UPDATE:
Before: {id=1}
After: {point=50}
→ name/email/bio 는 모름. 타겟이 불완전 문서를 만들거나 원본 DB 에 역질의 필요원본 DB 로의 역질의는 커넥터 처리량을 수 배 떨어뜨리고, 그 사이 원본이 또 바뀌면 일관성이 깨짐. 거의 모든 CDC 도구가 FULL 을 요구하는 이유임.
테스트 — MySQL 8.4.8 에서 FULL / MINIMAL 이벤트 크기 비교
아래 출력은 MySQL 8.4.8 컨테이너에서 직접 실행한 결과임. 400바이트짜리 bio 컬럼을 포함한 5컬럼 테이블에서 point 한 컬럼만 바꾸는 동일한 UPDATE 를 binlog_row_image 만 FULL/MINIMAL 로 바꿔가며 한 세션에서 차례대로 실행한 뒤, SHOW BINLOG EVENTS 로 Update_rows 이벤트 크기를 비교함.
준비
DROP TABLE IF EXISTS cdc_demo;
CREATE TABLE cdc_demo (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
bio VARCHAR(500),
point INT
) ENGINE=InnoDB;
INSERT INTO cdc_demo
VALUES (1, '홍길동', 'hong@example.com', REPEAT('x', 400), 30);
FLUSH BINARY LOGS;동일 UPDATE 를 binlog_row_image 만 바꿔 실행
SET SESSION binlog_row_image = 'FULL';
UPDATE cdc_demo SET point = 50 WHERE id = 1;
SET SESSION binlog_row_image = 'MINIMAL';
UPDATE cdc_demo SET point = 80 WHERE id = 1;MySQL 8.4.8 에서 직접 확인 —
SET SESSION은 현재 커넥션의 설정을 즉시 덮어씌우므로 위처럼 한 세션 안에서 FULL/MINIMAL 을 번갈아 실행해도 각 UPDATE 가 그 시점의 세션 값으로 기록됨. 다만SET GLOBAL만 바꾸면 이미 연결된 세션의 세션값은 그대로 남아 두 쿼리 결과가 같게 나옴 — 글로벌 변경으로 검증하려면 새 접속을 띄워야 함.
SHOW BINLOG EVENTS 결과
SHOW BINLOG EVENTS IN 'binlog.000008';Log_name Pos Event_type End_log_pos Info
binlog.000008 4 Format_desc 127 Server ver: 8.4.8, Binlog ver: 4
binlog.000008 127 Previous_gtids 158
binlog.000008 158 Anonymous_Gtid 237 SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
binlog.000008 237 Query 323 BEGIN
binlog.000008 323 Table_map 394 table_id: 106 (testdb.cdc_demo)
binlog.000008 394 Update_rows 1332 table_id: 106 flags: STMT_END_F ← FULL
binlog.000008 1332 Xid 1363 COMMIT /* xid=37677 */
binlog.000008 1363 Anonymous_Gtid 1442 SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
binlog.000008 1442 Query 1528 BEGIN
binlog.000008 1528 Table_map 1599 table_id: 106 (testdb.cdc_demo)
binlog.000008 1599 Update_rows 1645 table_id: 106 flags: STMT_END_F ← MINIMAL
binlog.000008 1645 Xid 1676 COMMIT /* xid=37682 */End_log_pos - Pos 로 각 Update_rows 이벤트 크기를 계산:
binlog_row_image | Update_rows 바이트 | 내역 |
|---|---|---|
FULL | 938 B (1332-394) | Before/After 전체 컬럼 (bio 400B × 2 포함) |
MINIMAL | 46 B (1645-1599) | Before PK + After 변경 컬럼만 |
동일 테이블·동일 쿼리인데 약 20배 차이. bio 처럼 변경되지 않는 넓은 컬럼이 있을수록 비율이 더 벌어짐.
CDC 관점 해석
46바이트 이벤트에는 name, email, bio 값이 없음. Debezium 은 이 이벤트로 Kafka 에 실을 Row 스냅샷을 만들 수 없고, ClickHouse ReplacingMergeTree 는 기존 버전과 머지할 때 참조할 수 없는 컬럼이 NULL 로 합쳐져 데이터가 유실됨. 따라서 CDC 소스 MySQL 은 FULL 전제. 복제만 필요한 일반 레플리카를 별도로 운용 중이라면 그 노드는 MINIMAL 로 두고, CDC 커넥터가 물리는 소스 노드만 FULL 로 맞추는 것이 용량·네트워크 비용 면에서 합리적임.
참고
- MySQL 8.4 —
binlog_row_image— FULL/MINIMAL/NOBLOB 동작 정의 - MySQL 8.4 —
binlog_format— 8.4 에서 STATEMENT/MIXED deprecated - MySQL 8.4 — Replication with GTIDs —
gtid_mode/enforce_gtid_consistency전환 절차 - Debezium MySQL Connector — Setting up MySQL —
server-id·binlog_format=ROW·binlog_row_image=FULL·binlog_expire_logs_seconds=864000표 명시, GTID 는 별도 섹션에서 “not required” 로 표기 - ClickHouse v24.3 LTS — MaterializedMySQL Settings on MySQL-server Side —
gtid_mode=on+enforce_gtid_consistency=on+default_authentication_plugin=mysql_native_passwordmandatory. ClickHouse 24.3 이후 공식 docs 에서 페이지가 제거돼 영구 GitHub 태그 링크로 인용