Articles

The Saga Pattern Explained: Distributed Transactions Without Locks

The saga pattern coordinates a multi-step transaction across services using local commits and compensating actions instead of a distributed lock.

The Lycoris Team The Lycoris Team · · 4 min read
Abstract illustration of connected database nodes

The saga pattern is a way to keep data consistent across a multi-step operation that spans several services or databases, without holding a single distributed lock across all of them for the duration. Instead of one all-or-nothing transaction, a saga breaks the operation into a sequence of local transactions, each committed independently, with a matching compensating action defined for every step in case something later in the chain fails.

The problem it solves

In a single database, a multi-step operation — debit one row, credit another — is wrapped in a transaction and committed atomically: either both changes land, or neither does. That guarantee gets much harder once the steps span separate services, each with its own database. There’s no single engine that can hold locks across all of them and roll everything back atomically on failure.

The traditional distributed answer is two-phase commit, which does provide atomicity across services, but at a cost: every participant holds locks on its resources from the “prepare” phase until every other participant has also agreed to commit. If one service is slow or unreachable, every other participant sits blocked, holding locks, until it resolves — a coordination cost that scales poorly with more services and doesn’t tolerate a coordinator failure gracefully.

A saga trades that atomicity guarantee for availability: each step commits locally and immediately, with no cross-service lock held while waiting on the rest of the chain.

How a saga runs

Consider booking a trip: reserve a flight, reserve a hotel, charge a card. As a saga:

  1. Reserve flight — commits locally in the flights service.
  2. Reserve hotel — commits locally in the hotels service.
  3. Charge card — commits locally in the payments service.

If step 3 fails — the card is declined — the saga doesn’t try to roll back a shared transaction, because there isn’t one. Instead, it runs compensating actions for every step that already committed, in reverse order: cancel the hotel reservation, then cancel the flight reservation. Each compensating action is itself a normal local operation against the service that owns that data, not a rollback in the database sense.

This means every step in a saga needs a defined compensating action before the saga is designed to run, and that action needs to be safe to execute even if the original step partially succeeded — which is why compensations are usually written to be idempotent, the same requirement covered in our piece on idempotency.

Choreography vs orchestration

Sagas are coordinated one of two ways:

Choreography — each service listens for events from the others and reacts on its own, with no central coordinator. The flights service reserves a seat and publishes a FlightReserved event; the hotels service is listening for that event and reserves a room in response, publishing its own event in turn. This keeps services decoupled but makes the overall flow harder to see in one place — tracing what happened requires following events across every service’s logs. This pattern often rides on the same message queue infrastructure used for other asynchronous service communication.

Orchestration — a central orchestrator explicitly calls each service in sequence and is responsible for triggering compensating actions if a step fails. This makes the flow easy to read in one place — it’s just a state machine — at the cost of a coordinating component that every service now depends on.

ChoreographyOrchestration
CoordinationDistributed via eventsCentralized coordinator
CouplingLooser between servicesServices coupled to orchestrator
Visibility of the flowSpread across servicesSingle place to read
Failure handlingEach service reacts to failure eventsOrchestrator triggers compensations
Best forFew steps, simple reactionsLonger, more complex workflows

What a saga does not give you

A saga is not equivalent to a single ACID transaction, and it’s important to be explicit about what’s given up. Between steps, the system is in an intermediate state that’s visible to the rest of the world — after the flight is reserved but before the payment succeeds, a concurrent process reading the flights service sees a reservation that might still get cancelled. This is a form of the eventual consistency described in our ACID transactions and eventual consistency articles, applied at the scale of a whole business process rather than a single database write.

Compensating actions also aren’t always a perfect undo. Cancelling a hotel reservation might incur a fee, or a payment refund might take days to actually clear even though the compensating action registers instantly. Saga design has to account for this by making the “semantic” undo acceptable to the business, not just technically correct.

When to reach for a saga

Sagas earn their complexity in systems already built as independent services with independent databases — the kind of microservices architecture where a shared distributed transaction across service boundaries isn’t realistically on the table. For a monolith with one database, a normal local transaction is simpler and gives stronger guarantees for free; introducing sagas there would be solving a problem that doesn’t exist yet.

The takeaway

A saga replaces one distributed transaction with a sequence of local transactions plus a compensating action for each, trading strict atomicity for availability and loose coupling between services. Choreography keeps services decoupled but spreads the flow across event logs; orchestration centralizes the flow at the cost of a shared coordinator. Either way, the tradeoff is the same: an intermediate, partially-completed state becomes visible for a while, and compensating actions have to be designed as acceptable business outcomes, not just as a technical undo.

Chisato Chisato · · 5 min read

Raft vs Paxos: Consensus Algorithms Compared

Raft and Paxos both let a distributed cluster agree on a value despite failures — Raft trades some flexibility for a design built to be understood.

#Distributed Systems #Computer Science #Databases
The Lycoris Team The Lycoris Team · · 4 min read

Read-Your-Writes Consistency Explained

Read-your-writes consistency guarantees a client sees its own writes immediately, even when other clients might not yet. How it's implemented.

#Databases #Distributed Systems #Computer Science
The Lycoris Team The Lycoris Team · · 5 min read

Two-Phase Commit vs. Saga Pattern Explained

Two-phase commit locks resources until every node agrees to a transaction; the saga pattern trades that guarantee for availability using compensating steps.

#Databases #Distributed Systems #Computer Science