534. 사가(Saga) 패턴과 보상 트랜잭션
⚠️ 이 문서는 MSA 환경에서 수십 개의 서비스가 얽혀 결제를 진행할 때, 2PC처럼 모두가 락을 걸고 기다리는 멍청한 방식(블로킹)을 버리고, **"일단 각자 알아서 결제하고, 만약 뒤에서 에러가 터지면 앞에서 성공했던 애들이 '취소 환불' 로직을 수동으로 실행하자!"는 비동기적 분산 트랜잭션 해결책인 '사가 패턴'**을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 분산된 시스템에서 롱 트랜잭션(Long Transaction)을 유지할 수 없을 때, 이를 여러 개의 '짧은 로컬 트랜잭션(Local Transaction)'으로 잘게 쪼개어 순차적으로(비동기적으로) 실행하는 아키텍처다.
- 보상 트랜잭션 (Compensating Transaction):
A -> B -> C순서로 일하다가C에서 에러가 났을 때, 2PC처럼 전체를 마법같이 롤백(ROLLBACK)할 방법이 없으므로 개발자가 직접B를 취소하는 쿼리,A를 취소하는 쿼리를 손으로 짜서 역순으로 실행해 주는 기술이다.- 종류: 지휘자가 중앙에서 순서를 통제하는 '오케스트레이션(Orchestration)' 방식과, 지휘자 없이 서비스들끼리 이벤트를 던지며 알아서 춤을 추는 '코레오그래피(Choreography)' 방식이 있다.
Ⅰ. 개요: 롤백(Rollback)이 없는 세계 (Context & Necessity)
당신이 배달 앱에서 '치킨'을 주문했다. 이 주문 하나를 위해 서버 3개가 움직여야 한다.
주문 서버: 주문 상태를 '생성'으로 만든다.결제 서버: 내 카드에서 2만 원을 뺀다.재고 서버: 치킨집 닭을 1마리 깎는다.
만약 재고 서버에 치킨이 없어서(에러) 결제를 취소해야 한다면?
- 옛날 (단일 DB): "어? 에러 났어? 그냥
ROLLBACK;한 번만 치면 1, 2, 3번 다 원래대로 돌아가!" (완벽한 ACID) - 현대 (MSA 환경): 주문은 MySQL, 결제는 Oracle, 재고는 MongoDB를 쓴다. 서로 다른 DB라서
ROLLBACK하나로 묶을 수가 없다! (2PC는 너무 느려서 못 씀 - 531번 문서 참조)
이 절망적인 상황을 구원하는 것이 **사가 패턴(Saga Pattern)**이다.
📢 섹션 요약 비유: 사가 패턴은 **'호텔 예약 취소 과정'**과 같습니다. 항공권, 렌터카, 호텔을 각각 예약했는데 호텔에 빈방이 없다는 걸 알았습니다. 이때 '타임머신(Rollback)'을 타고 처음으로 돌아갈 수는 없죠. 대신 내가 렌터카 회사에 전화해서 "취소할게요!", 항공사에 전화해서 "취소할게요!"라고 직접 환불 전화를 돌리는 수작업이 바로 보상 트랜잭션입니다.
Ⅱ. 사가 패턴의 핵심: 보상 트랜잭션 ★
사가 패턴에서 가장 중요한 것은 개발자의 노가다다. 개발자는 각 서비스(로컬 트랜잭션)를 만들 때마다, 그것을 완벽하게 뒤집는 '취소 버튼(보상 트랜잭션)'을 반드시 세트로 같이 만들어야 한다.
주문 생성()API를 만들었다면 $\rightarrow$주문 취소()API도 무조건 만든다.결제 승인()API를 만들었다면 $\rightarrow$결제 환불()API도 무조건 만든다.
[사가 패턴 실패 시나리오]
- (성공) 주문 서버:
주문 생성()완료. - (성공) 결제 서버:
결제 승인()완료. (내 카드에서 2만 원 빠져나감) - (실패) 재고 서버: 치킨이 다 떨어졌다! 에러 발생!
- (보상 시작): 사가 패턴이 에러를 감지하고 뒤로 돌아가기 시작한다.
- 결제 서버에게
결제 환불()명령을 던진다. (내 카드에 2만 원 들어옴) - 주문 서버에게
주문 취소()명령을 던진다. (주문 상태가 '취소됨'으로 바뀜) - 결과적으로 데이터는 100% 깔끔하게 원상 복구(최종적 일관성)되었다!
Ⅲ. 사가의 2가지 구현 방식
이 취소 명령(보상 트랜잭션)을 '누가' 지시할 것인가에 따라 나뉜다. (면접 단골 질문)
1. 코레오그래피 (Choreography - 안무)
- 방식: 대장(지휘자)이 없다. 댄서들(마이크로서비스)이 서로 약속된 안무에 맞춰 이벤트를 주고받는다.
- 주문 서버가 일이 끝나면 카프카(Kafka)에 "주문완료" 이벤트를 틱 던진다.
- 결제 서버가 그걸 줏어 먹고 결제를 한 뒤 "결제완료"를 틱 던진다.
- 장점: 중간에 껴드는 대장이 없어서 빠르고 유연하다.
- 단점: 서비스가 10개가 넘어가면, 도대체 이벤트가 어디서 어디로 날아다니는지 알 수가 없어 버그 잡기가 지옥이다.
2. 오케스트레이션 (Orchestration - 지휘)
- 방식: 오케스트라 지휘자(Saga Execution Coordinator) 서버를 중앙에 한 대 둔다.
- 지휘자: "주문 서버야, 일해!" $\rightarrow$ 완료
- 지휘자: "결제 서버야, 일해!" $\rightarrow$ 앗, 에러 났어!
- 지휘자: "전원 멈춰! 주문 서버야, 너 아까 했던 거 취소해(보상 트랜잭션)!"
- 장점: 비즈니스 흐름이 지휘자 코드 한 곳에 다 모여있어서 디버깅이 아주 편하다.
- 단점: 지휘자 서버가 뻗으면 전체가 멈춘다(SPOF). 지휘자 서버(보통 API Gateway나 특수 MSA 프레임워크)를 별도로 만들어야 한다.
┌──────────────────────────────────────────────────────────────┐
│ 사가 패턴 (Saga Pattern) 오케스트레이션 롤백 시각화 │
├──────────────────────────────────────────────────────────────┤
│ │
│ [ 🎼 지휘자 (Saga Coordinator) ] │
│ │ │
│ ├── 1. 지시 ──▶ [ 🛒 주문 서버 ] ──(성공)──▶ 2. 완료 보고 │
│ │ │
│ ├── 3. 지시 ──▶ [ 💳 결제 서버 ] ──(성공)──▶ 4. 완료 보고 │
│ │ │
│ ├── 5. 지시 ──▶ [ 🍗 재고 서버 ] ──(💥품절 에러!) │
│ │ │
│ ▼ 🚨 비상사태! 뒤로 돌아! (보상 트랜잭션 발동) │
│ │
│ ├── 6. 보상 ──▶ [ 💳 결제 서버 ] ──(환불)──▶ 7. 환불 완료 │
│ ├── 8. 보상 ──▶ [ 🛒 주문 서버 ] ──(취소)──▶ 9. 주문 취소 │
│ │
│ ★ 특징: DB 차원의 롤백이 아니라, 개발자가 만든 '취소 API'를 역순으로 호출함! │
└──────────────────────────────────────────────────────────────┘
Ⅳ. 결론
"분산 시스템에서 완벽함(ACID)은 포기하고, 유연함(BASE)을 안아라." 사가 패턴은 2PC(동기 블로킹 - 531번 문서)의 무거움을 피해, 메시지 큐(Kafka 등)를 이용한 '비동기 통신'으로 속도를 극한까지 끌어올린 클라우드 네이티브 시대의 핵심 디자인 패턴이다. 비록 내 카드가 결제되고 1초 뒤에 다시 환불되는 등 잠깐 데이터가 틀려 보이는 순간(결과적 일관성 - 464번 문서)이 존재하지만, 시스템 전체가 다운되는 것보다는 이 약간의 부자연스러움을 사용자(UX)에게 잘 설득하는 것이 훨씬 훌륭한 엔지니어링이다. 이제 분산 트랜잭션은 데이터베이스 엔진의 손을 떠나, 백엔드 개발자의 '보상 트랜잭션 설계 능력'으로 온전히 넘어왔다.
📌 관련 개념 맵
- 해결하려는 문제: Distributed Transaction (분산 트랜잭션), 2PC (531번 문서)
- 기반 철학: BASE 속성 (464번 문서), Eventual Consistency (결과적 일관성)
- 보조 아키텍처: Event Sourcing (533번 문서), CQRS
- 핵심 구현체: Axon Framework, AWS Step Functions
👶 어린이를 위한 3줄 비유 설명
- 사가 패턴은 '레고 성 조립 설명서'와 같아요. 1단계 문 만들기, 2단계 지붕 올리기 순서대로 각자 자기 일을 하죠.
- 근데 3단계 깃발을 달려고 보니까 깃발 블록이 없어요! (에러 발생)
- 이때 타임머신을 타고 처음으로 뿅 돌아갈 수는 없으니까, 지붕을 다시 뜯어내고 문을 다시 부숴서 원래 빈 상자 상태로 되돌리는 수작업(보상 트랜잭션)을 하는 거랍니다!