마이크로서비스 쿼리 구현
핵심 내용
- 마이크로서비스 아키텍처에서 데이터를 쿼리하기 어려운 이유
- 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 조합기는 리액티브 프로그래밍 모델을 사용해야 한다
- 지연 시간을 최소화하기 위해 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 패턴을 이용하면 쿼리 서비스를 정의하는 것도 가능
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 |