179. CQRS (Command Query Responsibility Segregation) 패턴
⚠️ 이 문서는 마이크로서비스 아키텍처(MSA)에서 데이터베이스가 분리됨에 따라 복잡한 조인(Join) 조회가 불가능해지는 한계를 극복하기 위해, '상태를 변경하는 명령(Command)'과 '데이터를 조회하는 질의(Query)'의 책임을 완전히 분리하는 CQRS 패턴을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 하나의 동일한 데이터 모델(엔티티)과 DB로 CRUD를 모두 처리하던 전통적인 방식에서 벗어나, 쓰기 전용 모델/DB와 읽기 전용 모델/DB를 물리적 또는 논리적으로 분리하는 아키텍처 설계 기법이다.
- 가치: 쓰기 트래픽(주문 생성)과 읽기 트래픽(주문 내역 조회)이 서로의 성능을 간섭하지 않으며, 각 화면의 성격에 맞게 읽기 전용 DB를 NoSQL 등으로 최적화하여 MSA 환경에서 극한의 조회 성능을 이끌어낸다.
- 기술 체계: 쓰기 DB에 데이터가 저장되면, 이벤트 기반(Event-Driven) 메시징(Kafka 등)을 통해 읽기 DB로 데이터가 비동기적으로 동기화(Eventual Consistency)되는 구조를 주로 사용한다.
Ⅰ. MSA의 저주: 쪼개진 DB와 사라진 Join
모놀리식(Monolithic) 시스템에서는 하나의 거대한 RDBMS가 모든 것을 해결했다.
- Database per Service의 한계:
- MSA에서는 '주문 DB', '회원 DB', '상품 DB'가 완전히 분리되어 있다. (강한 결합 방지)
- 복합 조회(Join)의 불가능:
- 사용자가 "내 주문 내역(상품 이름, 가격, 주문 일자, 배송 상태)" 화면을 보려면, 기존에는 3개 테이블을 SQL
JOIN한 번으로 가져왔지만, MSA에서는 불가능하다.
- 사용자가 "내 주문 내역(상품 이름, 가격, 주문 일자, 배송 상태)" 화면을 보려면, 기존에는 3개 테이블을 SQL
- API Composition의 성능 저하:
- 이를 해결하기 위해 게이트웨이가 주문, 회원, 상품 API를 각각 호출해 메모리에서 합치는 방식(API Composition)을 쓰면, 네트워크 지연(Latency)이 3배로 늘어나고 어느 한 서비스만 죽어도 조회를 할 수 없게 된다.
📢 섹션 요약 비유: 과거에는 한 권의 거대한 두꺼운 백과사전(단일 DB)에서 모든 내용을 찾았지만, 지금은 요리책, 역사책, 과학책이 다른 도서관(MSA)에 흩어져 있어서 세 도서관을 모두 뛰어다녀야만 하나의 보고서(조회 화면)를 쓸 수 있는 상황이 된 것입니다.
Ⅱ. CQRS의 개념과 읽기 전용 뷰(View)의 탄생
이 문제를 해결하기 위해 CQRS는 '조회 전용' 데이터베이스를 따로 구축한다.
- 책임의 분리 (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)] │ └─────────────────────────────────────────────────────────┘
- Command (명령):
- Materialized View (구체화된 뷰):
- Read DB는 여러 서비스에 흩어진 데이터(주문+회원+상품)를 이미
JOIN된 형태의 덩어리(Document)로 뭉쳐서 저장해 둔다. - 클라이언트가 요청하면 복잡한 연산 없이 Read DB에서 해당 문서를 1건만 읽어서 바로 반환하므로 속도가 경이롭게 빠르다.
- Read DB는 여러 서비스에 흩어진 데이터(주문+회원+상품)를 이미
📢 섹션 요약 비유: 본사 기획팀(Command)이 열심히 회계 자료와 영업 자료를 모아 문서를 만들면, 이를 보기 좋게 요약한 '발표용 PPT(Read DB)'를 따로 만들어 복사해두고, 사장님(클라이언트)이 자료를 요구할 때 즉시 PPT만 꺼내어 보여주는 것과 같습니다.
Ⅲ. CQRS의 트레이드오프: Eventual Consistency
CQRS는 공짜가 아니다. 치명적인 부작용을 감수해야 한다.
- 데이터 동기화 지연 (Replication Lag):
- Write DB에 데이터가 저장된 직후 Read DB로 복제되는 데 보통 수십 밀리초(ms)의 지연이 발생한다.
- 최종 일관성 (Eventual Consistency):
- 사용자가 '정보 수정' 버튼을 누른 직후 화면이 새로고침 되었을 때, Read DB 동기화가 아직 안 끝나서 '옛날 정보'가 잠시 보이는 현상이 발생한다. 즉, 항상 즉시(Strong Consistency) 일치하지는 않지만 "결국에는(Eventual) 일치한다"는 사상을 받아들여야 한다.
- 복잡도 증가:
- DB가 2배로 늘어나고, 그사이를 이어주는 메시지 큐(Kafka, RabbitMQ) 인프라가 추가되며, 동기화 실패 시 재시도/보상 로직 등 아키텍처 복잡도가 급증한다.
📢 섹션 요약 비유: 인터넷 쇼핑몰에서 상품을 구매(Command)하고 돈이 빠져나갔지만, '나의 주문 내역(Query)' 화면을 바로 새로고침하면 1~2초 동안은 아무것도 안 보이다가 잠시 후 짠하고 나타나는 현상이 바로 이 CQRS의 동기화 시차 때문입니다.