
Contoso Bank는 레거시와 새로운 애플리케이션이 공존하는 시장에서 새로운 기능을 빠르게 제공하기 위해 마이크로서비스 개발을 활용하는 새로운 결제 플랫폼의 구축하고자 한다. Contoso는 기존 monolothic 구조에서 DBMS의 ACID에 의존하던 금융 거래에서 데이터 일관성을 MSA 환경에서 보장하기 위해 새로운 아키텍처와 구현 설계가 필요하게 되었다. MSA환경에서는 하나의 큰 DBMS를 공유하는 것이 아니라 개별 서비스 단위로 DB를 사용하며, 또한 개별 서비스에 따라 NoSQL 등 이기종 DBMS를 사용한다. 따라서 기존의 ACID 방식은 운영 데이터가 이제 격리된 데이터베이스로 분산되기 때문에 더 이상 Contoso Bank에 적합하지 않은 구조이다. ACID 트랜잭션 대신 Saga 는 메시지 기반 로컬 트랜잭션 시퀀스를 통해 워크플로를 조정하여 데이터의 일관성을 보장함으로써 과제를 해결할 수 있는 방법이다.
기존의 Contoso Bank에서 적용했던 모놀리식 환경에서는 DBMS가 트랜잭션의 원자성과 일관성을 보장하였기 떄문에 트랜잭션의 작업 중 일부가 실패하면, 전체 작업을 롤백하는 All or Nothing 구현이 용이하였다. 그러나 MSA구조로 전환을 시도함에 따라, 계좌간 credit/debit 및 영수증 발행등의 서비스들이 기존 monolothic 구조와 달리 여러 마이크로 서비스를 걸쳐 수행되기 때문에 전체 트랜잭션이 하나의 DBMS에 의존할 수 없어, 마이크로 서비스의 물리적인 로컬 트랜잭션을 통합하여 만들어야 하는 상황이 되었다. 즉, 일부 마이크로 서비스가 실패하게 되면, DBMS에서 제공하던 ACID의 장점을 활용할 수 없어 데이터 일관성은 깨지게 될 수 있는 문제가 발생한다.
기존에는 이러한 분산 트랜잭션 문제를 해소하기 위하여 2PC(Two-Phase Commit) 패턴을 사용하였다. 2PC는 코디네이터(coordinator)와 여러 데이터베이스간의 합의를 통해 트랜잭션 커밋/롤백이 결정하는 방법으로, 글로벌 트랜잭션에 참여하는 모든 데이터베이스가 커밋이 가능한 상태 혹은 불가능한 상태임을 코디네이터에게 알리고(phase 1), 코디네이터가 커밋 또는 롤백을 수행(phase 2)하는 방식으로 동작한다.

