본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 8장: CDC(Change Data Capture)
2026년 2월 24일·데이터·

8장: CDC(Change Data Capture)

CDC의 원리와 WAL 기반 변경 캡처, Debezium 아키텍처, PostgreSQL/MySQL CDC 실습, Flink CDC 3.6, 아웃박스 패턴, 이벤추얼 컨시스턴시까지 데이터 통합의 핵심을 학습합니다.

18분798자10개 섹션
streamingdata-engineering
공유
realtime-pipeline8 / 11
1234567891011
이전7장: Spark Structured Streaming다음9장: 스키마 레지스트리와 데이터 계약

학습 목표

  • **CDC(Change Data Capture)**의 원리와 필요성을 이해합니다.
  • WAL(Write-Ahead Log) 기반 CDC의 동작 방식을 파악합니다.
  • Debezium의 아키텍처와 PostgreSQL/MySQL 연동을 학습합니다.
  • Flink CDC 3.6의 네이티브 커넥터와 새로운 기능을 살펴봅니다.
  • **Outbox Pattern(아웃박스 패턴)**과 **Eventual Consistency(이벤추얼 컨시스턴시)**를 이해합니다.
  • CDC 기반 분석 파이프라인 구성 방법을 학습합니다.

CDC란 무엇인가

**CDC(Change Data Capture)**는 데이터베이스의 변경 사항(INSERT, UPDATE, DELETE)을 실시간으로 감지하고 캡처하는 기술입니다. 데이터베이스에서 일어나는 모든 변경을 이벤트 스트림으로 변환합니다.

왜 CDC인가

데이터베이스의 데이터를 다른 시스템(검색 엔진, 캐시, 데이터 웨어하우스)과 동기화하는 전통적인 방법은 주기적 배치 추출입니다. 그러나 이 방식에는 근본적인 한계가 있습니다.

  • 높은 지연: 배치 주기(시간/일) 만큼의 데이터 지연
  • DB 부하: 전체 테이블 스캔으로 원본 DB에 부하 발생
  • DELETE 감지 불가: 삭제된 행을 감지하려면 소프트 삭제 또는 전체 비교 필요
  • 중간 상태 유실: 배치 사이의 변경 이력이 사라짐

CDC는 이 모든 문제를 해결합니다. 변경이 발생하는 즉시 캡처하고, 원본 DB에 거의 부하를 주지 않으며, 모든 변경 유형(INSERT/UPDATE/DELETE)을 빠짐없이 포착합니다.

CDC 구현 방식

방식원리장점단점
Polling주기적으로 테이블 조회구현 간단DB 부하, DELETE 미감지, 지연
TriggerDB 트리거로 변경 캡처즉시 반영DB 성능 저하, 트리거 관리 복잡
WAL 기반DB 내부 로그 읽기부하 최소, 모든 변경 캡처구현 복잡, DB별 프로토콜 상이

프로덕션 환경에서는 WAL 기반 CDC가 사실상 표준입니다.


WAL 기반 CDC의 원리

Write-Ahead Log란

관계형 데이터베이스는 데이터 변경 전에 반드시 **WAL(Write-Ahead Log)**에 변경 내역을 기록합니다. 이는 장애 복구를 위한 메커니즘입니다. PostgreSQL의 WAL, MySQL의 binlog, Oracle의 redo log가 이에 해당합니다.

WAL 기반 CDC는 이 로그를 읽어 변경 사항을 추출합니다. 데이터베이스의 레플리케이션 프로토콜을 활용하므로, CDC 도구가 마치 읽기 전용 레플리카처럼 동작합니다.

CDC 이벤트 구조

WAL에서 추출된 변경은 다음과 같은 이벤트 구조로 변환됩니다.

cdc-event.json
json
{
  "before": {
    "id": 1001,
    "status": "pending",
    "amount": 50000
  },
  "after": {
    "id": 1001,
    "status": "completed",
    "amount": 50000
  },
  "source": {
    "connector": "postgresql",
    "db": "shop",
    "schema": "public",
    "table": "orders",
    "txId": 12345,
    "lsn": 234567890
  },
  "op": "u",
  "ts_ms": 1710648000000
}
  • op 필드: c(create/INSERT), u(update), d(delete), r(read/스냅샷)
  • before: 변경 전 상태 (INSERT의 경우 null)
  • after: 변경 후 상태 (DELETE의 경우 null)
Info

before 값을 제공하려면 PostgreSQL에서는 REPLICA IDENTITY FULL 설정이 필요하고, MySQL에서는 binlog_row_image를 full로 설정해야 합니다. 기본 설정에서는 UPDATE 시 before에 기본 키 컬럼만 포함됩니다.


