← 문제로

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·증식 관리 필요.