Chris Collins

Tools · distributed

Transactions across services, simulated

Atomic commitment and its alternatives, animated: two-phase commit with forced log writes, in-doubt participants and a blocking coordinator crash, presumed abort and commit, three-phase commit failing under a partition, orchestrated and choreographed sagas with compensations and a semantic lock, and the dual-write problem fixed by a transactional outbox and an idempotent consumer.

Added

Works with

How to use it

How do you change data in several services or databases so that it all happens or none of it does? This page simulates the main answers and how each one fails: two-phase commit (atomic, but blocking), three-phase commit (non-blocking, but only if the network behaves), sagas (no blocking, no isolation) and the transactional outbox (one local write, then at-least-once delivery made safe by idempotency). Every message is a labelled dot; every node shows its database and its durable log. Nothing here touches a real system.

How to use it

Pick a scenario and press Start. Each step does something — starts a transaction, crashes a node at an exact moment, cuts the network — then plays until it has happened and pauses so you can read what happened and why, with links to the papers. Next step moves on, Back rewinds (the simulator is deterministic, so it replays the earlier steps), ⏭ jumps to the end of the step and Step ⇥ advances to the next single event. Space plays and pauses; → is the next step. The Playground has no script: choose its world (two-phase commit, sagas or the outbox) in the settings, press Play and break things.

Speed scales everything (at 1× a quarter of a simulated second per real second). Hop time is how long each message takes to cross the diagram at 1×: while a message is on its way the simulated clock slows, so nothing changes at the receiver until it lands; Sim clock turns that off. It changes only how the run is shown, never what happens. ⛶ Full screen (or f) gives the diagram the whole window, with the narration and panels in a side column you can hide; Escape leaves.

  • The diagram. The client(s) on the left; in the middle the coordinator (2PC/3PC), the saga orchestrator or the broker; on the right the participants or services. In the outbox world the chain runs left to right: client, orders service, outbox relay (below), broker, consumer. Each card shows the node’s database in bold — a locked row has 🔒 and the transaction holding it, with → the value it will have if it commits — its in-memory state (transactions collecting votes, in doubt, saga progress as ✓ … ✗ ↺ ↩ ⏸), and, in the shaded strip, the tail of its durable log: ■ a forced write (the node waits for the disk), □ a lazy one. Down nodes are crossed out; cut links are red. Tap a node to crash or restart it, cut it off, make it vote NO or fail its next request, or drop its next vote; tap a client to start a transaction, a saga or an order, or (the other transaction) to read a row.

  • Counters. Committed and aborted transactions, forced writes, messages, reads blocked by locks; completed and compensated sagas, compensations, retries and duplicates; orders, published events, lost events, duplicates in the broker and effects applied twice — and safety violations.

  • Do something. Start a transfer (choose the amount; 200 is more than alice has), a saga (one that fails at shipping), or orders; crash, restart or isolate any node; drop the next message of a kind; heal; 20% random loss.

  • Transactions. Per transaction its decision and each participant’s state; per saga each step; per order whether it is in the database, the outbox, the broker (missing, once, duplicated) and how many times it was consumed.

  • Durable logs. The last ten records of every node’s log.

  • Invariants. Atomicity (never committed at one participant and aborted at another), a YES voter never aborting alone, money conserved (alice + bob = 150), what is in doubt and locked, and the cost per transaction of the 2PC variant (forced writes and messages, from Mohan et al.); for sagas money and stock conserved and every saga ending approved or compensated; for the outbox no lost events and exactly-once effects.

