179. CQRS (Command Query Responsibility Segregation) 패턴

⚠️ 이 문서는 마이크로서비스 아키텍처(MSA)에서 데이터베이스가 분리됨에 따라 복잡한 조인(Join) 조회가 불가능해지는 한계를 극복하기 위해, '상태를 변경하는 명령(Command)'과 '데이터를 조회하는 질의(Query)'의 책임을 완전히 분리하는 CQRS 패턴을 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: 하나의 동일한 데이터 모델(엔티티)과 DB로 CRUD를 모두 처리하던 전통적인 방식에서 벗어나, 쓰기 전용 모델/DB와 읽기 전용 모델/DB를 물리적 또는 논리적으로 분리하는 아키텍처 설계 기법이다.
  2. 가치: 쓰기 트래픽(주문 생성)과 읽기 트래픽(주문 내역 조회)이 서로의 성능을 간섭하지 않으며, 각 화면의 성격에 맞게 읽기 전용 DB를 NoSQL 등으로 최적화하여 MSA 환경에서 극한의 조회 성능을 이끌어낸다.
  3. 기술 체계: 쓰기 DB에 데이터가 저장되면, 이벤트 기반(Event-Driven) 메시징(Kafka 등)을 통해 읽기 DB로 데이터가 비동기적으로 동기화(Eventual Consistency)되는 구조를 주로 사용한다.

Ⅰ. MSA의 저주: 쪼개진 DB와 사라진 Join

모놀리식(Monolithic) 시스템에서는 하나의 거대한 RDBMS가 모든 것을 해결했다.

  1. Database per Service의 한계:
    • MSA에서는 '주문 DB', '회원 DB', '상품 DB'가 완전히 분리되어 있다. (강한 결합 방지)
  2. 복합 조회(Join)의 불가능:
    • 사용자가 "내 주문 내역(상품 이름, 가격, 주문 일자, 배송 상태)" 화면을 보려면, 기존에는 3개 테이블을 SQL JOIN 한 번으로 가져왔지만, MSA에서는 불가능하다.
  3. API Composition의 성능 저하:
    • 이를 해결하기 위해 게이트웨이가 주문, 회원, 상품 API를 각각 호출해 메모리에서 합치는 방식(API Composition)을 쓰면, 네트워크 지연(Latency)이 3배로 늘어나고 어느 한 서비스만 죽어도 조회를 할 수 없게 된다.

📢 섹션 요약 비유: 과거에는 한 권의 거대한 두꺼운 백과사전(단일 DB)에서 모든 내용을 찾았지만, 지금은 요리책, 역사책, 과학책이 다른 도서관(MSA)에 흩어져 있어서 세 도서관을 모두 뛰어다녀야만 하나의 보고서(조회 화면)를 쓸 수 있는 상황이 된 것입니다.


Ⅱ. CQRS의 개념과 읽기 전용 뷰(View)의 탄생

이 문제를 해결하기 위해 CQRS는 '조회 전용' 데이터베이스를 따로 구축한다.

  1. 책임의 분리 (Segregation):
    • Command (명령): Create, Update, Delete와 같이 시스템의 상태를 변경하는 비즈니스 로직. 무결성이 중요하므로 주로 RDBMS를 사용.
    • Query (조회): Read 작업. 화면에 보여줄 데이터 형식을 그대로 가지고 있는 별도의 모델/DB를 사용. 빠른 조회를 위해 NoSQL(Elasticsearch, MongoDB)을 주로 사용.
    • ┌─────────────────────────────────────────────────────────┐ │ [UI] --(Command: 주문생성)--> [Write API] ---> [Write DB(RDBMS)] │ │ ↓ (이벤트 동기화)│ │ [UI] <--(Query: 주문내역)---- [Read API] <--- [Read DB(NoSQL)] │ └─────────────────────────────────────────────────────────┘
  2. Materialized View (구체화된 뷰):
    • Read DB는 여러 서비스에 흩어진 데이터(주문+회원+상품)를 이미 JOIN 된 형태의 덩어리(Document)로 뭉쳐서 저장해 둔다.
    • 클라이언트가 요청하면 복잡한 연산 없이 Read DB에서 해당 문서를 1건만 읽어서 바로 반환하므로 속도가 경이롭게 빠르다.

📢 섹션 요약 비유: 본사 기획팀(Command)이 열심히 회계 자료와 영업 자료를 모아 문서를 만들면, 이를 보기 좋게 요약한 '발표용 PPT(Read DB)'를 따로 만들어 복사해두고, 사장님(클라이언트)이 자료를 요구할 때 즉시 PPT만 꺼내어 보여주는 것과 같습니다.


Ⅲ. CQRS의 트레이드오프: Eventual Consistency

CQRS는 공짜가 아니다. 치명적인 부작용을 감수해야 한다.

  1. 데이터 동기화 지연 (Replication Lag):
    • Write DB에 데이터가 저장된 직후 Read DB로 복제되는 데 보통 수십 밀리초(ms)의 지연이 발생한다.
  2. 최종 일관성 (Eventual Consistency):
    • 사용자가 '정보 수정' 버튼을 누른 직후 화면이 새로고침 되었을 때, Read DB 동기화가 아직 안 끝나서 '옛날 정보'가 잠시 보이는 현상이 발생한다. 즉, 항상 즉시(Strong Consistency) 일치하지는 않지만 "결국에는(Eventual) 일치한다"는 사상을 받아들여야 한다.
  3. 복잡도 증가:
    • DB가 2배로 늘어나고, 그사이를 이어주는 메시지 큐(Kafka, RabbitMQ) 인프라가 추가되며, 동기화 실패 시 재시도/보상 로직 등 아키텍처 복잡도가 급증한다.

📢 섹션 요약 비유: 인터넷 쇼핑몰에서 상품을 구매(Command)하고 돈이 빠져나갔지만, '나의 주문 내역(Query)' 화면을 바로 새로고침하면 1~2초 동안은 아무것도 안 보이다가 잠시 후 짠하고 나타나는 현상이 바로 이 CQRS의 동기화 시차 때문입니다.