Post

CQRS와 Elasticsearch 읽기/쓰기 분리

CQRS로 변경 비용을 분리하고 Kafka→Elasticsearch 파이프라인에서 최종 일관성·멱등·스키마 리스크를 다루는 방법

CQRS와 Elasticsearch 읽기/쓰기 분리

요약 (5–7줄)

CQRS는 성능 튜닝 기법이 아니라 변경 비용을 분리하는 아키텍처 선택이다.
쓰기 모델은 “정합성”, 읽기 모델은 “질의 효율”에 최적화된다.
Kafka → Elasticsearch 파이프라인은 지연·누락·중복·스키마 변화라는 구조적 리스크를 내포한다.
CQRS와 이벤트 소싱은 독립 개념이며, 함께 쓰일 수도 분리될 수도 있다.
Elasticsearch는 트랜잭션 DB가 아니라 검색 최적화된 문서 스토어다.
이 구조의 핵심은 “언제 불일치를 허용할 것인가”에 대한 명시적 합의다.


1. 정의

CQRS(Command Query Responsibility Segregation)
시스템의 쓰기(Command)읽기(Query) 책임을 모델·저장소·확장 전략 차원에서 분리하는 설계다.

분리는 단순히 API를 나누는 것이 아니라, 변경 비용이 전파되는 경로를 끊는 것을 의미한다.


2. 직관

하나의 모델로 쓰기와 읽기를 모두 만족시키려 하면

  • 쓰기는 제약 때문에 느려지고
  • 읽기는 구조 때문에 비싸진다

CQRS는 “둘 다 적당히”가 아니라
각각을 극단적으로 최적화하는 선택이다.


3. CQRS의 ‘분리’가 의미하는 것

3.1 모델 분리

  • Write 모델: 불변식, 검증, 트랜잭션 중심
  • Read 모델: 조인 최소화, 질의 패턴 중심

3.2 저장소 분리

  • Write: RDB / 트랜잭션 DB
  • Read: Elasticsearch / 캐시 / 머티리얼라이즈드 뷰

3.3 배포 분리

  • Read 스케일링이 Write에 영향 없음
  • 검색 트래픽 폭증 시에도 쓰기 안정성 유지

3.4 팀 경계 분리

  • 쓰기 도메인 팀
  • 읽기/검색 최적화 팀

CQRS는 조직 구조와 강하게 결합되는 아키텍처다.


4. 작동 원리 (Kafka → Elasticsearch)

1) Write 서비스가 상태 변경을 이벤트로 발행
2) Kafka가 이벤트 로그를 보존
3) Read 파이프라인이 이벤트를 소비
4) Elasticsearch 인덱스를 갱신
5) Query API는 ES만 조회

핵심 전제: 읽기는 항상 쓰기보다 늦다.


5. Kafka → Elasticsearch 파이프라인의 최대 리스크 4가지

5.1 지연 (Latency)

  • 인덱싱 파이프라인 정체
  • 해결: SLA를 “최종 일관성” 기준으로 명시

5.2 누락 (Loss)

  • 소비 실패 + 오프셋 커밋 오류
  • 해결: 재처리 가능 구조 + 백필 전략

5.3 중복 (Duplication)

  • at-least-once 전달
  • 해결: 문서 ID 기준 멱등 업데이트

5.4 스키마 변화

  • 이벤트 필드 변경 → 인덱스 매핑 충돌
  • 해결: 스키마 버전 관리 + 점진적 매핑

6. 이벤트 소싱과 CQRS의 관계

  • 이벤트 소싱: 상태의 근원을 이벤트로 저장
  • CQRS: 읽기/쓰기 책임 분리

관계 정리:

  • CQRS ≠ 이벤트 소싱
  • 이벤트 소싱 없이 CQRS 가능
  • 이벤트 소싱 + CQRS는 강한 결합 패턴일 뿐 필수는 아님

실무에서는 CQRS만 도입하고 이벤트는 파이프라인 용도로만 사용하는 경우가 더 많다.


7. Elasticsearch를 Read Store로 쓰는 이유와 한계

7.1 쓰는 이유

  • 복잡한 검색 조건
  • 정렬·집계·전문 검색
  • 문서 단위 조회 최적화

7.2 한계

  • 트랜잭션 없음
  • 강한 정합성 없음
  • 즉시 반영 보장 불가

ES는 DB의 대체재가 아니라 읽기 전용 투영(projection)이다.


8. 운영 관점 핵심 개념

8.1 인덱스 매핑

  • 필드 타입은 한 번 정하면 변경 비용 큼
  • “미리 넉넉하게”가 아니라 질의 기준으로 최소화

8.2 리인덱싱

  • 구조 변경 시 불가피
  • 신규 인덱스 생성 → 스위치

8.3 백필 (Backfill)

  • 과거 이벤트 재적재
  • 재처리 가능한 시스템의 필수 능력

9. 트레이드오프

  • 장점: 읽기 확장성, 질의 자유도, 쓰기 안정성
  • 비용: 복잡도 증가, 운영 부담, 일시적 불일치

CQRS는 성능 문제가 아니라 변경 압력이 높을 때 의미가 생긴다.


10. 최소 예시

10.1 Toy 예시

  • Write 모델: Order(id, status, total)
  • Read 모델: OrderView(id, status, total, customerName)

→ 고객명은 쓰기 모델에 없음
→ 읽기 편의를 위해 투영된 데이터

10.2 실무 예시: 트윗/피드 검색

  • Write 최적화:
    • 트윗 생성은 빠르고 단순해야 함
  • Read 최적화:
    • 키워드, 해시태그, 최신순, 인기순 검색

충돌 지점:

  • 실시간성 vs 인덱싱 비용
  • 해결: “몇 초 지연”을 제품 요구사항으로 명시

11. 실무 함정 → 해결 패턴

함정결과해결 패턴
CQRS 남용시스템 과복잡변경 압력 기준 도입
ES를 DB처럼 사용데이터 오염Read 전용 규율
재처리 불가장애 시 복구 불능이벤트 기반 백필

12. 오해/실수 3개 + 교정

1) “CQRS = 성능 최적화” → 변경 비용 분리
2) “이벤트 소싱 필수” → 독립 개념
3) “ES는 그냥 DB” → 검색 특화 투영소


13. 판단 기준

사용해야 할 때

  • 읽기 패턴이 복잡하고 자주 바뀜
  • 검색 트래픽이 쓰기를 압도
  • 팀/도메인 분리가 필요

쓰지 말아야 할 때

  • CRUD 중심 단순 서비스
  • 강한 즉시 정합성 필수
  • 운영 인력·경험 부족

14. 재학습 체크리스트 (10–14)

  • 쓰기/읽기 변경 압력이 다른가?
  • 읽기 모델이 쓰기 모델을 오염시키고 있지 않은가?
  • 읽기 지연을 허용하는가?
  • 이벤트 누락 시 복구 경로가 있는가?
  • 중복 처리가 가능한가?
  • 스키마 버전 전략이 있는가?
  • 리인덱싱 절차가 문서화돼 있는가?
  • 백필이 가능한가?
  • ES 매핑 변경 비용을 인지하는가?
  • 읽기 트래픽 폭증 시 쓰기가 안전한가?
  • CQRS 도입 이유를 팀이 합의했는가?

This post is licensed under CC BY 4.0 by the author.