한 줄 요약: 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_binONbinlog 생성 자체의 전제. MySQL 8.0+ 기본값 ON
binlog_formatROW타겟은 SQL 이 아닌 Row 변경 이벤트를 소비함
binlog_row_imageFULL변경되지 않은 컬럼까지 필요 (아래 결론·테스트 참조)
server_id클러스터 내 유일CDC 커넥터도 레플리카처럼 binlog 을 구독하므로 기존 노드와 ID 충돌 시 끊김
binlog_expire_logs_seconds커넥터 다운타임 SLA 보다 길게커넥터 정지 사이 binlog 이 만료되면 재시작 시 스냅샷부터 다시 떠야 함
gtid_modeON (ClickHouse 필수, 그 외 권장)커넥터가 위치를 GTID 로 정확히 추적
enforce_gtid_consistencyON (GTID 병행)GTID 무결성 보장 — GTID 모드와 세트로 움직임

결론

왜 이 설정들이 필요한가

설정설계 이유
ROWSTATEMENT/MIXED 는 비결정적 함수(NOW(), UUID()) 때문에 타겟에서 재실행이 어긋남. CDC 는 “최종 값” 자체를 소비해야 함
FULL타겟이 원본 DB 에 재접속 없이 한 이벤트로 Row 전체 상태를 재구성 해야 함. MINIMAL 은 PK + 변경 컬럼만 실려 스냅샷이 불완전함
GTID커넥터 재시작·소스 장애 승격 시 위치를 파일+오프셋이 아닌 논리 식별자 로 이어받음

주요 도구별 요구 매트릭스

도구binlog_formatbinlog_row_imageGTID
Debezium MySQL ConnectorROW 필수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 가 강제하지 않을 뿐 구현이 강제함.

운영 시 체크

  1. 복제 전용 클러스터에 CDC 를 얹을 때 binlog_row_image 부터 확인. 복제 용량 절감을 위해 MINIMAL 로 쓰던 환경에 CDC 를 붙이면 타겟에 불완전 레코드가 실림.
  2. GTID 전환은 사전 합의. enforce_gtid_consistency=ONCREATE TABLE ... SELECT, 트랜잭셔널-비트랜잭셔널 혼합 같은 일부 쿼리를 거부함. 온라인 전환 절차(OFF → OFF_PERMISSIVE → ON_PERMISSIVE → ON) 가 필요함.
  3. binlog_expire_logs_seconds 가 커넥터 가용성 SLA 보다 긴지 확인. MySQL 기본값은 30일(2,592,000초)이지만 디스크 압박으로 줄여놓은 환경이 흔함. 커넥터가 그보다 오래 멈춰 있으면 만료된 binlog 를 못 찾고 GTID gap 으로 끊겨 결국 스냅샷부터 다시 떠야 함. Debezium 예시 설정은 864000(10일).
  4. server_id 충돌 점검. 기존 레플리카·다른 CDC 커넥터·툴(pt-online-schema-change 등)이 쓰는 ID 와 겹치면 소스가 binlog dump 연결 한쪽을 잘라버림. 커넥터마다 다른 값을 부여하고, MySQL 측 server_id 와도 분리.
  5. 테이블에 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 한 컬럼만 바꾸는 동일한 UPDATEbinlog_row_imageFULL/MINIMAL 로 바꿔가며 한 세션에서 차례대로 실행한 뒤, SHOW BINLOG EVENTSUpdate_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_imageUpdate_rows 바이트내역
FULL938 B (1332-394)Before/After 전체 컬럼 (bio 400B × 2 포함)
MINIMAL46 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 로 맞추는 것이 용량·네트워크 비용 면에서 합리적임.


참고