I. 서론
기업들이 모놀리식 아키텍처에서 마이크로서비스 아키텍처(MSA, Microservices Architecture)로 전환하면서, 데이터 관리에 대한 근본적인 재고가 필요해지고 있다. MSA에서는 각 서비스가 독립된 데이터베이스를 보유하므로(Database per Service), 여러 서비스에 걸친 데이터 조회가 복잡해지고 읽기/쓰기 워크로드의 독립적 확장이 어려워진다.
이 문제를 해결하기 위해 등장한 것이 CQRS(Command Query Responsibility Segregation, 명령과 조회의 책임 분리) 패턴이다. CQRS는 데이터의 쓰기 연산(Command)과 읽기 연산(Query)을 분리하여, 각각을 독립적으로 최적화하고 확장할 수 있게 한다.
본 글에서는 CQRS 패턴의 핵심 개념, 이벤트 소싱(Event Sourcing) 및 사가(Saga) 패턴과의 결합, 실제 적용 사례, 그리고 도입 시 핵심 고려사항을 살펴본다.
II. CQRS 패턴의 핵심 개념
1. CQS에서 CQRS로
CQRS의 이론적 기반은 버트란트 마이어(Bertrand Meyer)가 제안한 CQS(Command Query Separation) 원칙이다. CQS는 객체의 메서드를 상태를 변경하는 명령(Command)과 값을 반환하는 조회(Query)로 엄격히 구분한다. CQRS는 이 원칙을 아키텍처 수준으로 확장하여, 시스템의 읽기 모델과 쓰기 모델을 완전히 분리한다(Richardson, 2018).
전통적 CRUD 모델에서는 하나의 데이터베이스가 모든 읽기와 쓰기를 처리한다. 대부분의 애플리케이션에서 읽기 요청이 쓰기보다 훨씬 많은데, 동일한 모델을 공유하면 양쪽이 서로의 성능에 영향을 미친다. CQRS는 이를 분리하여 각각에 최적화된 모델과 저장소를 사용한다.

[그림1] 전통적 CRUD vs CQRS 패턴 비교
2. CQRS의 구성
Command Side(쓰기 측)는 데이터 생성, 수정, 삭제를 담당한다. Command Handler가 비즈니스 로직을 검증하고, Write DB(주로 RDBMS)에 정규화된 형태로 저장하여 ACID 트랜잭션을 보장한다.
Query Side(읽기 측)는 조회만 담당한다. Read DB는 클라이언트가 필요로 하는 뷰에 최적화된 비정규화 형태로, MongoDB, Elasticsearch, Redis 등이 활용된다. 복잡한 JOIN 없이 단일 문서 조회로 빠른 응답이 가능하다.
이벤트 기반 동기화는 Write DB의 상태 변경을 Read DB에 반영하는 메커니즘이다. 도메인 이벤트가 이벤트 브로커(Apache Kafka, RabbitMQ 등)를 통해 발행되면, Projector가 이를 구독하여 Read Model을 갱신한다.
| 구분 | Command Side | Query Side |
|---|---|---|
| 역할 | 생성/수정/삭제 | 조회 |
| 데이터 모델 | 정규화 | 비정규화 |
| 데이터베이스 | RDBMS | NoSQL / Cache |
| 최적화 방향 | 트랜잭션 무결성 | 조회 성능 |
| 확장 전략 | 수직 확장 / 샤딩 | 수평 확장 (Replica) |
[표1] Command Side vs Query Side 비교
3. MSA에서의 적용
MSA 환경에서 CQRS를 적용하면, 각 마이크로서비스가 자체적으로 Command와 Query를 분리한다. API Gateway를 통해 요청이 라우팅되며, 서비스 간 데이터 동기화는 이벤트 브로커를 통해 비동기로 수행된다. 각 서비스는 자신의 요구에 가장 적합한 기술 스택을 선택할 수 있다는 점에서 MSA의 기술 다양성 원칙과 자연스럽게 결합된다.

