본문 바로가기
Backend2026년 8월 11일6분 읽기

사가(Saga) 패턴 — 분산 트랜잭션과 보상 트랜잭션

YS
김영삼
조회 6
사가(Saga) 패턴 — 분산 트랜잭션과 보상 트랜잭션

사가(Saga) 패턴은 여러 서비스에 걸친 작업을 하나의 큰 트랜잭션으로 묶을 수 없을 때, 각 서비스의 로컬 트랜잭션을 순서대로 이어 붙이고 중간에 실패하면 보상 트랜잭션(compensating transaction)으로 앞의 작업을 되돌려 일관성을 맞추는 방법입니다. 한마디로 "롤백이 불가능한 세계에서 롤백을 흉내 내는" 기법이죠.

마이크로서비스로 쪼개는 순간 이 문제를 반드시 만납니다. 주문 서비스, 결제 서비스, 재고 서비스가 각자 다른 DB를 가지면, "주문 생성 + 결제 + 재고 차감"을 하나의 ACID 트랜잭션으로 묶을 방법이 없습니다. 한 서비스가 커밋한 걸 다른 서비스가 롤백시킬 수 없으니까요.

왜 그냥 분산 트랜잭션(2PC)을 안 쓰나

이론적으로는 2단계 커밋(2PC)으로 여러 DB를 묶을 수 있습니다. 하지만 실무에선 잘 안 씁니다. 2PC는 모든 참여자가 준비될 때까지 자원을 잠근 채 기다리고, 코디네이터가 죽으면 참여자들이 애매한 상태로 멈춰버립니다(blocking). 서비스가 많고 그중 하나라도 느리면 전체가 발목 잡혀요. 사가는 각 단계를 즉시 커밋하고 잠금을 오래 잡지 않기 때문에, 가용성과 확장성 면에서 분산 환경에 더 잘 맞습니다.

보상 트랜잭션 — 핵심 아이디어

사가에는 "취소" 대신 "되돌리는 별도의 작업"이 있습니다. 결제를 취소하려면 결제 취소라는 새로운 트랜잭션을, 재고 차감을 되돌리려면 재고 복원이라는 새 트랜잭션을 실행합니다.

정상 흐름:  주문생성 → 결제 → 재고차감 → 배송요청
실패 시:                    ↑ 재고차감 실패!
보상 흐름:  주문취소 ← 결제환불 ← (재고는 애초에 안 됐으니 스킵)

여기서 중요한 통찰. 보상은 완벽한 되돌림이 아닐 수 있다는 점입니다. 이미 고객에게 "결제 완료" 메일이 나갔다면 그 메일은 회수할 수 없죠. 그래서 보상은 "없던 일로"가 아니라 "잘못을 바로잡는 실제 비즈니스 행위"(환불, 취소 알림)로 설계해야 합니다.

두 가지 구현 방식

사가를 조율하는 방식은 크게 코레오그래피와 오케스트레이션으로 나뉩니다.

구분코레오그래피오케스트레이션
방식각 서비스가 이벤트를 듣고 반응중앙 오케스트레이터가 명령을 지시
제어분산(중앙 없음)중앙 집중
장점느슨한 결합, 단순한 시작흐름이 한눈에, 디버깅 쉬움
단점흐름 파악 어려움, 순환 위험오케스트레이터가 단일 지점

코레오그래피는 "주문 생성됨" 이벤트가 나가면 결제 서비스가 알아서 반응하고, "결제 완료됨"에 재고 서비스가 반응하는 식입니다. 참여자가 서넛일 땐 가볍고 좋아요. 그런데 단계가 늘고 실패·보상 분기가 많아지면, 전체 흐름이 여러 서비스에 흩어져 도대체 지금 어디까지 진행됐는지 아무도 모르는 상태가 됩니다. 나는 이걸 몇 번 겪고 나서, 단계가 4개를 넘어가면 오케스트레이션으로 갑니다.

오케스트레이션은 별도의 조율자(상태 기계)가 "결제해라 → 됐으면 재고 차감해라 → 실패하면 환불해라"를 순서대로 지시하고 상태를 추적합니다. 흐름이 코드 한 곳에 모여 있어 이해와 디버깅이 훨씬 쉽습니다. 대신 그 조율자를 잘 만들어야 하고, 조율자 자체의 상태를 안정적으로 저장(예: 이벤트 소싱, 워크플로 엔진)해야 합니다.

반드시 지켜야 할 것들

  • 각 단계와 보상은 멱등해야 한다. 사가는 재시도가 잦습니다. "환불" 명령이 중복 도착해도 두 번 환불되면 안 됩니다.
  • 보상은 실패할 수 있다. 환불 요청이 실패하면? 보상도 재시도·백오프가 필요하고, 그래도 안 되면 사람이 개입할 수 있게 알람을 걸어야 합니다.
  • 격리성이 없다. 사가 중간 상태가 다른 요청에 그대로 노출됩니다("결제는 됐지만 배송은 아직"). 필요하면 semantic lock(예: 주문 상태 PENDING)으로 중간 상태를 표시하세요.
솔직히 처음 사가를 도입했을 땐 "보상만 짜면 되겠지" 했습니다. 진짜 어려운 건 보상 자체가 아니라, 부분적으로 완료된 애매한 상태를 시스템 전체가 어떻게 다루느냐였습니다. 그걸 설계하지 않으면 데이터는 조용히 어긋납니다.

자주 묻는 질문

사가는 ACID의 어떤 부분을 포기하나요?

격리성(Isolation)을 포기하고, 원자성(Atomicity)도 즉시성이 아니라 보상을 통한 "최종적 원자성"으로 완화합니다. 대신 각 로컬 트랜잭션은 여전히 ACID입니다. 전체적으로는 강한 일관성 대신 최종 일관성(eventual consistency)을 받아들이는 거죠.

코레오그래피와 오케스트레이션 중 뭘 고르죠?

참여 서비스가 적고(2~3개) 흐름이 단순하면 코레오그래피가 가볍습니다. 단계가 많거나 조건 분기·보상이 복잡하면 오케스트레이션이 관리하기 쉽습니다. 둘을 섞는 것도 가능하니 도메인 복잡도로 판단하세요.

Temporal 같은 워크플로 엔진이 사가인가요?

워크플로 엔진은 오케스트레이션형 사가를 구현하는 아주 좋은 도구입니다. 상태 저장, 재시도, 타임아웃, 보상 로직을 엔진이 대신 챙겨줘서 조율자를 직접 만들 때의 상당 부분을 덜어줍니다. 다만 사가라는 개념 자체는 특정 도구에 묶이지 않습니다.

아웃박스 패턴과는 어떤 관계인가요?

상호 보완적입니다. 사가의 각 단계에서 "로컬 트랜잭션 커밋 + 이벤트 발행"을 안정적으로 하려면 이중 쓰기 문제를 풀어야 하는데, 그 도구가 바로 트랜잭셔널 아웃박스입니다. 실무에선 사가와 아웃박스를 함께 쓰는 경우가 많습니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록