What it models

  • Two-phase commit (Gray 1978, Notes on Data Base Operating Systems). The coordinator sends PREPARE; each participant does its work, locks the rows, force-writes PREPARED with the new values and votes YES — or votes NO and aborts at once (insufficient funds, or a row locked by another transaction: no waiting, so no distributed deadlock). All YES → the coordinator force-writes COMMIT (the commit point) and sends it; any NO or a vote timeout → ABORT. Participants write the outcome, install or discard the values, release their locks and acknowledge; the coordinator writes END and forgets. A participant that voted YES and hears nothing is in doubt: it keeps its locks and sends STATUS? to the coordinator; a crashed participant re-reads its log, takes its locks again and asks. A coordinator that crashes after the votes leaves everyone blocked until it recovers from its log.

  • Presumed abort and presumed commit (Mohan, Lindsay & Obermarck, Transaction Management in the R* Distributed Database Management System, ACM TODS 1986). Presumed abort: no record before the decision, aborts are not logged at the coordinator and not acknowledged, and a question about an unknown transaction is answered ABORT. Presumed commit: a forced COLLECTING record with the participants before PREPARE, commits not acknowledged (and not forced at participants), aborts forced and acknowledged, and an unknown transaction is answered COMMIT; a coordinator recovering with COLLECTING and no decision must actively abort.

  • Three-phase commit (Skeen, Nonblocking Commit Protocols, SIGMOD 1981). CanCommit? / YES, PreCommit / ACK, DoCommit. A participant that times out runs a termination protocol with the participants it can reach: if any is pre-committed (or committed) it commits, otherwise it aborts. Non-blocking under crash failures with reliable timeouts; under a partition the two sides decide differently. Consensus-based commit — Gray & Lamport’s Paxos Commit (2006), or 2PC between replicated groups as in Spanner — is what avoids both problems; see Raft and Paxos, simulated.

  • Sagas (Garcia-Molina & Salem, Sagas, SIGMOD 1987; Richardson, microservices.io). An order saga of local transactions — create the order, charge, reserve stock, book a courier, confirm the reservation, approve — each with a compensation (reject, refund, release, cancel). Orchestration: a coordinator sends commands, logs every reply, retries a command with the same id until it is answered, and on a failure runs the compensations of the completed steps in reverse order. Choreography: services publish events to a broker that redelivers until acknowledged, and each service reacts to the events it subscribes to — failures travel back as events. Services keep the ids of processed commands and events, so a duplicate is answered without being applied twice. The semantic lock countermeasure marks reservations of unfinished sagas, so another saga is told to retry instead of being refused.

  • The outbox (transactional outbox, idempotent consumer). Without it the orders service commits and then publishes — two writes, one of which a crash can lose. With it the order and an outbox row commit together; a relay publishes unsent rows and marks them sent (a crash in between publishes again); a consumer applies each message and, in the same local transaction, records its id, then commits its offset (a crash in between redelivers). At-least-once delivery plus idempotent processing gives exactly-once effects. Try the runnable versions in Postgres: the outbox lab and idempotency keys; for transactions inside Kafka (KIP-98) see the Kafka simulator’s exactly-once scenario. For the wider picture, Kleppmann’s Designing Data-Intensive Applications, chapter 9.

Limits

  • One kind of transaction per world: a money transfer across two banks plus audit and notification participants; one order saga; one orders topic. Participants vote NO instead of waiting for locks, so there are no deadlocks to detect.

  • Coordinator logs are never truncated; recovery is instant; the coordinator’s own failure is modelled but not its replication. XA, heuristic decisions and read-only optimisations are not modelled.

  • The 3PC termination protocol is simplified to "ask whoever you can reach, commit if anyone is pre-committed"; it is there to show why 3PC is not used, not as a reference implementation.

  • The saga orchestrator’s saga log survives crashes as a whole; compensations always succeed eventually (as the saga model requires). Choreography has no time-outs of its own beyond broker redelivery.

  • The broker is a single durable log with one consumer; no partitions, no rebalancing — see the Kafka simulator for those.

  • The network is a fixed latency with jitter, optional loss, cut links and injected drops; a message is lost if its sender or receiver is down when it arrives.

Linking to an example

Links can be written by hand. A link’s settings replace this page’s saved settings (a button restores yours); a scenario brings its own world, and parameters given with it override the rest.

Parameter Meaning Example

example

A scenario (list below)

example=coordinator-crash

scenario

The same, without clearing your settings

scenario=outbox

step

Open the scenario at this step

step=2

mode

The playground’s world: 2pc, saga or outbox

mode=saga

participants

2PC participants, 2–4

participants=2

presume

2PC variant: abort or commit

presume=commit

flow

Saga style in the playground: orchestration or choreography

flow=choreography

amount

Default transfer / order amount

amount=60

idempotent

The consumer de-duplicates by message id (true/false)

idempotent=false

semanticLock

Reservations of unfinished sagas are semantic locks (true/false)

semanticLock=true

fsync

Time a forced log write takes, ms

fsync=30

latency

One-way network latency, ms

latency=40

jitter

Extra random latency up to this, ms

jitter=0

timeout

The coordinator’s vote timeout, ms

timeout=300

seed

Random seed (jitter, random loss)

seed=7

speed

Playback speed: 0.25, 0.5, 1, 2, 4 or 8

speed=2

hop

Hop time in seconds (0.25, 0.5, 1, 2, anything from 0.1 to 5) or clock

hop=1

Scenarios:

example= Shows

2pc

Two-phase commit: PREPARE, forced PREPARED, YES, forced COMMIT, COMMIT, ACK, END (open)

2pc-abort

A NO vote: the voter aborts at once and the coordinator aborts everywhere (open)

participant-crash

A participant crashes after voting YES and recovers in doubt from its log (open)

coordinator-crash

The coordinator crashes after logging COMMIT: participants blocked with locks held, other transactions wait (open)

presumed

Presumed abort: crash before deciding, recovery by presumption, the cost of commits and aborts (open)

3pc

Three-phase commit: non-blocking after a coordinator crash, inconsistent after a partition (open)

saga-orchestration

An orchestrated saga: compensations in reverse, a retried compensation, a duplicate refund ignored (open)

saga-choreography

The same saga with events through a broker, failure events, redelivery (open)

saga-isolation

A dirty read between sagas causes a lost sale; a semantic lock makes the second saga wait (open)

dual-write

Commit then publish: a crash in between loses the event for good (open)

outbox

Transactional outbox, relay duplicates, an idempotent consumer, a redelivery, and Kafka transactions (open)

playground

No script; pick the world with mode (open)

Runs entirely in your browser; nothing you type leaves this page.