Notepad

마이크로서비스 쿼리 구현

핵심 내용

  • 마이크로서비스 아키텍처에서 데이터를 쿼리하기 어려운 이유
  • API 조합 패턴을 응용한 쿼리 구현
  • CQRS 패턴을 응용한 쿼리 구현

마이크로서비스 아키텍처에서 두 가지 쿼리 구현 패턴

  • API 조합 패턴
    • 서비스 클라이언트가 데이터를 가진 여러 서비스를 직접 호출하여 그 결과를 조합하는 패턴
    • 가장 단순한 방법으로 가급적 이 방법을 쓰는 것이 좋음
  • CQRS(커맨드 쿼리 책임 분산) 패턴
    • 쿼리만 지원하는 하나 이상의 뷰 전용 DB를 유지하는 패턴
    • API 조합 패턴보다 강력한 만큼 구현하기 복잡함

API 조합 패턴 응용 쿼리

예시: 주문 정보 조회

  • 조회 정보: 주문과 주방, 배달, 회계 정보가 포함된 주문 정보
  • 모놀리틱 애플리케이션
    • 하나의 DB에서 SELECT 문으로 여러 테이블을 조인해서 주문 내역 조회
  • 마이크로서비스 아키텍처
    • 주문과 주방, 배달, 회계 서비스에서 데이터 요청

API 조합 패턴 개요

  • API 조합 패턴
    • API 조합기와 둘 이상의 프로바이더 서비스로 구성
    • 여러 서비스에 있는 데이터를 API를 통해 조회하고 그 반환 결과를 조합하여 쿼리 구현
  • API 조합 패턴의 두 종류 참여자
    • API 조합기: 프로바이더(provider, 제공자) 서비스를 쿼리하여 데이터 조회
    • 프로바이더 서비스: 최종 결과로 반활할 데이터의 일부를 갖고 잇는 서비스

API 조합 패턴으로 findOrder() 쿼리 구현

  • 주문과 주방, 배달, 회계 서비스 모두 oredrId를 키로 가지고 있는 데이터이며, 총 4개의 서비스를 호출한 결과를 조합
  • API 조합기: GET /orders/{orderId}
  • 프로바이더 서비스
    • 주문 서비스: GET /orders/{orderId}
    • 주방 서비스: GET /tickets?orderId={orderId}
    • 배달 서비스: GET /deliveries?orderId={orderId}
    • 회계 서비스: GET /charges?orderId={orderId}

API 조합 설계 이슈

  • API 조합 설계 패턴의 두 가지 설계 이슈
    • 어느 컴포넌트를 쿼리 작업의 API 조합기로 선정할 것인가?
    • 어떻게 해야 효율적으로 취합 로직을 작성할 것인가?
  • API 조합기 역할 선정에 대한 세가지 옵션
    • 서비스 클라이언트를 API 조합기로 임명
      • 클라리언트들이 동일 LAN에 실행중이라면 좋음
      • 클라이언트가 방화벽 외부에 있고 네트워크가 느리다면 실용적이지 않음
    • API 게이트웨이를 API 조합기로 만드는 것
      • API 게이트웨이에 API 조합 로직을 구현하여 각각의 외부 API의 호출 결과를 API 게이트웨이에서 조합
      • 쿼리 작업이 애플리케이션의 외부 API 중 일부라면 이 방법이 타당
    • API 조합기를 스탠드얼론 서비스로 구현(예: 주문 검색 서비스)
      • 내부적으로 여러 서비스가 사용하는 쿼리 작업에 좋은 방법
      • 취합 로직이 너무 복잡해서 API 게이트웨이를 일부로 만들기 곤란하고 외부에서 접근 가능한 쿼리 자겁을 구현할 경우에도 좋음
  • API 조합기는 리액티브 프로그래밍 모델을 사용해야 한다
    • 지연 시간을 최소화하기 위해 API 조합기가 프로바이더 서비스를 병렬 호출해야함
    • 선행 서비스 호출 결과가 필요한 경우에는 순차적으로 호출하도록 설계
    • CompletableFuture, RxJava의 Observable 등 리액티브 설계 기법 동원 필요
  • API 조합 패턴의 장단점
    • 장점
      • 쉽고 단순하게 쿼리 작업을 구현할 수 있음
    • 단점
      • 오버헤드 증가
        • 쿼리하기 위해 리소스가 더 많이 소모됨
      • 가용성 저하 우려
        • 개별 서비스에 비해 여러 서비스를 호출하며 가용성이 떨어짐
        • 가용성을 높이기 위해 캐시 활용 및 서비스에 지장이 없는 선에서 미완성 데이터 반환 고려
      • 데이터 일관성 결여
        • 여러 DB를 대상으로 여러 쿼리를 실행하기 때문에 일관되지 않은 데이터가 반환될 수 있음