[그림2] MSA 환경에서의 CQRS 적용 아키텍처
III. Event Sourcing과의 결합
이벤트 소싱(Event Sourcing)은 시스템의 상태를 직접 저장하는 대신, 상태 변경 이벤트들의 순차적 기록을 저장하는 방식이다. 현재 상태는 이벤트를 처음부터 재생(Replay)하여 도출한다. 모든 이벤트는 불변(Immutable)이며 추가(Append)만 가능하므로, 완전한 감사 추적(Audit Trail)이 자연스럽게 구현된다.
CQRS와 Event Sourcing은 독립적인 패턴이지만, 함께 사용할 때 시너지가 크다(Fowler, 2011). Event Store의 이벤트 로그는 그 자체로는 조회에 비효율적이지만, CQRS의 Projector가 이벤트 발생 시마다 Read Model을 갱신하므로 조회 시에는 사전 구성된 뷰에서 즉시 응답할 수 있다.
이 결합의 주요 이점은 세 가지다. 첫째, 금융·의료 등 규정 준수 도메인에서 모든 상태 변경 이력이 보존된다. 둘째, 이벤트 재생을 통해 특정 시점의 상태를 복원할 수 있다. 셋째, 새로운 조회 요구사항이 생기면 이벤트 로그를 재생하여 새로운 Read Model을 구축할 수 있다.
| 구분 | 전통적 상태 저장 | Event Sourcing |
|---|---|---|
| 저장 대상 | 현재 상태 (최종 값) | 상태 변경 이벤트 (전체 이력) |
| 수정 방식 | 기존 행 덮어쓰기 (UPDATE) | 새 이벤트 추가 (APPEND) |
| 이력 보존 | 별도 이력 테이블 필요 | 자동 보존 |
| 특정 시점 조회 | 구현 어려움 | 이벤트 재생으로 복원 가능 |
| 적합 도메인 | 단순 CRUD | 금융, 의료, 감사 추적 필수 도메인 |
[표2] 전통적 상태 저장 vs Event Sourcing 비교

[그림3] Event Sourcing과 CQRS의 결합 흐름
IV. 실제 적용 사례
1. 이커머스 주문 관리 시스템
이커머스 플랫폼에서 CQRS가 가장 빈번하게 적용되는 영역은 주문 관리다. 주문 생성은 재고 확인, 결제 처리 등 복잡한 로직을 수반하지만, 주문 조회는 단순하면서 훨씬 높은 빈도로 발생한다.
Command Side는 주문을 PostgreSQL에 정규화 형태로 저장하고 OrderCreatedEvent를 Kafka에 발행한다. Query Side의 Projector는 이 이벤트를 구독하여 주문·상품·배송 정보를 하나의 비정규화 문서로 병합하여 MongoDB에 저장한다. 조회 시에는 JOIN 없이 단일 문서를 즉시 반환하므로 평균 응답 시간 15ms 이하를 유지하며, 대규모 프로모션 시 Read 서비스만 독립적으로 확장할 수 있다.

[그림4] 이커머스 주문 시스템의 CQRS 적용
2. 핀테크 거래 플랫폼
금융 서비스에서도 CQRS와 Event Sourcing의 결합이 효과를 입증하고 있다. 한 핀테크 기업은 모놀리식 백엔드를 MSA로 전환하면서, 읽기/쓰기 부하가 극단적으로 비대칭적인 거래 체결 서비스에 선별적으로 CQRS를 도입하였다(Niessen, 2025). Write Side에서는 Event Store에 모든 거래 이벤트를 불변 로그로 기록하여 금융 규정의 감사 추적 요건을 충족하였고, Read Side에서는 포트폴리오 뷰, 거래 내역 피드, 실시간 대시보드 등 용도별 Read Model을 구성하였다.
V. Saga 패턴과의 결합
MSA에서 주문 처리 시 주문 생성, 재고 차감, 결제, 배송이 각기 다른 서비스에서 수행되므로 하나의 트랜잭션으로 묶을 수 없다. Saga 패턴은 이를 여러 로컬 트랜잭션으로 분해하고, 실패 시 보상 트랜잭션(Compensating Transaction)을 실행하여 데이터 일관성을 유지한다.
Saga 패턴에는 두 가지 구현 방식이 있다. 오케스트레이션(Orchestration)은 중앙의 Orchestrator가 각 서비스의 Command를 순차 호출하고 실패 시 역순으로 보상을 실행한다. 코레오그래피(Choreography)는 각 서비스가 로컬 트랜잭션 완료 후 이벤트를 발행하고, 다음 서비스가 이를 구독하여 자신의 트랜잭션을 수행하는 방식이다.
| 구분 | Orchestration | Choreography |
|---|---|---|
| 조율 방식 | 중앙 Orchestrator가 순차 호출 | 각 서비스가 이벤트로 자율 연계 |
| 흐름 가시성 | 높음 (한 곳에서 전체 흐름 파악) | 낮음 (서비스별 이벤트 추적 필요) |
| 결합도 | Orchestrator에 의존 | 서비스 간 느슨한 결합 |
| 적합 상황 | 복잡한 비즈니스 흐름, 단계 많을 때 | 단순한 흐름, 서비스 자율성 중시 |
| 단점 | 단일 장애점 가능성 | 전체 흐름 디버깅 어려움 |
[표3] Saga 패턴 — Orchestration vs Choreography 비교
CQRS의 Command Side에서 Saga를 통해 분산 트랜잭션을 관리하면, 각 단계의 성공·실패 이벤트가 Read Model에 실시간 반영된다. 사용자는 주문 상태가 “결제 처리 중”에서 “배송 준비 중”으로, 또는 실패 시 “주문 취소됨”으로 변경되는 과정을 확인할 수 있다.