Debezium

Debezium은 가장 널리 사용되는 오픈소스 CDC 플랫폼입니다. Kafka Connect 기반으로 동작하며, 다양한 데이터베이스의 변경을 Kafka 토픽으로 스트리밍합니다.

지원 데이터베이스

Debezium은 주요 관계형 및 NoSQL 데이터베이스를 지원합니다.

  • PostgreSQL (logical replication)
  • MySQL / MariaDB (binlog)
  • MongoDB (oplog / change streams)
  • Oracle (LogMiner / XStream)
  • SQL Server (CT / CDC)
  • Cassandra, Db2, Vitess 등

PostgreSQL CDC 설정

PostgreSQL에서 Debezium을 사용하기 위한 설정 과정입니다.

1단계: PostgreSQL 설정

postgresql-config.sql
sql
-- postgresql.conf에서 logical replication 활성화
-- wal_level = logical
-- max_replication_slots = 4
-- max_wal_senders = 4
 
-- 리플리케이션 사용자 생성
CREATE ROLE debezium WITH REPLICATION LOGIN PASSWORD 'secret';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO debezium;
 
-- publication 생성 (캡처할 테이블 지정)
CREATE PUBLICATION dbz_publication FOR TABLE orders, payments;

2단계: Debezium 커넥터 배포

debezium-postgres-connector.json
json
{
  "name": "pg-orders-cdc",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgres",
    "database.port": "5432",
    "database.user": "debezium",
    "database.password": "secret",
    "database.dbname": "shop",
    "database.server.name": "shop-db",
    "schema.include.list": "public",
    "table.include.list": "public.orders,public.payments",
    "topic.prefix": "cdc",
    "plugin.name": "pgoutput",
    "publication.name": "dbz_publication",
    "slot.name": "debezium_slot",
    "snapshot.mode": "initial",
    "transforms": "unwrap",
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
    "transforms.unwrap.drop.tombstones": "false",
    "transforms.unwrap.delete.handling.mode": "rewrite"
  }
}

MySQL CDC 설정

debezium-mysql-connector.json
json
{
  "name": "mysql-orders-cdc",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "database.hostname": "mysql",
    "database.port": "3306",
    "database.user": "debezium",
    "database.password": "secret",
    "database.server.id": "1",
    "database.server.name": "shop-mysql",
    "database.include.list": "shop",
    "table.include.list": "shop.orders,shop.payments",
    "topic.prefix": "cdc",
    "include.schema.changes": "true",
    "snapshot.mode": "initial"
  }
}
Warning

초기 스냅샷(snapshot.mode=initial)은 기존 데이터를 전체 읽은 후 WAL 스트리밍으로 전환합니다. 대규모 테이블에서는 수 시간이 걸릴 수 있으므로, 테이블 크기와 서비스 영향을 미리 검토해야 합니다.


Flink CDC 3.6

Flink CDC는 Flink에 내장된 CDC 커넥터입니다. Debezium이 Kafka Connect를 통해 Kafka에 쓰는 방식이라면, Flink CDC는 Kafka 없이 직접 데이터베이스의 WAL을 읽어 Flink 작업에서 처리할 수 있습니다.

Flink CDC vs Debezium

항목Debezium + KafkaFlink CDC
중간 저장소Kafka 필요불필요 (직접 읽기)
아키텍처 복잡도높음낮음
이벤트 재사용여러 컨슈머가 Kafka에서 소비Flink 작업 전용
운영 대상Kafka + Connect + DebeziumFlink만
적합한 경우이벤트를 여러 시스템이 소비Flink 전용 파이프라인

Flink CDC 3.6의 주요 기능

Flink CDC 3.6은 여러 중요한 기능을 추가했습니다.

  • Oracle Source Connector: Oracle 데이터베이스 지원 추가
  • Hudi Sink Connector: Apache Hudi 레이크하우스로 직접 적재
  • Lenient 모드 스키마 진화: 소스 스키마 변경을 유연하게 처리
  • PostgreSQL 스키마 진화: PostgreSQL 스키마 변경의 자동 전파

Flink CDC SQL 예제

