소프트웨어 아키텍처

이벤트 스트리밍과 CDC

meellon 2026. 7. 6. 05:20

학습 목표

이벤트 스트리밍 로그(Kafka)의 partition·offset·consumer group을 설명할 수 있다.

Transactional outbox로 DB commit과 이벤트 publish 원자성을 확보할 수 있다.

CDC(Change Data Capture)WAL/binlog에서 변경을 읽어 다운스트림에 전파함을 설명할 수 있다.

Eventual consistency지연·멱등·순서SLO와 함께 설계할 수 있다.

문제 상황

  • 주문 저장 성공 후 Kafka publish 타임아웃 — DB에는 있는데 재고는 미반영
  • 반대로 publish 성공 후 DB rollback유령 이벤트만 흐름
  • 캐시 invalidation을 앱 코드에만 의존 — 배포 누락 시 영구 stale
  • 검색 인덱스(Elasticsearch)가 배치 15분마다 sync — 신상품 노출 지연
  • consumer 재시작 후 offset earliest중복 결제 알림 3천 건
  • Debezium 스키마 변경 후 connector 중단silent drift

캐싱까지 봤다. DB가 진실인 상태에서 다른 저장소·서비스언제·어떻게 맞출지가 데이터 아키텍처의 마지막 축이다.

1. 스트리밍 vs 큐 (실무 관점)

EDA에서 pub/sub스트리밍 로그를 구분했다. 이번 편은 운영에 필요한 Kafka 개념에 집중한다.

  Message queue Event stream (log)
저장 consume 후 삭제·TTL append-only, retention
재생 어렵다 offset으로 replay
다중 소비 fan-out·competing consumer consumer group독립 offset
대표 RabbitMQ, SQS Kafka, Pulsar
  • Task queue — 작업 한 번 처리 (이메일 1통)
  • Event log사실 기록 + 여러 projection (검색·분석·캐시)

2. Kafka 핵심 구조

개념 역할
Topic 이벤트 카테고리 (OrderEvents, db.catalog.products)
Partition 순서·병렬 단위 — key hash → 같은 key는 같은 partition
Offset partition 내 위치 — consumer가 어디까지 읽었는지
Consumer group group당 partition 1 readerscale-out
Retention 시간·크기 한도까지 보관 — replay·신규 consumer catch-up
# partition key — 같은 orderId는 순서 보장
key = order_id
value = OrderPlaced { ... }
전달 보장 의미 실무
At-most-once 유실 가능 로그·메트릭만
At-least-once 중복 가능 기본 — 멱등 필수
Exactly-once EOS transaction 비용·범위 제한, Kafka Streams 등
  • Broker 장애 — replication factor·min.insync.replicas 설계
  • Poison messageDLQ·skip·수동 격리 (16편 복원력과 연계)

3. Dual write 문제

Dual write — DB write와 message publish를 별도 호출.

1. INSERT order     OK
2. publish event    FAIL  → DB만 반영, downstream 누락

또는

1. publish event    OK
2. INSERT order     FAIL  → 이벤트만 존재
증상 원인
재고·알림 누락 publish 실패
유령 이벤트 DB rollback 후 publish 성공
순서 뒤바뀜 비동기 race
  • 2PC/XA — 운영·가용성 비용 큼, MSA에서 드묾
  • Outbox·CDC진실(DB) 에서 한 번 기록하고 전파

4. Transactional outbox

Transactional outbox같은 DB 트랜잭션에 비즈니스 row + outbox row를 쓴다. 별도 relay가 Kafka로 publish.

BEGIN;
  INSERT INTO orders (...);
  INSERT INTO outbox (aggregate_id, event_type, payload, status)
    VALUES ('9001', 'OrderPlaced', '{...}', 'PENDING');
COMMIT;

-- relay (polling or log-tailing)
SELECT * FROM outbox WHERE status = 'PENDING' FOR UPDATE SKIP LOCKED;
→ publish to Kafka → UPDATE status = 'PUBLISHED';
장점 비용
DB·이벤트 원자성 outbox 테이블·relay 운영
앱이 도메인 이벤트 제어 relay 지연 (poll interval)
Kafka 장애 시 재시도 안전 중복 publish 가능 → consumer 멱등
  • Relay — 폴링(Debezium outbox connector, 자체 worker) 또는 CDC on outbox table
  • Idempotent producer + consumer dedup — at-least-once 전제
  • 상세 DDL은 code/outbox-schema.sql 참고

