A saga is a sequence of local transactions across multiple services, where each step commits its own database change and publishes an event or calls the next step directly, and every step has a matching compensating action that undoes it if a later step fails. Sagas exist because distributed two-phase commit does not scale across microservices — it requires every participant to hold locks until the slowest one responds, which turns a network partition into a system-wide outage. A saga trades that all-or-nothing atomicity for eventual consistency: the system passes through intermediate states, but a failure partway through is repaired by compensation rather than a global rollback.
How it works
Each step in a saga is a normal local transaction against one service's own database. There is no distributed lock spanning services. If step three of five fails, the saga does not roll back the whole world — it runs compensating transactions for steps one and two, in reverse order, to bring the system back to a consistent state. A payment saga might look like: reserve inventory, charge payment, create shipment. If shipment creation fails, the compensations are refund payment and release inventory — new forward-moving transactions that semantically undo the earlier steps, not a magic rollback.
Orchestration vs choreography
There are two ways to coordinate the steps.
| Approach |
How it works |
Strength |
Weakness |
| Orchestration |
A central coordinator tells each service what to do and calls compensations on failure |
Saga logic lives in one place, easy to trace |
Coordinator is a new component and a potential bottleneck |
| Choreography |
Each service publishes an event; the next service reacts to it with no central controller |
No single point of control, services stay decoupled |
Saga flow is implicit, scattered across services, harder to trace end-to-end |
Orchestration tends to win as the number of steps grows, because having the sequence and compensation logic in one place — often built on a workflow engine like Temporal, AWS Step Functions, or Camunda — makes the saga's behavior auditable. Choreography fits smaller sagas of two or three steps where adding a coordinator would be more machinery than the flow warrants.
Common mistakes
- Treating compensations as an afterthought. A compensating action is not automatic; someone has to write "refund payment" as its own real code path, including what happens if the refund itself fails. Design compensations at the same time as the forward steps, not after a production incident.
- Making saga steps non-idempotent. Messages get redelivered. If "charge payment" runs twice because a retry fired after a slow response, the customer gets charged twice unless the step is written to be safe to run more than once with the same input.
- Skipping a dead-letter path. When a step keeps failing and retries are exhausted, the saga needs an explicit terminal state — flagged for a human, not silently dropped or retried forever.
- Expecting saga to mean atomic. A saga guarantees the system eventually reaches a consistent state, not that intermediate states are invisible. Any code path that reads mid-saga state needs to handle seeing a partially completed transaction.
FAQ
Is a saga the same as a distributed transaction?
No. A saga replaces a single distributed transaction with a series of local ones plus compensations. It achieves eventual consistency, not the atomicity a real distributed transaction (like two-phase commit) provides.
Do I need a workflow engine to implement sagas?
Not for simple choreography-based sagas — a message queue and event handlers are enough. Once you have more than a handful of steps or need visibility into in-flight sagas, a workflow engine like Temporal or Step Functions earns its keep.
What happens if a compensating transaction fails?
This is the hard case sagas do not solve for free. Compensations need their own retry logic, and a saga that cannot complete forward or backward should land in a dead-letter state for manual intervention rather than looping indefinitely.
When should I avoid the saga pattern entirely?
When all the state you need to change lives in one database. A single ACID transaction is simpler and gives you real atomicity — sagas are a workaround for state distributed across services, not a general-purpose transaction replacement.
Where to go next
Sagas are often paired with CQRS so the read side can show saga progress without querying every participating service directly. Each step in a saga should call downstream services defensively — see the circuit breaker pattern for how to stop one failing step from cascading into repeated timeouts. And if your saga's step handlers need to construct different downstream clients based on runtime config, the factory pattern is the standard way to keep that decision logic in one place.