마이크로서비스 관계 패턴 - 저장소격리 와 SAGA패턴

Updated:

저장소 격리와 SAGA패턴

아키텍처 유형 패턴 유형 설명
마이크로서비스 외부 아키텍처 인프라 패턴 마이크로서비스를 지탱하는 하부구조 인프라를 정의하는 패턴들
  플랫폼 패턴 인프라 위에서 마이크로서비스를 운영 관리를 지원하는 플랫폼 차원의 패턴들
  마이크로서비스 통신/관계 패턴 마이크 서비스 유형 간의 관계를 정의하고 처리하는 패턴들
마이크로서비스 내부 아키텍처 마이크로서비스 내부 패턴 마이크로서비스 내부의 외부 연동, 비지니스로직,저장소 처리를 정의하는 패턴들

저장소 관련 패턴

마이크로서비스를 독립적으로 수정 배포하기 위한 저장소 형태는 어떻게 가져가야 하나?

모노리스 시스템과 통합 저장소

기존 모노리스 시스템의 저장소는 통합저장소이다. 즉 어플리케이션 모듈은 분리하되 저장 처리는 모듈 별로 격리하지 않고 다른 모듈에서의 호출을 허용하는 구조였다. 특히 국내 엔터프라이즈 어플리케이션의 내부를 보면 모든 비지니스 로직(Business Logic)이 데이터베이스의 SQL처리에 의존하는 경우가 대부분이다. 이러한 것은 단순한 방식이지만 어플리케이션 소스 코드 라인 수 와 데이터 처리를 하는 SQL구문의 라인 수를 세어봐도 알 수 있다. 업무 규칙 및 흐름 처리를 위해 존재하는 어플리케이션의 코드 라인 수 보다 SQL의 코드 라인 수가 몇 배 이상이다.

이런 구조를 데이터 중심 어플리케이션이라 하는데 특정 관계형 DB벤더에 구속되고 복잡해져 유지보수가 어려워 지고 성능 문제 발생 시 SQL구문 튜닝, 저장소 증설(스케일 업)에 의존될 수 밖에 없다. 또한 그런 구조의 어플리케이션을 클라우드 인프라에 올렸을 경우 서비스는 한가하고 여전히 저장소(통합DB)만 바쁜 상황이 되어 마이크로서비스의 확장(스케일 아웃)이 별 의미가 없어지는 상황이 될 수 있다.

저장소 분리 패턴

이를 해결하기 위한 마이크로서비스의 패턴인 저장소 분리 패턴을 살펴보자. 저장소 분리 패턴은 각각의 마이크로서비스는 각자의 비즈니스 처리를 위한 데이터를 직접 소유해야 한다는 것을 말한다. 그렇기 때문에 자신이 소유한 데이터는 다른 서비스에 직접 호출되게 하지 않고 자신의 공개한API를 통해서만 액세스 할 수 있다(정보 은닉). 또한 저장소가 격리되어 있기 때문에 각각의 저장소를 자율적으로 선택할 수 있다(폴리그랏 저장소). 궁극적으로 이러한 제약이 데이터를 통한 변경의 파급효과(영향도)를 줄여 서비스가 독립적이게 된다.

아래 그림을 통해 살펴보면 주문서비스가 주문 수행을 위해 고객 정보를 필요로 한다면, 바로 고객 테이블을 질의를 할 수 없고, 반드시 고객 서비스의 API를 통해서만 호출할 수 있다.

저장소 격리

그러나 이렇게 마이크로서비스별로 기능을 분리하고 저장소를 격리하게 됨에 따라 기존에 이슈가 되지 않았던 문제들이 생긴다. 즉 여러 개의 분산된 서비스를 걸쳐 비지니스 처리를 해야 하는 경우 비지니스 정합성 및 데이터 일관성을 어떻게 보장할 것인가에 대한 문제이다.

전통적인 분산처리 트랜잭션의 문제점

쉽게 접근할 수 있는 방법은 여러 개의 분산된 서비스를 하나의 일관된 트랜잭션으로 묶는 방법이다.