CQRS 패턴

  • 관심사의 분리/구분에 관한 패턴
  • RDBMS 특유의 트랜잭션 기능과 텍스트 검색 DB의 탁월한 쿼리 능력을 융합하여 활용하는 아키텍처를 일반화한 것
  • 여러 서비스에 있는 데이터를 가져오는 쿼리는 이벤트를 이용하여 해당 서비스의 데이터를 복제한 읽기 전용 뷰로 유지

CQRS의 필요성

  • API 조합 패턴으로는 효율적으로 구현하기 어려운 다중 서비스 쿼리들에 대한 대안
    • 필터 속성을 저장하지 않는 데이터에 대한 인-메모리 조인
    • 데이터를 가진 서비스에 쿼리를 구현하는 것이 부적절한 경우(관심사 분리 필요)
    • 서비스 DB가 효율적인 쿼리를 지원하지 않는 경우(예: 지리 공간 기능을 지원하지 않는 DB의 경우)

CQRS 개요

  • 마이크로서비스 아키텍처에서 쿼리 구현 시 흔히 봉착하는 세 가지 난관을 해결할 수 있는 CQRS
    • API를 조합하여 여러 서비스에 흩어진 데이터를 조회하려면 값 비싸고 비효율적인 인-메모리 조인을 해야함
    • 데이터를 가진 서비스는 필요한 쿼리를 효율적으로 지원하지 않는 DB에 또는 그런 형태로 데이터를 저장(예: 지리 공간 데이터)
    • 관심사를 분리할 필요가 있다는 것은 데이터를 가진 서비스가 쿼리 작업을 구현할 장소로 부적합
  • CQRS는 커맨드와 쿼리를 서로 분리
    • 영속적 데이터 모델과 그것을 사용하는 모듈을 커멘드와 쿼리로 나눔
    • 조회(R) 기능은 쿼리 쪽 모듈 및 데이터 모델에 구현
    • 생성/수정/삭제(CUD) 기능은 커맨드 쪽 모듈 및 데이터 모델에 구현
    • 양쪽 데이터 모델 사이의 동기화는 커맨드 쪽에서 발행한 이벤트를 쿼리 쪽에서 구독하는 식으로 동기화
  • CQRS와 쿼리 전용 서비스
    • CQRS 패턴을 이용하면 쿼리 서비스를 정의하는 것도 가능
      • 커맨드 작업이 전혀 없는 오직 쿼리 작업만으로 API 구성
      • 하나 이상의 다른 서비스가 발행한 이벤트를 구독하여 항상 최신의 상태로 유지되는 DB를 쿼리하는 로직을 구현
    • 쿼리 쪽 서비스는 여러 서비스가 발행한 이벤트를 구독해서 구축된 뷰를 구현하기 좋은 방법
    • 이러한 뷰는 특정 서비스에 종속되지 않기에 스탠드얼론 서비스로 구현하는 것이 타당
    • 예: 주문 이력 서비스
      • 주문과 주방, 배달, 회계 서비스들의 이벤트를 구독하여 주문 이력 뷰 DB를 업데이트
    • 한 서비스가 가진 데이터를 복제한 뷰를 구현하는 수단으로도 유용

