CQRS(Command Query Responseibility Segregation)패턴

CQRS (Command Query Responsibility Segregation)는 데이터의 조회(Query)와 업데이트(Command)에 대한 책임을 명확히 분리하는 아키텍처 패턴입니다.

이 구조는 복잡한 비즈니스 애플리케이션의 성능, 유지보수성, 그리고 확장성을 극대화하기 위해 등장했는데요. 본질적으로, CQRS는 데이터베이스의 읽기(Read)와 쓰기(Write) 작업을 분리하는 전략을 채택함으로써, 효율적인 데이터 처리와 관리를 가능하게 해줍니다.

이 패턴을 적용함으로써, 단순히 데이터를 조회하는 데에 최적화된 데이터베이스와, 생성(Create), 갱신(Update), 삭제(Delete) 작업을 수행하는 데이터베이스를 별도로 운영할 수 있게 됩니다.

이는 쿼리 수행 시 CUD 작업에 따른 락(lock) 경쟁을 최소화하며 성능상의 이점을 제공합니다. 추가적으로, 조회 전용 데이터베이스는 스키마 재구성, 비정규화, 인덱싱 최적화 등을 통해 읽기 성능을 극대화할 수 있으며, 이는 업데이트 작업을 처리하는 서비스의 부하도 상당히 줄여줍니다.

CQRS In MSA

데이터베이스를 독립적으로 운영하고, 서비스의 책임을 분리한다는 이 사실. 굉장히 익숙하게 느껴지시지 않나요. 맞습니다. MSA와 짝짜꿍이 좋은 설계 패턴입니다. 예시를 만들어 볼까요.

예시

게시글을 작성하는 서비스가 있다고 했을때, 다음 그림과 같이 설계했다고 가정하겠습니다.

스크린샷 2024-04-18 오후 10 41 16

게시글을 작성하고, 수정하고, 삭제하는 서비스인 post-command-service와 그에 연결된 post-command 데이터베이스가 있구요.

게시글을 조회하는 서비스인 post-query-service와 그에 연결된 post-query 데이터베이스가 있습니다.

참고로 두 데이터베이스의 테이블 구성은 같지만, 경우에따라 query(response)에 적합한 스키마와 데이터 타입을 설정할 수 있습니다. 또, 추가로 인덱싱이나 캐싱을 세팅할 수 있겠죠.

그럼 이제 데이터베이스 싱크를 맞추기 위한 프로세스를 설계 해보겠습니다.

스크린샷 2024-04-18 오후 11 35 19

프로세스는 다음과 같습니다.

  1. post-command-service가 사용자의 요청을 받아 post-command데이터베이스에 CUD(Create, Update, Delete) 등의 쓰기 작업을 수행합니다.
  2. post-command-service는 작업이 발생한 내용을 이벤트로 발행해 Kafka에 전송합니다.
  3. post-query-service는 해당 토픽을 구독하고 있다가 컨슘합니다.
  4. post-query-service는 컨슘한 이벤트를 통해 post-query 데이터베이스에 CUD(Create, Update, Delete) 등의 쓰기 작업을 수행하여 싱크를 맞춥니다.
  5. 사용자는 post-query-service를 통해 post-query데이터베이스의 데이터를 읽습니다.

프로세스를 보다보면 자연스레 생기는 의문점들이 있습니다.

  1. post-query-sevice는 읽기작업만 한다고 해놓고, 결국엔 쓰기작업도 해야되네?
  2. 데이터베이스 동기화 과정중에 실패하면 어떡하지?
  3. 카프카가 제대로 데이터 스트리밍을 못하면 어떡하지?

등등.. 의문이 생기실텐데요. 첫번째 의문점에 대한 제 생각은 예스입니다. ‘서버 부하 경감’ 관점과 CQRS 패턴은 큰 상관관계에 있는것은 아닙니다. CQRS 패턴의 목적을 다시 정리하면서 생각해보겠습니다.

성능 최적화

  • 읽기 작업: 읽기 모델은 조회 성능을 최적화하기 위해 구조화될 수 있습니다. 예를 들어, 자주 사용되는 쿼리에 맞춰 데이터를 집계하거나 인덱싱할 수 있습니다.
  • 쓰기 작업: 쓰기 모델은 트랜잭션 무결성과 도메인 이벤트 처리에 초점을 맞출 수 있습니다. 이는 복잡한 비즈니스 로직을 처리하는 데 도움이 됩니다.

확장성 향상

  • 명령과 쿼리 요구 사항이 다르기 때문에, 각각의 서비스를 독립적으로 확장할 수 있습니다. 이는 특히 대규모 시스템에서 효과적입니다.

유연성과 유지보수성

  • 서비스가 명확하게 분리되어 있어 각 컴포넌트를 독립적으로 개발하고 유지보수할 수 있습니다. 이는 또한 기능을 업데이트하거나 수정할 때 발생할 수 있는 리스크를 감소시킵니다.

CQRS는 직접적으로 서버 부하 감소를 목적으로 하는 것은 아니지만, 읽기와 쓰기 작업의 효율적 분리를 통해 간접적으로 부하를 분산시킬 수 있습니다. 예를 들어, 읽기 모델을 별도로 스케일링하여 쿼리 요청에 더 많은 리소스를 할당하고, 쓰기 모델은 비즈니스 로직 처리에 집중할 수 있습니다. 이는 특히 읽기가 많은 애플리케이션에서 읽기 성능을 크게 향상시킬 수 있습니다.

첫번째 의문점에 대하여 더 좋은 생각이나 관점, 해결책이 있으시다면 댓글 부탁드립니다용

2,3번째 의문에 관한 것은 Kafka를 통해 해결책을 제시할 수 있습니다. 다음 포스팅 때, Spring Cloud와 EDA, CQRS 패턴에서 Kafka를 어떻게 사용하는지 코드와 함께 포스팅 하도록 하겠습니다.