반응형

 

 

CQRS(Command Query Responsibility Segregation) 패턴

  • CQRS는 데이터 저장소에 대한 읽기 및 업데이트 작업을 구분하는 패턴인 명령과 쿼리의 역할 분리를 의미한다.
  • 애플리케이션에서 CQRS를 구현하면 성능, 확장성 및 보안을 최대화할 수 있다.
    • 예) 읽기 작업이 많이 발생하면 읽기 작업에 대한 확장, 쓰기 작업은 쓰기 작업 확장
  • CQRS로 마이그레이션하면 유연성이 생기므로 시스템이 점점 진화하고 업데이트 명령이 도메인 수준에서 병합 충돌을 일으키지 않도록 할 수 있다.
  • 기존의 접근 방식은 데이터 저장소와 데이터 액세스 계층에 가해지는 부하뿐 아니라 정보를 검색하는 데 필요한 쿼리의 복잡성으로 인해 성능에 좋지 않은 영향을 미칠 수 있다. 이러한 경우 CQRS로 성능을 개선할 수 있다.
    • 예) 쓰기와 읽기 작업이 동일한 테이블에서 발생

 

문제점

  • 작업의 일부로 필요하지 않더라도 정확하게 업데이트되어야만 하는 추가 열이나 속성과 같이 데이터의 읽기와 쓰기 표현 사이에 불일치가 나타나는 경우가 있다.
  • 동일한 데이터 세트에서 작업을 병렬로 수행할 때 데이터 경합이 발생할 수 있다.
  • 엔터티 각각이 읽기와 쓰기 작업의 대상으로 잘못된 컨텍스트에 데이터를 노출시킬 수 있기 때문에 보안 및 권한 관리가 더 복잡해질 수 있다.

 

해결 방법

  • CQRS는 데이터를 업데이트하는 명령 및 데이터를 읽는 쿼리를 사용하여 읽기 및 쓰기를 다른 모델로 구분한다.
  • 명령은 데이터 중심이 아닌 작업을 기반으로 해야 한다.
    • 사용자 상호 작용 스타일을 약간 변경해야 할 수 있다. 그 중 다른 부분은 더 자주 성공하도록 해당 명령을 처리하는 비즈니스 논리를 수정하는 것이다.
    • 이를 지원하는 한 가지 방법은 명령을 보내기 전에 클라이언트에서 몇 가지 유효성 검사 규칙을 실행하는 것이다. 버튼을 사용하지 않도록 설정하여 UI에서 이유를 설명하도록 한다. (예 : "남은 방 없음"). 이러한 방식으로 서버 쪽 명령 오류의 원인은 경합 조건(마지막 방을 예약하려는 두 명의 사용자)으로 좁힐 수 있으며, 경우에 따라 더 많은 데이터와 논리를 사용하여 해결할 수 있다(예 :대기자 목록에 게스트 배치).
  • 명령은 동기적으로 처리되지 않고 비동기 처리를 위해 큐에 배치될 수 있다.
  • 쿼리는 데이터베이스를 수정하지 않는다. 쿼리는 도메인 정보를 캡슐화하지 않는 DTO를 반환한다.

 

CQRS 모델(하나의 DB에서 읽기 쓰기 스키마 분리)

  • 별도의 쿼리 및 업데이트 모델을 사용하면 디자인 및 구현이 간소화된다.
  • 단점은 O/RM 도구와 같은 스캐폴딩 메커니즘을 사용하여 데이터베이스 스키마에서 CQRS 코드를 자동으로 생성할 수 없다.
  • 더 높은 격리 수준을 위해 쓰기 데이터에서 읽기 데이터를 물리적으로 구분할 수 있다. 이 경우에 읽기 데이터베이스는 쿼리에 대해 최적화된 고유한 데이터 스키마를 사용할 수 있다.
  • 예를 들어 복잡한 조인이나 복잡한 O/RM 매핑을 방지하기 위해 데이터의 구체화된 뷰를 저장할 수 있다.

 

스캐폴딩(Scaffolding)은 프로그래밍에서 새로운 프로젝트나 모듈을 시작할 때, 초기 구조와 설정을 자동으로 생성해주는 과정이나 도구를 가리킨다. 이는 개발자가 처음부터 모든 것을 수동으로 설정하고 작성하는 번거로움을 덜어주고, 더 빠르게 개발을 시작할 수 있도록 도와준다.

 