분산 트랜잭션 처리는 앞에 언급한 것처럼 즉 여러 서비스 간의 비지니스 및 데이터 일관성을 유지할 필요가 있다는 말이다. 이런 분산 트랜잭션 처리를 위해 전통적인 방법으로 2PC( two-phase commit) 같은 기법이 있다. 분산 데이터베이스 환경에서 원자성(Atomicity)을 보장하기 위해 분산 트랜잭션에 포함되어 있는 모든 노드가 Commit되거나 Rollback하는 메커니즘 이다.
그런데 이런 방법은 각각 서비스에 동시에 락인(lock in)을 걸게 되면 발생하는 퍼포먼스의 문제로 효율적인 방법이 아니다. 특히 각각의 서비스가 다른 인스턴스로 로딩되기 때문에 통제하기 어렵다. 또한 이런 방법은 서비스 각각의 저장소가 틀릴 경우에 문제가 되며 특히 몽고DB같은 No SQL저장소는 2PC 자체를 지원하지 않는다.

특히 클라우드의 가장 큰 장애는 네트워크 장애인 경우가 많은데 이런 네트워크 장애 등으로 특정 서비스의 트랜잭션 처리가 안 될 경우 묶인 서비스가 즉시 영향을 받기도 한다. 독립적이지 않고 비자율적이다. 그렇다면 어떻게 해야 할까?

SAGA 패턴

그래서 이를 지원하는 패턴이 사가(Saga) 패턴이다. 사가 패턴은 여러 개의 분산된 서비스들을 하나의 트랜잭션을 묶지않고 각 로컬트랜잭션과 보상 트랜잭션을 이용하여 비지니스 및 데이터 정합성을 맞춘다. 사가 패턴은 각 서비스의 로컬 트랜잭션을 순차적으로 처리하는 패턴이다. 아래 그림과 같이 각 로컬 트랜잭션은 자신의 데이터베이스를 업데이트한 다음에 Saga 내에 있는 다음 로컬 트랜잭션을 트리거 하는 메시지 또는 이벤트를 게시하여 데이터의 일관성을 맞춘다.

그렇다면 다른 트랜잭션의 결과에 따라 롤백이 필요하다면 어떻게 해야 할까? 여기서 나오는 개념이 보상 트랜잭션이다. 보상 트랜잭션은 어떤 서비스에서 트랜잭션 처리에 실패할 경우, 그 서비스의 앞선 다른 서비스에서 처리된 트랜잭션을 되돌리게 하는 트랜잭션이다.

정리하면 Saga는 일관성 유지가 필요한 트랜잭션을 모두 묶어 하나의 트랜잭션으로 처리하지 않고, 각각의 로컬 트랜잭션으로 분리하여 순차적으로 처리하는 방법이다. 그러다가 트랜잭션이 실패한 경우 이전 로컬 트랜잭션이 작성한 변경 사항을 취소하는 일련의 보상 트랜잭션을 통해 전체의 일관성을 유지한다.

분산 트랜잭션 과 사가 패턴

예를 들어 하나의 업무를 가지고 생각해 보겠다. 아래 그림과 보면 주문서비스와 고객서비스가 있다. 그리고 주문 처리 시 고객의 신용한도 정보에 따라 최종 주문을 승인하는 업무가 있다. 이 두 서비스의 트랜잭션을 하나로 묶지 않고 보상 트랜잭션과 이벤트를 활용해서 처리할 수 있다. 살펴보면 다음과 같다.

  1. 주문 처리가 시작되면 주문 서비스는 가 주문을 생성하고 주문자정보가 담긴 ‘가 주문 생성됨’ 이벤트를 발행하고 트랜잭션을 종료한다.

  2. 그러면 고객서비스가 ‘주문 생성됨’ 이벤트를 확인한 뒤, a.이벤트에 존재하는 주문자 정보로 고객의 신용한도를 조회하여 신용한도가 충족된다면 ‘신용 승인됨’ 이벤트를 발행한다. b.그리고 신용한도가 충족되지 않는다면 ‘신용한도 초과됨’ 이벤트를 발행한다.

  3. 그럼 다시 주문 서비스는 고객 서비스가 발행한 이벤트를 확인하는데
    a. 고객서비스가 발행한 이벤트가 ‘신용 승인됨’인 경우에는 주문승인처리하고 b. ‘신용한도 초과됨’ 이벤트인 경우에는 보상트랜잭션인 주문처리취소를 수행한다. 이와 같이 하나의 큰 트랜잭션으로 묶지 않고 4개의 분리된 로컬 트랜잭션으로 비지니스의 정합성을 맞출 수 있다.

사가 패턴 사례

데이터 일관성에 대한 생각의 전환: 결과적 일관성

모든 어플리케이션은 비지니스 처리를 위한 규칙들이 존재하고 이 비지니스 규칙을 만족하도록 데이터 일관성이 유지되어야 한다. 이전까지는 이러한 데이터 일관성이 실시간으로 반드시 맞아야 한다는 생각이 주류였다.

