CQRS란? 명령과 조회를 분리하는 이유

CQRS란 무엇인가

CQRS는 ‘Command Query Responsibility Segregation’의 약자로, 우리말로는 ‘명령과 조회의 책임 분리’라고 부릅니다. 이름 그대로 데이터를 변경하는 작업(Command)과 데이터를 조회하는 작업(Query)을 서로 다른 모델로 분리하는 설계 패턴입니다. 일반적인 CRUD 방식에서는 하나의 모델이 생성, 수정, 삭제, 조회를 모두 처리하지만, CQRS에서는 ‘쓰기 전용 모델’과 ‘읽기 전용 모델’을 완전히 분리해서 설계합니다.

비유하자면 음식점의 주방과 홀 직원을 나누는 것과 비슷합니다. 주문을 받아 요리를 만드는 주방(Command)과, 손님에게 메뉴판을 보여주고 안내하는 홀 직원(Query)의 역할이 다르듯, CQRS도 데이터를 바꾸는 로직과 데이터를 보여주는 로직을 분리해서 각자 최적화된 방식으로 동작하게 만듭니다.

동작 원리와 예시

일반적인 서비스에서는 ‘주문 서비스’ 하나가 주문 생성, 주문 조회, 주문 취소를 모두 처리합니다. 하지만 CQRS를 적용하면 다음처럼 구조가 나뉩니다.

  • Command 모델: 주문 생성, 결제 처리, 재고 차감 등 상태를 변경하는 로직 담당. 정합성과 트랜잭션이 중요해서 정규화된 DB 구조를 사용하는 경우가 많습니다.
  • Query 모델: 주문 목록 조회, 상세 조회 등 읽기 전용 로직 담당. 조회 속도가 중요해서 비정규화된 테이블이나 캐시, 별도의 조회 전용 DB를 사용하기도 합니다.

예를 들어 쇼핑몰에서 주문을 생성할 때는 OrderCommandService.createOrder()가 호출되어 재고 확인, 결제, DB 저장 같은 복잡한 로직을 처리합니다. 반면 주문 내역을 볼 때는 OrderQueryService.getOrderList()가 호출되어 이미 가공된 조회 전용 테이블에서 빠르게 데이터를 가져옵니다. 두 모델은 이벤트를 통해 데이터를 동기화하기도 하는데, 이 경우 즉시 반영되지 않고 약간의 지연이 발생할 수 있습니다. 이를 ‘최종 일관성(Eventual Consistency)’이라고 부릅니다.

실무에서 CQRS를 쓰는 이유

실무에서 CQRS를 도입하는 가장 큰 이유는 읽기와 쓰기의 트래픽 패턴이 다르기 때문입니다. 대부분의 서비스는 조회 요청이 쓰기 요청보다 훨씬 많습니다. 하나의 모델로 두 요청을 모두 처리하면 조회 성능을 높이기 위한 최적화가 쓰기 로직에 영향을 주거나, 반대로 복잡한 쓰기 검증 로직 때문에 조회 쿼리가 무거워지는 문제가 생깁니다.

CQRS를 적용하면 각 모델을 독립적으로 확장할 수 있습니다. 조회 트래픽이 몰리면 읽기 전용 DB만 스케일 아웃하면 되고, 쓰기 로직은 트랜잭션 안정성에만 집중해서 설계할 수 있습니다. 다만 모든 서비스에 CQRS가 필요한 것은 아닙니다. 시스템이 단순하다면 오히려 구조만 복잡해지고 동기화 이슈까지 관리해야 하는 부담이 생기므로, 트래픽 규모나 도메인 복잡도를 보고 신중하게 도입 여부를 판단해야 합니다.

관련 글

댓글 남기기