CQRS Pattern
Command Query Responsibility Segregation: 데이터 쓰기(Command)와 읽기(Query) 모델을 물리적으로 분리하는 아키텍처 패턴. Write Model의 이벤트를 구독하여 Read Model을 동기화하는 비동기 프로세스. 결과적...
목차 보기22
1. Overview
CQRS(Command Query Responsibility Segregation)는 데이터를 바꾸는 쪽(Command)과 읽는 쪽(Query)의 모델을 분리해 각각을 자기 목적에 맞게 설계하는 아키텍처 패턴입니다. 한 모델로 쓰기와 읽기를 모두 처리하는 전통적 CRUD 구조에서는 두 요구가 서로의 설계를 제약하는데, CQRS는 이를 분리해 쓰기 모델은 도메인 규칙의 일관성에, 읽기 모델은 조회 성능과 형태에 최적화합니다.
이 패턴은 Bertrand Meyer의 CQS(Command-Query Separation) 원칙을 객체 수준에서 시스템 수준으로 넓힌 것으로 설명되며, Greg Young이 이름을 붙이고 Martin Fowler가 널리 소개했습니다. 이 글은 두 모델의 역할 분담, 이벤트로 읽기 모델을 갱신하는 Projection, 결과적 일관성이라는 대가, 그리고 이 패턴이 맞는 경우와 과설계가 되는 경우를 정리합니다.
2. Scope & Boundaries
In-Scope
- 핵심 분리 (Command/Query Split): Write Model과 Read Model의 책임과 저장 구조.
- Projection: Write Model의 이벤트로 Read Model을 동기화하는 과정과 결과적 일관성.
- 적용 판단: 도입이 맞는 조건과 주의점.
Out-of-Scope
- 이벤트 소싱의 저장 방식 상세 → Event Sourcing 영역.
- 마이크로서비스 분할 전략 → Microservices Principles & Decomposition 영역.
- 메시지 브로커 제품별 설정: 이벤트 전달 인프라의 구체 설정은 다루지 않습니다.
Boundaries
- CQRS vs. Event Sourcing: 둘은 자주 함께 쓰이지만 서로 다른 패턴입니다. CQRS는 읽기/쓰기 모델의 분리이고, Event Sourcing은 상태 대신 변경 이벤트 열을 저장하는 방식입니다. CQRS를 쓰면서 Write Model을 일반 DB로 둘 수도 있습니다.
- 모델 분리 vs. 저장소 분리: 분리의 최소 단위는 "모델"입니다. 같은 데이터베이스 위에 별도의 읽기 모델을 두는 구성부터, 쓰기와 읽기 저장소를 완전히 나누는 구성까지 폭이 있으며, 물리적 분리는 필요에 따라 선택하는 단계입니다.
- CQRS vs. 단순 CRUD: 도메인이 단순하면 CRUD가 더 적절하며, CQRS는 한 시스템의 일부 영역에 선택적으로 적용하는 편이 일반적입니다.
3. Counterexample
- 단순 CRUD 서비스에 CQRS 도입: 도메인 규칙이 적고 읽기와 쓰기 형태가 거의 같은 서비스에 두 모델과 동기화 파이프라인을 얹는 경우. 두 모델 유지로 시스템 복잡도가 증가하므로 단순 CRUD 서비스에는 과설계입니다. Fowler도 대부분의 시스템에서는 CQRS가 위험한 복잡도를 더한다고 경고하며 신중히 적용할 것을 권합니다.
- 읽기 모델이 즉시 반영된다는 가정: Projection이 비동기이면 읽기/쓰기 모델 간 동기화 지연이 생겨, 사용자가 방금 작성한 데이터가 즉시 안 보일 수 있습니다. 화면 설계(낙관적 UI, 응답에 결과 포함 등)와 합의된 일관성 수준 없이 도입하면 사용자 불만이 됩니다.
- 모든 곳에 같은 모델을 쓰는 전역 적용: 시스템 전체를 CQRS로 통일하려는 시도. 일관성이 중요한 영역과 조회 최적화가 중요한 영역은 서로 다르므로 영역별로 판단해야 합니다.
4. Prerequisites
- 트랜잭션과 정규화 (Basic): Write Model이 불변식을 어떻게 지키는지, 정규화 스키마가 무엇을 보장하는지 알아야 합니다.
- 결과적 일관성 (Basic): 비동기 갱신이 읽기에 어떤 지연과 불일치를 만드는지 이해해야 합니다.
- 이벤트 기반 설계 (Recommended): Projection이 이벤트를 구독해 읽기 모델을 갱신하므로 → Event Sourcing을 함께 보면 도움이 됩니다.
5. Learning Map
연결 노트
- Event Sourcing — CQRS의 Write Model로 Event Store를 사용하는 일반적 조합
- Microservices Principles & Decomposition — 마이크로서비스별 독립 Read Model 운용
- SLI, SLO & Error Budget — Eventual Consistency 허용 범위를 SLO로 정의
6. Learning Topics
Basic
Core Topic 01: Command와 Query의 분리 (Write Model / Read Model)
- Why to Learn: 쓰기와 읽기는 요구가 다릅니다. 쓰기는 규칙 검증과 일관성이, 읽기는 조회 속도와 표현 형태가 중요하므로 하나의 모델로 둘을 만족시키려 할 때의 충돌을 이해해야 합니다.
- What to Learn:
- Concepts: 데이터 쓰기(Command)와 읽기(Query) 모델을 분리합니다. Write Model은 도메인 불변식(Invariant) 검증에 최적화되어 정규화와 트랜잭션 중심이고, Read Model은 조회 성능에 최적화된 비정규화 Materialized View로 스키마를 쿼리 요구사항에 맞게 자유롭게 설계합니다.
- Skills: Command는 상태를 바꾸고 결과 데이터를 돌려주지 않는 방향으로, Query는 상태를 바꾸지 않는 방향으로 인터페이스를 나눕니다.
- Tools: 관계형 DB(쓰기), 캐시·검색 인덱스(읽기).
- Trade-offs: 각 모델을 목적에 맞게 최적화할 수 있는 대신 모델이 둘이 되어 운영 대상이 늘어납니다.
요청 흐름은 아래처럼 두 갈래로 나뉩니다.
CODE
클라이언트
├─ 쓰기 요청(Command) ──▶ Write Model : 이벤트 저장소 또는 정규화 DB
└─ 읽기 요청(Query) ──▶ Read Model : 비정규화 캐시 또는 검색 인덱스
- How to Learn:
- 1단계: 주문 도메인에서 "주문 생성", "주문 취소"는 Command로, "주문 목록 조회", "일별 매출 집계"는 Query로 분류해 봅니다.
- 2단계: 같은 데이터를 쓰기용 정규화 테이블과 조회용 비정규화 뷰로 나눠 설계하고 두 스키마를 비교합니다.
- Implement: Command 핸들러와 Query 핸들러를 별도 모듈로 나눈 작은 예제 서비스.
Recommended
Core Topic 02: Projection과 결과적 일관성 (Eventual Consistency)
- Why to Learn: 두 모델을 분리하면 둘 사이를 잇는 동기화가 시스템의 핵심 위험 지점이 되므로, 동기화가 어떻게 이루어지고 어떤 불일치가 생기는지 알아야 합니다.
- What to Learn:
- Concepts: Projection은 Write Model에서 나오는 이벤트를 받아 Read Model에 반영하는 비동기 과정이고, 그래서 읽기 쪽은 잠시 뒤처질 수 있는 결과적 일관성(Eventual Consistency)을 받아들입니다.
- Skills: 이벤트 순서 보장, 중복 전달에 대비한 멱등(idempotent) 처리, Projection 재구축(replay).
- Tools: 메시지 브로커, 이벤트 로그.
- Trade-offs: 읽기 확장과 비정규화의 이점 대신 쓰기 직후 조회 시 지연이 생깁니다.
- How to Learn:
- 1단계: 쓰기 직후 조회에서 값이 안 보이는 상황을 의도적으로 재현합니다.
- 2단계: 같은 이벤트가 두 번 전달돼도 읽기 모델이 같은 상태가 되도록 멱등 처리를 설계합니다.
- Implement: 이벤트 목록을 읽어 비정규화 읽기 모델을 구축하고, 처음부터 다시 재생해 같은 결과가 나오는지 확인하는 Projection.
Practical
Core Topic 03: 도입 판단과 운영 (When to Use & Cautions)
- Why to Learn: CQRS는 복잡도를 늘리는 패턴이므로 어떤 문제에 쓰고 어떤 문제에 쓰지 않을지 판단하는 능력이 구현 능력만큼 중요합니다.
- What to Learn:
- Concepts: 읽기 부하가 쓰기보다 압도적으로 높을 때(일반적 웹 서비스), 동일 데이터를 다양한 형태로 조회해야 할 때(대시보드, 검색, 리포트), 변경 이력을 빠짐없이 남기는 감사 추적이 필요해 Event Sourcing과 함께 쓸 때도 어울립니다.
- Skills: 읽기/쓰기 모델 간 동기화 지연을 사용자 경험과 SLO로 정의하는 법.
- Tools: 지연 모니터링 대시보드.
- Trade-offs: 두 모델 유지로 시스템 복잡도가 증가하므로 단순 CRUD 서비스에는 과설계입니다.
- How to Learn:
- 1단계: 현재 운영 중인 서비스의 읽기:쓰기 비율과 조회 형태 종류를 측정합니다.
- 2단계: 동기화 지연의 허용 한계(예: 몇 초)를 정하고 이를 SLO 항목으로 적어 봅니다.
- Implement: 도입 판단 체크리스트(읽기/쓰기 비율, 조회 형태 수, 일관성 요구)를 점수화한 간단한 평가표.
7. Terminology
8. References
Primary References
- [Fowler, M. "CQRS." martinfowler.com/bliki/CQRS.html] — 읽기와 쓰기 모델 분리의 개요, 장점, 그리고 복잡도에 대한 경고.
- [Young, G., 2010. "CQRS Documents."] — Command와 Query 분리를 체계적으로 설명한 Greg Young의 문서.
- [Meyer, B., 1988. "Object-Oriented Software Construction." Prentice Hall] — Command-Query Separation 원칙의 출처.
Secondary References
- [Fowler, M. "Event Sourcing." martinfowler.com/eaaDev/EventSourcing.html] — 이벤트 열로 상태를 저장하는 방식과 CQRS와의 관계.
Industry References
- [Microsoft Azure Architecture Center: "CQRS pattern." learn.microsoft.com/en-us/azure/architecture/patterns/cqrs] — 읽기/쓰기 모델 분리의 적용 시나리오, 이점, 일관성 고려 사항.
9. Final Checklist
Primary Checklist
- Write Model과 Read Model이 각각 무엇에 최적화되는지 설명할 수 있는가?
- CQRS와 Event Sourcing이 서로 다른 패턴임을 구분해 말할 수 있는가?
Secondary Checklist
- Projection이 비동기일 때 생기는 읽기 지연과 그 대응(낙관적 UI 등)을 설계했는가?
- 이벤트 중복 전달에 대비한 멱등 처리와 Projection 재구축 절차가 있는가?
Industry Checklist
- 단순 CRUD로 충분한 서비스에 CQRS를 도입하고 있지 않은지 읽기/쓰기 비율과 조회 형태로 검토했는가?
- 결과적 일관성의 허용 범위를 SLO로 정의하고 지연을 모니터링하고 있는가?
태그
cqrscommandqueryread-modelwrite-modeldistributed-systemscqrs-patternsystem-architecturepatternsystemarchitecturereadevent-drivenreactive-systems