개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Saga Pattern — 분산 트랜잭션을 관리하는 방법

2025년 1월 9일
SagaSagaCompensationChoreographyOrchestration

단일 데이터베이스에서는 ACID 트랜잭션 하나로 모든 것을 묶을 수 있다. 그러나 마이크로서비스 환경에서는 서비스마다 DB가 분리되어 있어 2PC(Two-Phase Commit)는 가용성 문제를 일으키고 실용적이지 않다. Saga Pattern은 하나의 긴 트랜잭션을 여러 개의 로컬 트랜잭션으로 분리하고, 실패 시 Compensation(보상 트랜잭션)으로 롤백하는 방식으로 이 문제를 해결한다.

Distributed Transaction 이란

Distributed Transaction은 서로 다른 서비스·DB에 걸쳐 원자성을 보장해야 하는 작업이다. 주문 생성 → 결제 → 재고 차감이 각각 다른 서비스에서 일어날 때, 결제는 성공했는데 재고 차감이 실패하면 데이터 불일치가 발생한다.

2PC(Two-Phase Commit): 분산 참여자 전원이 커밋에 동의해야만 커밋하는 프로토콜. 코디네이터 장애 시 블로킹 문제가 있다.

Choreography vs Orchestration

Saga를 구현하는 방식은 두 가지다.

방식제어 주체결합도복잡도
Choreography각 서비스 (이벤트 기반)낮음흐름 추적 어려움
Orchestration중앙 Orchestrator높음흐름 명확

Choreography 방식에서는 각 서비스가 이벤트를 발행하고 다른 서비스가 구독해 다음 단계를 처리한다.

Order Service          Payment Service        Inventory Service
     |                       |                       |
     |-- OrderCreated ------>|                       |
     |                       |-- PaymentCompleted -->|
     |                       |                       |-- InventoryReserved
     |<-----------------------------------------------|

Orchestration 방식에서는 Saga Orchestrator가 각 서비스를 순서대로 호출한다.

         Saga Orchestrator
              |
     1. POST /payments  --> Payment Service
              |
     2. POST /inventory --> Inventory Service
              |
     3. PATCH /orders   --> Order Service (confirm)

Orchestrator: 사가의 단계별 실행 순서와 실패 처리를 중앙에서 관리하는 컴포넌트.

Compensation 동작

각 로컬 트랜잭션에 대응하는 Compensation Transaction을 미리 정의해야 한다. 재고 차감 단계에서 실패하면, 이미 완료된 결제를 취소하는 보상 트랜잭션이 역순으로 실행된다.

정상 흐름:   주문생성 → 결제 → 재고차감
실패 발생:                     ↑ 실패
보상 흐름:   주문취소 ← 결제취소 ←

TypeScript로 Orchestrator의 보상 로직을 단순화하면 다음과 같다.

async function runSaga(steps: SagaStep[]): Promise<void> {
  const completed: SagaStep[] = [];

  for (const step of steps) {
    try {
      await step.execute();
      completed.push(step);
    } catch (err) {
      // 역순으로 보상 트랜잭션 실행
      for (const done of completed.reverse()) {
        await done.compensate();
      }
      throw err;
    }
  }
}

SagaStep: execute(실행)와 compensate(보상) 메서드를 가진 단위 작업 인터페이스.

실무 적용 시 고려사항

Saga는 최종 일관성(Eventual Consistency)을 전제로 한다. 중간 상태가 외부에 노출될 수 있으므로 UI에서 "처리 중" 상태를 명시적으로 다뤄야 한다.

멱등성(Idempotency) 확보가 필수다. 네트워크 재시도 시 동일 요청이 중복 처리되지 않도록 각 단계에 idempotency key를 적용한다.

Choreography 방식은 서비스 수가 늘어날수록 이벤트 흐름 추적이 어려워진다. 이 경우 Distributed Tracing(예: Jaeger, Zipkin)을 함께 도입해 전체 Saga 흐름을 가시화하는 것이 좋다.

← 이전 글CQRS — 조회와 명령을 분리하는 아키텍처 패턴
다음 글 →Backend For Frontend — 클라이언트별 전용 백엔드를 두는 패턴