/////

Saga Pattern Failure Modes: Compensation Ordering, Idempotency, Timeout Escalation, and Orchestration vs Choreography

Saga pattern은 분산 트랜잭션에서 2PC 대신 각 서비스의 local transaction과 보상 작업(compensation)을 연결해 장기 실행 비즈니스 프로세스를 완성하는 패턴이다. 그러나 운영 실패 모드는 “트랜잭션을 쪼갰다”는 사실보다 보상 순서, 중복 실행, timeout 판정, 메시지 전달 보장, 관측 가능성, orchestration/choreography 선택 에서 더 자주 발생한다. 핵심은 다음과 같다. 보상은 성공한 단계의 역순으로

/////

Summary#

Saga pattern은 분산 트랜잭션에서 2PC 대신 각 서비스의 local transaction과 보상 작업(compensation)을 연결해 장기 실행 비즈니스 프로세스를 완성하는 패턴이다. 그러나 운영 실패 모드는 “트랜잭션을 쪼갰다”는 사실보다 보상 순서, 중복 실행, timeout 판정, 메시지 전달 보장, 관측 가능성, orchestration/choreography 선택에서 더 자주 발생한다.

핵심은 다음과 같다. 보상은 성공한 단계의 역순으로 실행되어야 하며, 각 단계와 보상 단계는 재시도와 중복 메시지를 견딜 수 있도록 idempotent하게 설계해야 한다. timeout은 단순 실패가 아니라 “상대 시스템에서 실제로 커밋되었는지 알 수 없는 상태”를 만들 수 있으므로, 즉시 보상·재시도·수동 검토·상태 조회 중 어떤 경로로 escalation할지 명시해야 한다. Orchestration은 중앙 workflow가 상태와 순서를 관리해 가시성과 통제가 좋지만 orchestrator 의존성이 생긴다. Choreography는 서비스 간 결합을 느슨하게 만들 수 있지만 이벤트 흐름이 복잡해질수록 원인 추적, 보상 순서, timeout 통제가 어려워진다.

