포스트

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 도입 이유를 팀이 합의했는가?

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.