8. 질문 — 메시지 큐, 이벤트 소싱, CQRS
난이도 상내 답안
모범답안
모범답안 — 메시지 큐, 이벤트 소싱, CQRS
난이도: 상
핵심 답변
메시지 큐는 생산자와 소비자를 디커플링하고, 트래픽을 버퍼링하며, 실패 시 재시도를 가능하게 해 장애 전파를 막는다. 전달 보장은 현실적으로 at-least-once + 멱등 처리로 exactly-once를 근사한다(소비자가 중복을 안전하게 무시). 이벤트 소싱은 상태가 아니라 상태 변화(이벤트)의 로그를 진실로 저장하는 방식으로, 재화 원장·감사처럼 "어떻게 이 값이 됐는지"가 중요한 도메인에 강하다. CQRS는 쓰기 모델과 읽기 모델을 분리하는 패턴.
깊이 있는 설명
메시지 큐의 이점
- 비동기/디커플링: 결제 서비스가 우편 서비스를 직접 호출하지 않고 이벤트만 발행 → 우편이 잠시 죽어도 결제는 진행, 큐가 쌓아뒀다 나중에 처리.
- 버퍼링(평탄화): 트래픽 스파이크를 큐가 흡수해 소비자를 과부하에서 보호.
- 재시도/내구성: 처리 실패 시 재시도, 메시지가 디스크에 보존돼 유실 방지. 처리 불가 메시지는 DLQ(dead-letter queue)로.
전달 보장
- at-most-once: 재시도 안 함 → 유실 가능(중요치 않은 텔레메트리).
- at-least-once: 실패 시 재시도 → 중복 가능. 가장 흔함.
- exactly-once: 이상적이지만 분산에서 비용 큼. 현실 해법 = at-least-once + 소비자 멱등성(중복 메시지를 처리해도 결과 동일). 메시지에 고유 ID를 달고 "이미 처리한 ID면 무시"하는 dedup.
이벤트 소싱 / CQRS
- 이벤트 소싱: 현재 잔액이 아니라 "충전 +100, 사용 -30…" 이벤트 시퀀스를 저장. 현재 상태는 이벤트를 재생(replay)해 도출. 장점: 완전한 감사 추적, 시점 복원, 디버깅. 단점: 복잡도↑, 조회 시 재생 비용(→ 스냅샷·읽기모델로 보완), 스키마 진화 어려움.
- CQRS: 쓰기(Command) 모델과 읽기(Query) 모델 분리. 이벤트 소싱과 자주 짝지어, 쓰기는 이벤트 로그·읽기는 비정규화된 뷰로.
순서와 파티셔닝
- Kafka 등은 파티션 내에서만 순서 보장. "같은 플레이어 이벤트 순서"를 지키려면 플레이어ID를 파티션 키로 써서 한 플레이어의 이벤트가 항상 같은 파티션·같은 소비자로 가게 한다.
응용/실무 연결
- 재화/아이템 지급: 이벤트 발행 → 멱등 소비로 중복 지급 방지(가챠/결제 문제의 정석 해법).
- 랭킹·통계: 게임플레이 이벤트를 큐로 흘려보내 별도 집계 파이프라인에서 처리(메인 루프 부담↓).
- 감사/사기 탐지: 이벤트 소싱 로그가 그대로 증거.
흔한 오답·함정
- "큐 쓰면 exactly-once 공짜" — 아니다. 멱등성을 소비자가 보장해야 한다.
- 모든 도메인에 이벤트 소싱 적용 → 과한 복잡도. 원장·감사처럼 이력이 핵심인 곳에 선택적으로.
- 전역 순서를 기대 → 분산 큐는 보통 파티션 내 순서만. 키 설계가 중요.
꼬리질문 대비
- Q. DLQ란? 반복 실패한 메시지를 격리하는 큐. 본 파이프라인을 막지 않고 나중에 수동/자동 처리.
- Q. 이벤트 소싱에서 조회가 느린 문제는? 주기적 스냅샷 + CQRS 읽기 모델(미리 계산된 뷰)로 해결.
- Q. 멱등 키를 어디에 저장? 처리 완료 ID를 DB UNIQUE 제약이나 Redis set으로. TTL·증식 관리 필요.