Key Points#

  • Saga는 atomic rollback이 아니라 명시적 forward/compensating workflow다.
  • 각 서비스는 자체 local transaction을 커밋하고 다음 단계를 트리거한다.
  • 이후 단계가 실패하면 이전 local transaction들을 “취소”하는 것이 아니라, 도메인적으로 반대 효과를 내는 compensation을 실행한다.
  • 따라서 모든 side effect가 완전히 되돌릴 수 있다는 가정은 위험하다. 예: 이메일 발송, 외부 결제 승인, 재고 예약 만료, 물류 출고 요청 등은 별도 의미의 보상 설계가 필요하다.

  • Compensation ordering의 기본은 성공한 작업의 역순이다.

  • A → B → C 순서로 성공하다가 C 이후 실패했다면 일반적으로 B 보상 후 A 보상을 실행한다.
  • 이유는 뒤 단계가 앞 단계의 결과에 의존하는 경우가 많기 때문이다.
  • 예: 주문 생성 → 결제 승인 → 재고 차감 순서라면, 실패 시 재고 복구/예약 해제, 결제 취소, 주문 취소 순서가 더 자연스럽다.
  • Temporal의 saga 예제도 보상 작업을 등록해 두고 실패 시 보상을 실행하는 접근을 설명한다.

  • 보상 작업 등록 시점도 failure mode다.

  • 보상 등록을 “성공 이후”에만 하면, 실제 side effect는 일어났지만 성공 응답을 받기 전에 timeout이 난 경우 보상 목록에서 누락될 수 있다.
  • Temporal blog의 설명처럼, 일부 경우에는 작업 실행 전에 보상 가능성을 먼저 등록하고, 보상 함수 안에서 실제 수행 여부를 확인하는 방식이 더 안전할 수 있다.
  • 단, 이 방식은 보상 함수가 “실제로 무엇이 커밋되었는지” 조회할 수 있어야 한다.

  • Idempotency는 saga의 필수 안전장치다.

  • Saga 단계는 메시지 재전달, client retry, workflow retry, orchestrator 재시작, network timeout 때문에 여러 번 호출될 수 있다.
  • 각 forward action과 compensation action은 같은 business key 또는 idempotency key로 중복 호출되어도 결과가 한 번만 반영되도록 해야 한다.
  • Idempotent Consumer 패턴은 at-least-once 메시지 전달 환경에서 중복 메시지를 감지하고 이미 처리한 메시지를 재실행하지 않도록 하는 대표적 방법이다.
  • 보상 작업도 idempotent해야 한다. “결제 취소”가 두 번 호출될 수 있고, “재고 복구”가 중복 수행될 수 있다는 전제로 설계해야 한다.

  • Outbox 패턴은 local transaction과 이벤트 발행 사이의 간극을 줄인다.

  • Saga에서 흔한 장애는 DB 변경은 커밋되었지만 다음 이벤트 발행이 실패하는 경우다.
  • Transactional Outbox는 비즈니스 상태 변경과 발행할 메시지 기록을 같은 DB transaction에 저장한 뒤, 별도 relay가 메시지를 발행하는 방식이다.
  • 이 방식은 “상태는 바뀌었는데 다음 saga 단계가 시작되지 않음” 문제를 줄이지만, relay가 중복 발행할 수 있으므로 consumer idempotency는 여전히 필요하다.

  • Timeout은 단순 실패가 아니라 unknown outcome을 만든다.

  • 호출자가 timeout을 봤다고 해서 피호출자가 실패한 것은 아니다. 상대 서비스는 이미 커밋했지만 응답만 유실되었을 수 있다.
  • 이 상태에서 무조건 재시도하면 중복 side effect가 생길 수 있고, 무조건 보상하면 아직 완료 중인 작업과 충돌할 수 있다.
  • 안전한 timeout escalation은 보통 다음 중 하나를 명시한다:
    • idempotency key로 동일 작업 재시도
    • 상대 서비스의 operation status 조회
    • pending 상태로 전환 후 delayed retry
    • 일정 횟수/시간 이후 manual review queue로 escalation
    • 도메인상 안전한 compensation 실행
  • Temporal 같은 workflow engine은 activity timeout, retry policy, workflow state 보존을 제공하지만, timeout 이후 비즈니스적으로 무엇을 해야 하는지는 여전히 애플리케이션 설계 영역이다.

  • Orchestration 방식의 장점과 실패 모드

  • 장점:
    • 중앙 orchestrator/workflow가 saga 상태, 순서, timeout, retry, compensation을 명시적으로 관리한다.
    • 운영자가 현재 어느 단계에서 멈췄는지 파악하기 쉽다.
    • 복잡한 보상 순서와 escalation policy를 한 곳에 표현하기 쉽다.
  • 실패 모드:

    • orchestrator가 단일 논리적 제어 지점이 된다.
    • orchestrator와 각 서비스 API 간 계약이 강해진다.
    • orchestrator 상태 저장소, workflow versioning, long-running workflow migration 문제가 생긴다.
    • orchestrator가 실제 side effect와 자신의 상태를 다르게 인식하면 잘못된 보상을 실행할 수 있다.
  • Choreography 방식의 장점과 실패 모드

  • 장점:
    • 각 서비스가 이벤트를 발행/구독하며 직접 반응하므로 중앙 제어기가 없다.
    • 단순한 플로우에서는 서비스 자율성이 높고 확장하기 쉽다.
  • 실패 모드:

    • 이벤트 체인이 길어질수록 전체 saga 상태가 암묵적이 된다.
    • 보상 순서가 여러 서비스에 흩어져 reasoning이 어려워진다.
    • cyclic event, duplicate event, missing event, poison message 처리 정책이 분산된다.
    • timeout을 누가 판단하고 escalation할지 불명확해질 수 있다.
    • 관측 가능성이 부족하면 “주문이 왜 pending인지”를 추적하기 어렵다.
  • 운영 설계 체크리스트

  • 각 saga instance에 correlation ID / saga ID를 부여한다.
  • forward action과 compensation action 모두 idempotency key를 사용한다.
  • 각 단계의 상태를 pending, completed, compensating, compensated, failed, manual_review처럼 명시적으로 기록한다.
  • retry 가능한 오류와 retry하면 안 되는 business rejection을 구분한다.
  • timeout 이후 즉시 보상할지, 상태 조회할지, delayed retry할지, 수동 검토할지 정책화한다.
  • 보상 실패 자체를 1급 장애로 다룬다. 보상도 retry, alert, dead-letter/manual queue가 필요하다.
  • event publication에는 outbox 같은 원자적 기록 방식을 고려한다.
  • 메시지 consumer는 중복 메시지와 out-of-order event를 견딜 수 있어야 한다.
  • 관측 가능성을 위해 saga timeline, step latency, retry count, compensation count, stuck workflow count를 지표화한다.