CQRS의 장점

  • 마이크로서비스 아키텍처에서 쿼리를 효율적으로 구현할 수 있음
    • API 조합 패턴의 거대한 데이터 뭉치를 인-메모리 조인하는 작업을 CQRS 뷰를 이용하여 효율적으로 처리 가능
  • 다양한 쿼리를 효율적으로 구현할 수 있음
    • 각 쿼리가 효율적으로 구현된 하나 이상의 뷰를 정의하여 단일 데이터 저장소의 한계 극복
  • 이벤트 소싱 애플리케이션에서 쿼리가 가능
    • 기본키 쿼리만 지원하는 이벤트 저장소의 이벤트 소싱 한계를 극복하게 해줌
  • 관심사를 더 분리할 수 있음
    • CQRS 패턴은 서비스의 커맨드 쪽, 쿼리 쪽에 각각 알맞은 코드 모듈과 DB 스키마를 별도로 정의

CQRS의 단점

  • 아키텍처가 더 복잡함
    • 뷰를 조회/수정하는 쿼리 서비스를 작성해야하며, 별도의 데이터 저장소를 관리해야하는 운영 복잡도 가중
  • 복제 시차를 처리해야 함
    • 커맨드/쿼리 양쪽 뷰 사이의 시차를 처리해야 함
    • 해결을 위한 두가지 방법
      • 커맨드/쿼리 양쪽 API가 클라이언트에 버전 정보를 전달하고 클라이언트는 최신 데이터를 받을 때까지 쿼리 쪽 뷰를 계속 폴링하는 방법
      • SPA 같은 UI 어플리케이션은 쿼리를 하지 말고 커맨드가 성공하면 자신의 로컬 모델을 업데이트 하는 방법으로 복제 시차 해소

CQRS 뷰 설계

  • 뷰 모듈
    • 뷰 DB와 이벤트 핸들러, 쿼리 API, 데이터 접근 모듈(DAO)로 구성
    • 이벤트 핸들러 모듈
      • 이벤트를 구독해서 데이터 접근 모듈을 통해 뷰 DB를 업데이트
    • 쿼리 API 모듈
      • 데이터 접근 모듈을 통해 뷰 DB 데이터를 조회
  • 뷰 모듈 개발 시 중요한 설계 결정 사항
    • DB를 선정하고 스키마를 설계
    • 데이터 접근 모듈을 설계할 때 멱등한/동시 업데이트 등 다양한 문제 고려
    • 기존 애플리케이션에 새 뷰를 구현하거나 기존 스키마를 바꿀 경우, 뷰를 효율적으로 재 빌드할 수 있는 수단 강구
    • 뷰 클라이언트에서 복제 시차를 어떻게 처리할 지 결정

뷰 DB 선택

  • SQL 대 NoSQL DB
  • 업데이트 작업 지원
  • 데이터 접근 모듈 설계
  • 동시성 처리
  • 멱등한 이벤트 핸들러
  • 클라이언트 애플리케이션이 최종 일관된 뷰를 사용할 수 있다

CQRS 뷰 추가 및 이벤트

  • 뷰 추가/수정 작업
    • 새 뷰를 생성하려면 쿼리 쪽 모듈을 개발
    • 데이터 저장소를 세팅
    • 서비스 배포
  • 아카이빙된 이벤트를 이용하여 CQRS 뷰 구축
    • 메시지 브로커가 메시지를 무기한 보관할 수 없기에 아카이빙된 이벤트를 이용해야함
  • CQRS 뷰를 단계적으로 구축
    • 전체 이벤트를 처리하는 시간/리소스가 점점 증가하는 뷰 생성의 또다른 문제 발생
    • 2단계 증분 알고리즘을 적용하여 해결
      • 1단계: 주기적으로 각 Aggregate 인스턴스의 스냅샷을 그 이전의 스냅샷과 이 스냅샷이 생성된 이후로 발생한 이벤트를 바탕으로 계산
      • 2단계: 1단계에서 계산된 스냅샷과 그 이후 발생한 이벤트를 이용하여 뷰를 생성

참고

  • 마이크로서비스 패턴 자바(저 : 크리스 리처드슨)

'dev > 아키텍처' 카테고리의 다른 글

마이크로서비스 패턴(9)  (0) 2022.04.21
마이크로서비스 패턴(8)  (0) 2022.04.14
마이크로서비스 패턴(6)  (0) 2022.03.31
마이크로서비스 패턴(5)  (0) 2022.03.24
마이크로서비스 패턴(4)  (0) 2022.03.10
profile

Notepad

@Apio

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!