studyDDIAChapter 13 · A Philosophy of Streaming Systems

Uniqueness Needs a Total Order; Async Multi-Leader Grants 'alice' Twice

Two users race to claim the username alice under a UNIQUE(username) constraint. Two independent leaders each check their local table, find alice free, and both GRANT — the constraint is silently violated and the conflict only surfaces at merge. Route the same two claims through one totally-ordered log partition and a single consumer grants one, rejects the other — exactly one owner.


Concept

Two users race to claim the username alice; a UNIQUE(username) constraint says exactly one may own it. A tempting design: two datacenters, each a leader that enforces the constraint locally, replicating asynchronously. Model it — two independent leaders, each with its own taken set, each check-then-set against its local table — and both requests find alice free and both GRANTED. The constraint is silently violated; the conflict is only discovered at replication merge, by which point two users have been told they own alice. Now route every claim through one totally-ordered log partition (partition by hash of the username), consumed single-threaded: the consumer sees a definite first writer, grants one and REJECTS the rest — exactly one owner. Same two claims; the only difference is whether the contenders share an order.

Provenance

Designing Data-Intensive Applications, 2nd ed. (Kleppmann & Riccomini, 2025), Chapter 13 — The Future of Data Systems (stream processing / a philosophy of streaming systems), §"Uniqueness constraints require consensus" and §"Uniqueness in log-based messaging":

Enforcing a uniqueness constraint requires consensus [...] The most common way of achieving this consensus is to make a single node the leader, and put it in charge of making all the decisions.

The book rules out asynchronous multi-leader replication for uniqueness because different leaders can concurrently accept conflicting writes, and offers the log-based route: funnel all requests for a key through one partition of a totally-ordered log and let a single consumer decide. Here you watch both.

Prerequisites

The surprise is that a local uniqueness check is not a uniqueness constraint. Each line notes where the idea shows up.

  1. Uniqueness constraintsUNIQUE(username) must admit exactly one owner across the whole system, not per node. It shows up as the two-owner outcome in Model 1: each leader's local constraint held, yet the global one broke.
  2. Consensus / total order — agreeing on a single value (here: who was first) is consensus; a total order over the contenders is what supplies that agreement. It shows up as the log partition's fixed order in Model 2.
  3. Asynchronous multi-leader replication & write conflicts — independent leaders accept writes without coordinating, so two conflicting writes can both commit and only clash at merge time. It shows up as the CONFLICT line after "asynchronous replication merges the two leaders."
  4. Log-based single-consumer serialization — appending to one ordered partition and consuming it single-threaded turns concurrent requests into a sequence with a definite first element. It shows up as the offset-0 grant, offset-1 reject.