그러나 그동안 사용되어 오던 2PC는 아래 사유들로 인해 MSA구조에서는 적합하지 않다.
Lock을 사용하므로 micro services 간에 의존성이 높아지고, 동기IPC 형태이므로 분산환경에서의 장애 대응(가용성이 낮아짐)이 어려워지며, lock으로 인한 대기로 인해 성능 측면에서 불리하다.
DBMS의 지원 혹은 메시지 인프라 등에서 2PC를 지원해야 하나, NoSQL을 포함한 이기종 DBMS를 사용하거나, 메시지 인프라(apache kafka 등)에서 지원하지 않기 떄문에 사용이 어려워지고 있다.
MSA는 각 마이크로 서비스가 각자의 데이터베이스를 소유하여 local transaction에만 ACID를 보장하므로(database per service), 여러 데이터베이스에 걸친 글로벌 트랜잭션을 하나의 코디네이터가 관리하는 구조는 적합하지 않다.
코디네이터에 의존적이므로 코디네이터 장애 상황에서 각 데이터베이스는 커밋/롤백 여부를 스스로 결정할 수 없는 문제가 발생한다.
Saga 패턴은 MSA와 같은 분산 아키텍처에서 데이터 일관성을 보장하기 위해 등장한 설계 패턴으로, 크리스 리처드슨의 마이크로 서비스 패턴에서 "Saga는 비동기 메시징을 이용하여 편성된 일련의 로컬 트랜잭션이다. 서비스 간 데이터 일관성은 saga로 유지한다"로 정의한대로 연속된 개별 서비스의 로컬 트랜잭션이 이어져, 전체 비즈니스 트랜잭션을 구성하는 패턴이다. 첫번째 트랜잭션이 완료되면 두번째 트랜잭션이 트리거 되고, 두번째 트랜잭션이 완료되면 세번째 트랜잭션이 트리거되는 형태이다.트랜잭션이 실패하면 2PC에서는 rollback을 수행했으나 이미 local DB에 commit 된 MSA에서는 rollback 처리가 불가능하다. 따라서 saga 패턴에서는 개별 서비스가 실패했을 때 보상 트랜잭션(compensating transaction)을 발생시켜 원래의 상태로 돌려주어야 한다. 즉, Saga 패턴에서는 데이터 일관성을 관리하는 주체가 DBMS가 아닌 애플리케이션이어야 한는 것이 Saga 패턴의 핵심이다. 보상 트랜잭션이 적용되기 전까지 일시적으로 데이터 정합이 깨져있을 수 있으나, 보상 트랜잭션이 완료되면 ‘결과적 정합성(eventually consistent)’을 보장할 수 있다.

코레오그래피 사가(choreography saga) 패턴은 분산 트랜잭션 및 보상 트랜잭션에 대한 의사 결정과 순서화를 saga 참여자에게 자율적으로 맡기며, 각 참여자들은 이벤트 교환 방식으로 서로 통신을 수행하는 방식이다. 각 서비스는 트랜잭션이 완료되면, 완료 이벤트를 발행하고, 그 다음에 수행되어야할 이벤트를 구독한 마이크로 서비스가 이어서 트랜잭션을 실행한다. 중간에 로컬 트랜잭션이 실패하는 마이크로 서비스가 발생되면, 트랜잭션이 발생했던 역순으로 이에 대한 보상 트랜잭션 이벤트를 발생하여 롤백을 시도한다.

결론적으로 복잡하지 않은 간단한 saga 라면 코레오그래피 방식으로도 손쉽게 개발이 가능하나, 규모가 커지고 복잡한 saga가 필요한 경우 유지보수 등에 적합하지 않을 수 있다.
오케스트레이션 사가(Orchestration saga)는 중앙의 saga Orchestrator가 참여자들에게 어떤 로컬 트랜잭션을 실행해야하는지 알려주는 방식으로, 오케스트레이터는 커맨드/비동기 응답에 상호 작용을 관리하며 모든 트랜잭션에 대한 처리 흐름과, 필요시 보상 트랜잭션을 발생시켜 롤백을 시도를 수행한다.Saga orchestrator는 state machine으로 모델링에 적합하며, local transaction이 완료되는 시점에 상태 전이가 trigger되어, local transaction의 결과에 따라 상태 전의 및 action을 수행할 수 있어 모델링을 쉽게 할 수 있다.
오케스트레이터는 global transaction의 flow를 관리하는 것만 책임져야 하며, business logic은 개별 micro service가 처리해야 한다. orchestrator에 business logic을 처리하도록 하면 안된다.
출처: https://happycloud-lee.tistory.com/154
Event가 발생하면 다음 실행을 위한 Command가 Trigger(촉발)됩니다. 아래는 코끼리를 냉장고에 넣기 위한 State machine 모델링입니다. 정상적인 처리에 대해서만 기술하면 아래와 같다.
| Command | Command Handler | State | Event |
|---|---|---|---|
| 냉장고 문을 열어라 | 문을 연다 | 문 상태 변경: 열림/안 열림 | 냉장고 문을 열었다. |
| 코끼리를 넣어라 | 코끼리를 넣는다 | 코끼리 상태 변경: 넣음/못넣음 | 코끼리를 넣었다. |
| 냉장고 문을 닫아라 | 문을 닫는다 | 문 상태 변경: 닫힘/못 닫힘 | 냉장고 문이 닫혔다. |
| 성공보고 해라 | 성공보고 | 레포트 생성 | 성공보고를 했다. |

