Distributed Systems Theory

Distributed Transactions

Committing a change across multiple independent services or shards atomically — all-or-nothing — without a single database to lean on. Two-phase commit (2PC) is the classical blocking protocol; the saga pattern trades atomicity for availability by sequencing local transactions with compensating actions.
  • 2PC has a coordinator ask every participant to prepare (lock resources, promise to commit), then tell them all to commit only if everyone agreed
  • 2PC's fatal weakness: if the coordinator crashes after some participants prepare but before it sends commit/abort, those participants block holding locks indefinitely
  • Sagas replace one distributed transaction with a sequence of local transactions, each with a compensating action to undo it if a later step fails
  • Sagas give up isolation — intermediate states are visible to other transactions — in exchange for not blocking on a coordinator
  • Idempotent operations (Exactly Once And Idempotency) are what make retrying a saga step (or a crashed 2PC participant) safe

A single database gives you transactions for free. The moment a change spans two databases, two services, or two shards, you have to build that atomicity guarantee back yourself — and the two dominant answers, 2PC and sagas, sit at opposite ends of a consistency/availability tradeoff.

Two-phase commit

Two-phase commit: prepare then commit
If any participant votes no, the coordinator sends abort to everyone instead

Every participant that votes yes must hold its locks and survive a crash with that promise durably logged — it cannot unilaterally decide to commit or abort afterward, only the coordinator's final word matters. That is exactly the problem: if the coordinator dies after collecting votes but before broadcasting the decision, every prepared participant is stuck holding locks, unable to proceed, waiting for a coordinator that may never come back. This is why 2PC is called a blocking protocol.

Sagas: local transactions plus compensation

A saga as an explicit list of steps and compensations
interface SagaStep {
    void execute();
    void compensate();     // undo, called if a LATER step fails
}

void runSaga(List<SagaStep> steps) {
    Deque<SagaStep> completed = new ArrayDeque<>();
    try {
        for (SagaStep step : steps) {
            step.execute();
            completed.push(step);
        }
    } catch (Exception e) {
        while (!completed.isEmpty()) {
            completed.pop().compensate();   // unwind in reverse order
        }
        throw new SagaFailedException(e);
    }
}

A typical order-placement saga: reserve inventory, charge payment, schedule shipping. If shipping scheduling fails, the saga runs compensations in reverse — refund the payment, release the inventory reservation — rather than one giant lock held across all three services for the whole duration.

2PC vs sagas
Two-phase commitSaga
AtomicityTrue all-or-nothing, enforced by locksEventual — a failure partway is undone, not prevented
IsolationFull — nothing sees intermediate stateNone — other transactions can see a saga's partial progress
Failure modeBlocks: prepared participants wait forever for the coordinatorNever blocks — always makes forward or compensating progress
FitsA single trusted coordinator, low-latency network, few participantsMicroservices, long-running or cross-organization workflows
Sources
  • Designing Data-Intensive ApplicationsCh. 9 — Distributed Transactions
  • System Design Interview – An Insider's Guide, Volume 2Ch. — Distributed Transactions and Sagas