마이크로서비스 관계 패턴 - 읽기와 쓰기 분리: CQRS패턴
Updated:
읽기와 쓰기 분리: CQRS패턴
아키텍처 유형 | 패턴 유형 | 설명 |
---|---|---|
마이크로서비스 외부 아키텍처 | 인프라 패턴 | 마이크로서비스를 지탱하는 하부구조 인프라를 정의하는 패턴들 |
플랫폼 패턴 | 인프라 위에서 마이크로서비스를 운영 관리를 지원하는 플랫폼 차원의 패턴들 | |
마이크로서비스 통신/관계 패턴 | 마이크 서비스 유형 간의 관계를 정의하고 처리하는 패턴들 | |
마이크로서비스 내부 아키텍처 | 마이크로서비스 내부 패턴 | 마이크로서비스 내부의 외부 연동, 비지니스로직,저장소 처리를 정의하는 패턴들 |
SAGA패턴과 이벤트 메시지 기반 비동기 통신을 사용하여 가용성을 높인 방법을 살펴봤다. 비지니스의 기존 관념을 조금만 다르게 생각하면 가용성을 높일 방법이 다양하다.
아래 그림은 마이크로서비스의 핵심 설계 철학인 서비스 별 데이터 저장소 패러다임을 채택한 모습이다. 그런데 서비스의 성능향상을 위해 서비스 인스턴스(Instance)를 스케일 아웃(Scale Out)하여 여러 개로 실행한 경우에 데이터 읽기/업데이트 작업으로 인한 리소스 교착 상태가 발생할 수가 있다. 이 문제를 해결하기 위한 방법을 살펴보도록 하겠다. CQRS 패턴이라고 하는데 Command Query Responsibility Segregation, 명령 조회 책임 분리 패턴 이다.
CQRS는 기존의 일반적인 개념이었던 동일한 저장소에 데이터를 넣고 입력/수정/삭제/조회를 모두 하는 방식에 도전하는 흥미로운 패러다임을 도입하고 있다. 일반적으로 사용자의 비즈니스 요청은 시스템 상태를 변경하는 명령과 시스템의 상태를 가져오는 조회의 두 부분으로 크게 나눌 수 있다. 사실 이러한 두 가지 요청 중 비즈니스에서 많이 쓰이는 부분은 상태를 조회하는 부분이다.
즉 일반적인 비지니스 모델에서 입력, 수정, 삭제가 조회보다 적게 쓰이고 조회 요청이 훨씬 많이 사용된다. 그런데 이렇게 서비스 내에 모든 기능을 넣어 두면 조회 요청 빈도가 증가함에 따라 모든 기능을 같이 확장해야 한다. 이런 방식은 효율적이지 않다.
따라서 아래와 같이 이렇게 하나의 저장소에 쓰기 모델과 읽기 모델을 분리하는 방식으로 변화시켜서 쓰기 서비스와 조회서비스를 분리할 수도 있고 좀 더 나아가서 아예 물리적으로 쓰기 트랜잭션 용 저장소와 조회 용 저장소를 따로 준비할 수 있다. 이렇게 쓰기와 조회의 전략을 각각 분리하면 쓰기 시스템의 부하를 줄이고 조회 대기 시간을 줄이는 등 엄청난 이점을 가져올 수 있다.
이러한 CQRS 방식을 이벤트 메시지 주도 아키텍처와 연계하여 살펴보도록 하겠다. 아래 그림처럼 우선 마이크로서비스를 명령 측면과 조회 측면의 두 부분으로 나눈다. 명령 측면 마이크로서비스는 입력,수정,삭제(Create, Update, Delete) 처리를 수행하고 저장소로는 쓰기에 최적화된 관계형 데이터베이스를 사용한다. 그리고 프로그램언어도 업무 규칙을 표현하기 좋은 Java 언어를 사용한다.
반면에 쿼리 측면의 마이크로서비스는 조회 성능이 높은 몽고디비(Mongo DB)나 일레스틱서치(Elasticsearch)와 같은 NO-SQl DB를 사용한다. 그리고 프로그램언어도 조회를 간단하게 구현할 수 있는 스크립트(Script) 기반의 노드제이에스(Node.js) 을 사용한다. 그리고 조회서비스는 사용량이 많기 때문에 스케일 아웃하여 인스턴스를 증가시켜 놓을 수 있다.
그런데 이런 구조에서는 명령 측면 서비스가 사용됨에 따라 조회측면서비스와의 데이터 일관성이 깨지게 된다. 따라서 이때 데이터 일관성 유지를 위한 이벤트 메시지 주도 아키텍처가 등장한다. 쓰기 서비스는 저장소에 데이터를 쓰면서 저장한 내역이 담긴 이벤트를 발생시켜서 메시지브로커에 전달한다. 조회 서비스는 이러한 메시지 브로커의 이벤트를 구독하고 있다가 이벤트데이터를 가져와 자신의 데이터를 최신 상태로 동기화 시켜준다.
물론 명령 측면 서비스에 데이터가 들어간 즉시 조회 측면 서비스의 데이터가 일치할 수 없고 시간적 간격이 있을 수 있지만 어느 시점이 되면 결과적으로 일치하게 된다. 앞서 말 한 결과적 일관성(Eventual Consistency)추구하는 것이다.
API Composition과 CQRS
이 CQRS 패턴은 다른 문제를 해결하기도 한다. 마이크로서비스의 저장소가 격리되어 있고 각 마이크로서비는 자신이 보유한 기능을 구현했을 때 여러 개의 마이크로서비스들이 연계되어 서비스를 제공해야 하는 경우에는 어떻게 해야 하나?
첫번째 방법은 API Composition이다. API Composition 은 아래 그림과 같이 각 기능을 제공하는 마이크로서비스를 조합하는 상위의 마이크로서비스를 만들어서 조합된 기능을 제공할 수 있다. 하위 서비스는 각자 독립적인API를 제공하면서 연계API를 위해 상위서비스에 정보를 제공해 준다. 그렇지만 이런 구조는 상위 서비스가 하위 서비스에 의존하는 결과를 가져온다. 상위 서비스가 제공하는 API에 정보를 제공하는 하위 서비스 중에 하나라도 API를 변경하게 되면 상위 서비스는 따라서 변경될 수 밖에 없다. 이런 의존성을 줄이기 위한 방법이 필요하다.
두번째 방법은 CQRS를 적용하는 것이다. 아래 그림과 같이 주문 이력을 보여주는 마이크로서비스가 필요하다고 보자. 주문 이력 마이크로서비스를 별도로 만들고 주문 이력의 세세한 원천 정보를 보유한 각 서비스도 독자적으로 자신의 서비스를 제공한다. 그리고 이 원천데이터를 보유한 복수의 마이크로서비스들은 각자의 데이터 변경 이벤트를 발행한다.
그럼 주문 이력 마이크로서비스는 이 이벤트를 구독하고 있다가 이벤트를 가져와서 조회 용 자신의 저장소에 일관성을 맞춰주고 주문 이력조회 서비스를 제공한다. 즉 조회 용 마이크로서비스를 별도 생성하고 다른 서비스로부터 비동기 이벤트로 일관성을 맞춰 줌으로써 API Composition 방식의 단점인 직접적인 의존성을 줄일 수 있는 것이다.