위 State machine을 Saga로 표현하면 아래와 같습니다. (응답채널은 1개 입니다.)

Azuer에서 구축하고자 하는 솔루션은 신용/직불 작업을 통해 은행 계좌 간에 금액이 이체되고 요청자에 대한 작업 영수증이 생성되는 송금 시나리오를 시뮬레이션한다. Azure의 서버리스 아키텍처에서 오케스트레이션 방식을 통한 Saga 패턴을 구현한다. 이 솔루션은 Saga 참여자 구현을 위해 Azure Functions , Saga 오케스트레이터 구현을 위해 Azure Durable Functions , 데이터 스트리밍 플랫폼으로 Azure Event Hubs , 데이터베이스 서비스로 Azure Cosmos DB를 활용한다.

Saga 클라이언트: 새로운 트랜잭션을 시작하기 위한 HTTP 요청을 받는 HTTP 트리거 바인딩이 있는 Azure Durable Functions로 구현된 Web API로 각 요청에 대해 무작위 트랜잭션 ID를 생성하고, 새로운 Saga 오케스트레이터 인스턴스를 시작하며, HTTP 응답의 일부로 트랜잭션 ID를 제공Saga 오케스트레이터: Event Hubs에 명령을 생성하고 Saga 참여자로부터의 이벤트를 기다리는 방식으로 트랜잭션 워크플로우를 조정하는 장기 실행 Durable 오케스트레이터Saga 오케스트레이터 액티비티: Cosmos DB 바인딩이 있는 액티비티 함수로, Saga 상태(대기 중, 성공, 실패)를 Cosmos DB에 유지명령 생성자 액티비티: Event Hubs 바인딩이 있는 액티비티 함수로, 오케스트레이션에 의해 생성된 명령을 Event Hubs에 생성검증자: Event Hubs 트리거와 Cosmos DB 바인딩이 있는 Azure Function으로 구현된 Saga 참여자로, 계좌 간 송금을 진행하기 전에 일련의 은행 계좌 유효성 검사를 시뮬레이션 수행(예: 두 계좌가 존재하는지, 계좌에 충분한 잔액이 있는지 등). 결과 이벤트(예: InvalidAccountEvent)는 Saga 응답 Event Hubs에 생성되고 Cosmos DB에 저장송금: Event Hubs 트리거와 Cosmos DB 바인딩이 있는 Azure Functions로 구현된 Saga 참여자로, 은행 계좌의 입금 및 출금 작업을 시뮬레이션. 결과 상태(예: TransferSucceededEvent)는 Saga 응답 Event Hubs에 생성되고 Cosmos DB에 저장영수증: Event Hubs 트리거와 Cosmos DB 바인딩이 있는 Azure Function으로 구현된 Saga 참여자로, 발행자를 위한 영수증 ID를 생성. 결과 상태(예: ReceiptIssuedEvent)는 Saga 응답 Event Hubs에 생성되고 Cosmos DB에 저장Saga 이벤트 프로세서: Cosmos DB 바인딩과 Event Hubs 트리거가 있는 Azure Durable Functions로, Saga 참여자들이 생성한 모든 이벤트를 소비하고, 오케스트레이터 인스턴스에 대한 외부 이벤트를 발생시키며, 이벤트를 Cosmos DB에 저장Saga 상태 확인자: HTTP 응답의 일부로 Saga 상태(예: 대기 중, 완료, 실패)와 Saga 오케스트레이터 런타임 상태(예: 실행 중, 완료)를 포함하는 스키마를 제공하는 Azure Functions 의 HTTP 트리거
Saga 클라이언트에 전송Saga 클라이언트는 새 트랜잭션 ID를 생성하고, 오케스트레이터의 새 인스턴스를 시작(트랜잭션 ID를 오케스트레이터 인스턴스 ID로 할당). 그리고 HTTP POST 응답의 일부로 트랜잭션 ID를 제공하여 사용자가 Saga 상태 확인 서비스를 통해 saga 상태를 조회할 수 있도록 응답Saga 오케스트레이터는 Saga 오케스트레이터 액티비티를 호출하여 초기 Saga 상태를 Cosmos DB에 'PENDING'으로 저장Saga 오케스트레이터는 명령 생성자 액티비티를 호출하여 Event Hubs에 은행 계좌 검증을 위한 새 명령을 생성한 후 워크플로우의 다음 단계로 넘어기 전 계좌 검증 결과를 알려주는 외부 이벤트를 대기검증자는 Event Hubs를 통해 오케스트레이터가 생성한 명령을 소비하고, 은행 계좌 검증 작업을 수행하여 Saga 응답 Event Hubs에 새로운 성공 이벤트를 생성하고 서비스 데이터를 Cosmos DB에 저장Saga 이벤트 프로세서는 검증자가 생성한 이벤트를 소비하고, 이벤트 데이터를 Cosmos DB에 저장하며, 이벤트 이름을 포함한 외부 이벤트를 Saga 오케스트레이터에 발생(계좌 검증 완료)Saga 오케스트레이터는 외부 이벤트 응답을 받고 명령 생성자 액티비티를 호출하여 Event Hubs에 새 명령(state machine의 다음 action)을 생성하여 송금 프로세스를 시작하도록 처리한 후 워크플로우의 다음 단계로 넘어가기 전까지 송금 결과를 알려주는 외부 이벤트를 대기송금은 Event Hubs를 통해 오케스트레이터가 생성한 명령을 소비하고, 두 은행 계좌에 대한 입금 및 출금 작업을 처리하여, Saga 응답 Event Hubs에 새로운 성공 이벤트를 생성하고 서비스 데이터를 Cosmos DB에 저장Saga 이벤트 프로세서는 송금이 생성한 이벤트를 소비하고, 이벤트 데이터를 Cosmos DB에 저장하며, 이벤트 이름을 포함한 외부 이벤트를 Saga 오케스트레이터에 발생(송금 완료)Saga 오케스트레이터는 외부 이벤트 응답을 받고 명령 생성자 액티비티를 호출하여 Event Hubs에 트랜잭션 영수증 생성을 위한 새 명령을 생성하고, Saga를 완료하기 위해 영수증 생성 프로세스의 결과를 알려주는 외부 이벤트를 대기영수증은 Event Hubs를 통해 오케스트레이터가 생성한 명령을 소비하고, 무작위 영수증 ID를 생성하며, Saga 응답 Event Hub에 새로운 성공 이벤트를 생성하고 서비스 데이터를 Cosmos DB에 저장합니다.Saga 이벤트 프로세서는 영수증이 생성한 이벤트를 소비하고, 이벤트 데이터를 Cosmos DB에 저장하며, 이벤트 이름을 포함한 외부 이벤트를 Saga 오케스트레이터에 생성Saga 오케스트레이터는 외부 이벤트 응답을 받고 Saga 오케스트레이터 액티비티를 호출하여 Cosmos DB에서 Saga 상태를 '성공'으로 업데이트하여 트랜잭션 종료참고로 사용자는 HTTP GET 요청을 통해 Saga 상태 확인자를 사용하여 언제든지 saga 상태와 오케스트레이터 런타임 상태를 확인할 수 있다.
(참고 1~10 단계는 정상 케이스와 동일)

