포스트

Kafka를 활용한 CQRS (2) - CQRS

Kafka를 활용한 CQRS (2) - CQRS

1. 개요

CQRS(Command and Query Responsibility Segregation)는 데이터 저장소로부터 읽기와 업데이트 작업을 분리하는 아키텍처 패턴이다. 이름 그대로 상태를 변경하는 Command와 상태를 조회하는 Query의 책임을 나눠서, 하나의 데이터 모델이 조회와 수정을 모두 떠맡던 기존 방식의 한계를 해결하는 것이 목표다. 이렇게 읽기·쓰기 책임을 나누면 애플리케이션의 퍼포먼스와 확장성, 보안성을 각각 최적화할 수 있고, 여러 요청에서 동시에 들어오는 업데이트 명령들 사이의 충돌도 방지할 수 있다. Kafka는 이 패턴에서 Command가 발생시킨 이벤트를 Query 모델로 전파하는 메시징 계층으로 흔히 쓰인다.


2. 기존 방식의 문제점

전통적인 아키텍처에서는 데이터베이스에서 데이터를 조회하고 업데이트하는 데 같은 데이터 모델을 사용한다. 단순한 CRUD 수준에서는 문제가 없지만, 애플리케이션이 복잡해질수록 이 방식은 다음과 같은 문제를 낳는다.

  • 모델의 책임 과다: 조회 시 필요한 데이터 형태가 저마다 다르면, 각기 다른 DTO(Data Transfer Object)를 반환하는 다양한 쿼리를 작성하고 매핑해야 한다. 반대로 쓰기·업데이트 시에는 복잡한 유효성 검사와 비즈니스 로직을 수행해야 하는데, 이 모든 걸 하나의 모델이 담당하면 지나치게 많은 일을 하는 비대한 모델이 된다.
  • 읽기/쓰기의 부하 불균형: 읽기와 쓰기의 부하는 일반적으로 동일하지 않아서 서로 다른 성능 요구사항을 갖는데, 같은 모델을 쓰면 둘을 따로 최적화하기 어렵다.
  • 불필요한 데이터 업데이트: 읽기와 쓰기 작업에서 사용되는 데이터 표현이 서로 일치하지 않는 경우가 많아, 일부 작업에서는 필요하지도 않은 컬럼이나 속성까지 함께 업데이트해야 하는 상황이 생긴다.
  • 데이터 경합: 동일한 데이터 세트에 대해 병렬로 작업이 수행될 때 데이터 경합이 발생할 수 있다.
  • 조회 성능 저하: 정보 조회를 위해 복잡한 쿼리(조인 등)가 요구되는 경우 성능에 부정적인 영향을 줄 수 있다.

3. 해결책: Command와 Query의 분리

CQRS는 읽기와 쓰기를 각각 다른 모델로 분리해서, Command로 데이터를 수정하고 Query로 데이터를 조회하게 한다.

  • Command는 데이터 중심이 아니라 작업(task) 중심이어야 한다. 즉 “필드를 이렇게 바꿔라”가 아니라 “주문을 취소하라”처럼 도메인 행위를 표현하는 것이 이상적이다.
  • Command는 동기적으로 처리하기보다는 비동기적으로 큐에 쌓인 뒤 처리하는 방식이 흔히 쓰인다.
  • Query는 절대로 DB를 수정하지 않으며, 어떤 도메인 로직도 캡슐화하지 않은 순수한 DTO만을 반환한다.
  • 읽기와 쓰기를 완전히 분리하면 각 작업에 특화된 데이터베이스를 따로 사용하는 것도 가능해진다. 다만 이 경우 ORM 툴로 DB 스키마를 통해 모델을 자동 생성하는 방식은 쓰기 어려워진다는 단점이 있다.

4. 장점

장점설명
독립적인 스케일링읽기와 쓰기의 부하가 다르므로, 각 모델을 서로 다른 규모로 독립적으로 확장할 수 있다
최적화된 데이터 스키마읽기 저장소는 조회에 최적화된 스키마를, 쓰기 저장소는 쓰기에 최적화된 스키마를 각각 사용할 수 있다
보안Command와 Query의 권한을 분리해서 관리할 수 있어 보안 측면에서 유리하다
관심사 분리복잡한 비즈니스 로직은 쓰기 모델에 몰리고, 읽기 모델은 상대적으로 간단한 조회 로직만 갖는다
간단한 쿼리읽기 저장소에 materialized view를 두면 복잡한 조인문 없이도 빠르게 조회할 수 있다

5. 구현 이슈

  • 복잡성: 아이디어 자체는 단순하지만, 이벤트 소싱 패턴을 함께 도입할 경우 시스템이 훨씬 복잡해질 수 있다.
  • 메시징: Command를 수행한 뒤 업데이트 이벤트를 발행해 읽기 모델에 반영하는 방식이 보편적인데, 이 경우 애플리케이션이 메시지 실패나 중복 메시지 처리를 반드시 책임져야 한다. Kafka 같은 메시지 브로커가 바로 이 역할을 맡는다.
  • 데이터 일관성: 쓰기 모델과 읽기 모델이 물리적으로 분리되어 있으므로, 두 모델 사이의 데이터가 즉시 일치하지 않는 최종 일관성(eventual consistency) 문제를 고려해야 한다.

6. 언제 사용해야 할까

도입을 고려해야 하는 경우권장되지 않는 경우
많은 사용자가 동일한 데이터에 병렬로 접근하는 경우도메인이나 비즈니스 로직이 단순한 경우
복잡한 프로세스나 도메인 모델에 따라 흐르는 작업 기반 UI가 필요한 경우단순한 CRUD로 충분한 경우
읽기 성능과 쓰기 성능을 서로 독립적으로 조정해야 하는 경우 
쓰기와 읽기를 명확히 분리해서 구현하려는 경우 
하나의 시스템이 장기간 여러 버전으로 유지·수정되는 경우 
다른 시스템과의 통합이 필요한 경우 

7. 정리

CQRS는 하나의 모델이 조회와 수정을 모두 책임지던 전통적인 방식에서 벗어나, Command(쓰기)와 Query(읽기)의 책임을 분리해 각각을 독립적으로 최적화하는 패턴이다. 그 대가로 두 모델 사이의 데이터 일관성을 관리하고 메시지 실패·중복을 처리해야 하는 복잡성이 늘어나기 때문에, 병렬 접근이 많거나 읽기·쓰기 부하가 크게 다른 시스템처럼 실제로 그 이점을 누릴 수 있는 곳에 선택적으로 적용하는 것이 중요하다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.