데이터 집약 시스템 패턴
Saga·Outbox·CQRS·Event Sourcing으로 분산 트랜잭션의 일관성·재처리·읽기 분리를 다루는 패턴 조합 가이드
한 문단 요약 — 분산 트랜잭션이 깨지는 이유 5가지
결론: 분산 환경에서 트랜잭션은 원자성 보장 비용이 기하급수적으로 커지기 때문에 구조적으로 깨진다.
첫째, 네트워크 호출은 실패·지연·중복을 기본 전제로 한다.
둘째, 서비스별 데이터 소유권 분리로 단일 커밋 지점이 사라진다.
셋째, 재시도는 중복 실행을 낳고 정확히 한 번(exactly-once)은 환상에 가깝다.
넷째, 읽기 트래픽이 쓰기보다 훨씬 커지며 읽기 최적화 요구가 분리된다.
다섯째, 운영·배포 주기 단축으로 데이터 스키마와 흐름이 계속 변한다.
공통 사례 — 주문 생성 흐름
결론: 모든 패턴은 같은 주문 흐름을 어디까지 분리하고 언제 맞출지의 선택이다.
주문 생성
→ 결제 승인
→ 재고 차감
→ 배송 준비
이 단일 사례를 끝까지 유지한다.
Saga 패턴
결론: Saga는 트랜잭션을 유지하려는 시도가 아니라 일관성을 단계적으로 회복하는 전략이다.
정의
여러 로컬 트랜잭션을 이벤트/명령으로 연결해 전체 비즈니스 흐름을 완성하는 패턴.
직관
하나의 큰 거래가 아니라 연속된 상태 전환의 묶음이다.
작동 원리
- 각 서비스는 자기 DB에만 커밋
- 다음 단계는 이벤트/명령으로 촉발
- 실패 시 이전 상태로 보상 트랜잭션 실행
Choreography Saga vs Orchestration Saga
결론: 선택 기준은 흐름 가시성 vs 결합도 허용치다.
| 구분 | Choreography | Orchestration |
|---|---|---|
| 제어 방식 | 이벤트 반응 | 중앙 제어 |
| 결합도 | 낮음 | 높음 |
| 흐름 가시성 | 낮음 | 높음 |
| 변경 비용 | 분산 | 집중 |
| 운영 난이도 | 높음 | 중 |
보상 트랜잭션의 본질
결론: 보상은 “되돌리기”가 아니라 새로운 상태로의 전환이다.
결제 취소는 과거를 삭제하지 않는다. 취소됨이라는 새 상태를 추가한다.
트레이드오프
- 장점: 강한 결합 없이 일관성 유지
- 비용: 구현·운영 복잡도 상승
Transactional Outbox
결론: Outbox는 exactly-once를 보장하는 장치가 아니라 중복을 관리 가능한 문제로 바꾸는 구조다.
정의
비즈니스 데이터 변경과 이벤트 기록을 같은 로컬 트랜잭션으로 저장하는 패턴.
왜 필요한가
- DB 커밋 성공 후 메시지 발행 실패
- 메시지 발행 성공 후 DB 롤백
→ 둘 다 데이터/이벤트 불일치를 만든다.
작동 원리
- 주문 테이블 + Outbox 테이블 동시 커밋
- 별도 프로세스가 Outbox를 읽어 브로커로 발행
- 중복 발행은 컨슈머 멱등성으로 처리
CDC/메시지 브로커와의 관계
결론: CDC는 Outbox를 읽는 구현 방식 중 하나일 뿐, 패턴의 본질은 아니다.
트레이드오프
- 장점: 데이터-이벤트 정합성 확보
- 비용: 지연 증가, 운영 컴포넌트 추가
CQRS + Materialized View
결론: CQRS는 성능 최적화가 아니라 읽기/쓰기 책임 분리 선언이다.
정의
쓰기 모델과 읽기 모델을 분리해 각각 최적화하는 패턴.
직관
주문은 한 번 생성되지만, 조회는 수천 번 발생한다.
작동 원리
- 쓰기: 정규화·검증 중심
- 읽기: 조회 전용 모델(Materialized View)
- 동기화: 이벤트 기반 갱신
의사결정 조건
- 읽기 트래픽이 압도적으로 큰가?
- 최신 데이터가 반드시 필요한가?
- 지연 허용치(초/분 단위)가 명확한가?
UX 설계 포인트
결론: 최종 일관성은 UX 문제다.
- “처리 중” 상태 명시
- 재시도/새로고침 유도
- 강한 일관성이 필요한 화면 분리
트레이드오프
- 장점: 읽기 성능 극대화
- 비용: 데이터 중복, 동기화 복잡도
Event Sourcing
결론: Event Sourcing은 저장 방식을 바꾸는 것이 아니라 시스템의 시간 개념을 바꾸는 선택이다.
정의
현재 상태가 아니라 상태 변경 이벤트의 로그를 저장하는 패턴.
장점
- 완전한 감사 로그
- 상태 재생/시간 여행
- 복구·분석 용이
비용
- 스키마 진화 난이도
- 리플레이 비용
- 쿼리 복잡도 증가
하지 말아야 하는 경우
- 단순 CRUD 시스템
- 이벤트 의미가 자주 변하는 도메인
- 팀의 운영 역량이 부족한 경우
패턴 간 조합 지도
결론: 이 패턴들은 단독보다 조합으로 쓰일 때 의미가 커진다.
Saga + Outbox
- Saga의 이벤트 신뢰성을 Outbox가 보완
- 사실상 실무 표준 조합
CQRS + Materialized View
- CQRS를 선택했다면 MV는 거의 필수
- 읽기 성능 목표 없으면 CQRS 도입 이유 없음
Event Sourcing ↔ CQRS
- Event Sourcing은 쓰기 모델
- CQRS는 읽기 모델과 자연스럽게 결합
패턴별 비교표
| 패턴 | 적용 난이도 | 운영 난이도 | 장애 복원력 | 데이터 모델 복잡도 |
|---|---|---|---|---|
| Saga | 중 | 중 | 중 | 중 |
| Outbox | 중 | 중 | 높음 | 낮음 |
| CQRS | 중 | 중 | 중 | 높음 |
| MV | 낮음 | 중 | 중 | 중 |
| Event Sourcing | 높음 | 높음 | 높음 | 매우 높음 |
실패 시나리오와 대응
결론: 실패는 피할 수 없고, 대응 설계만 가능하다.
- 타임아웃
→ 재시도 정책 + 보상 트랜잭션 - 중복 처리
→ 멱등 키 + 상태 전이 검증 - 메시지 누락
→ DLQ + 재처리 파이프라인
마무리 — 면접에서 말할 수 있어야 하는 문장 8개
- 분산 트랜잭션은 깨진다.
- Saga는 일관성 회복 전략이다.
- 보상은 되돌리기가 아니다.
- exactly-once는 환상이다.
- Outbox는 중복을 관리한다.
- CQRS는 책임 분리 선언이다.
- 최종 일관성은 UX 문제다.
- Event Sourcing은 시간 모델 선택이다.
재학습 체크리스트
- 단일 DB 트랜잭션을 포기했는가?
- 일관성 수준을 명시했는가?
- 보상 전략이 있는가?
- 이벤트 신뢰성을 확보했는가?
- 읽기/쓰기 요구를 분리했는가?
- 지연 허용치를 UX로 표현했는가?
- 중복 처리를 고려했는가?
- DLQ 전략이 있는가?
- 스키마 진화를 계획했는가?
- 운영 난이도를 감당할 수 있는가?
- 패턴 조합 이유를 설명할 수 있는가?
- “왜 이 구조인가”를 말할 수 있는가?
