Skip to main content
KBY Technologies Logo
cd ../lexicon
sys/docs/lexicon/saga-pattern-distributed-transaction-choreography.md
Lexicon Entry

Saga Pattern (Distributed Transaction Choreography)

A pattern for managing data consistency across microservices using a sequence of local transactions, each with a corresponding compensating transaction for failure recovery.

Practical example

An e-commerce order saga: charge payment, reserve inventory, schedule shipping. If shipping fails, the saga triggers compensations — release inventory reservation, then refund the payment — each as an idempotent, independently retryable operation rather than a true undo.

The Saga pattern exists because Two-Phase Commit (2PC) does not scale across autonomous service boundaries with independent datastores. 2PC requires a coordinator to hold locks on all participants until every node votes to commit, which creates a blocking dependency graph that collapses under partition or coordinator failure. Sagas trade atomicity for availability: each step commits locally and immediately, and failure recovery is handled retroactively via compensating transactions rather than proactively via locking. This is a direct application of the BASE model (Basically Available, Soft state, Eventually consistent) over ACID.

There are two execution topologies, and the choice has significant architectural blast radius:

  • Orchestration-based Sagas: a central orchestrator (state machine) explicitly invokes each participant and issues compensations on failure. This centralizes the transaction graph, making it observable and testable, but the orchestrator becomes a critical-path dependency and a potential single point of coupling.
  • Choreography-based Sagas: services react to events emitted by prior steps (typically via a broker) with no central controller. This maximizes decoupling but makes the end-to-end transaction graph implicit — debugging a stuck saga requires reconstructing intent from a distributed event trace, and cyclic event dependencies are a real failure mode.

The critical engineering hazard is that compensations are not rollbacks: they are new, forward-moving transactions that must be designed to be semantically valid regardless of intermediate state. If step 3 fails after step 1 (charge card) and step 2 (reserve inventory) succeeded, the compensation for step 1 is refund(), not undo_charge() — the money may have already left the account. This introduces the requirement that every participant expose an idempotent compensating operation, because the orchestrator/choreography layer will retry on ambiguous failures (timeout vs. actual failure), and duplicate compensations must be safe. Idempotency is typically enforced via a deduplication key stored alongside an outbox or saga-log entry.

A subtler failure class is the semantic lock window: between a step committing and its potential compensation, the system is in an intermediate, externally visible state (e.g., inventory reserved, payment pending). Concurrent readers can observe this uncommitted-in-aggregate state, which is why Sagas are frequently paired with the Outbox Pattern for reliable event emission and with explicit “pending/reserved” states in the domain model rather than pretending the operation is atomic. Sagas also fundamentally cannot guarantee isolation — two concurrent sagas can interleave and produce results neither would produce individually (a lost-update-style anomaly), so systems with high concurrency on the same aggregate often need additional application-level countermeasures such as semantic locks, versioning, or reordering compensations to be commutative.