flink-cdc-sql.sql
sql
-- PostgreSQL CDC 소스 테이블
CREATE TABLE orders_source (
    id INT,
    user_id VARCHAR,
    amount DECIMAL(10, 2),
    status VARCHAR,
    created_at TIMESTAMP(3),
    PRIMARY KEY (id) NOT ENFORCED
) WITH (
    'connector' = 'postgres-cdc',
    'hostname' = 'postgres',
    'port' = '5432',
    'username' = 'debezium',
    'password' = 'secret',
    'database-name' = 'shop',
    'schema-name' = 'public',
    'table-name' = 'orders',
    'slot.name' = 'flink_cdc_slot',
    'decoding.plugin.name' = 'pgoutput'
);
 
-- Elasticsearch 싱크 테이블
CREATE TABLE orders_search (
    id INT,
    user_id VARCHAR,
    amount DECIMAL(10, 2),
    status VARCHAR,
    created_at TIMESTAMP(3),
    PRIMARY KEY (id) NOT ENFORCED
) WITH (
    'connector' = 'elasticsearch-7',
    'hosts' = 'http://elasticsearch:9200',
    'index' = 'orders'
);
 
-- CDC 데이터를 실시간으로 Elasticsearch에 동기화
INSERT INTO orders_search
SELECT * FROM orders_source;
Tip

Flink CDC는 Kafka 없이 직접 CDC를 처리하므로 아키텍처가 단순해집니다. 그러나 CDC 이벤트를 여러 시스템이 소비해야 하는 경우에는 Debezium + Kafka 조합이 더 적합합니다. 용도에 따라 선택하세요.


아웃박스 패턴

문제: 이중 쓰기

마이크로서비스에서 데이터베이스를 업데이트하고 이벤트를 발행해야 하는 상황은 흔합니다. 그러나 데이터베이스 커밋과 메시지 발행을 원자적으로 수행하는 것은 불가능합니다. 이를 **Dual Write Problem(이중 쓰기 문제)**이라고 합니다.

dual-write-problem
text
1. 데이터베이스에 주문 저장 ← 성공
2. Kafka에 이벤트 발행 ← 실패 (네트워크 오류)
→ DB에는 주문이 있지만 이벤트는 발행되지 않음 (불일치)

아웃박스 패턴의 해법

**Outbox Pattern(아웃박스 패턴)**은 이벤트를 Kafka에 직접 발행하는 대신, 동일 데이터베이스의 outbox 테이블에 이벤트를 기록합니다. 단일 트랜잭션으로 비즈니스 데이터와 이벤트를 함께 저장하므로 원자성이 보장됩니다.

outbox-table.sql
sql
-- 아웃박스 테이블 생성
CREATE TABLE outbox (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    aggregate_type VARCHAR(255) NOT NULL,
    aggregate_id VARCHAR(255) NOT NULL,
    event_type VARCHAR(255) NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);
outbox_write.py
python
# 단일 트랜잭션으로 주문과 이벤트를 함께 저장
def place_order(conn, order):
    with conn.begin():
        # 1. 주문 저장
        conn.execute(
            "INSERT INTO orders (id, user_id, amount, status) "
            "VALUES (%s, %s, %s, %s)",
            (order.id, order.user_id, order.amount, "pending")
        )
        # 2. 아웃박스에 이벤트 기록 (같은 트랜잭션)
        conn.execute(
            "INSERT INTO outbox (aggregate_type, aggregate_id, event_type, payload) "
            "VALUES (%s, %s, %s, %s)",
            ("Order", order.id, "OrderPlaced",
             json.dumps({"orderId": order.id, "amount": order.amount}))
        )
    # 트랜잭션 커밋 → 주문과 이벤트가 원자적으로 저장됨

이후 CDC(Debezium)가 outbox 테이블의 변경을 감지하여 Kafka로 스트리밍합니다.

Debezium은 아웃박스 패턴을 위한 전용 SMT(outbox.EventRouter)를 제공합니다.

outbox-connector.json
json
{
  "transforms": "outbox",
  "transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter",
  "transforms.outbox.table.fields.additional.placement": "event_type:header:eventType",
  "transforms.outbox.route.by.field": "aggregate_type",
  "transforms.outbox.route.topic.replacement": "events.${routedByValue}"
}

Eventual Consistency

CDC 기반 아키텍처에서는 **Eventual Consistency(이벤추얼 컨시스턴시, 최종 일관성)**를 이해하고 수용해야 합니다.

원본 데이터베이스에 변경이 기록된 후 CDC를 통해 하위 시스템에 전파되기까지 지연이 발생합니다. 이 지연 동안 시스템 간 데이터가 일시적으로 불일치합니다.

