[MSK+Kafka Connect] 트리거 없는 실시간 DB 동기화 구현하기
기존 시스템의 한계와 개편 필요성시스템 개편을 하면서 새로운 아키텍처를 구상해야 하는 과제가 있었습니다.오랫동안 DB 링크와 트리거 기반의 데이터 연계 시스템(ESB)을 운영해 왔었는데, 기
skillfromthesky.tistory.com
안녕하세요. 지난 글에서는 트리거 기반 ESB 구조를 제거하고, Kafka Connect + Debezium 기반의 CDC 파이프라인을 구성하는 전체 아키텍처에 대해 작성하였습니다.
이번 글에서는 실제 운영 환경에서 억 단위 레코드가 유입될 때 발생하는 병목과 튜닝 포인트를 정리하고, 소스 커넥터 데이터 추출 시 DB에서의 내부 동작과 Debezium의 로그 추출 메커니즘에 대해 작성해 보려고 합니다.
1. 대용량 처리 시 발생하는 병목 지점
대량 데이터를 한 번에 전송하거나, CDC backlog가 쌓여 있을 때 파이프라인은 다음 세 가지 단계에서 병목이 발생합니다.
1.1 초기 스냅샷 단계 (SnapShot)
- 신규 커넥터 최초 등록 시 기본 테이블 풀 스캔 후 Kafka로 적재 (snapshot mode: initial)
- 테이블이 수백 GB 이상이면 EC2 네트워크와 JVM Heap이 병목됨
- Source Connector는 생성되어 Topic에 적재가 되고 있는데 Sink Connector가 없을 때 병목 현상 발생
- Producer(Debezium)은 계속 데이터를 밀어 넣고, Broker는 파티션 단위로 메세지를 받아서 저장
- 파티션당 처리량 한계를 초과하면 Broker I/O와 네트워크가 saturate 됨
- 이 상태가 지속되면 다음과 같은 현상이 나타남:
broker에 메세지가 쌓여 retention 한계에 도달
partition write queue가 밀려서 producer send 지연 증가
connector task가 backpressure에 걸림 (lag 급증)
-> 즉, Sink가 없는데 snapshot으로 대량 데이터를 한 번에 밀어 넣으면 Kafka broker의 partition 처리량이 포화되어 병목이 생김
해결책
- Sink Connector를 먼저 배포 후 Snapshot 실행
- snapshot 시작 전에 Sink 가 먼저 붙어 있어야 producer -> broker -> consumer 플로우가 정상 동작함
- Sink 없이 snapshot만 먼저 실행하면 토픽에 backlog만 쌓이고 broker가 먼저 터짐
- Snapshot 대상 테이블 분할 또는 사전 덤프 후 resume 하는 방식
- 대용량 테이블은 한 번에 snapshot 하지 않고, 소스 테이블의 데이터를 미리 타겟 테이블에 migration 해 둔 후, snapshot mode를 never로 설정하여 cdc만 이어받는 방식이 안정적임
- Debezium snapshot fetch/batch 사이즈 조정
- snapshot.fetch.size, max.batch.size 값을 줄여 한 번에 producer가 밀어 넣는 메세지 수를 제어한다
- JVM Heap 및 Broker I/O burst 방지
- Kafka Connect 서버가 설치된 ec2 혹은 ecs 사양을 늘리거나 네트워크 통신 속도가 빠른 이미지로 변경한다
- Kafka Broker 파티션/디스크 용량 여유 확보
- CDC 사용 시 카프카 파티션은 무조건 1개로 설정할 수밖에 없음 (순서 보장을 위해)
- 따라서 retention.ms나 segment.bytes 등을 조정하여 backlog 흡수 버퍼를 확보해야 함
1.2 CDC 스트리밍 단계 (Streaming)
- 대량 트랜잭션이 짧은 시간에 commit -> WAL/Redo log에 급격한 이벤트 누적
- Debezium task thread가 로그 처리 속도를 못 따라면 lag(ms) 지수적으로 증가
해결책
- DB 로그 설정 튜닝 (WAL / Redo Log 용량 확보)
- Oracle은 Redo + 아카이브 로그 용량을 넉넉히 확보하여 backlog 구간에서 덮어쓰임 방지
- PG는 replication slot lag가 생겨도 WAL이 삭제되지 않도록 wal_keep_size을 늘리거나, retention 정책을 세밀하게 설정한다
- DB 트랜잭션 패턴 튜닝
- application 레벨에서 대량 batch commit 대신 일정 batch 사이즈로 쪼개 commit -> CDC 처리 부하 완화
- Lag 모니터링 및 알람 시스템 구축
- DB 모니터링 + Kafka consumer lag + Debezium metrics(source_lag_ms) 통합 모니터링 필요
select
a.slot_name,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), a.confirmed_flush_lsn)) AS lag_size,
pg_size_pretty(pg_wal_lsn_diff(a.confirmed_flush_lsn, a.restart_lsn)) AS processed_range,
a.restart_lsn,
a.confirmed_flush_lsn,
a.wal_status,
b.client_addr,
b.state
FROM pg_replication_slots a join pg_stat_replication b on a.active_pid = b.pid

