Architecture guide

Distributed transactions and sagas

The cheapest solution to a distributed transaction is not having one. If two writes must both succeed or both fail, that is strong evidence they belong in the same service, and moving the boundary costs less than every alternative on this page. Sagas are what you use when the split is genuinely justified, and they are a business design problem wearing engineering clothes.

Why not just use a distributed transaction?

Two-phase commit exists and works, and it is still the wrong tool for services. It requires a coordinator that every participant trusts, and it holds locks in each database from the prepare phase until the commit, across a network, for the duration of the slowest participant. A coordinator failure between prepare and commit leaves participants holding locks with no authority to resolve them.

It is also unavailable in practice. Most of what a modern service talks to does not support it: HTTP APIs, message brokers, cloud object storage, third-party payment providers. A transaction that spans your database and someone else's API is not a technical possibility, so the pattern cannot cover the cases you most want covered.

Which leaves the honest first question. If two writes must be atomic, why are they in different services? A boundary that cuts through a genuine atomic requirement was drawn in the wrong place, and merging the two services back is a legitimate and often correct answer.

What is a saga?

A sequence of local transactions where each step is committed immediately, and each step has a compensating action that runs if a later step fails. There is no rollback, because the earlier writes are already visible to everyone. Instead there is a second business action that undoes the effect.

That distinction matters more than any code. Compensation is a new fact, not an erasure. You do not un-charge a card, you issue a refund, and the refund appears on the statement. You do not un-reserve stock in silence, you release the reservation, which may already have been visible to a customer who saw the item as unavailable. Each compensating action needs a business owner who agrees to what it does.

The other property to accept early is that sagas have no isolation. Between the first step and the last, other parts of the system can see a half-finished state: an order that exists with no payment, stock reserved for an order that will not complete. Somebody has to decide what those states are called and who is allowed to see them.

Which coordination approach should you pick?

Four options, in rough order of preference for most systems. The first one is not a saga at all, and it should be seriously considered before the others.

ApproachHow it worksUse whenCost
Move the boundaryBoth writes in one service, one local transactionThe two writes are genuinely atomic to the businessA larger service, which is usually acceptable
Choreographed sagaEach service reacts to the previous service's eventTwo or three steps, stable flowNo single place describes the process, hard to debug
Orchestrated sagaOne coordinator issues commands and tracks stateFour or more steps, or steps that change oftenA component that must itself be durable and observable
Two-phase commitCoordinator locks all participants, then commitsTwo databases you control, inside one trust boundaryLocks held across the network, unsupported by most APIs

What is the dual write problem?

Writing to your database and publishing a message are two separate operations with no shared transaction, so one can succeed while the other fails. If the database commit succeeds and the publish fails, the rest of the system never learns what happened. If you publish first and the commit fails, you have announced something that did not occur. Both happen regularly under normal restarts and network faults.

The standard fix is the transactional outbox. The service writes the message into an outbox table in the same local transaction as the state change, so either both are committed or neither is. A separate relay process, or change data capture reading the database log, then publishes from that table and marks rows as sent. Publication becomes at-least-once, which is why consumers must be idempotent anyway.

This is unavoidable rather than optional. Any service that changes state and tells other services about it has this problem, and a system without an outbox or an equivalent is silently losing events at a rate proportional to how often it restarts.

What happens when a step cannot be undone?

You order the steps so that irreversible actions happen last. An email cannot be unsent, a payout to a third party cannot be recalled, a parcel handed to a courier cannot be intercepted. Once a saga has taken such a step, the only remaining direction is forward, and the design must ensure everything that could fail has already been tried.

The technique is to split each risky step into a reservation and a confirmation. Authorise the card first and capture later. Reserve the stock, then commit it at dispatch. Compose the email, then send it after the last reversible step succeeds. The reservation is compensatable, the confirmation is not, and all reservations are taken before any confirmation is made.

Where an irreversible action genuinely must go first, the compensation is a human process, and it should be designed as one: an alert, a queue, and a named team that handles the exceptions. Pretending an automated compensation exists when it does not is how a system ends up quietly refunding nobody.

What does a saga cost you operationally?

A saga that is stuck halfway is a normal occurrence, so you need to see it. That means persisting the saga state with the step it reached, a timeout per step so a missing reply is detected rather than waited on forever, an alert on sagas older than their expected duration, and a way for an operator to retry or compensate a specific instance by hand.

It also means customer-facing consequences that are not engineering decisions. Support will see orders in intermediate states and needs language for them. A user may receive a confirmation for something that later fails, so somebody must decide whether to confirm optimistically or wait for the whole sequence. These conversations are much cheaper before launch.

The test worth running now: take your most important multi-step flow and write down, for each step, what the customer sees if the process stops there permanently. If any of those answers is unknown or embarrassing, the saga is not designed yet, no matter how much of the code exists.

Common questions

What is a saga in microservices?
A sequence of local transactions in different services, each committed immediately, where every step has a compensating action that runs if a later step fails. There is no rollback because earlier writes are already visible. Compensation is a new business action rather than an erasure: a refund rather than an un-charge, which is why each compensating step needs an owner who agrees to what it does.
Why is two-phase commit a bad fit for microservices?
It holds locks in every participating database from prepare until commit, across a network, for as long as the slowest participant takes, and a coordinator failure between the phases leaves participants stuck. It is also unsupported by most of what a service talks to, including HTTP APIs, message brokers and third-party payment providers, so it cannot cover the cases that most need covering.
What is the transactional outbox pattern?
A way to make a state change and its notification atomic. The service writes the outgoing message into an outbox table in the same database transaction as the state change, so both commit or neither does. A relay process or change data capture then publishes from that table and marks rows sent. It solves the dual write problem, where a database commit succeeds but the message publish fails.
What happens if a saga step cannot be compensated?
Irreversible steps such as sending email, paying a third party or handing a parcel to a courier are ordered last, after everything that can fail has already succeeded. Risky steps are split into a reservation and a confirmation, with all reservations taken before any confirmation. Where an irreversible action must come first, the compensation is a human process and needs an alert, a queue and a named team.
How do you monitor sagas in production?
Persist the saga state including the step it reached, set a timeout per step so a missing reply is detected rather than awaited indefinitely, alert on instances older than their expected duration, and provide an operator with a way to retry or compensate a single instance by hand. Stuck sagas are routine rather than exceptional, so the tooling to see and resolve them is part of the feature.

More on Microservices architecture

Let’s create something out of this world together.

Have a project in mind? Contact us for expert design and development solutions. Let’s discuss how we can help grow your business.

Azaadi Offer

Claim a free security assessment

Until 31 August we're covering the cost of a full vulnerability assessment and penetration test. Mention it in your message and we'll scope it with you.

  • Web application testing, authenticated and unauthenticated
  • Mobile application testing across iOS and Android
  • External network and infrastructure assessment
  • Manual exploitation by engineers, not scanner output

Testing and the report are free. Fixing what we find is quoted separately, with no obligation to accept.

Read the full offer

Tell us what you are trying to build and we will tell you plainly whether we are the right people for it. Book a call with an expert to work through the detail, or ask for a fixed quote if the scope is already clear. No obligation either way.

Four fields is all we need to get started.

Fastnexa Logo

© 2026 fastnexa. All rights reserved.