BizTechLab

IDEASINNOVATIONIMPACT

System Design Concepts

Distributed Transactions

Two-Phase Commit and the Saga pattern — two very different ways to keep a transaction consistent across multiple services or shards.

3 August 20268 min read

Overview

Once data is sharded or spread across multiple services, a single logical transaction might need to touch several of them atomically. and the are the two standard approaches to keeping that kind of transaction consistent, with very different trade-offs.

Why It Exists

ACID transactions, covered earlier in this journey, work cleanly within one database. Once an operation spans multiple shards or independently-owned services, no single engine can natively guarantee atomicity across all of them. Two-Phase Commit and Sagas exist to recreate some version of that guarantee across independent systems — just via very different mechanisms, with very different costs.

Real World Example

An order-placement flow needs to deduct inventory (Service A) and charge a payment (Service B) together. Two-Phase Commit has a coordinator ask both services to 'prepare' the change — locking resources, ready to commit or abort — and only tells both to actually commit once both confirm they're ready. It's strongly consistent, but both services stay blocked holding locks until the coordinator decides, and the coordinator itself is a single point of failure. A Saga instead runs the steps as a sequence of independent local transactions, each with a corresponding compensating action — like 'restore inventory' — that runs if a later step fails. No distributed lock, but the system passes through visibly intermediate states along the way.

Example Data

Two-Phase Commit — the two phases, one at a time

PhaseCoordinator ActionParticipant Action
1. PrepareAsks every participant to prepareLocks resources, votes yes/no
2. CommitTells all participants to commit (only if all voted yes)Applies the change, releases locks

Two Approaches to Cross-Service Consistency

Two-Phase Commit (2PC) — Prepare, Then Commit

A coordinator asks every participant to prepare (lock resources, vote yes/no), and only instructs a commit once every participant has voted yes. Strongly consistent, but every participant blocks holding locks until the coordinator decides.

2PC's Real Cost: Blocking and a Single Point of Failure

If the coordinator crashes after participants have prepared but before it sends the final decision, participants can be left blocked, holding locks, until the coordinator recovers — a genuinely painful operational failure mode.

The Saga Pattern — a Sequence of Local Transactions

Each step commits as its own local transaction immediately, no cross-service locking involved. If a later step fails, previously completed steps are undone via their own compensating actions, run in reverse order.

Compensating Actions — Undoing a Completed Step

A compensating action has to correctly undo a step that may have already had real side effects — reversing a payment authorization, restoring reserved inventory — and has to be designed and tested with the same rigor as the forward action itself.

Diagram

2PC blocks until everyone agrees; a Saga commits each step immediately and compensates on failure

2PC: Prepare

all participants lock, vote

Saga: Step 1 commits

locally, immediately

2PC: Commit

only once all voted yes

Saga: Step 2 fails

compensating action undoes Step 1

Common Mistakes

Using 2PC across services owned by different teams or systems without accounting for its blocking nature

Why: If the coordinator crashes or a participant is slow, every other participant sits blocked holding locks — which can cascade into a much broader outage than the original failure.

Fix: Use 2PC sparingly, ideally within a tightly controlled boundary, and prefer Sagas for operations spanning independently-operated services.

Writing a Saga's compensating actions as an afterthought

Why: A compensating action has to correctly undo a step that may have already had real side effects — a poorly designed compensation can itself fail or leave the system in an inconsistent state.

Fix: Design compensating actions with the same rigor as the forward action, and explicitly test failure and rollback paths, not just the happy path.

Assuming a Saga gives the same isolation guarantees as a real ACID transaction

Why: Between a Saga's individual steps, the system is in a real, visible intermediate state that other operations could observe or act on — there's no isolation guarantee hiding that in-progress state the way a single-database transaction would.

Fix: Design the surrounding system to tolerate or explicitly account for visible intermediate states, rather than assuming Saga steps are invisible until the whole thing completes.

Interview Questions

beginner

What problem do distributed transactions solve that a normal ACID transaction can't?

An ACID transaction guarantees atomicity within a single database. A distributed transaction is needed when an operation must succeed or fail as a unit across multiple independent databases or services, where no single engine can natively enforce that guarantee.

intermediate

What's the main operational risk of Two-Phase Commit?

Its blocking nature — every participant locks its resources during the prepare phase and can't release them until the coordinator sends the final commit or abort decision. If the coordinator crashes or is slow at that point, participants stay blocked, which can cascade into a broader outage.

senior

You're designing a checkout flow spanning an inventory service and a payment service. Would you use 2PC or a Saga, and why?

A Saga is usually the better fit for this kind of cross-service flow: it avoids holding distributed locks across two independently-operated services, and the compensating action (releasing reserved inventory if payment fails) is a natural fit for the domain. 2PC would give stronger immediate consistency, but the blocking risk across two services that might be operated, deployed, and scaled independently makes it a worse operational trade for this specific use case.

Production Best Practices

Do

Prefer Sagas for operations spanning independently-operated services.

Design and test compensating actions with the same rigor as the forward action.

Account for visible intermediate states when using a Saga.

Don't

Don't use 2PC across loosely-coupled or independently-operated services without accounting for blocking risk.

Don't treat compensating actions as an afterthought.

Don't assume a Saga provides ACID-style isolation between its steps.

Comparison

ConsistencyBlocking?Failure Handling
Two-Phase CommitStrong — all or nothingYes — participants block until decidedCoordinator retries/recovers
SagaEventual — steps commit individuallyNoCompensating actions undo completed steps

Related Articles