wal_status가 reserved 이면 정상
lag_size는 db 트랜잭션이 발생하는 속도 (wal 발생 속도) 보다 debezium이 해당 데이터를 읽는 속도가 느리면 발생한다.
processed_range는 debezium 커넥터에서 처리를 완료했지만, PG에서 아직 정리되지 못해서 남아있는 wal의 크기이다.
-> 해당 부분은 db에서 commit 혹은 트랜잭션 커밋을 해 주어야 사라짐
상세 내용
| 컬럼 | 의미 |
| pg_current_wal_lsn() | 현재 DB의 WAL LSN(로그 시퀀스 넘버) 위치 (최신 위치) |
| confirmed_flush_lsn | replication slot을 사용하는 커넥터가 소비를 완료하고 flush까지 끝낸 LSN 위치 |
| restart_lsn | PostgreSQL이 이전 WAL segment를 삭제해도 안전하다고 판단하는 최소 LSN. 즉, WAL 보존 시작점 |
| lag_size = pg_current_wal_lsn() - confirmed_flush_lsn | 현재 DB의 최신 WAL과 replication slot이 flush 완료한 위치 간의 거리. 즉, Connector가 아직 처리 못 한 WAL backlog 용량 |
| processed_range = confirmed_flush_lsn - restart_lsn | replication slot이 이미 읽어서 flush까지 끝냈지만 아직 DB가 물리적으로 WAL 파일을 정리하지 않은 범위 |
1.3 Kafka Connect 내부 큐 & Worker 처리
- Connect task 당 버퍼 큐 (capacity 제한 있음) -> Consumer가 느리면 큐가 쌓임
- 싱글 쓰레드 처리 모델에서는 단일 테이블이 전체 커넥터 처리량을 잡아먹는 경우가 발생함
해결책
- max.queue.size / max.batch.size 조정
- 큐 크기를 늘려 순간 burst를 버퍼링하거나, 반대로 batch 사이즈를 줄여 GC 및 backpressure 방지.
- 실제 환경에 맞춰 JVM Heap 대비 최적값 튜닝 필요.
- Connect Worker 수 및 Task 분산 설계
- 1개의 Worker에 너무 많은 Task를 몰지 말고, EC2 인스턴스를 여러 대 구성해서 Connect cluster 모드로 분산.
- task 간 workload imbalance가 없는지 주기적으로 점검.
- Sink 처리 속도 개선
- 병목의 근본 원인은 source가 아니라 sink 쪽일 때도 많음.
- sink DB 적재 속도, batch insert 튜닝, indexing 최적화 등도 같이 봐야 한다.
- JVM Heap/GC 최적화
- 대용량 처리 환경에서는 G1GC 사용 및 heap 사이즈 조정으로 stop-the-world 최소화.
- connect worker 프로세스에 CPU와 메모리 충분히 할당해야 함.
2. Debezium Source Connector와 DB 로그 메커니즘
2.1 Oracle Redo Log + LogMiner 구조
Oracle은 CDC를 위해 Redo Log → Archived Log → LogMiner를 통해 변경 사항을 읽습니다.
DML 발생
↓
Redo Log 기록 (online redo)
↓ (ARCHIVELOG 모드일 경우)
Archived Redo Log
↓
LogMiner 프로세스가 로그 해석
↓
Debezium Oracle Connector가 LogMiner 뷰 조회
↓
Kafka Topic 전송
- Redo Log: DB 트랜잭션 commit 이전에도 모든 변경 사항이 기록되는 circular buffer 구조. 용량이 작으면 빠르게 overwriting됨.
- Archived Log: Redo Log가 가득 차면 디스크에 순차적으로 저장됨. LogMiner는 주로 여기서 읽음.
- LogMiner: Oracle 내부의 로그 분석 엔진. V$LOGMNR_CONTENTS 등을 통해 SQL 변경 이벤트를 추출.
- Debezium은 LogMiner 세션을 유지하며 특정 SCN(System Change Number)부터 로그를 순차적으로 읽고 Kafka 이벤트로 변환.
병목 포인트:
- Redo Log가 작으면 backlog 쌓일 때 덮여버려 CDC 데이터 유실 가능.
- LogMiner는 CPU I/O 모두 무겁기 때문에 병렬 트랜잭션 많은 환경에서는 속도 따라가기 어려움.
AWS RDS LogMiner 활성 쿼리
GRANT CREATE SESSION TO debezium;
GRANT SELECT ON ALL_TABLES TO debezium;
GRANT SELECT ANY TABLE TO debezium;
GRANT SELECT ANY TRANSACTION TO debezium;
GRANT SELECT ANY DICTIONARY TO debezium;
GRANT EXECUTE_CATALOG_ROLE TO debezium;
GRANT LOGMINING TO debezium;
GRANT SELECT ON SYS.V_$DATABASE TO debezium;
GRANT SELECT ON SYS.V_$ARCHIVED_LOG TO debezium;
GRANT SELECT ON SYS.V_$LOGMNR_CONTENTS TO debezium;
GRANT SELECT ON SYS.V_$LOGMNR_LOGS TO debezium;
GRANT SELECT ON SYS.V_$LOG TO debezium;
GRANT SELECT ON SYS.V_$LOGFILE TO debezium;
GRANT SELECT ON SYS.V_$INSTANCE TO debezium;
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;
-- LogMiner 관련 권한
GRANT SELECT ON V_$DATABASE TO DEBEZIUM;
GRANT SELECT ON V_$LOGMNR_CONTENTS TO DEBEZIUM;
GRANT SELECT ON V_$ARCHIVED_LOG TO DEBEZIUM;
GRANT SELECT ON V_$LOGFILE TO DEBEZIUM;
GRANT SELECT ON V_$LOG TO DEBEZIUM;
GRANT SELECT ON V_$THREAD TO DEBEZIUM;
GRANT SELECT ON V_$PARAMETER TO DEBEZIUM;
GRANT SELECT ON V_$NLS_PARAMETERS TO DEBEZIUM;
GRANT SELECT ON V_$TIMEZONE_NAMES TO DEBEZIUM;
GRANT SELECT ON ALL_INDEXES TO DEBEZIUM;
GRANT SELECT ON ALL_OBJECTS TO DEBEZIUM;
GRANT SELECT ON ALL_USERS TO DEBEZIUM;
GRANT SELECT ON ALL_CATALOG TO DEBEZIUM;
GRANT SELECT ON ALL_CONSTRAINTS TO DEBEZIUM;
GRANT SELECT ON ALL_CONS_COLUMNS TO DEBEZIUM;
GRANT SELECT ON ALL_TAB_COLS TO DEBEZIUM;
GRANT SELECT ON ALL_IND_COLUMNS TO DEBEZIUM;
GRANT SELECT ON ALL_PART_TABLES TO DEBEZIUM;
GRANT SELECT ON ALL_PART_KEY_COLUMNS TO DEBEZIUM;
GRANT SELECT ON ALL_TYPES TO DEBEZIUM;
GRANT SELECT ON ALL_SOURCE TO DEBEZIUM;
-- LogMiner 실행 권한
GRANT EXECUTE ON DBMS_LOGMNR TO DEBEZIUM;
GRANT EXECUTE ON DBMS_LOGMNR_D TO DEBEZIUM;
GRANT SELECT ANY TRANSACTION TO DEBEZIUM;
GRANT SELECT ANY DICTIONARY TO DEBEZIUM;
GRANT LOGMINING TO DEBEZIUM; -- Oracle 19c 이상에서 필요
-- 추가 권한 (FlashBack 쿼리 사용 시)
GRANT FLASHBACK ANY TABLE TO DEBEZIUM;
GRANT SELECT ANY TABLE TO DEBEZIUM;
CALL RDSADMIN.RDSADMIN_UTIL.ALTER_SUPPLEMENTAL_LOGGING('ADD', 'ALL');
SELECT LOG_MODE, SUPPLEMENTAL_LOG_DATA_MIN, SUPPLEMENTAL_LOG_DATA_PK,
SUPPLEMENTAL_LOG_DATA_ALL
FROM V$DATABASE;
해당 부분에서 모두 YES가 떠야 debezium oracle이 사용 가능하다
SELECT *
FROM V$LOG;
RDS Oracle은 V$LOG 를 활용하여 cdc를 해야 한다.
아카이브 로그(V$ARCHIVED_LOG)는 rds 접근 권한 때문에 실행할 수 없음
2.2 PostgreSQL WAL(Logical Replication) 구조
PostgreSQL은 WAL(Write-Ahead Log) 기반 Logical Replication을 사용합니다.
DML 발생
↓
WAL(물리 로그) 기록
↓
Logical Decoding → replication slot
↓
Debezium이 replication slot에서 읽기
↓
Kafka Topic 전송
- wal_level=logical 설정이 필수.
- Debezium은 replication slot을 통해 PostgreSQL의 logical stream을 지속적으로 소비
- publication 설정을 통해 어떤 테이블을 replication 대상으로 할지 명시
병목 포인트:
- replication slot이 lag나면 WAL 파일이 pg_wal에 계속 쌓임 → DB 디스크 가득 참
- publication table이 많고 트랜잭션이 크면 logical decoding thread가 병목
2.3 Debezium이 로그를 읽는 방식
- Oracle: 주기적으로 LogMiner 뷰에서 쿼리 → JDBC 기반 Pull 방식
- PostgreSQL: replication 프로토콜을 통해 지속적인 logical stream 소비
- 둘 다 DB 로그 레벨에서 변경 이벤트를 100% 캡처하기 때문에 DB 부하가 적지만, 로그 사이즈·I/O·네트워크가 병목이 되면 lag는 순식간에 쌓임
3. 병목은 피할 수 없지만, 구조를 이해하면 대응할 수 있다
실시간 CDC 파이프라인은 단순히 커넥터만 붙인다고 끝나는 단순한 구조가 아닙니다.
특히 Debezium + PostgreSQL(Aurora), Oracle 환경에서는 DB 내부의 로그 메커니즘 자체가 파이프라인의 성능과 안정성에 직접적인 영향을 주는 핵심 요소입니다. 이 구조를 정확히 이해하고, 병목이 어디서 발생하는지를 알고 있어야 안정적인 운영이 가능합니다.
이번 글에서 다룬 핵심 포인트를 다시 정리해보면 다음과 같습니다.
- 초기 Snapshot 구간은 Kafka Broker와 네트워크 자원을 가장 강하게 압박하는 병목 구간이다.
Sink Connector를 먼저 붙이고, 대용량 테이블은 dump + CDC resume 방식으로 분리하는 것이 실전에서 가장 안정적이다. - CDC 스트리밍 병목은 WAL/Redo Log → replication slot → Debezium task → Kafka Broker로 이어지는 단일 파이프라인 상에서 발생한다.
DB 트랜잭션 패턴, 로그 보존 정책, connector 설정 모두 이 경로에 직접 영향을 준다. - Oracle과 PostgreSQL은 CDC 처리 방식이 근본적으로 다르다.
Oracle은 LogMiner 기반 Pull 방식, PostgreSQL은 Logical Decoding 기반 Stream 방식이며 각각의 병목 지점(LogMiner I/O, replication slot lag)이 다르다. - PostgreSQL은 replication slot 단위로 task가 1개만 동작하는 구조이기 때문에 병렬화를 하려면 slot/publication/DB 분리를 통한 설계가 필수다.
테이블 일부만 publication에 등록해서는 WAL 누적을 막을 수 없으며, 오히려 replication slot lag이 급증할 수 있다.
결국 실시간 CDC 파이프라인의 안정성은
→ DB 로그 구조
→ Debezium의 내부 동작 원리
→ Kafka 인프라의 특성
이 세 가지를 얼마나 정확히 이해하고 설계했는지에 달려 있습니다.
마무리
이번 글에서는 실시간 CDC 파이프라인을 운영하면서 대용량 데이터가 유입될 때 실제로 어디서 병목이 발생하는지, 그리고 Oracle Redo Log와 PostgreSQL WAL 기반의 로그 추출 메커니즘이 Debezium과 어떻게 맞물려 동작하는지를 정리해 보았습니다.
대규모 트랜잭션 환경에서는 단순히 커넥터만 잘 설정한다고 해서 안정적인 데이터 동기화가 보장되지 않습니다.
DB 로그 구조, Kafka 인프라 특성, Debezium의 내부 동작 방식을 제대로 이해하고 병목 지점을 명확히 파악해야만 예측 가능한 파이프라인 운영이 가능합니다.
다음 글에서는 Sink Connector 운영 전략, 대용량 적재 시 Target DB 구조 설계 방법, 그리고 Connector CI/CD 자동화 체계에 대해 다룰 예정입니다.
Kafka → Target DB 구간은 실제 운영 환경에서 Source보다 더 다양한 병목과 장애 요인이 발생하는 핵심 구간이기 때문에, 이 부분을 얼마나 견고하게 설계하느냐가 전체 CDC 파이프라인의 안정성을 결정짓는 핵심 포인트가 됩니다.
'Develop > DATA Engineering' 카테고리의 다른 글
| [MSK+Kafka Connect] 트리거 없는 실시간 DB 동기화 구현하기 (0) | 2025.08.31 |
|---|---|
| 대용량 데이터를 전송하는 방법(1) - Message Queue (0) | 2024.01.07 |
| [빅데이터를 지탱하는 기술] 01 - 1주차 스터디 (0) | 2022.09.14 |
| Airflow (0) | 2022.05.13 |