[Cloud Design Pattern] SAGA 패턴
트랜잭션과 정의
트랜잭션은 한 번에 실행되어야 하는 여러 작업을 포함할 수 있는 작업의 단위이며, ACID 원칙을 준수해야 합니다.
- 원자성: 모든 작업이 성공하거나 작업이 성공하지 않습니다.
- 일관성: 데이터가 유효한 상태에서 다른 유효한 상태로 전환됩니다.
- 격리: 동시 트랜잭션은 순차 트랜잭션과 동일한 결과를 생성합니다.
- 내구성: 변경 내용은 커밋된 후에도 오류가 발생하는 경우에도 유지됩니다.
2-Phase 방식
기존 단일 서비스에서는 이런 트랜잭션을 2-Phase 커밋을 바탕으로 구현되어졌습니다.
MSA 환경에서 2-Phase 커밋을 통해 트랜잭션을 관리하게 될 경우 아래와 같은 방식으로 구현되어집니다.
- A 서비스와 B 서비스에게 Prepare 요청을 보냅니다.
- A 서비스와 B 서비스 모두 Prepared(ACK)를 응답합니다.
- A 서비스와 B 서비스에 Commit 작업을 합니다.
하지만 이러한 방식은 구현하기 어려움과 둘 중 하나의 서비스가 장애가 되어질 때, 모든 서비스가 장애와 맞닥드리게 됩니다.
이러한 방식은 MSA의 정의 자체를 위반하게 됩니다.
SAGA 패턴
MSA 환경에서 기존의 2-Phase 커밋의 한계를 극복하기 위해 SAGA 패턴이라는 개념이 사용됩니다.
기존의 단일 서비스에서 원자성을 보장하는 방식으로 로컬 트랜잭션을 하고, 작업 완료 후 Event나 메세지를 활용해 다음 서비스가 그 뒤에 로컬 트랜잭션을 하는 방식입니다.
일련의 과정 중 문제가 발생할 경우 보상 트랜잭션을 처리하여 전체 내용을 원복시킵니다.
앞서 처리된 트랜잭션의 결과를 원래 상태로 되돌리는 작업으로, 트랜잭션 실패 시 전체 흐름을 복구하기 위한 역할을 합니다.
이런 SAGA 패턴을 구현하는데는 크게 두 가지 방식으로 나뉘어집니다.
Choreography-based SAGA
서비스들끼리 트랜잭션에 관한 정보를 비동기 메세지 큐(Kafka, Event Hub)를 활용하여 구현합니다.
이를 통해 각 서비스들은 자신의 처리 정보를 다음 서비스에게 알릴 수 있으며 만약 문제가 발생할 경우 메세지 큐를 통해 보상 트랜잭션을 처리합니다.
이러한 방식은 구현하기 편하지만, 운영자 입장에서는 현재 트랜잭션 상태를 확인하기 어려운 디버깅의 문제가 존재합니다. 또한 서비스 간 직접적인 이벤트 의존도가 생기면서 결합도가 높아지고, 새로운 트랜잭션 단계를 추가할 때 변경 범위가 넓어집니다.
Orchestration based SAGA
SAGA 패턴을 조절하는 컨트롤러를 추가합니다.
Orchestrator는 하나의 request에 대해 연관된 모든 Service에 대해 순차적으로 트랜잭션을 호출시키고 이를 관찰한다. 모든 트랜잭션이 정상일시에 응답값을 사용자에게 반납하고 문제가 있을 경우 실행시킨 트랜잭션에 대한 보상 트랜잭션을 처리한다.
많은 서비스가 있는 워크플로우에 적합한 패턴으로 운영자가 적절하게 Orchestrator를 통해 제어(추가 및 디버깅)이 가능하다. 하지만 Orchestrator 자체가 단일 장애 지점(Single Point of Failure)이 될 수 있으며, 이를 고가용성으로 구성하지 않으면 전체 트랜잭션 흐름에 영향을 줄 수 있습니다.
보상 트랜잭션 작동 방식
- 서비스 A에서 트랜잭션 수행 (
T1
) - 성공하면 이벤트 또는 메시지를 통해 서비스 B 호출 (
T2
) - 서비스 B에서 실패 발생 시
- 서비스 A에 대해 보상 트랜잭션
C1
실행하여T1
의 결과를 취소
즉, T1 → T2 → T3
중간에 오류 발생 시 C2 → C1
순으로 되돌리는 방식입니다.
2PC(Lock) vs SAGA(보상 트랜잭션)
중앙 컨트롤러가 모든 서비스를 호출한다는 점에서 2PC와 SAGA는 유사한 패턴이라고 보일 수 있습니다.
하지만 결정적으로 모든 서비스에 대해 Lock을 하는 2PC 방식은 하나의 서비스가 장애시 DB 밀림, 장애 전파 등에 취약해질 것입니다.
SAGA는 하나의 서비스가 장애 발생하여도 즉시 보상 트랜잭션을 통해 DB의 Lock을 풀어 장애가 다른 서비스로 전파 되는것을 막고, 이용자 입장에서 과도한 대기시간을 기다리지 않고 즉시 Alert을 보여줄 수 있습니다.
항목 | 2PC (2-Phase Commit) | SAGA (Orchestration/Choreography) |
---|---|---|
기본 개념 | 분산 트랜잭션을 위한 표준. 모든 참여자가 트랜잭션 커밋 준비 완료 시 전체 커밋 | 각 서비스가 개별 트랜잭션을 수행하고 실패 시 보상 트랜잭션으로 롤백 |
락 처리 | 모든 자원에 락 유지 (트랜잭션 끝날 때까지) → 지연, 병목, 교착 가능성 ↑ | 락을 최소화. 서비스 간 비동기 처리 → 락으로 인한 병목 ↓ |
처리 시간 | 트랜잭션 종료 전까지 전체 락 유지 → 처리 시간 길어짐 | 각 단계가 빠르게 커밋 → 실패하면 보상 처리 |
실패 복구 | Coordinator가 실패 발생 시 전체 트랜잭션 취소 | 실패한 단계만 보상 트랜잭션으로 복구 |
복잡성 | 구현 단순하지만 분산 락, 장애 처리 어렵고 유지보수 어려움 | 보상 로직 설계가 복잡하지만 확장성과 복원력 높음 |
DB 밀림 위험 | 높음. 트랜잭션 수 증가 시 DB 락 경합과 대기열 폭증 → 성능 저하 | 낮음. 락이 오래 유지되지 않고 서비스 분리되어 병렬 처리 가능 |
실제 현업에서도 이용자가 과도하게 이용자가 몰리면서 하나의 외부 서비스가 장애 발생시 DB 이용률이 90%까지 발생하여 전체 서비스가 다운되었던 점을 기억해보면 SAGA 패턴은 대규모 서비스에 필수적인 기능이라고 생각이 듭니다.
이러한 SAGA 패턴은 Azure Durable Functions을 통해 구현될 수 있습니다.