CQRS 모델(읽기 쓰기 작업을 물리적으로 DB 분리)

  • 읽기 저장소는 쓰기 저장소의 읽기 전용 복제본이거나 읽기 및 쓰기 저장소가 전혀 다른 구조일 수 있다.
    • 예) 쓰기 데이터베이스는 관계형 데이터베이스이고 읽기 데이터베이스는 문서 데이터베이스일 수 있다.
    • 읽기 전용 복제본을 여러 개 사용하면 특히 읽기 전용 복제본이 애플리케이션 인스턴스에 가깝게 위치하는 분산 시나리오에서 쿼리 성능을 높일 수 있다.
  • 읽기 및 쓰기 저장소를 분리하면 부하를 감안해 각 저장소를 적절하게 확장할 수도 있다.
    • 예를 들어 보통 읽기 저장소는 쓰기 저장소보다 부하가 훨씬 더 높다. 이러한 경우 읽기 저장소만 확장하여 리소스를 적절하게 관리할 수 있다.
  • 읽기 및 쓰기 데이터베이스를 분리해서 사용하는 경우 동기화를 유지해야 한다.
  • 일반적으로 동기화는 데이터베이스를 업데이트할 때마다 쓰기 모델에서 이벤트를 발행함으로써 수행된다.
  • 메시지 브로커 및 데이터베이스는 일반적으로 단일 분산 트랜잭션에 등록할 수 없으므로 데이터베이스를 업데이트하고 이벤트를 게시할 때 일관성을 보장하는 데 문제가 있을 수 있다.

 

CQRS의 이점

  • 독립적인 크기 조정
    • CQRS를 통해 읽기 및 쓰기 워크로드를 독립적으로 확장하고 더 적은 수의 잠금 경합이 발생할 수 있다.
  • 최적화된 데이터 스키마
    • 읽기 쪽에서는 쿼리에 최적화된 스키마를 사용하는 반면 쓰기 쪽에서는 업데이트에 최적화된 스키마를 사용할 수 있다.
  • 보안
    • 올바른 도메인 엔터티만 데이터에서 쓰기를 수행할 수 있는지 쉽게 확인할 수 있다.
  • 관심사의 분리
    • 읽기 및 쓰기 쪽을 구분하면 유지 가능하고 유연한 모델을 생성할 수 있다. 대부분의 복잡한 비즈니스 논리는 쓰기 모델로 이동합니다. 읽기 모델은 상대적으로 간단할 수 있다.
  • 단순한 쿼리
    • 읽기 데이터베이스에서 구체화된 뷰를 저장하여 쿼리할 때 애플리케이션은 복잡한 조인을 방지할 수 있다.

 

구현 문제 및 고려 사항

  • 복잡성
    • 이벤트 소싱 패턴을 포함하는 경우에 특히 더 복잡한 애플리케이션 디자인을 만들 수 있다.
  • 메시징
    • CQRS에 메시징이 필요하지 않지만 명령을 처리하고 업데이트 이벤트를 게시하는 데 공통적으로 메시징을 사용한다.이 경우에 애플리케이션은 메시지 오류 또는 중복 메시지를 처리해야 한다.
  • 결과적 일관성
    • 읽기 및 쓰기 데이터베이스를 구분하는 경우 읽기 데이터는 기한이 경과되었을 수 있다.
    • 읽기 모델 저장소는 쓰기 모델 저장소의 변경 사항을 반영하도록 업데이트되어야 한다.
    • 사용자가 오래된 읽기 데이터를 기반으로 요청을 발급하면 변경된 사항을 검색하기 어려울 수 있다.

 

CQRS가 권장되는 상황

  • 많은 사용자가 동일한 데이터에 병렬로 액세스하는 경우
  • 읽기 수가 쓰기 수보다 훨씬 큰 경우 데이터 읽기의 성능을 데이터 쓰기 성능과 별도로 미세 조정해야 하는 경우
    • 이 시나리오에서는 읽기 모델을 스케일 아웃할 수 있지만 몇 가지 인스턴스에서만 쓰기 모델을 실행할 수 있다. 소수의 쓰기 모델 인스턴스는 병합 충돌 발생을 최소화하는 데도 기여한다.
  • 개발자 중 한 팀은 쓰기 모델에 포함되는 복잡한 도메인 모델에 집중하고 또 한 팀은 읽기 모델과 사용자 인터페이스에 집중할 수 있는 환경의 경우
  • 시스템이 시간이 지나면서 진화할 것으로 예상되어 여러 버전의 모델을 포함할 수 있거나 비즈니스 규칙이 정기적으로 변하는 경우
  • 이벤트 소싱과 조합해 다른 시스템과 통합하는 경우
    • 이때 하위 시스템 하나의 일시적인 장애가 다른 시스템의 가용성에 영향을 주지 않아야 한다.

 

CQRS가 권장되지 않는 상황

  • 도메인 또는 비즈니스 규칙이 간단한 경우
  • 간단한 CRUD 스타일로 사용자 인터페이스 및 데이터 액세스 작업이 충분한 경우

 

 

참고

반응형

+ Recent posts