← 기술 목록으로

파일 기반 배치형 CDC와 UPSERT 재실행

mysqlbinlog 파이프로 파일 전체를 재생하던 고객사 동기화를 바꾼 작업입니다. Go Updater가 배치 binlog 파일을 오프라인으로 파싱하고, 정상 변환된 INSERT·UPDATE는 다시 실행해도 중복 반영되지 않게 했습니다.

기간2025.09 제안 · 2026.01–04 설계·구현·배포 시나리오 리뷰
소속래브라도랩스(LabradorLabs)
역할Data Engineer · CDC 전환 제안 및 설계·구현
GoCDCMySQL Binary LogUPSERTZSTDBatch Delivery

0재시도를 멱등하게 만든 이유

레거시 동기화의 단위는 binlog 파일 전체였습니다. 파일 안의 이벤트 하나가 실패해도 파일 전체가 막히고, 어디까지 들어갔는지는 stderr 텍스트를 긁어 세는 수준이라 복구 위치를 특정할 수 없었습니다. 품질 개선 회의에서 CDC 구조 전환을 직접 제안한 이유입니다.

정상 변환된 INSERT·UPDATE에 한해 UPSERT로 적용했습니다. DELETE·DDL과 truncation skip은 이 보장에 포함하지 않았으며, 파일 전체가 언제나 무손실로 재실행된다고 주장하지 않습니다.

1배경

레거시(v2/v3) Updater는 mysqlbinlog 유틸리티를 파이프로 실행해 binlog를 타겟 DB에 그대로 밀어넣는 단순 리플레이였습니다. SQL을 제어할 수 없으니 중복 키 에러가 나면 수동 개입이 필요했고, DEFINER 절·Virtual Column처럼 고객사 환경에서 깨지는 구문도 거를 수 없었습니다. 일부 고객사는 MySQL replica 방식의 상시 TCP 연결을 허용하지 않아, 기존 HTTPS 파일 배포 경계를 유지한 채 변경분을 전달해야 하는 제약도 있었습니다.

Before

파일 단위 All-or-Nothing 리플레이 → 이벤트 하나의 실패가 파일 전체를 막음, 중복 적용 시 에러, 복구 위치 추적 불가

After

배치 binlog 파일 → Go Updater 오프라인 파싱 → 정상 변환된 INSERT·UPDATE를 UPSERT로 적용해 재실행 시 중복 반영 방지

2한 일

A아키텍처 (데이터 흐름)

Source DB의 ROW-format binlog 배치 파일을 Python FastAPI 서버가 암호화해 전달하고 Go Updater가 오프라인으로 파싱한 뒤 정상 변환된 INSERT와 UPDATE를 UPSERT로 고객사 Target DB에 적용하는 흐름
파일 전달은 기존 배치 경계를 유지하고, 정상 변환된 INSERT·UPDATE에 한해 재실행 시 중복 반영을 막았습니다.

파일 배포 경계를 바꾸지 않은 채 Go Updater의 오프라인 파싱과 DML 변환을 적용했습니다. 재실행 안전성은 정상 변환된 INSERT·UPDATE 범위이며, truncation skip은 별도 정합성 확인이 필요합니다.

3결과

재실행 범위 — 정상 변환된 INSERT·UPDATE는 UPSERT로 중복 반영을 방지
운영 인계 — 설계·구현과 배포 시나리오 리뷰 뒤 고객사 배포·운영은 기술지원팀에 이관
남은 경계 — truncation skip은 무손실 처리로 주장하지 않고 후속 정합성 확인 대상으로 구분

4역할

2025년 9월 품질 개선 회의에서 파일 단위 동기화의 한계를 제기하며 CDC 구조 전환을 제안했고, 2026년 1분기에 설계·구현과 배포 시나리오 리뷰를 마친 뒤 고객사 배포는 기술지원팀에 이관했습니다. 도구 선정 단계에서 Maxwell은 FileSink PoC로 DML·DDL JSON 출력을 확인했지만, 공식 MySQL sink가 없고 MySQL 8.4의 ZSTD 압축 Transaction_payload를 처리하지 못해 제외했습니다. 배포 경계는 Python/FastAPI download server가 암호화된 binlog 파일을 제공하는 기존 HTTPS 방식을 유지했습니다. 기술지원팀은 레거시를 포함해 약 10개 고객 환경의 배포와 일상 운영을 맡고, 저는 운영 중 접수된 이슈를 재현해 구현을 개선하고 기술 지원을 제공했습니다.