Where to learn the prerequisites Uniqueness = consensus (#1, #2): DDIA §"Uniqueness constraints require consensus" and Ch. 10 §"Consensus"; the constraint is equivalent to all contenders agreeing on a single first writer. Multi-leader write conflicts (#3): DDIA Ch. 6 §"Handling write conflicts" — concurrent writes to the same key on different leaders conflict, and detection happens asynchronously, after both have committed. Log-based uniqueness (#4): DDIA §"Uniqueness in log-based messaging" — route all requests for a key to the same log partition and decide with a single-threaded consumer. If only one carries the result, it is uniqueness ≡ agreeing on a first writer: the whole exercise is that a uniqueness constraint is a consensus problem in disguise, and consensus needs a shared order the async leaders lack.
Environment these numbers came from Deterministic — the concurrency is modeled explicitly, so the outcome is fixed. Captured on macOS 26.5.2 (Darwin 25.5.0), arm64, Python 3.15.0a8, standard library only — no Docker, no deps. No real threads: the two concurrent claims and the log order are laid out in code, so both models produce the same result every run. Fixed workload: two users (user-1 via req-1, user-2 via req-2) both claim alice.

Mental model

A uniqueness constraint requires all contenders for a name to agree who was first. Two ways to try to supply that agreement:

Both models run against the identical two claims. The whole thing is one script, code/uniqueness_needs_total_order.py.

Setup

cd study/ddia/ch13/code
python3 uniqueness_needs_total_order.py

Do not read the output yet — make each prediction first.


Step 1 · the async multi-leader constraint

Predict. Two datacenters, each a leader enforcing UNIQUE(username) on its own local table, replicating asynchronously. Two users concurrently claim alice, one request routed to each leader. How many of the two claims are GRANTED?

How many of the two claims are GRANTED?
MODEL 1 -- asynchronous multi-leader replication two independent leaders, each enforces UNIQUE(username) on its LOCAL table two users concurrently claim 'alice', one request routed to each leader: L1 receives req-1 (user-1): checks local table, 'alice' is free -> GRANTED L2 receives req-2 (user-2): checks local table, 'alice' is free -> GRANTED

Both GRANTED. Each leader's local check passed — from L1's view alice was free, and from L2's view alice was free too. Neither local constraint was violated; the global one was.

Step 2 · the async merge

Predict. Replication now merges the two leaders' indexes. Does the merge catch the conflict in time to keep the constraint — and how many owners does alice end up with?

Does the merge save the constraint, and how many owners?
... asynchronous replication merges the two leaders ... L1.taken['alice'] = req-1 L2.taken['alice'] = req-2 CONFLICT at merge: two owners for 'alice' -- detected, but both users were already told they won RESULT: 2 grants for 'alice' -- UNIQUE constraint VIOLATED (two owners) owner: user-1 via req-1 (granted by L1) owner: user-2 via req-2 (granted by L2)

Two owners — the constraint is violated. The merge detects the conflict, but detection is too late: both grants already committed and both users were told they own alice. Async replication moves conflict discovery to after the decision, which is exactly when a uniqueness constraint can no longer be enforced.

Step 3 · one totally-ordered log partition

Predict. Now route both claims through a single log partition (chosen by hash of the username) and consume it single-threaded. How many of the two claims are GRANTED?

How many claims are GRANTED through the ordered log?
MODEL 2 -- one totally-ordered log partition, single-threaded consumer all claims for 'alice' route to log partition #0 (hash of username) the log fixes ONE order; a single consumer grants the first, rejects the rest: log partition #0 order: req-1(user-1) , req-2(user-2) offset 0: consume req-1 (user-1): 'alice' is free -> GRANTED offset 1: consume req-2 (user-2): 'alice' already owned by req-1 -> REJECTED RESULT: 1 grant for 'alice' -- UNIQUE constraint HELD (exactly one owner) owner: user-1 via req-1

Exactly one owner. The log gives the two claims a definite order; the single consumer grants offset 0 and, seeing alice already taken, rejects offset 1. The constraint holds before anyone is told they won — no merge, no conflict, no double grant.

Step 4 · the two models side by side

Predict. Summarize: how many owners does each model produce, and what is the one structural difference that decides it?

How many owners each, and what one difference decides it?
==================================================================== async multi-leader : 2 owners -> constraint violated totally-ordered log: 1 owner -> constraint held uniqueness needs all contenders to agree who was FIRST; independent leaders have no shared order, so both think they won; one ordered partition gives every contender the same first writer.

2 vs 1 — the difference is a shared order. Nothing about the claims changed; what changed is whether the contenders were forced through one order. That order is the consensus the constraint needs.


What you should see

Why

A uniqueness constraint is a consensus problem wearing a schema keyword. To grant alice to exactly one claimant, the system must linearize the claims: put them in a single order and let the first one win. Everything turns on whether that order exists at decision time.

Asynchronous multi-leader replication has no such order. Each leader is an independent decider with its own copy of the index, and by construction it commits a write before replicating it. So when two claims land at the same instant on different leaders, each check-then-set runs against a table that has not yet heard about the other — both read alice free, both write, both grant. The conflict is real, but it becomes visible only when the two logs merge, and a merge that runs after both commits can at best detect a double grant, never prevent it. (A compare-and-set inside a leader doesn't rescue this: CAS serializes contenders on one register, and the two leaders are testing two different registers. Local atomicity buys nothing when the deciders are independent.) That is precisely why the book rules async multi-leader out for uniqueness.

Routing every claim for a key through one totally-ordered log partition is the consensus that was missing. Appending to a single partition assigns the concurrent claims a definite sequence — offset 0, then offset 1 — and a single-threaded consumer reading that sequence sees an unambiguous first writer. It grants offset 0 and rejects everything after, so at most one claim is ever granted. The log doesn't detect the conflict after the fact; it dissolves it, because there is never a moment when two deciders both believe alice is free. Uniqueness = agreeing on a first writer = a total order over the contenders.

The boundary — the violation needs concurrent INDEPENDENT deciders A single leader (or a single partition per key) already provides the order for free: one decider processing claims in sequence has a first writer by construction, so it needs no extra consensus machinery — a plain UNIQUE index on one primary does the job. The anomaly appears only when two or more nodes decide the same key without a shared order. And the fix does not require a global bottleneck: partitioning the log by the unique key keeps every key's decisions serial on its own partition while different keys proceed in parallel — you serialize claim('alice') against other alice claims, not against claim('bob'). The constraint scales precisely because uniqueness is per-key, so the order it needs is per-key too.

Go deeper

Sources: DDIA 2e, Ch. 13, §"Uniqueness constraints require consensus", §"Uniqueness in log-based messaging" · Ch. 6 §"Handling write conflicts" · Ch. 10 §"Consensus".