Saga 클라이언트에 전송Saga 클라이언트는 새 트랜잭션 ID를 생성하고, 오케스트레이터의 새 인스턴스를 시작(트랜잭션 ID를 오케스트레이터 인스턴스 ID로 할당). 그리고 HTTP POST 응답의 일부로 트랜잭션 ID를 제공하여 사용자가 Saga 상태 확인 서비스를 통해 saga 상태를 조회할 수 있도록 응답Saga 오케스트레이터는 Saga 오케스트레이터 액티비티를 호출하여 초기 Saga 상태를 Cosmos DB에 'PENDING'으로 저장Saga 오케스트레이터는 명령 생성자 액티비티를 호출하여 Event Hubs에 은행 계좌 검증을 위한 새 명령을 생성한 후 워크플로우의 다음 단계로 넘어기 전 계좌 검증 결과를 알려주는 외부 이벤트를 대기검증자는 Event Hubs를 통해 오케스트레이터가 생성한 명령을 소비하고, 은행 계좌 검증 작업을 수행하여 Saga 응답 Event Hubs에 새로운 성공 이벤트를 생성하고 서비스 데이터를 Cosmos DB에 저장Saga 이벤트 프로세서는 검증자가 생성한 이벤트를 소비하고, 이벤트 데이터를 Cosmos DB에 저장하며, 이벤트 이름을 포함한 외부 이벤트를 Saga 오케스트레이터에 발생(계좌 검증 완료)Saga 오케스트레이터는 외부 이벤트 응답을 받고 명령 생성자 액티비티를 호출하여 Event Hubs에 새 명령(state machine의 다음 action)을 생성하여 송금 프로세스를 시작하도록 처리한 후 워크플로우의 다음 단계로 넘어가기 전까지 송금 결과를 알려주는 외부 이벤트를 대기송금은 Event Hubs를 통해 오케스트레이터가 생성한 명령을 소비하고, 두 은행 계좌에 대한 입금 및 출금 작업을 처리하여, Saga 응답 Event Hubs에 새로운 성공 이벤트를 생성하고 서비스 데이터를 Cosmos DB에 저장Saga 이벤트 프로세서는 송금이 생성한 이벤트를 소비하고, 이벤트 데이터를 Cosmos DB에 저장하며, 이벤트 이름을 포함한 외부 이벤트를 Saga 오케스트레이터에 발생(송금 완료)Saga 오케스트레이터는 외부 이벤트 응답을 받고 명령 생성자 액티비티를 호출하여 Event Hubs에 트랜잭션 영수증 생성을 위한 새 명령을 생성하고, Saga를 완료하기 위해 영수증 생성 프로세스의 결과를 알려주는 외부 이벤트를 대기영수증은 Event Hubs를 통해 오케스트레이터가 생성한 명령을 소비하지만, 영수증 ID 생성 중 오류가 발생하는 것으로 가정. 영수증에서 실패한 작업 결과가 Cosmos DB에 저장되고 새로운 실패 이벤트가 Saga 응답 Event Hub에 생성Saga 이벤트 프로세서는 영수증이 생성한 이벤트를 소비하고, 이벤트 데이터를 Cosmos DB에 저장하며, '실패' 상태와 함께 외부 이벤트를 Saga 오케스트레이터에 발생Saga 오케스트레이터는 외부 이벤트 응답을 받고 명령 생성자 액티비티를 호출하여 Event Hubs에 송금 프로세스를 보상하기 위한 새 보상 명령을 생성 후 Saga를 완료하기 위해 보상 트랜잭션의 결과를 알려주는 외부 이벤트를 대기송금은 Event Hubs를 통해 오케스트레이터가 생성한 보상 명령을 소비하고, 두 은행 계좌에 대한 입금 및 출금 작업을 취소하며, Saga 응답 Event Hubs에 새로운 보상 이벤트를 생성하고 서비스 데이터를 Cosmos DB에 저장Saga 이벤트 프로세서는 송금이 생성한 보상 이벤트를 소비하고, 이벤트 데이터를 Cosmos DB에 저장하며, 이벤트 이름을 포함한 외부 이벤트를 Saga 오케스트레이터에 발생Saga 오케스트레이터는 외부 이벤트 응답을 받고 Saga 오케스트레이터 액티비티를 호출하여 Cosmos DB에서 Saga 상태를 '취소됨'으로 업데이트합니다.이 설명들은 Saga 패턴을 구현할 때 사용되는 주요 설계 패턴과 접근 방식을 설명하고 있으며, 비동기 HTTP API는 장기 실행 작업을 처리하는 데 적합하고, 재시도와 서킷 브레이커 패턴은 시스템의 안정성을 높이는 데 도움되며, 마이크로서비스별 데이터베이스 패턴은 각 서비스의 데이터 독립성을 보장할 수 있다.
Saga 패턴은 코레오그래피 접근 방식을 통해서도 구현될 수 있으며, 코레오그래피에서는 워크플로우를 조정하는 오케스트레이터 없이 saga 참여자들간에 명령과 이벤트를 교환한다. 오케스트레이션과 코레오그래피 접근 방식 모두 장단점이 있다.
| Saga | 장점 | 단점 |
|---|---|---|
| 오케스트레이션 | 많은 수의 saga 참여자가 필요하거나 시간이 지남에 따라 새로운 참여자가 추가되는 복잡한 Saga 워크플로우에 적합 | 조정 로직 구현이 필요한 추가적인 설계 복잡성 |
| 오케스트레이터가 saga 참여자에 의존하지만 그 반대는 아니므로 순환 종속성을 도입하지 않음 | 전체 워크플로우를 관리하므로 추가적인 실패 지점이 됨 | |
| saga 참여자가 다른 참여자를 위해 생성해야 하는 명령에 대해 알 필요가 없으므로 결합도가 낮음 | ||
| 관심사의 명확한 분리로 비즈니스 로직 단순화 | ||
| 코레오그래피 | 조정 로직 구현 설계가 필요 없으므로 적은 수의 saga 참여자가 필요한 간단한 Saga 워크플로우에 적합 | 새로운 단계를 추가할 때 워크플로우가 혼란스러워질 수 있음, 어떤 saga 참여자가 어떤 명령을 듣는지 추적하기 어려움 |
| 추가적인 서비스 구현 및 유지보수가 필요 없음 | saga 참여자들이 서로의 명령을 소비해야 하므로 saga 참여자 간에 순환 종속성을 추가할 잠재적 위험이 있음 | |
| 책임이 saga 참여자들에게 분산되어 있어 단일 실패 지점을 도입하지 않음 | 모든 서비스가 트랜잭션을 시뮬레이션하기 위해 실행 중이어야 하므로 통합 테스트가 어려운 경향이 있음 |
Cosmos DB 또는 다른 NoSQL 서비스에서 데이터를 모델링하려면 적용된 전략에 따라 일부 이점과 단점을 가정이 필요하다. 제안된 솔루션은 마이크로서비스별 데이터베이스 패턴을 활용하고 있으며, 각 Saga 참여자는 분리를 위해 격리된 데이터베이스에서 자신의 데이터를 관리한다. 이는 Saga 참여자의 데이터를 다른 Saga 참여자나 오케스트레이터에서 직접 접근할 수 없음을 의미하는 것으로, Cosmos DB 관점에서 이 솔루션은 마이크로서비스별 DB를 수합한 형태로 설계되었다. 이는 몇 가지 이점과 단점이 발생된다.
| 데이터 접근 방식 | 장점 | Tradeoffs |
|---|---|---|
| 마이크로서비스별 컬렉션 | 도메인 데이터가 saga 참여자 내에 캡슐화됨 | 여러 컬렉션의 데이터를 조인하는 쿼리 생성이 복잡해질 가능성이 있으며, 시간이 지남에 따라 데이터 양이 증가하면 성능에 기하급수적인 영향을 미칠 수 있음 |
| 데이터 스키마가 다른 saga 참여자에 직접적인 영향을 주지 않고 진화할 수 있음 | Cosmos DB는 요청 단위(RU)에 제한이 있어 최대 컬렉션 수에 영향을 미침, 따라서 saga 참여자 수에 따라 제한이 될 수 있음 | |
| 각 saga 참여자의 데이터 저장소가 독립적으로 확장 가능 | 더 많은 컬렉션은 더 많은 RU가 필요하므로 비용 증가를 의미함 | |
| 한 saga 참여자의 데이터 저장소 실패가 다른 참여자에 직접적인 영향을 주지 않음 |
복잡한 쿼리 생성과 관련된 단점을 해결하기 위해, 이 솔루션은 비정규화 접근 방식을 활용하여 모든 Saga 참여자의 상태 결과를 통합하는 추가 컬렉션을 생성함으로써 읽기 쿼리를 최적화한다. 이 접근 방식은 관찰 가능성 목적으로 복잡성을 크게 줄이고 성능을 향상시킬 수 있다.데이터 모델링을 위한 또 다른 접근 방식은 Saga 참여자 간에 공유되는 단일 컬렉션을 정의하는 것이다. 이는 비용을 크게 줄일 수 있지만, 위에서 설명한 데이터베이스의 분리에 따른 이점을 얻을 수 없다. Cosmos DB에서 처리량, 컨테이너 및 데이터베이스를 프로비저닝하는 방법을 결정하려면 Cosmos DB가 두 가지 수준에서 처리량(throghput)을 프로비저닝할 수 있다는 점을 고려해야 한다.

