MSA 분산 트랜잭션과 Saga 패턴 정리.
22 Sep 2026 | MSA Saga 분산 트랜잭션 Choreography Orchestration🧩 MSA 아키텍처 시리즈 (전체 3편)
- MSA 기본 개념
- MSA 회복탄력성(Resilience) 패턴
- MSA 분산 트랜잭션과 Saga 패턴 (현재 글)
이 블로그의 Spring 에서의 트랜잭션 처리 글에서 다룬 트랜잭션은 하나의 DB 안에서의 이야기였다. MSA에서는 서비스마다 DB가 분리되므로(MSA 기본 개념 글의 “Database per Service”), 여러 서비스에 걸친 작업을 어떻게 하나의 트랜잭션처럼 묶을지가 새로운 문제가 된다.
문제 상황
전자상거래 주문 처리를 생각해보자. 모놀리스라면 하나의 DB 트랜잭션으로 묶어서 처리했을 것이다.
BEGIN;
INSERT INTO orders (...); -- 주문 생성
UPDATE inventory SET stock = stock - 1 WHERE product_id = ...; -- 재고 차감
INSERT INTO payments (...); -- 결제 처리
COMMIT; -- 셋 중 하나라도 실패하면 전부 ROLLBACK
MSA에서는 주문/재고/결제가 각각 다른 서비스, 다른 DB에 있다. BEGIN...COMMIT으로 묶을 수 있는 대상이 아니다 — 재고 서비스 DB를 주문 서비스가 직접 트랜잭션으로 묶을 방법이 없다(DB 자체가 물리적으로 분리되어 있으므로).
2PC(Two-Phase Commit) — 왜 MSA에서 잘 안 쓰이나
분산 트랜잭션의 고전적 해법이다. 코디네이터가 모든 참여자에게 “준비됐어?”(Prepare) 물어보고, 전부 “그렇다”고 하면 “커밋해”(Commit)를 보내는 2단계 방식이다.
MSA에서 잘 안 쓰이는 이유
- 모든 참여자가 준비 완료 응답을 할 때까지 각자 락을 걸어둔 채 대기해야 함 → 참여 서비스가 많아질수록, 그 중 하나만 느려도 전체가 블로킹됨.
- 코디네이터가 죽으면 참여자들이 영원히 대기(blocking) 상태에 빠질 수 있음.
- 가용성(Availability)을 강하게 희생하는 방식이라, MSA가 애초에 추구하는 “서비스별 독립성/가용성”과 철학적으로 충돌.
그래서 MSA에서는 강한 일관성(Strong Consistency) 대신, 최종적 일관성(Eventual Consistency)을 받아들이는 Saga 패턴을 주로 사용한다.
Saga 패턴 — 보상 트랜잭션으로 되돌리기
아이디어는 이렇다: 하나의 큰 트랜잭션을, 각 서비스가 처리하는 일련의 로컬 트랜잭션으로 쪼갠다. 중간에 실패하면, 이미 실행된 이전 단계들을 되돌리는 보상 트랜잭션(Compensating Transaction)을 순서대로 실행한다.
1. 주문 서비스: 주문 생성 (로컬 트랜잭션, 커밋)
2. 재고 서비스: 재고 차감 (로컬 트랜잭션, 커밋)
3. 결제 서비스: 결제 시도 -> 실패!
-- 실패 이후 보상 트랜잭션 (역순으로)
2'. 재고 서비스: 차감했던 재고 복구 (보상)
1'. 주문 서비스: 주문 상태를 '취소'로 변경 (보상)
각 단계는 “성공하거나, 실패하면 이전 단계를 취소하는 보상 로직이 반드시 존재”해야 한다는 게 전제다. 각 로컬 트랜잭션 자체는 각 서비스 DB 안에서 원자적(ACID)이지만, 그 여러 개를 묶는 “사가 전체”는 원자적이지 않고, 실패 시 명시적으로 되돌리는 코드를 짜야 한다.
방식 1) Choreography (안무 방식) — 이벤트를 통한 자율 조정
주문 서비스 --(OrderCreated 이벤트)--> 재고 서비스 --(StockReserved 이벤트)--> 결제 서비스
|
결제 실패!
v
(PaymentFailed 이벤트) <-----------+
재고 서비스가 구독 -> 재고 복구
주문 서비스가 구독 -> 주문 취소
각 서비스가 이벤트(메시지)를 발행하고, 다른 서비스들이 그 이벤트를 구독해서 각자 다음 행동을 결정한다. 중앙에서 지휘하는 주체가 없다 (RabbitMQ/Kafka 같은 메시지 브로커 위에서 구현).
- 장점: 서비스 간 결합이 낮음(누가 이 이벤트를 구독하는지 발행자는 몰라도 됨).
- 단점: 서비스가 많아질수록 “지금 전체 흐름이 어떻게 되고 있는지”를 한눈에 파악하기 어려움 — 이벤트를 따라다니며 로직이 여기저기 흩어짐.
방식 2) Orchestration (오케스트레이션 방식) — 중앙 조정자
┌──────────────────┐
│ Saga Orchestrator │
└──────────────────┘
| | |
v v v
주문 재고 결제
서비스 서비스 서비스
중앙의 오케스트레이터(Saga Manager)가 “1단계 호출 → 성공하면 2단계 호출 → 실패하면 보상 호출” 순서를 명시적으로 관리한다.
- 장점: 흐름이 한 곳(오케스트레이터 코드)에 명시적으로 드러나서 파악/디버깅이 쉬움.
- 단점: 오케스트레이터 자체가 각 서비스를 다 알아야 하므로 결합도가 상대적으로 높아지고, 오케스트레이터가 또 하나의 장애 지점(이전 글의 회복탄력성 패턴을 여기에도 똑같이 적용해야 함)이 될 수 있음.
실무에서는 참여 서비스 수가 많고 흐름이 복잡할수록 Orchestration을, 단순하고 서비스가 적을수록 Choreography를 선호하는 경향이 있다.
Outbox 패턴 — “DB에 저장했는데 이벤트 발행을 깜빡하면?”
Saga에서 실제로 자주 겪는 함정이 있다: “DB 커밋은 성공했는데, 그 직후 이벤트 발행에 실패하면?” (또는 반대로 이벤트는 발행됐는데 DB 커밋이 롤백되면?) → 둘 사이에 정합성이 깨진다.
해결책은 DB 트랜잭션 안에 “발행할 이벤트” 자체를 같은 트랜잭션으로 outbox 테이블에 함께 저장하고, 별도의 프로세스(polling 또는 CDC, Change Data Capture)가 그 outbox 테이블을 읽어서 실제 메시지 브로커로 발행하는 것이다. 이렇게 하면 “비즈니스 데이터 저장”과 “이벤트 저장”이 같은 로컬 트랜잭션으로 묶여서 원자성이 보장되고, 실제 브로커 발행은 그 이후 안정적으로 재시도할 수 있는 별개의 문제로 분리된다.
3편에 걸쳐 MSA의 기본 개념부터 회복탄력성 패턴, 분산 트랜잭션까지 정리해봤다. 이 시리즈에서 다룬 내용 대부분은 “모놀리스에서는 고민할 필요 없던 문제를, MSA로 넘어가면서 새로 떠안게 된다”는 흐름으로 이어진다 — 첫 글에서 강조했듯, MSA는 기술적 우열이 아니라 그 복잡도를 감당할 조직/운영 역량이 있는지의 문제라는 걸 다시 한번 짚고 마무리한다.
참고자료
- Chris Richardson, Saga pattern (microservices.io)
- Chris Richardson, Transactional outbox pattern
Comments