그렇지만 과연 모든 비지니스 규칙이 정말 그래야 하는 가는 생각해 봐야 한다. 예를 들어 아래 그림을 보자. 대규모 쇼핑몰인데 주문을 하면 결제 처리가 되어야 하고 그 결제가 완료되면 결제 내용과 주문처리 내역이 주문자에게 이메일로 전송되어야 한다. 일반적으로 주문되고 다음에 결제가 되고 결제 내용이 이메일로 통보되는 것이 순차적인 일 처리 순서이다. 그렇지만 주문자가 폭주했을 경우를 생각 해보자. 주문,결제,이메일 처리가 이런 순차적인 실시간 일관성을 추구하기 경우 주문이 폭주하면 결제 처리가 안되는 경우 나 지연되는 경우가 발생될 수 있을 것이다. 그러면 앞선 주문 처리가 계속 지연될 것이다. 북미의 블랙 프라이데이(Back Friday) 같은 큰 쇼핑이벤트를 생각해 보자. 수 만개의 주문이 발생되는데 결제서비스의 타사 외부 연동 장애가 발생되어 더이상 주문을 받을 수 없는 상황이 발생될 수도 있다. 따라서 이런 상황을 고려했을 때 비즈니스 관점에서 보면 아래 그림처럼 주문과 결제, 이메일 전송을 순차적으로 처리하는 것보다 먼저 주문을 많이 받아 놓는 것이 좋을 수 있다.

주문 지연 상황 발생

잘 생각해 보면 모든 비지니스 처리가 반드시 이런 실시간성을 요구하는 것은 아니다. 어떤 비지니스는 실시간상으로 데이터 일관성이 안 맞아도 어느 시점이 되면 일관성을 만족해도 되는 것이 있다. 이러한 개념을 결과적 일관성(eventual consistency) 이라고 한다. 이런 결과적 일관성 개념은 고 가용성을 극대화 시킨다. 위에 그림은 이런 실시간성을 강조하여 결제서비스의 장애가 다른 서비스들의 가용성을 떨어뜨린 사례이다. 결제서비스의 장애로 주문서비스마저 사용할 수 가 없다.

그럼 결과적 일관성 개념으로 이 문제를 어떻게 해결할지 알아보자. 위의 그림 사례에 SAGA패턴과 이벤트 메시지 기반 비동기 통신 방법을 적용한 모습이다. 각 마이크로서비스의 트랜잭션은 독립적이고 각각의 트랜잭션 성공 시 상태 변경 이벤트를 발행하여 이 이벤트를 구독한 다른 서비스의 로컬 트랜잭션이 작동되게 한다. 살펴보면

  1. 가 주문이 생성되고 ‘가 주문됨’ 이벤트를 발행한다. 주문은 독립적 로컬 트랜잭션이기 때문에 끊임없이 받을 수 있다. 주문이 몰릴 경우 주문서비스만 확장하여 가용성을 높일 수 있다.

  2. ‘가 주문됨’이벤트는 메시지 브로커에 비동기로 전송된다.

  3. 결제 서비스는 발행된 ‘가 주문됨’ 이벤트를 확인하여 결제서비스는 자신의 대금결제 트랜잭션을 수행하고 ‘결제 처리됨’ 이벤트를 발행한다.

  4. 이메일 서비스는 ‘결제 처리됨’ 이벤트를 확인하여 주문결제완료 이메일을 사용자에게 발송한다.

  5. 주문 서비스는 ‘결제 처리됨’이벤트를 확인하여 가 주문 처리되었던 주문을 최종 승인한다. 그리고 ‘최종 주문 완료됨’이벤트를 발행한다.

  6. 이메일 서비스는 주문 서비스가 발행한 ‘최종 주문 완료됨’이벤트를 확인하여 최종 주문 완료됐다는 이메일을 사용자에게 발송한다.

  7. 각 서비스는 해당 작업 수행하다 오류가 발생하면 마찬가지로 ‘실패 이벤트’를 발행하여 다른 서비스가 비지니스 정합성을 맞출 수 있도록 한다.

  8. 이때 별도로 메시지 큐에 쌓이는 이벤트들을 모니터링 서비스와 연계해 모니터링하고 추적하여 전체적인 비지니스 정합성 여부를 관리자가 확인할 수도 있다.

비동기통신과 사가패턴으로 처리

이렇게 이벤트 기반 아키텍처와 메시지브로커, 사가 패턴으로 비지니스 정합성을 결과적으로 보장할 수 있고 비지니스 및 시스템 가용성은 극대화 할 수 있다.