제안된 솔루션이 노출해야 할 요청 목록은 다음과 같다.
[C] 항목의 경우, 각 Saga 참여자 컬렉션에 항목을 생성하거나 업데이트하기만 하면 되므로 요청을 간단하게 구현할 수 있다. 요청은 트랜잭션 ID가 기본 및 파티션 키로 정의되어 있어 모든 파티션에 잘 분산된다.
[Q1] 항목의 경우, saga 상태를 검색하는 것은 트랜잭션 컬렉션에서 해당 트랜잭션 ID를 읽는 것으로 수행되며, 요청은 트랜잭션 ID가 기본 및 파티션 키로 정의되어 있어 모든 파티션에 잘 분산된다.
[Q2] 항목의 경우, 모든 saga 참여자 상태가 saga 컬렉션에 통합되어 있으므로 요청을 간단하게 구현할 수 있다. 요청은 ID를 기본 키로, 트랜잭션 ID를 파티션 키로 정의하여 모든 파티션에 잘 분산된다.
Event Hubs는 파티션된 소비자 패턴을 통해 메시지 스트리밍을 제공한다. 이 패턴에서 각 소비자는 메시지 스트림의 특정 하위 집합 또는 파티션만 읽어서 처리한다. 파티션은 이벤트 허브에 저장되는 순서가 지정된 이벤트 시퀀스로 새로운 이벤트가 도착하면 이 시퀀스의 끝에 추가된다. 파티션은 "커밋 로그"로 생각할 수도 있다.
트랜잭션 ID 필드는 주어진 트랜잭션과 관련된 모든 메시지가 각 서비스에 의해 순서대로 처리되도록 보장하기 위해 사용된다. 각 서비스에는 전용 수신함 Event Hub가 있기 때문에 각 서비스에 대해 다른 소비자 그룹을 만들 필요가 없다.
각 Saga 참여자 인스턴스는 단일 Event Processor Host (EPH)에 의해 지원되며, EPH는 체크포인팅, 임대(leasing), 병렬 이벤트 수신자(reader)의 관리를 단순화하는 지능형 소비자 에이전트이다. Event Hubs 트리거는 하나의 EPH 인스턴스만이 주어진 파티션에 대한 리스를 얻을 수 있도록 보장한다. Saga 참여자를 위한 Event Hub의 다음 시나리오를 고려해보자.
N개의 파티션W개의 명령 = 각 파티션에 W/N개의 메시지함수가 시작될 때 Saga 참여자 함수의 인스턴스가 하나만 생성됩니다. 이를 SagaParticipant-0 인스턴스라고 하자. SagaParticipant-0은 모든 N개의 파티션에 대한 리스를 보유하는 EPH의 단일 인스턴스를 가지며, 파티션 0-(N-1)에서 명령을 읽습니다. 그런 다음 다음과 같은 시나리오가 발생할 수 있다.
| 시나리오 | 설명 |
|---|---|
| 새로운 Saga 참여자 인스턴스가 필요 없음 | SagaParticipant-0이 Functions 스케일링 로직이 적용되기 전에 모든 W 명령을 처리할 수 있음. |
| 새로운 Saga 참여자 인스턴스가 추가 | Functions 스케일링 로직이 SagaParticipant-0이 처리할 수 있는 것보다 더 많은 메시지가 있다고 판단하면, 새로운 함수 앱 인스턴스(SagaParticipant-1)를 생성하고 새로운 EPH 인스턴스를 연결. 기본 Event Hubs가 새로운 호스트 인스턴스가 메시지를 읽으려고 시도하는 것을 감지하면, 파티션 0-4는 SagaParticipant-0에, 파티션 5-9는 SagaParticipant-1에 할당 형태로 파티션 간에 부하를 분산 처리 |
| 더 많은 Saga 참여자 인스턴스가 추가됨 | Functions 스케일링 로직이 SagaParticipant-0과 SagaParticipant-1 모두 처리할 수 있는 것보다 더 많은 메시지가 있다고 판단하면, Y가 N Event Hub 파티션보다 커질 때까지 새로운 SagaParticipant-Y 인스턴스가 생성됨. 이 스케일링 시나리오에서 Event Hubs는 다시 SagaParticipant-0부터 SagaParticipant-(N-1)까지의 인스턴스 간에 파티션의 부하를 균형 있게 분배 |
참고: saga 참여자에 대한 동일한 스케일링 메커니즘이 Saga Event Processor 서비스에도 사용되며, 이 서비스도 Event Hubs 트리거 바인딩을 활용
Saga 오케스트레이터, 명령 생성자 액티비티, 그리고 Saga 오케스트레이터 액티비티 함수들은 Azure Functions의 내부 큐에 의해 트리거된다. 이 내부 큐는 제어 큐라고 알려져 있으며, 다양한 오케스트레이션 수명 주기 메시지 유형을 포함한다. 오케스트레이터와 액티비티 인스턴스는 상태를 가진 싱글톤이므로, 메시지는 VM 간에 부하를 분산하는 경쟁 소비자 모델을 사용하는 대신 제어 큐 전체에 걸쳐 부하가 분산된다.
Durable Functions 작업은 스토리지 트랜잭션 비용에 대한 유휴 큐 폴링의 영향을 줄이기 위해 무작위 지수 백오프 알고리즘을 구현한다. 메시지가 발견되면 런타임은 즉시 다른 메시지를 확인하고, 메시지가 발견되지 않으면 다시 시도하기 전에 일정 시간 동안 대기한다. 큐 메시지를 가져오는 데 연속적으로 실패한 시도 후에는 대기 시간이 계속 증가하여 최대 대기 시간에 도달할 때까지 계속 증가되며, 기본 최대 대기 시간은 30초이다.
https://medium.com/cloud-native-daily/microservices-patterns-part-04-saga-pattern-a7f85d8d4aa3
https://learn.microsoft.com/ko-kr/azure/architecture/reference-architectures/saga/saga
https://www.baeldung.com/cs/saga-pattern-microservices
https://engineerinsight.tistory.com/210
https://tlatmsrud.tistory.com/118
https://s-y-130.tistory.com/232
https://happycloud-lee.tistory.com/154
https://github.com/Azure-Samples/saga-orchestration-serverless/tree/main