변경 데이터 캡처(Change Data Capture)로 이벤트 기반 아키텍처를 구동하는 실시간 데이터 스트림
Min-jun Kim
Dev Intern · Leapcell

소개
오늘날 빠르게 변화하는 디지털 환경에서는 애플리케이션이 분산 시스템 전반에서 응답성과 데이터 일관성을 점점 더 많이 요구하고 있습니다. 기존의 배치 처리 또는 폴링 메커니즘은 종종 지연 시간을 도입하고 데이터베이스에 불필요한 부하를 주면서 기능이 부족합니다. 이때 이벤트 기반 아키텍처의 개념이 빛을 발하며, 애플리케이션이 기본 데이터 저장소에서 발생하는 변경 사항에 실시간으로 반응할 수 있도록 합니다. 하지만 문제는 데이터베이스에서 이러한 변경 사항을 효율적이고 안정적으로 캡처하는 것입니다. 이 글에서는 변경 데이터 캡처(Change Data Capture, CDC), 특히 Debezium과 **논리적 디코딩(logical decoding)**과 같은 도구를 활용하여 이 문제를 어떻게 우아하고 강력하게 해결하는지, 진정으로 이벤트 기반 아키텍처의 실시간 기능을 어떻게 지원하는지 살펴봅니다. 이 패턴을 이해하는 것은 즉각적인 비즈니스 이벤트에 즉시 반응할 수 있는 확장 가능하고 탄력적이며 고응답성인 최신 애플리케이션을 구축하는 데 매우 중요합니다.
변경 데이터 캡처 및 관련 드라이버 이해하기
메커니즘을 자세히 알아보기 전에 관련된 핵심 개념을 명확하게 이해해 봅시다.
핵심 용어
- 변경 데이터 캡처(Change Data Capture, CDC): 데이터베이스 내에서 변경된 데이터를 식별하고 추적하는 데 사용되는 소프트웨어 디자인 패턴 세트입니다. 전체 테이블을 반복적으로 쿼리하는 대신 CDC는 수정(삽입, 업데이트, 삭제)만 캡처하는 데 중점을 둡니다.
- 논리적 디코딩(Logical Decoding): PostgreSQL의 특정 기능으로, 외부 시스템이 데이터베이스의 쓰기 전 로그(WAL)에서 디코딩된 사람이 읽을 수 있는 변경 스트림을 스트리밍할 수 있도록 합니다. 본질적으로 저수준 WAL 항목을 논리적 작업(예: "INSERT INTO users VALUES (1, 'Alice')", "UPDATE products SET price = 20 WHERE id = 10")으로 변환합니다. MySQL의 바이너리 로그(binlog) 또는 SQL Server의 변경 추적/변경 데이터 캡처와 같은 다른 데이터베이스에도 유사한 메커니즘이 있습니다.
- Debezium: CDC를 위한 오픈 소스 분산 플랫폼입니다. 특정 데이터베이스 관리 시스템(PostgreSQL, MySQL, MongoDB, SQL Server, Oracle 등)의 행 수준 변경을 모니터링하는 커넥터 세트를 제공합니다. Debezium은 이러한 변경 사항을 이벤트로 메시지 브로커(일반적으로 Apache Kafka)로 스트리밍하여 다른 애플리케이션에서 소비할 수 있도록 합니다.
- 이벤트 기반 아키텍처(Event-Driven Architecture, EDA): 이벤트의 생성, 감지, 소비 및 반응을 중심으로 하는 소프트웨어 아키텍처 패러다임입니다. 이벤트는 시스템 내의 중요한 발생(예: "UserRegistered," "OrderPlaced," "ProductPriceUpdated")을 나타냅니다.
- 쓰기 전 로그(Write-Ahead Log, WAL) / 트랜잭션 로그 / 재실행 로그(Redo Log): 관계형 데이터베이스의 필수 구성 요소로, 영구적으로 디스크에 쓰기 전에 데이터베이스에 대한 모든 변경 사항을 기록합니다. 주로 충돌 복구 및 복제를 위해 사용됩니다. CDC 메커니즘은 종종 이러한 로그를 기반으로 합니다.
논리적 디코딩을 사용한 CDC 원리
CDC를 위해 Debezium과 논리적 디코딩을 사용하는 기본 원리는 데이터베이스 자체의 트랜잭션 로그를 활용하는 것입니다. 이벤트를 내보내기 위해 애플리케이션 코드를 수정하거나 트리거(오버헤드와 복잡성 증가 가능)를 사용하는 대신, Debezium은 고도로 최적화되고 안정적인 트랜잭션 로그에 "접근"합니다.
프로세스에 대한 자세한 내용은 다음과 같습니다.
- 데이터베이스 변경: 데이터베이스(예: PostgreSQL)에서 수행되는 모든
INSERT
,UPDATE
,DELETE
작업은 먼저 쓰기 전 로그(WAL)에 기록됩니다. - 논리적 디코딩 플러그인: PostgreSQL은 논리적 디코딩 기능으로, 구성된 플러그인(
pgoutput
또는wal2json
등)이 이진 WAL 항목을 구조화된 논리적 형식으로 해석하고 디코딩할 수 있는 메커니즘을 제공합니다. - Debezium 커넥터: Debezium 커넥터(예:
PostgreSQLConnector
)는 데이터베이스에 연결하여 논리적 디코딩 스트림의 클라이언트 역할을 합니다. 디코딩된 변경 사항을 지속적으로 읽어옵니다. - 이벤트 변환: Debezium은 이러한 원시 데이터베이스 변경 이벤트를 표준화된 이벤트 형식(종종 JSON 또는 Avro,
before
및after
상태, 작업 유형, 타임스탬프 등을 포함하는 구조화된 체계를 따름)으로 변환합니다. - 이벤트 스트리밍: Debezium은 이러한 구조화된 변경 이벤트를 메시지 브로커, 가장 일반적인 것은 Apache Kafka로 게시합니다. 각 테이블 변경은 특정 Kafka 토픽(예:
dbserver.public.users
)에 해당할 수 있습니다. - 이벤트 소비: 다운스트림 마이크로서비스 또는 애플리케이션은 관련 Kafka 토픽을 구독합니다. 새 이벤트가 도착하면 소스 데이터베이스를 직접 쿼리하지 않고 실시간으로 처리하며 데이터 변경에 반응할 수 있습니다.
Debezium 및 Kafka를 사용한 구현 예시
PostgreSQL 데이터베이스, Kafka 브로커(Zookeeper 포함), Debezium 커넥터를 시작하기 위한 Docker Compose를 사용한 간단한 예시를 통해 이를 설명해 보겠습니다.
1. docker-compose.yml
:
version: '3.8' services: zookeeper: image: confluentinc/cp-zookeeper:7.4.0 container_name: zookeeper ports: - "2181:2181" environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 kafka: image: confluentinc/cp-kafka:7.4.0 container_name: kafka ports: - "9092:9092" - "9093:9093" depends_on: - zookeeper environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:9093 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 postgres: image: debezium/postgres:16 container_name: postgres ports: - "5432:5432" environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: mydatabase healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 # Configure WAL level for logical decoding command: ["-c", "wal_level=logical", "-c", "max_wal_senders=10", "-c", "max_replication_slots=10"] connect: image: debezium/connect:2.4 container_name: connect ports: - "8083:8083" depends_on: - kafka - postgres environment: BOOTSTRAP_SERVERS: kafka:9092 GROUP_ID: 1 CONFIG_STORAGE_TOPIC: connect_configs OFFSET_STORAGE_TOPIC: connect_offsets STATUS_STORAGE_TOPIC: connect_status
2. 스택 배포:
docker-compose up -d
3. Debezium PostgreSQL 커넥터 구성:
모든 서비스가 시작되면(1~2분 정도 기다립니다) Debezium PostgreSQL 커넥터를 배포합니다. connector-config.json
파일을 만듭니다.
{ "name": "postgres-cdc-connector", "config": { "connector.class": "io.debezium.connector.postgresql.PostgresConnector", "tasks.max": "1", "database.hostname": "postgres", "database.port": "5432", "database.user": "postgres", "database.password": "postgres", "database.dbname": "mydatabase", "database.server.name": "dbserver", "plugin.name": "pgoutput", "table.include.list": "public.users", "topic.prefix": "dbserver_cdc", "heartbeat.interval.ms": "5000", "schema.history.internal.kafka.bootstrap.servers": "kafka:9092", "schema.history.internal.kafka.topic": "schema-changes-dbserver" } }
이제 이 커넥터 구성을 Debezium Connect에 제출합니다.
curl -X POST -H "Content-Type: application/json" --data @connector-config.json http://localhost:8083/connectors
커넥터가 생성되었음을 나타내는 응답을 봐야 합니다.
4. 데이터베이스와 상호 작용:
PostgreSQL 데이터베이스에 연결합니다.
docker exec -it postgres psql -U postgres -d mydatabase
테이블을 생성하고 데이터를 삽입/업데이트합니다.
CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE ); INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); UPDATE users SET email = 'alice.smith@example.com' WHERE name = 'Alice'; INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com'); DELETE FROM users WHERE name = 'Bob';
psql을 종료합니다.
5. Kafka 이벤트 관찰:
이제 Kafka 콘솔 소비자를 사용하여 CDC 이벤트를 볼 수 있습니다. 토픽은 topic.prefix
및 table.include.list
에 따라 dbserver_cdc.public.users
가 됩니다.
docker exec -it kafka kafka-console-consumer --bootstrap-server kafka:9092 --topic dbserver_cdc.public.users --from-beginning --property print.key=true
그러면 행의 before
및 after
상태를 포함한 삽입, 업데이트 및 삭제를 나타내는 JSON 메시지가 표시됩니다. 이제 이 실시간 변경 스트림을 다운스트림 서비스에서 소비할 준비가 되었습니다.
이벤트 기반 아키텍처에서의 응용
이 CDC 스트림이 설정되면 이벤트 기반 아키텍처의 가능성은 매우 광범위합니다.
- 실시간 분석: 데이터 웨어하우스 또는 데이터 레이크를 실시간으로 채웁니다.
- 캐시 무효화: 원본 데이터가 변경될 때 즉시 캐시를 무효화하거나 업데이트합니다.
- 검색 인덱싱: 검색 인덱스(예: Elasticsearch)를 기본 데이터베이스와 동기화 상태로 유지합니다.
- 마이크로서비스 통합: 독립적인 마이크로서비스가 다른 서비스의 데이터 변경에 직접적인 데이터베이스 연결 없이 반응할 수 있도록 합니다. 예를 들어, "주문 처리" 서비스는 "주문 관리" 서비스의
orders
테이블 삽입에서 파생된OrderPlaced
이벤트에 반응할 수 있습니다. - 감사 및 규정 준수: 모든 데이터베이스 변경에 대한 불변 감사 추적을 구축합니다.
- 데이터 동기화: 다른 데이터베이스 유형 또는 클라우드 공급자 간의 데이터를 동기화합니다.
결론
효과적인 데이터베이스 변경 캡처는 최신, 반응형 및 확장 가능한 이벤트 기반 아키텍처를 구축하는 데 필수적인 요소입니다. Debezium과 같은 강력한 도구와 PostgreSQL 논리적 디코딩과 같은 데이터베이스의 내장 기능을 활용함으로써 조직은 데이터 파이프라인을 배치 지향에서 실시간 스트림으로 전환할 수 있습니다. 이 접근 방식은 지연 시간을 최소화하고 데이터베이스 부하를 줄이며 서비스를 분리하여 애플리케이션이 데이터 수정에 즉시 반응할 수 있도록 하여 새로운 수준의 응답성과 데이터 일관성을 확보합니다. 궁극적으로 Debezium과 논리적 디코딩은 수동적인 데이터베이스 변경을 능동적이고 실행 가능한 비즈니스 이벤트로 전환하는 중요한 다리 역할을 하여 진정한 이벤트 기반 시스템을 구동합니다.