최종 일관성 다루기

  • 읽기 자체 쓰기 일관성(Read-your-writes): 사용자가 방금 수행한 변경을 즉시 확인해야 하는 경우, 원본 DB에서 직접 읽습니다.
  • 인과적 일관성(Causal consistency): 이벤트 간 인과 관계가 있으면 순서를 보장합니다. 파티션 키를 적절히 설계하여 같은 엔티티의 이벤트가 순서대로 처리되게 합니다.
  • 보상 트랜잭션(Compensating transaction): 불일치가 감지되면 보상 이벤트를 발행하여 상태를 조정합니다.
Info

최종 일관성은 단점이 아니라 트레이드오프입니다. 강한 일관성(Strong Consistency)을 위해 동기적으로 모든 시스템을 업데이트하면, 하나의 시스템 장애가 전체 작업을 실패시킵니다. 최종 일관성은 가용성과 확장성을 확보하면서도 궁극적으로는 모든 시스템이 일관된 상태에 도달하도록 보장합니다.


CDC 기반 분석 파이프라인

CDC를 활용한 전형적인 분석 파이프라인을 구성해 보겠습니다.

이 아키텍처의 장점은 다음과 같습니다.

  • 원본 DB 부하 최소화: WAL 읽기는 DB 성능에 거의 영향을 주지 않음
  • 실시간 동기화: 변경 즉시 캡처하여 분석 시스템에 반영
  • 다중 소비: 하나의 CDC 스트림을 검색, 분석, 레이크하우스 등 여러 목적으로 활용
  • 스키마 진화 대응: Flink CDC 3.6의 lenient 모드로 스키마 변경을 유연하게 처리

정리

이번 장에서는 CDC의 원리와 실전 적용을 학습했습니다.

  • CDC: 데이터베이스 변경을 실시간으로 캡처하는 기술로, WAL 기반이 표준입니다.
  • Debezium: Kafka Connect 기반의 CDC 플랫폼으로, 다양한 DB를 지원합니다.
  • Flink CDC 3.6: Kafka 없이 직접 CDC를 처리하며, 스키마 진화를 유연하게 지원합니다.
  • 아웃박스 패턴: 이중 쓰기 문제를 해결하는 패턴으로, CDC와 결합하여 안정적 이벤트 발행을 보장합니다.
  • 최종 일관성: CDC 기반 아키텍처의 기본 특성으로, 적절한 전략으로 관리해야 합니다.

다음 장 미리보기

9장에서는 스키마 레지스트리와 데이터 계약을 다룹니다. 프로듀서와 컨슈머 사이의 스키마 호환성을 관리하는 방법, Avro/Protobuf/JSON Schema의 비교, 호환성 규칙, 데이터 계약의 개념까지 실시간 파이프라인의 안정성을 높이는 핵심 기술을 학습합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#streaming#data-engineering

관련 글

데이터

9장: 스키마 레지스트리와 데이터 계약

스키마 진화의 필요성, Confluent Schema Registry, Avro/Protobuf/JSON Schema 비교, 호환성 규칙, 데이터 계약 개념까지 스키마 관리 전략을 체계적으로 학습합니다.

2026년 2월 26일·18분
데이터

7장: Spark Structured Streaming

Spark Structured Streaming의 마이크로배치와 연속 처리 모드, DataFrame API 기반 스트리밍, 윈도우와 워터마크, Kafka 소스/싱크 연동, Flink와의 비교를 학습합니다.

2026년 2월 22일·14분
데이터

10장: Exactly-once 보장과 신뢰성

At-least-once/At-most-once/Exactly-once 비교, Kafka 트랜잭션과 Flink 체크포인트의 조합, 멱등성 설계, DLQ, 백프레셔, 장애 복구 전략까지 프로덕션 신뢰성을 학습합니다.

2026년 2월 28일·19분
이전 글7장: Spark Structured Streaming
다음 글9장: 스키마 레지스트리와 데이터 계약

댓글

목차

약 18분 남음
  • 학습 목표
  • CDC란 무엇인가
    • 왜 CDC인가
    • CDC 구현 방식
  • WAL 기반 CDC의 원리
    • Write-Ahead Log란
    • CDC 이벤트 구조
  • Debezium
    • 지원 데이터베이스
    • PostgreSQL CDC 설정
    • MySQL CDC 설정
  • Flink CDC 3.6
    • Flink CDC vs Debezium
    • Flink CDC 3.6의 주요 기능
    • Flink CDC SQL 예제
  • 아웃박스 패턴
    • 문제: 이중 쓰기
    • 아웃박스 패턴의 해법
  • Eventual Consistency
    • 최종 일관성 다루기
  • CDC 기반 분석 파이프라인
  • 정리
  • 다음 장 미리보기