CQRS 란?

GwanMtCat·2023년 11월 8일
0

전 회사를 다니면서 CQRS를 적용하여 명령과 조회 모델을 구분하고, 데이터베이스 replication으로 slave DB를 조회전용으로 사용하였는데 남들한테 이걸 설명하자니 어떻게 설명해야 할지 모르겠다.

최범균님의 유튜브를 통해 정의를 할 수 있었다.


CQRS (Command Query Responsibility Segregation) 란?

명령 (시스템 데이터 변경) 역할을 수행하는 구성 요소와
쿼리 (시스템 데이터 조회) 역할을 수행하는 구성 요소를 나누는 것을 CQRS이라고 한다.

여기서 구성 요소란 적용 범위에 따라

  • 클래스, 함수
  • 모듈, 패키지
  • 웹서버(프로세스), 데이터베이스
    가 될 수 있다.

예로 들면 다음과 같이

조회를 위한 MemberQueryApi 는 MemberDao, MemberData 라는 조회 모델(클래스)로 분리하고,

변경을 위한 MemberCommandAPI 는 Member라는 변경 모델로 분리하는 것이다.


왜 분리해야 할까?

생각해보면 그렇다. 그냥 조회와 변경을 위한 모델을 그냥 한 모델로 사용하면 되지 않을까? 괜히 클래스 숫자만 더 늘어나니 관리 비용만 더 늘어난 것 같다.

예로 위 그림 처럼 Member 라는 모델을 조회,변경 모델로 동시에 사용하면 작업하기 매우 쉽지 않을까?

답은 아니다. 이유는 다음과 같다.

  • 코드 역할/책임 모호
    • 이 클래스에서 데이터를 조회하는 필드는 뭐고, 변경하는 필드는 무엇일까?
  • 의미/가독성 등 나빠짐
    • 이 클래스는 어떤 역할을 하는지 이해하기 어렵다.
  • 시스템이 복잡해질수록 유지보수성이 떨어진다.
    • 시스템이 복잡해질수록, 모델도 복잡해지고 그럼 문제는 더 커진다.

예를 들어 JPA를 사용한다고 해보자.
그럼 주문을 취소하고, 주문의 목록을 조회하기 위해서는 어떤 데이터를 즉시 불러오고(eager) 어떤 데이터는 나중에 불러와야 할까? (lazy)

명령과 쿼리는 즉 다루는 데이터가 다르다.
복잡해지는 시스템에서 명령과 쿼리의 단일 모델을 사용하는 경우에는 유지보수성이 떨어질 수 밖에 없다.

위 그림의 경우
명령은 한 영역의 데이터, Order, OrderLine 만 다룬다.
쿼리는 여러 영역의 데이터, Order, User,Item 영역의 데이터를 다룬다.

명령과 쿼리는 코드 변경 빈도와 사용자가 다르다.

예로

  • 백오피스의 주문 목록 조회 기능
  • 사용자의 주문 기능

는 사용자도 다르고, 코드 변경 빈도가 다를 수 잇다.

변경 빈도가 다른 기능이 한 코드에 있으면
서로 다른 이유로 코드가 바뀌고, 이는 곧 책임의 크기가 적당하지 않다는 것을 의미한다.

또 기능마다 성능 요구가 다르다.

예로 조회인

  • 사용자의 상품 목록 조회, 상품 상세 조회
    • 트래픽이 상대적으로 많은 시간대가 있으니 빨리 보여줘야 한다.
    • 상품 목록이 늦게 나오면 고객이 가버린다.

명령인

  • 사용자의 댓글 등록
  • 사용자의 주문
  • 백오피스의 판매 수치

등은 조금 늦게 동작해도 대세에 크게 지장이 없다.

즉, 기능마다 서로 다른 성능 향상 방법이 필요하다.
근데 단일 모델로는 다른 성능 향상 방법을 적용하기가 어렵다.

즉 이것을 위해 명령과 쿼리를 구분하는 것이다.


구현 형태

CQRS는 여러형태로 구현을 할 수 있다.
크게 명령과 쿼리 모델이

같은 프로세스를 사용하느냐, 다른 프로세스를 사용하느냐
같은 데이터베이스를 사용하느냐, 다른 데이터베이스를 사용하느냐에 따라서 구현 형태가 달라질 수 있다.

코드 수준에서만 명령과 쿼리를 구분하고, 데이터베이스에서는 분리하지 않는 것이다.
가장 단순하고 트랜잭션 처리가 매우 쉽다.

프로세스와 데이터베이스는 같지만 테이블이 다른 형태이다.
명령과 쿼리가 코드 수준에서 분리되고, 데이터 수준에서도 분리되는 방식이다.

다만, 데이터가 같은 데이터베이스에 있는 형태로 쿼리 전용 테이블로 별도 분리하는 것이다.
보통 이 방식에서는 명령이 상태를 변경하면 쿼리 전용 테이블을 함께 변경한다.

같은 프로세스 다른 데이터베이스를 사용하는 형태이다.
상품 목록을 레디스와 같은 저장소에 캐싱하고, 쿼리 모델은 레디스를 사용하는 것이다.
이 방식에서는 명령이 데이터를 변경하면, 변경 내용을 쿼리쪽 데이터베이스에 내용을 전파한다.

다음은 프로세스도 다르고 데이터베이스도 다르다.
전과 같이 명령이 데이터를 변경한다면, 변경 내용을 쿼리쪽 데이터베이스에 전파하여야 한다.

명령과 쿼리를 다른 데이터베이스를 사용하게 되면, 명령의 변경 내용을 쿼리쪽 데이터베이스에 내용을 전파해줘야 한다.

방법으로는 다음과 같은 방법들이 있다.

CDC는 뭘까?

명령과 쿼리에 다른 데이터베이스를 사용하면 데이터를 전파하는 방식이 다양해지는데 이때 주의 사항이 있다.

첫번째는 데이터 유실로, 데이터 유실을 어느정도 허용하냐에 따라서 데이터베이스 트랜잭션 범위가 중요해진다.
예로 주문 목록 쿼리의 경우 데이터가 유실되면 곤란하지만, 최근 인기글 쿼리 목록 그렇게 문제가 되는것은 아니다.

두번째는 명령의 변경 내용을 얼마나 쿼리쪽에 반영해야하느냐에 따라서 구현의 선택이 달라질 수 있다.

세번쨰는 중복 전달로, 쿼리쪽 데이터베이스로 변경된 데이터를 전달하는 과정에서 문제가 발생하면 다시 전달할 수 있는 수단이 필요하다.
다시 전달할 수 있는 수단을 만들다보면 쿼리쪽에 이미 반영된 데이터를 중복으로 전달하는 일이 생겨날 수 있다.
중복으로 전달하더라도 쿼리쪽 데이터에 문제가 생기지 않도록 별도의 처리가 필요하다.

이외에도 여러가지 주의사항이 있다. 범균님 이와 관련해서 메시징과 관련된 글을 찾아보면 도움이 될거라고 하셨다.


참조한 책 및 사이트

https://youtu.be/xf0kXMTFJm8
https://rastalion.me/aurora-for-mysql%EC%97%90%EC%84%9C-cdc%EB%A5%BC-%EC%A4%80%EB%B9%84%ED%95%98%EB%8A%94-%EA%B3%BC%EC%A0%95/

0개의 댓글