시스템 간 비동기 연동, 이 3가지만 통달하면 끝이다
메시징 / 데이터베이스(Outbox·Inbox 포함) / CDC
시스템 간 비동기 연동 정리
메시지 큐 / 아웃박스·인박스 / CDC(Change Data Capture)
1. 개요
비동기 연동은 서비스 간 결합도를 낮추고, 사용자 요청에 대한 응답 시간을 단축하며, 피크 트래픽을 완충하기 위한 핵심 설계 방식이다. 실무에서는 보통 다음 세 가지 방식을 조합하여 사용한다.
- 메시지 큐: 이벤트 발행–구독 기반 처리
- 아웃박스·인박스(데이터베이스 활용): 데이터베이스 변경과 이벤트 기록의 원자적 커밋
- CDC: 데이터베이스 변경 로그를 기반으로 한 스트리밍 전파
2. 방식별 정리
2.1 메시지 큐
개념 프로듀서 서비스가 토픽(또는 큐)에 메시지를 발행하고, 컨슈머 서비스가 구독하여 비동기 처리한다. 파티션과 컨슈머 그룹으로 수평 확장이 용이하다.
장점
- 다수의 이기종 소비자(알림, 적재, 인덱싱 등)를 유연하게 추가 가능
- 리플레이(과거 시점부터 재처리) 및 탄력적 확장 용이
고려사항
- 전달 보장: 현실적으로 대부분 at-least-once이므로 중복 수신에 대비한 멱등 처리가 필요하다.
- 순서 보장: 파티션 키 범위에서만 순서가 보장되므로, 사용자 ID·주문 ID 등 “순서를 함께 보장해야 하는 단위”로 키를 설계한다.
- 장애 처리: 지수 백오프 기반 재시도와 데드 레터 큐(Dead Letter Queue, DLQ) 격리 정책을 운영한다.
- 원자성(dual-write): 애플리케이션 DB 쓰기와 메시지 발행을 한 번에 보장하기 어렵다. 본 문제는 2.2의 아웃박스 패턴으로 해결한다.
적합한 상황
- 여러 팀/서비스가 동일 이벤트를 각자 처리해야 하는 경우
- 고처리량과 확장성이 중요한 경우, 리플레이가 필요한 경우
2.2 아웃박스·인박스(데이터베이스 활용)
개념 비즈니스 데이터 쓰기와 아웃박스 테이블에 이벤트 기록을 동일 트랜잭션으로 커밋한다. 별도의 워커가 아웃박스를 읽어 메시지 브로커로 발행한다. 소비자 측은 인박스 테이블에 처리 이력을 기록하여 중복 수신에도 한 번만 효과를 적용한다.
장점
- 원자성 보장: 데이터 변경과 이벤트 기록을 하나의 커밋으로 처리하여 dual-write 문제를 제거한다.
- 메시지 큐의 장점(다중 구독, 리플레이)을 이후 단계에서 그대로 활용 가능
고려사항
- 아웃박스 스키마(예:
event_type, aggregate_id, idempotency_key, payload(JSON), created_at, processed_at, attempts)와 보관·정리 정책(TTL, 아카이브, 파티셔닝)을 정의한다. - 릴레이어(아웃박스→브로커)는 재시도가 가능해야 하며, 소비자 측 인박스/멱등 처리와 함께 “정확히 한 번처럼 보이는” 동작을 달성한다.
- 트래픽 증가 시 데이터베이스 부하를 고려하여 브로커 도입과 워커 수평 확장을 병행한다.
간단 예시(의미 전달용)
1
2
3
4
5
6
7
8
9
10
11
12
13
from django.db import transaction
from myapp.models import Order, Outbox
def create_order(user_id):
with transaction.atomic():
order = Order.objects.create(user_id=user_id, status='CREATED')
Outbox.objects.create(
event_type='order.created',
aggregate_id=str(order.id),
idempotency_key=f'order:{order.id}',
payload={'order_id': order.id},
)
return order.id
적합한 상황
- 데이터 정합성과 원자성이 최우선인 업무
- 메시지 큐 도입 전 단계에서 시작하여, 규모 확장 시 브로커와 결합하려는 경우
2.3 CDC(Change Data Capture)
개념 데이터베이스의 변경 로그(binlog, WAL 등)를 읽어 테이블 변경을 스트리밍으로 전파한다. 애플리케이션에 별도의 “발행” 코드를 작성하지 않아도 된다.
장점
- 애플리케이션 코드 단순화(커밋이 곧 이벤트)
- 데이터베이스 트랜잭션 경계와 순서가 자연스럽게 반영
고려사항
- 도메인 맥락 부족: “왜 바뀌었는지”가 이벤트에 포함되지 않으므로 비즈니스 로직 구동에는 제한이 있다.
- 스키마 변화 관리: 컬럼 추가·변경 시 커넥터 및 다운스트림 호환성 관리가 필요하다.
- 초기 스냅샷/재빌드 비용, 삭제 처리(tombstone), upsert 정책을 명확히 한다.
- 개인정보는 필드 수준 마스킹·필터링을 기본값으로 적용한다.
적합한 상황
- 데이터 웨어하우스 적재, 검색 인덱스 동기화, 캐시 일괄 갱신 등 데이터 파이프라인 중심의 요구
3. 선택 가이드
- 여러 서비스가 같은 이벤트를 소비하고 확장성·리플레이가 중요하다면 메시지 큐를 중심으로 설계하고, 원자성은 아웃박스로 보장한다.
- 원자성이 가장 중요하고 작게 시작하려면 데이터베이스 폴링/아웃박스로 출발하여, 규모가 커지면 브로커를 결합한다.
- 데이터베이스 변경을 데이터 파이프라인·검색·캐시에 그대로 반영하려면 CDC를 채택한다.
- 이벤트의 도메인 의미(변경 이유)가 중요하다면 명시적 이벤트 + 아웃박스를 우선하고, CDC는 보조로 활용한다.
4. 안전장치와 관찰성
필수 안전장치
- 멱등성 키: 주문 ID 등으로 중복 수신 시에도 단 한 번만 효과 적용
- 데드 레터 큐(DLQ) + 지수 백오프: 반복 실패 메시지 격리 및 점진적 재시도
- 인박스 패턴: 소비자 측 처리 이력 저장으로 “정확히 한 번처럼 보이게” 처리
관찰 지표
- 메시지 소비 지연(consumer lag), 재시도·실패율, DLQ 적재량
- 아웃박스 적체량, 릴레이어 처리 속도
- 스키마 버전 호환성 및 이벤트 스키마 변화 이력
5. 참조 아키텍처(개략도)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
1. 사용자 요청(주문)
└─ 2. API 서버
├─ 3. 트랜잭션 시작 → DB 쓰기
│ ├─ 3.1 비즈니스 데이터 저장
│ └─ 3.2 Outbox 테이블에 이벤트 기록(원자적 커밋)
│
├─ [4. CDC 커넥터(선택)]
│ ├─ 4.1 DB 변경 로그(binlog/WAL) 읽기
│ └─ 4.2 데이터 파이프라인 적용(DW/검색/캐시)
│
├─ 5. 릴레이어(Outbox → 브로커 발행)
│ ├─ 5.1 Outbox 미처리 레코드 조회/잠금
│ └─ 5.2 메시지 큐로 발행(헤더에 idempotency_key 포함)
│
└─ 6. 메시지 큐(토픽/파티션)
├─ 6.1 파티션 키로 순서 범위 고정(예: 사용자ID/주문ID)
└─ 6.2 컨슈머 그룹으로 전달
├─ 7. 메일 서비스
│ ├─ 7.1 Inbox 테이블에 처리 기록(멱등성 보장)
│ ├─ 7.2 메일 발송 로직 실행
│ ├─ 7.3 성공 시 커밋
│ └─ 7.4 실패 시 재시도(지수 백오프) → 7.5 초과 시 DLQ 격리
│
├─ 8. 포인트 서비스
│ ├─ 8.1 Inbox 기록(멱등성)
│ ├─ 8.2 적립/차감 처리
│ ├─ 8.3 성공 커밋
│ └─ 8.4 실패 재시도 → 8.5 DLQ
│
└─ 9. 통계/분석 서비스
├─ 9.1 Inbox 기록(멱등성)
├─ 9.2 집계/적재 처리
├─ 9.3 성공 커밋
└─ 9.4 실패 재시도 → 9.5 DLQ
[운영/관찰성]
└─ A. 지표 모니터링: 소비 지연(consumer lag), 재시도/실패율, DLQ 적재량, Outbox 적체량
└─ B. 경보: 임계치 초과 시 알림
└─ C. 정리 정책: Outbox/Inbox TTL·아카이브, 토픽 보관기간
6. 실무 체크리스트
- 이벤트마다 멱등성 키를 포함한다.
- 아웃박스를 도입하고 비즈니스 쓰기와 동일 트랜잭션으로 커밋한다.
- 소비자에 인박스를 적용하여 중복 수신에도 단 한 번만 효과를 적용한다.
- 재시도·DLQ 정책과 알림을 설정한다.
- 이벤트 스키마 버전과 호환성 정책을 문서화하고 검증 절차(계약 테스트 등)를 운영한다.
7. 결론
비동기 연동은 메시지 큐, 아웃박스·인박스, CDC라는 세 가지 축을 상황에 맞게 조합하는 일이다. 핵심은 원자성, 멱등성, 관찰성을 일관되게 확보하는 것이다. 위 원칙을 바탕으로 선택과 조합을 설계하면, 응답성·확장성·안정성을 모두 충족하는 백엔드 아키텍처를 구현할 수 있다.
더 알아보기
👉 [최범균님 유튜브] 초식 - 시스템 간 비동기 연동 4종 세트
👉 [NHN FORWARD 22] 분산 시스템에서 데이터를 전달하는 효율적인 방법