A payment moves 11 from account A to account B — two different shards, no distributed transaction. Crash the processor mid-transfer, after crediting B and before debiting A, forcing redelivery. With dedup by request id the invariant holds — A=89, B=111, sum stays 200, each account touched once. Drop the dedup and the same crash double-credits B to 122, sum jumps to 211, money is created.
A payment moves 11 from account A (payer shard) to account B (payee shard) — two different shards, no distributed transaction between them. The transfer is written to a request log as one atomic append; a deterministic stream processor consumes that request and emits two events, a credit to B and a debit to A, each carrying the original request id. Now inject a crash mid-transfer — after the payee is credited, before the payer is debited and before the request is marked done — forcing the request to be redelivered. A reader predicts: without an atomic commit across both shards, this crash must either lose money or double-credit someone. Run it and the invariant holds — A=89, B=111, sum stays 200, each account touched exactly once — because the replay re-emits identical events and the shards dedupe by request id. Drop the dedup and the same crash double-credits B to 122, sum jumps to 211, money is created. Atomicity came from atomically writing one request, not from 2PC.
Designing Data-Intensive Applications, 2nd ed. (Kleppmann & Riccomini, 2025), Chapter 13 — The Future of Data Systems, §"Exactly-once execution of an operation" and §"The end-to-end argument in databases" (Figure 13-2):
The fact that the message is delivered atomically along with its side effects [...] is what makes exactly-once semantics possible.
The book's construction: you do not need a distributed transaction to make a cross-shard operation exactly-once. You make the initial request an atomic event in a single log, derive all downstream effects deterministically from it, and make the consumers idempotent (dedupe by an operation id). At-least-once delivery plus idempotent application equals effectively-once.
The surprise is that no step spans both shards atomically, yet the pair stays consistent. Four ideas carry it; each line notes where it shows up.
request-log append line before any event is emitted.req-1:credit / req-1:debit out, every replay. The event ids are derived from the request id, never freshly generated. It shows up as the recovery run re-emitting the exact same event ids.B: event req-1:credit already applied -> SKIP.Instead of atomically committing across the payer and payee shards (a distributed transaction, 2PC), you atomically commit one request event, then derive every shard effect deterministically from it.
A crash after the first effect just replays the derivation. Because replay is deterministic and application is idempotent, at-least-once redelivery becomes effectively-once, and the two shards end up consistent with no blocking commit protocol. The whole thing is one script, code/multishard_exactly_once.py.
cd study/ddia/ch13/code
python3 multishard_exactly_once.py
Do not read the output yet — make each prediction first.
Predict. Transfer 11 from A (starts 100) to B (starts 100), no crash. The processor credits B then debits A. What are the two balances, and what is their sum?
A=89, B=111, sum 200. No surprise yet — the request appended, the processor derived a credit and a debit, both applied, done. The question is what a crash does to it.
Predict. Same transfer, but the processor crashes after crediting B, before debiting A and before marking the request done. On recovery the request (still un-done) is reprocessed from the log — so the credit to B is emitted a second time. With per-shard dedup by event id, what are the final balances? Is B credited once or twice?
A=89, B=111, sum 200 — the invariant held, and each account was touched exactly once. The redelivered credit hit B's dedup set (req-1:credit already applied -> SKIP) and changed nothing; the debit, which the crash had prevented, ran once. A crash mid-transfer across two shards, no distributed transaction, and the money is conserved.
Predict. Identical crash and redelivery, but now the shards keep no memory of applied event ids. The credit to B is delivered twice and there's nothing to dedupe it. What is B's balance now? What is the sum — still 200?
B=122, sum 211 — money was created out of nothing. The redelivered credit applied a second time (balance 122), B was touched twice, and the conservation invariant broke by exactly the transfer amount. Same log, same deterministic processor, same crash — the only removed ingredient was the dedup, and that alone is the difference between exactly-once and a double-credit.
The reader's prediction — "without an atomic commit across both shards a crash must lose money or double-credit" — assumes atomicity has to span the two shards. It doesn't. Look at where all-or-nothing is actually needed: only the request must be recorded atomically, and that's a single append to one log — a single-object write, atomic for free. Everything downstream is a deterministic function of that one durable fact. Because it's a function, re-running it on recovery yields byte-identical events: the recovery run re-emits req-1:credit and req-1:debit, the exact ids from the first run, not fresh ones. That determinism is what makes redelivery safe to attempt at all.
The second half is idempotence. At-least-once delivery means an event can arrive more than once (the crash guarantees it here). A shard that records the event ids it has applied turns a duplicate into a no-op: max-like, apply(x) then apply(x) equals apply(x). Deterministic re-emission gives the same id to retry against; the dedup set recognizes it; the effect lands exactly once. Compose the two and you get the book's equation: at-least-once delivery + idempotent (deduped) application = effectively-once, and it spans both shards without either one ever participating in a cross-shard commit. Scenario 3 removes just the idempotence and the composition collapses — the duplicate credit lands for real, and 200 becomes 211.
uuid4() per event, a now() timestamp, a random shard pick — and the retry emits a different id that the dedup set has never seen, so it applies as new and you double-credit even with dedup on. Same failure mode as statement-based replication forwarding NOW() or RAND() (the Ch. 6 exercise): a non-deterministic statement replays to a different result. Determinism is not a nice-to-have here; it is the thing that makes the dedup key stable enough to catch the duplicate. Effects must also be things you can dedupe (carry a stable operation id); a side effect with no id — sending a raw email, poking a non-idempotent external API — falls outside the guarantee and needs its own idempotency key.
f"{rid}:credit" to include uuid4() or time.time(), rerun Scenario 2, and watch dedup fail to catch the redelivered credit — the invariant breaks with dedup still on. Cross-reference the Ch. 6 statement-based-replication exercise: same root cause.Sources: DDIA 2e, Ch. 13, §"Exactly-once execution of an operation", §"The end-to-end argument in databases" (Figure 13-2), §"Idempotence" · Kleppmann, "Exactly-once Semantics is Possible" (Confluent, 2017).