5. CDC (Change Data Capture)

CDC — DB 변경 로그(PostgreSQL WAL, MySQL binlog)를 읽어 이벤트로 보낸다.

방식 설명
Log-based WAL/binlog tail — Debezium, AWS DMS
Trigger-based DB trigger → change table — 부하·운영 부담
Timestamp poll updated_at poll — 누락·순서 약함
PostgreSQL UPDATE products SET price = 9900 WHERE id = 42
  → WAL record
  → Debezium connector
  → Kafka topic db.catalog.products
  → Search indexer / Cache invalidator / Warehouse
CDC 장점 CDC 비용
앱 코드 변경 최소 스키마·DDL 이벤트 처리
모든 subscriber가 동일 변경 스트림 지연·스냅샷 초기화
레거시 DB 그대로 연동 PII·삭제 정책 설계
  • Payload — before/after row (Debezium envelope), code/cdc-product-event.json 참고
  • 캐시 invalidation — 18편 delete-on-write 대신 CDC consumerDEL (앱 누락 방지)
  • 검색·read model — CQRS projection 입력으로 자연스러움

6. Outbox vs CDC

  Outbox CDC
이벤트 형태 도메인 (OrderPlaced) row change (UPDATE products)
앱 결합 outbox insert 명시 connector 외부
스키마 팀이 설계 DB 그대로 노출
적합 비즈니스 이벤트·saga 동기화·검색·warehouse·cache
조합 outbox → Kafka + CDC on read replica 흔히 병행
  • 같은 Kafka 위에 domain topic + CDC topic 공존
  • CDC 이벤트를 도메인으로 변환하는 anti-corruption layer 검토

7. Eventual consistency 실무

14편 eventual운영한다.

항목 설계
Lag SLO 검색 ≤30s, 캐시 ≤5s — 도메인별 문서화
멱등 eventId·(aggregate_id, version) unique
순서 partition key = order_id
재생 consumer idempotentearliest reset 대비
관측 consumer lag, outbox PENDING count, connector state
# consumer dedup table (conceptual)
INSERT INTO processed_events (event_id) VALUES ('evt-8f2a')
ON CONFLICT DO NOTHING;
-- then apply side effect
실패 패턴 대응
Lag 폭증 consumer scale·hot partition·batch size
Outbox 적체 relay 장애·DB lock — alert on PENDING age
Schema drift registry·backward compatible evolution
Replay consumer group + offset — side effect 멱등 확인
  • 사용자 UX — "반영까지 최대 N초" 명시 vs 강한 일관 필요 시 동기 read your writes
  • Saga 보상 — 15편과 연계, 역순 compensating event

8. 실무 체크리스트

질문 좋은 답
진실은? DB only — stream은 파생
publish 안전? outbox 또는 CDC — dual write 금지
중복? at-least-once + 멱등 consumer
순서? partition key 문서화
지연 허용? SLO·모니터링 대시보드
PII? CDC payload 마스킹·topic ACL
조합 효과
outbox + Kafka + idempotent consumer 도메인 이벤트 안전 전파
CDC + Elasticsearch 검색 near-real-time
CDC + cache invalidator 누락 없는 무효화
domain events + CDC 읽기 모델·분석 분리

9. 정리

  • Kafka log — retention·offset·consumer group으로 replay·확장
  • Dual write 피하기 — outbox(도메인) 또는 CDC(row change)
  • Outbox — TX에 outbox row, relay가 at-least-once publish
  • CDC — WAL에서 변경 캡처, 검색·캐시·warehouse 동기화
  • Eventual consistency — lag SLO·멱등·순서·관측으로 운영
  • Part 5 데이터 아키텍처 마무리 — 다음은 아키텍처 문서화

다음에 다룰 것

  • 아키텍처 문서화 (C4 Model)
  • 다이어그램 레벨, API 계약

해당 내용은 Fundamentals of Software Architecture (Mark Richards, Neal Ford) 및 Designing Data-Intensive Applications (Martin Kleppmann) 의 내용을 기반으로 합니다.

'소프트웨어 아키텍처' 카테고리의 다른 글

ADR과 의사결정 기록  (0) 2026.07.08
아키텍처 문서화  (0) 2026.07.07
캐싱 전략  (0) 2026.07.05
데이터 저장소 선택  (0) 2026.07.04
내고장성과 복원력  (0) 2026.07.03