[그림5] CQRS와 Saga 패턴의 결합
VI. 도입 시 핵심 고려사항
CQRS는 시스템 전체에 일괄 적용하는 패턴이 아니다. 복잡도가 증가하므로 효과가 명확한 Bounded Context에만 선별 적용해야 한다.

[그림5] CQRS 도입 의사결정 가이드
최종 일관성(Eventual Consistency) — Write DB와 Read DB 간 동기화 지연이 필연적이다. 사용자가 주문 직후 조회 시 반영이 안 될 수 있으므로, 상황에 맞는 대응 전략을 수립해야 한다. 대표적으로 Optimistic Update는 클라이언트에서 즉시 UI에 반영한 뒤 서버 응답을 확인하는 방식이고, Read-your-writes는 쓰기를 수행한 세션에서만 Write DB를 직접 조회하는 방식이다.
멱등성(Idempotency) 보장 — 네트워크 장애로 동일 이벤트가 중복 전달될 수 있다. Projector가 같은 이벤트를 여러 번 처리해도 결과가 동일하도록 이벤트 ID 기반 중복 방지 로직이 필수다.
인프라·운영 비용 — 별도 Read DB, 이벤트 브로커, Projector 서비스 등 추가 인프라가 필요하며, 팀의 이벤트 기반 아키텍처에 대한 이해도가 전제되어야 한다.
| 적용 권장 | 적용 비권장 |
|---|---|
| 읽기 ≫ 쓰기 비대칭 워크로드 | 읽기/쓰기 균등한 단순 CRUD |
| 복잡한 다중 서비스 조회 | 단일 서비스 내 조회 완결 |
| 독립 확장 필요한 대규모 트래픽 | 소규모 내부 시스템 |
| 감사 추적 필수 도메인 | MVP / 프로토타입 |
[표4] CQRS 적용 판단 기준
VII. 결론
CQRS 패턴은 MSA 환경에서 읽기와 쓰기의 책임을 분리하여 확장성, 성능, 유연성을 확보하는 아키텍처 패턴이다. Event Sourcing과 결합하면 감사 추적과 시간 여행 쿼리가 가능해지며, Saga 패턴과 결합하면 분산 트랜잭션을 효과적으로 관리할 수 있다.
다만 아키텍처 복잡도가 증가하므로 모든 시스템에 일괄 적용하는 것은 적절하지 않다. 읽기/쓰기 비대칭, 복잡한 조회 요구사항, 독립 확장 필요성 등이 실제로 존재하는 서비스에 선별 적용하되, 최종 일관성 관리와 멱등성 보장 등 운영 고려사항을 설계 초기부터 반영하는 것이 성공의 열쇠다.
참고문헌
- Richardson, C. (2018). Microservices Patterns. Manning Publications.
- Fowler, M. (2011). “CQRS”. martinfowler.com.
- Microsoft. (2024). “CQRS pattern”. learn.microsoft.com.
- Meyer, B. (1988). Object-Oriented Software Construction. Prentice Hall.
- Vernon, V. (2013). Implementing Domain-Driven Design. Addison-Wesley.
본 글의 도표 및 시각자료는 필자가 직접 제작하였으며, 참고문헌을 기반으로 재구성하였습니다.