CQRS
CQRS(Command Query Responsibility Segregation)는 데이터를 바꾸는 경로와 읽어 오는 경로를 서로 다른 모델로 분리하는 아키텍처 패턴입니다. 쓰기 쪽은 도메인 규칙과 정합성 검증에 맞게, 읽기 쪽은 화면이나 API가 필요한 형태로 각각 따로 유지합니다. 같은 데이터라도 '저장하는 질문'과 '보여주는 질문'이 다르면 모델도 달라야 한다는 전제에서 출발하며, 복잡한 도메인 시스템에서 쓰기 규칙과 읽기 성능이 서로를 잡아당기는 충돌을 풀어냅니다.
▶아키텍처 다이어그램
📊 데이터 흐름 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
주문 처리 화면을 만들 때, 주문 생성은 재고 확인, 결제 검증, 상태 전이 규칙 같은 복잡한 도메인 로직을 거쳐야 합니다. 반면 주문 목록 화면은 고객명, 상품명, 금액, 배송 상태를 한 번에 보여줘야 해서 여러 테이블을 조인해야 합니다. 하나의 모델로 이 둘을 함께 감당하려 하면, 쓰기 규칙을 반영한 엔티티 구조가 읽기용 조인을 느리게 만들고, 읽기를 빠르게 하려고 구조를 바꾸면 도메인 검증이 어색해집니다. 캐시를 붙여봐도 모델 불일치 자체는 해결되지 않고, 기능이 늘수록 이 충돌이 더 자주 나타납니다.
초창기 엔터프라이즈 시스템은 단일 데이터베이스에 읽기와 쓰기를 함께 맡겼고, 복잡한 도메인 로직은 저장 프로시저와 트리거로 처리하는 경우가 많았습니다. 도메인 모델 설계가 발전하면서 ORM과 집계 루트(Aggregate Root) 개념이 도입됐지만, 화면 요구는 점점 다양해지고 하나의 엔티티 모델이 읽기와 쓰기 모두를 만족시키기 어려워졌습니다. 이벤트 소싱과 메시지 기반 아키텍처가 현실적인 선택지가 되면서, 읽기 모델을 이벤트 스트림으로 따로 투영(projection)하는 접근이 가능해졌습니다. 이 흐름 속에서 CQRS는 성능 튜닝 기법이 아니라, 서로 다른 질문에 서로 다른 모델로 답하겠다는 설계 원칙으로 자리를 잡았습니다.
쓰기 경로(Command)는 상태 변경 요청을 받아 도메인 검증과 비즈니스 규칙을 통과시킨 뒤 저장소에 반영합니다. 이때 상태 변경 사실을 이벤트로 남기기도 합니다. 읽기 경로(Query)는 그 이벤트나 변경 사실을 받아 조회에 최적화된 별도 저장소나 뷰 모델로 비동기 투영합니다. 읽기 모델은 화면 단위로 설계해도 되고 여러 화면이 공유해도 됩니다. 결과적으로 두 경로는 같은 데이터를 다루지만 각자 자신의 질문에 맞는 구조를 유지하고, 동기화는 이벤트나 프로젝션 파이프라인이 담당합니다.
읽기 성능 개선이 목표라면 캐싱과 CQRS 사이에서 고민하게 됩니다. 캐싱은 기존 읽기 모델을 그대로 두고 반복 조회를 빠르게 만드는 기법이라, 모델 구조 자체는 바뀌지 않습니다. CQRS는 읽기 모델 자체를 조회 패턴에 맞게 다시 설계하므로, 캐시로는 해결되지 않는 구조적 불일치를 다룰 수 있습니다. 쓰기 규칙이 단순하거나 읽기 패턴이 일정한 시스템이라면 CQRS의 부담이 오히려 커질 수 있고, 그 경우 캐싱이나 단순 읽기 복제가 더 적합합니다.
읽기 화면이 매우 다양하고 빠른 응답이 필요하지만 쓰기 쪽은 복잡한 도메인 검증이 중요한 시스템, 예를 들어 주문 관리, 금융 거래, 콘텐츠 피드처럼 사용자마다 보는 형태가 다른 곳에서 CQRS가 자주 등장합니다. '읽기 쿼리가 점점 느려지는데 쓰기 로직을 건드리지 않고 싶다'거나 '화면마다 필요한 데이터 형태가 달라서 조인이 복잡해지고 있다'는 신호가 보이면 검토할 만합니다. 다만 읽기와 쓰기 저장소를 따로 운영하면 최종 일관성(eventual consistency) 구간이 생기고, 프로젝션 파이프라인 장애 시 읽기 모델이 뒤처질 수 있습니다. 단순 조회 개선만 필요한 시스템에 도입하면 운영 복잡도가 불필요하게 올라갑니다.