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
- Distributed patterns on Redis Databases and streamingsaga state, idempotent consumers and dedup records kept in Redis, with the races they prevent
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 |
|---|---|---|
|
A scenario (list below) |
|
|
The same, without clearing your settings |
|
|
Open the scenario at this step |
|
|
The playground’s world: |
|
|
2PC participants, 2–4 |
|
|
2PC variant: |
|
|
Saga style in the playground: |
|
|
Default transfer / order amount |
|
|
The consumer de-duplicates by message id ( |
|
|
Reservations of unfinished sagas are semantic locks ( |
|
|
Time a forced log write takes, ms |
|
|
One-way network latency, ms |
|
|
Extra random latency up to this, ms |
|
|
The coordinator’s vote timeout, ms |
|
|
Random seed (jitter, random loss) |
|
|
Playback speed: |
|
|
Hop time in seconds ( |
|
Scenarios:
example= |
Shows |
|---|---|
|
Two-phase commit: PREPARE, forced PREPARED, YES, forced COMMIT, COMMIT, ACK, END (open) |
|
A NO vote: the voter aborts at once and the coordinator aborts everywhere (open) |
|
A participant crashes after voting YES and recovers in doubt from its log (open) |
|
The coordinator crashes after logging COMMIT: participants blocked with locks held, other transactions wait (open) |
|
Presumed abort: crash before deciding, recovery by presumption, the cost of commits and aborts (open) |
|
Three-phase commit: non-blocking after a coordinator crash, inconsistent after a partition (open) |
|
An orchestrated saga: compensations in reverse, a retried compensation, a duplicate refund ignored (open) |
|
The same saga with events through a broker, failure events, redelivery (open) |
|
A dirty read between sagas causes a lost sale; a semantic lock makes the second saga wait (open) |
|
Commit then publish: a crash in between loses the event for good (open) |
|
Transactional outbox, relay duplicates, an idempotent consumer, a redelivery, and Kafka transactions (open) |
|
No script; pick the world with |
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.