분산 트랜잭션(Distributed Transaction) 이란 여러 개의 독립적인 서비스(또는 데이터베이스)에 걸쳐 하나의 논리적 트랜잭션을 수행하는 것을 말한다.
모놀리식 아키텍처에서는 하나의 데이터베이스에 대해 로컬 트랜잭션(BEGIN → COMMIT/ROLLBACK)으로 ACID를 보장할 수 있었다. 하지만 MSA에서는 각 서비스가 자체 데이터베이스를 소유(Database per Service)하기 때문에, 여러 서비스에 걸친 데이터 일관성을 보장하는 것이 근본적으로 어렵다.
핵심 문제: 서비스 A의 DB 커밋은 성공했는데, 서비스 B의 DB 커밋이 실패하면 어떻게 할 것인가?
| 항목 | 모놀리식 | MSA |
|---|---|---|
| 데이터베이스 | 단일 DB (공유) | 서비스별 독립 DB |
| 트랜잭션 범위 | 로컬 트랜잭션 (단일 DB) | 여러 DB에 걸친 분산 트랜잭션 |
| ACID 보장 | DB 엔진이 보장 | 네트워크를 통해 직접 구현해야 함 |
| 롤백 | 단일 ROLLBACK 명령 | 각 서비스에 보상 트랜잭션 필요 |
| 복잡도 | 낮음 | 매우 높음 |
전통적인 분산 트랜잭션 프로토콜로, 코디네이터(Coordinator) 가 모든 참여자에게 커밋 준비를 요청하고, 모두 준비되면 최종 커밋을 지시하는 방식이다.
Phase 1 — Prepare (투표 단계)
코디네이터 → 참여자 A: "커밋할 준비 됐나?" → A: "Yes"
코디네이터 → 참여자 B: "커밋할 준비 됐나?" → B: "Yes"
Phase 2 — Commit (결정 단계)
코디네이터 → 참여자 A: "커밋해라" → A: COMMIT
코디네이터 → 참여자 B: "커밋해라" → B: COMMIT
만약 하나라도 실패하면:
코디네이터 → 모든 참여자: "롤백해라" → 전체 ROLLBACK
| 장점 | 단점 |
|---|---|
| 강한 일관성(Strong Consistency) 보장 | 동기 블로킹 방식으로 성능 저하 |
| 구현이 비교적 명확 | 코디네이터 단일 장애점(SPOF) |
| 참여자가 많을수록 지연 증가 | |
| MSA의 느슨한 결합 원칙에 위배 |
⚠️ MSA 환경에서는 성능과 가용성 문제로 2PC 사용을 권장하지 않는다.
MSA에서 가장 널리 사용되는 분산 트랜잭션 패턴이다. 각 서비스의 로컬 트랜잭션을 순차적으로 실행하고, 중간에 실패하면 이전에 완료된 트랜잭션을 보상 트랜잭션(Compensating Transaction) 으로 되돌리는 방식이다.
기본 원리:
T1 → T2 → T3 → ... → Tn (정상 흐름)
T1 → T2 → T3(실패) → C2 → C1 (보상 흐름)
T = 로컬 트랜잭션
C = 보상 트랜잭션 (해당 T를 되돌리는 작업)
각 서비스가 자신의 로컬 트랜잭션을 완료한 후 이벤트를 발행하고, 다음 서비스가 해당 이벤트를 구독하여 자신의 트랜잭션을 실행하는 방식이다. 중앙 조정자가 없다.
주문 서비스 결제 서비스 재고 서비스
| | |
|-- OrderCreated 발행 ----->| |
| |-- PaymentCompleted 발행 ->|
| | |-- StockReserved 발행
| | |
| [실패 시] | |
| |<-- StockFailed 발행 ------|
|<-- PaymentRefunded 발행 --| |
| | |
OrderCancelled | |
| 장점 | 단점 |
|---|---|
| 서비스 간 느슨한 결합 | 전체 흐름 파악이 어려움 |
| 단순한 구조 (이벤트 발행/구독) | 서비스가 많아지면 복잡도 급증 |
| 중앙 조정자 없이 동작 | 디버깅과 모니터링이 어려움 |
| 순환 의존성 발생 가능 |
적합한 경우: 참여 서비스가 2~4개로 적고, 흐름이 단순한 경우
Saga Orchestrator(오케스트레이터) 가 중앙에서 전체 트랜잭션 흐름을 관리하고, 각 서비스에 명령(Command)을 보내 트랜잭션을 실행시키는 방식이다.
Saga Orchestrator
(주문 Saga 관리)
/ | \
↓ ↓ ↓
주문 서비스 결제 서비스 재고 서비스
[정상 흐름]
Orchestrator → 주문 서비스: "주문 생성해라" → 성공
Orchestrator → 결제 서비스: "결제 처리해라" → 성공
Orchestrator → 재고 서비스: "재고 차감해라" → 성공
→ Saga 완료
[실패 흐름]
Orchestrator → 주문 서비스: "주문 생성해라" → 성공
Orchestrator → 결제 서비스: "결제 처리해라" → 성공
Orchestrator → 재고 서비스: "재고 차감해라" → 실패!
Orchestrator → 결제 서비스: "결제 취소해라" → 보상 완료
Orchestrator → 주문 서비스: "주문 취소해라" → 보상 완료
→ Saga 롤백 완료
| 장점 | 단점 |
|---|---|
| 전체 흐름이 한 곳에서 관리됨 | 오케스트레이터에 로직 집중 |
| 복잡한 워크플로우 처리 가능 | 오케스트레이터가 SPOF가 될 수 있음 |
| 디버깅, 모니터링이 용이 | 서비스 간 결합도가 다소 증가 |
| 보상 트랜잭션 관리가 명확 |
적합한 경우: 참여 서비스가 많고, 비즈니스 흐름이 복잡한 경우
| 항목 | Choreography | Orchestration |
|---|---|---|
| 조정 방식 | 분산 (이벤트 기반) | 중앙 집중 (오케스트레이터) |
| 결합도 | 낮음 | 중간 |
| 흐름 가시성 | 낮음 (추적 어려움) | 높음 (한 곳에서 확인) |
| 복잡도 관리 | 서비스 수 증가 시 급증 | 비교적 선형 증가 |
| 테스트 용이성 | 어려움 | 상대적으로 쉬움 |
| 적합한 규모 | 소규모 (2~4개 서비스) | 중대규모 (5개 이상 서비스) |
보상 트랜잭션은 네트워크 장애 등으로 중복 실행될 수 있기 때문에 반드시 멱등성을 보장해야 한다. 같은 보상 트랜잭션을 여러 번 실행해도 결과가 동일해야 한다.
// 멱등하지 않은 예시 (위험)
balance = balance - 10000;
// 멱등한 예시 (안전)
if (transaction_id가 아직 처리되지 않았으면) {
balance = balance - 10000;
transaction_id를 처리 완료로 기록;
}
일부 작업은 완료 후 되돌리기가 불가능하다. 이런 경우에 대한 대안이 필요하다.
| 사례 | 대안 |
|---|---|
| 이메일/알림 발송 완료 | 정정 이메일 발송, Saga의 마지막 단계로 배치 |
| 외부 API 호출 완료 | 취소 API 호출, 수동 처리 큐에 등록 |
| 물리적 작업 완료 (배송 출발) | 반품/회수 프로세스 연계 |
Saga의 현재 진행 상태를 Saga Log 또는 Saga State Store 에 영속화하여, 장애 발생 시 중단된 지점부터 재개하거나 보상을 수행할 수 있어야 한다.
MSA에서는 강한 일관성(Strong Consistency) 대신 최종 일관성(Eventual Consistency) 을 수용하는 것이 일반적이다.
"모든 데이터가 항상 일관된 상태일 필요는 없다. 일정 시간이 지나면 결국 일관된 상태에 도달하면 된다."
이는 CAP 정리(CAP Theorem)에 따른 현실적 선택이다. 분산 환경에서 네트워크 파티션(P)이 발생할 수 있는 상황에서, 가용성(A)과 일관성(C)을 동시에 완벽히 보장할 수 없으므로, MSA에서는 가용성을 우선하고 최종 일관성을 채택한다.
| 구분 | 도구/기술 |
|---|---|
| Saga 프레임워크 | Axon Framework, Eventuate Tram, MicroProfile LRA |
| 이벤트 브로커 | Apache Kafka, RabbitMQ, Amazon SNS/SQS |
| 상태 머신 | Spring State Machine, Temporal, Camunda |
| 분산 트랜잭션 (2PC) | Atomikos, Narayana (JTA) |