분산 트랜잭션을 여러 개의 로컬 트랜잭션으로 나누고, 실패하면 보상 트랜잭션으로 되돌리는 패턴.
발상
- 2PC — : 전부 잠그고 한 번에 커밋 (원자성 유지, 비쌈)
- Saga — : 하나씩 커밋하고, 실패하면 되돌린다 (원자성 포기, 가볍다)
각 단계는 즉시 커밋된다. 중간 상태가 외부에 보인다는 뜻이다.
흐름
주문 생성 ✓ → 결제 ✓ → 재고 차감 ✗ (실패!)
↓
보상: 결제 취소 → 주문 취소
(역순으로 되돌린다)
보상 트랜잭션은 "롤백"이 아니다
이 구분이 중요하다.
롤백 : 없었던 일로 만든다. 흔적이 남지 않는다 보상 : 반대 작업을 새로 수행한다. 양쪽 기록이 모두 남는다
결제 100원 → 환불 100원 (결제 기록도 남고 환불 기록도 남는다)
그래서 되돌릴 수 없는 작업은 Saga에 넣기 어렵다 — 이메일 발송, 외부 결제 확정, 물리적 배송. 이런 것은 가장 마지막에 배치하거나 "보류 후 확정" 구조로 바꾼다.
두 가지 방식
① 코레오그래피 (안무) — 각 서비스가 이벤트를 듣고 알아서 반응
주문서비스 ──OrderCreated──► 결제서비스
│
PaymentDone
▼
재고서비스
- 중앙 조정자가 없어 결합도가 낮다
- 흐름이 코드 곳곳에 흩어져 전체를 파악하기 어렵다
- 단계가 4개를 넘으면 추적이 힘들어진다
② 오케스트레이션 (지휘) — 중앙 조정자가 순서대로 지시
[Saga 오케스트레이터]
│ │ │
주문 결제 재고
- 흐름이 한 곳에 명시되어 이해·수정이 쉽다
- 오케스트레이터가 단일 장애점이 될 수 있다
- 복잡한 흐름에는 이쪽이 낫다
실무 기준 — 단계가 3개 이하면 코레오그래피, 그 이상이면 오케스트레이션.
반드시 챙겨야 할 것들
① 멱등성 — 재시도와 중복 메시지가 전제다. 같은 보상이 두 번 실행돼도 안전해야 한다.
② 상태 저장 — Saga가 어디까지 진행됐는지 DB에 남겨야 한다. 안 그러면 오케스트레이터 재시작 시 복구할 수 없다.
③ 보상 실패 처리 — 보상마저 실패하면? 무한 재시도 + 알림 + 수동 개입. 자동으로 해결할 수 없는 지점이 반드시 남는다.
④ 중간 상태 노출 — 사용자에게 "결제 완료, 배송 준비 중"처럼 진행 상태를 보여 주는 UI가 필요하다. Saga는 시간이 걸리는 흐름이다.
격리성이 없다 — 가장 중요한 한계
Saga 진행 중에 다른 트랜잭션이 중간 상태를 읽을 수 있다
주문은 생성됐는데 결제 전 상태를 다른 서비스가 "확정 주문"으로 오인
대응은 의미적 락(상태 컬럼으로 PENDING 표시)이나
교환 가능한 업데이트(순서와 무관한 연산으로 설계)다.
면접 답변 골격
"서비스 경계를 넘는 트랜잭션은 2PC 대신 Saga로 풉니다. 원자성 대신 최종 일관성을 받아들이고, 각 단계를 멱등하게 만들며, 보상 실패는 알림 후 수동 개입으로 남겨 둡니다. 되돌릴 수 없는 작업은 흐름의 끝에 배치합니다."