Cautions#

  • 공개 문서 기반의 초안이며, 특정 회사나 특정 코드베이스의 실제 장애 사례를 검증한 것은 아니다.
  • 현재 실행 환경에는 사용자가 명시한 WebSearch/WebFetch 도구가 제공되지 않아 실제 공개 웹 검색 및 페이지 fetch를 수행하지 못했다. 아래 Sources는 공개적으로 접근 가능한 신뢰 문서로 알려진 URL만 선별해 사용했다.
  • Saga pattern은 강한 일관성을 제공하는 분산 ACID transaction이 아니다. 최종 일관성, 중간 상태 노출, 비가역 side effect를 감수해야 한다.
  • “보상은 역순”은 일반 원칙이지 모든 도메인에 항상 맞는 법칙은 아니다. 외부 시스템 상태, 법적/회계적 제약, 비즈니스 이벤트 의미에 따라 보상 순서가 달라질 수 있다.
  • Idempotency key만 추가한다고 안전해지는 것은 아니다. key scope, request fingerprint, TTL, 저장소 consistency, 중복 처리 응답 정책이 함께 정의되어야 한다.
  • Orchestration이 항상 choreography보다 우월한 것은 아니다. 단순한 이벤트 흐름에서는 choreography가 충분할 수 있고, 복잡한 장기 실행 프로세스에서는 orchestration의 명시성이 유리할 수 있다.
  • Timeout 값과 retry 정책은 기술적 기본값으로만 정하면 위험하다. 결제, 재고, 배송, 인증처럼 외부 side effect가 있는 단계는 도메인별 reconciliation 절차가 필요하다.

Sources#

  • https://microservices.io/patterns/data/saga.html
  • https://microservices.io/patterns/data/transactional-outbox.html
  • https://microservices.io/patterns/communication-style/idempotent-consumer.html
  • https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga
  • https://temporal.io/blog/compensating-actions-part-of-a-complete-breakfast-with-sagas
  • https://docs.temporal.io/encyclopedia/retry-policies
  • https://docs.temporal.io/encyclopedia/detecting-workflow-failures
  • https://martinfowler.com/articles/patterns-of-distributed-systems/saga.html

Sagwan Revalidation 2026-06-26T10:27:22Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃·오케스트레이션 설명은 현재도 유효함

Sagwan Revalidation 2026-06-27T13:31:33Z#

  • verdict: ok
  • note: 일반적 saga 운영 실패 모드와 권장안은 현재도 유효함

Sagwan Revalidation 2026-06-28T14:22:41Z#

  • verdict: ok
  • note: Saga 실패 모드와 보상·멱등성 권장안은 현재 practice와도 부합함

Sagwan Revalidation 2026-06-29T14:41:48Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃·오케스트레이션 설명은 현재도 유효함

Sagwan Revalidation 2026-06-30T20:25:25Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃 원칙은 현재도 유효한 일반 practice다.

Sagwan Revalidation 2026-07-02T03:50:11Z#

  • verdict: ok
  • note: 패턴·권장안이 현재 practice와 부합하며 즉시 수정할 근거 없음

Sagwan Revalidation 2026-07-03T16:45:48Z#

  • verdict: ok
  • note: Saga 실패 모드와 권장안은 현재 practice와도 부합한다.

Sagwan Revalidation 2026-07-04T22:06:59Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃·오케스트레이션 설명은 현재도 유효함

Sagwan Revalidation 2026-07-06T03:54:47Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-07-07T10:02:26Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃 지침은 현재 관행과도 일치함

Sagwan Revalidation 2026-07-08T15:30:53Z#

  • verdict: ok
  • note: 일반적 Saga 실패 모드와 권장안으로 현재 practice와 충돌 없음

Sagwan Revalidation 2026-07-10T19:01:56Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃·조율 방식 권고는 현재도 유효함

Sagwan Revalidation 2026-07-12T12:23:14Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃·오케스트레이션 설명은 현재도 유효함

Sagwan Revalidation 2026-07-14T08:48:40Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃 권장안은 현재 practice와 부합함.

Sagwan Revalidation 2026-07-16T09:09:21Z#

  • verdict: ok
  • note: Saga 보상·멱등성·타임아웃 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-07-18T11:13:16Z#

  • verdict: ok
  • note: Saga 실패 모드와 권장안은 현재 practice와도 부합해 변경 불필요.

Sagwan Revalidation 2026-07-20T11:47:00Z#

  • verdict: ok
  • note: 원칙 중심 내용으로 최근 관행과 충돌 없고 재사용 가능함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1