Distributed State & Consistency: Placing Each Operation on the Spectrum · the spectrum
On one machine you chose a representation. Across many, you lose the luxury of having the truth and the speed at once. This take-home asks you to take a real application and place its operations on the consistency spectrum — each at the weakest guarantee it can correctly tolerate, and not a rung stronger. The grade is in showing that different operations need different rungs, and naming what each one costs.
Time: ~45–60 min · Due: before Module 4 · Submit: post your write-up in the cohort channel.
Pick one application — a ride-share backend, a messaging app, an online store — and choose three core operations that genuinely differ in what they can tolerate.
⚠️ Two systems are off-limits as your submission: the collaborative document editor placed below (it's the worked demo), and the retail bank / cash machine we placed together in class. Pick something else.
In a ~300–400 word write-up, place each operation on the spectrum:
- The weakest guarantee it can correctly tolerate — eventual, causal, linearizable, or external. Weakest-tolerable, not strongest-available.
- What it promises on concurrent access, and what during a partition. Two questions, asked of every store.
- The backing system (or a modern descendant) that provides that rung.
- The cost you pay for that rung — latency, coordination, or tolerated staleness. Name the price; a guarantee with no visible cost just means you haven't found it yet.
- A time you reached for eventual consistency on something that couldn't tolerate a stale read — or the reverse, paying for strong consistency something didn't need. What broke, or what did you overpay?
- A guarantee whose cost you can now name precisely — "this cost me exactly this."
- Where predict-then-reconcile shows up in something you've built or used — optimistic UI, collaborative editing, a game.
Reply to one classmate's audit. Find an operation placed at the wrong rung — paying for strong consistency it doesn't need, or tolerating staleness it can't afford — and propose the right placement with its cost.
Everything below is just these, applied to one application. Keep them in front of you:
- You can't have the truth and the speed at once. It's a standing condition, not a bug. The question is never "how do I avoid it" but "which do I give up, where."
- Place each operation at its weakest tolerable rung. A like-count tolerates a stale read; a balance does not. Same app, different rungs — that's the whole skill.
- A cost buys a guarantee. You pay with a restriction (MapReduce's pure functions) or with latency (Spanner's commit-wait). Name the price every time.
- Predict, then reconcile when you can't wait for the truth. Authoritative anchor + optimistic local action + reconciliation — invisible when right, graceful when wrong.
Let's run the placement on something that is not your assignment: a collaborative document editor — many people editing one shared doc at once. Three operations, three rungs. Watch the moves, then make them on your own system.
Operation 1 · Edit the document text. Two people type in different paragraphs simultaneously. Does this need a single global order? No — it needs convergence: everyone ends up at the same document, and edits that didn't causally depend on each other can merge in any order. So this sits at causal / eventual (this is exactly what CRDTs and OT are for). On concurrent access: edits merge. During a partition: you keep editing offline and reconcile on reconnect. The cost: merge complexity, and occasionally a surprising-but-consistent merge result. This is predict-then-reconcile in the open — type now, reconcile when peers' edits arrive.
Operation 2 · Change sharing permissions. Revoke someone's access. Can this be stale? Absolutely not — a stale read here means a removed collaborator keeps editing a document they've been locked out of. This wants linearizable consistency: the revocation takes effect in a single, unambiguous order, visible to all. On concurrent access: serialised. During a partition: I'd rather deny than risk a stale grant (fail closed — a taste of Module 5). The cost: latency on every permission check, and possibly unavailability during a partition. Worth it — the cost of a wrong answer is unbounded.
Operation 3 · Show presence (who's online, cursor positions). A cursor a second out of date is fine; a name lingering in the sidebar for two seconds after someone leaves is fine. Availability beats accuracy here. Eventual consistency. On concurrent access: best-effort. During a partition: show stale presence rather than nothing. The cost: visible staleness — and it costs nothing that matters.
Notice what just happened: one app, three operations, three different rungs — eventual for presence, causal for text, linearizable for permissions — each justified by what it can tolerate, each with its cost named. Choosing one consistency level for the whole editor would have over-paid for presence or under-served permissions. That's the whole of Part A. Your job is to make those moves on your system.
One application, three operations, placed at three points on a ladder where cost rises as you climb. Your audit puts your operations on this ladder and names the bill at each rung.
flowchart TD
EVENT["EVENTUAL<br/>presence / cursors<br/>cost: visible staleness"]
CAUSAL["CAUSAL<br/>document edits<br/>cost: merge complexity"]
LIN["LINEARIZABLE<br/>permission changes<br/>cost: latency · maybe unavailable"]
EXT["EXTERNAL<br/>(global order)<br/>cost: latency you wait out"]
EVENT -- "+ latency, + order" --> CAUSAL
CAUSAL -- "+ latency, + order" --> LIN
LIN -- "+ latency, + order" --> EXT
style EVENT stroke:#6FCF97,stroke-width:1.5px
style CAUSAL stroke:#FFB938,stroke-width:1.5px
style LIN stroke:#F5A623,stroke-width:2px
style EXT stroke:#C97E12,stroke-width:1.5px
Copy this into your doc and fill one block per operation. Keep it tight — most of the marks are in justifying the rung and naming the cost.
APPLICATION (one line): ___________________________________
OPERATION 1: ______________________________________________
Weakest tolerable rung: _________________________________
On concurrent access / during a partition: ______________
Backing system: _________________________________________
Cost paid for this rung: ________________________________
OPERATION 2: ______________________________________________
Weakest tolerable rung: _________________________________
On concurrent access / during a partition: ______________
Backing system: _________________________________________
Cost paid for this rung: ________________________________
OPERATION 3: ______________________________________________
Weakest tolerable rung: _________________________________
On concurrent access / during a partition: ______________
Backing system: _________________________________________
Cost paid for this rung: ________________________________
If you want a diagram, copy the Mermaid ladder above and place your three operations on it — it renders as a flowchart in your Gist.
Part B — starter prompts (answer in your own words; don't just restate these):
- Find a real bug or near-miss: a stale read that mattered, or a strong guarantee you paid for and never needed. What was the operation's true tolerance?
- Pick one guarantee in something you've shipped and finish the sentence precisely: "this consistency level cost me exactly ______." If you can't finish it, the cost is still hiding.
- Spot predict-then-reconcile in the wild — the like that turns blue before the server confirms, the doc that lets you type offline. What's the authoritative anchor, and how graceful is the reconciliation when the prediction was wrong?
- One rung for the whole app. "We use a strongly-consistent database" is not an analysis — it's the over-paying-everywhere failure. Different operations, different rungs.
- Strongest-available instead of weakest-tolerable. "Linearizable to be safe" ignores the operation. Safe isn't the goal; correctly tolerable is.
- Default-eventual on something that can't be stale. The mirror error. A balance, a permission, an inventory count — these break on a stale read.
- A guarantee with no named cost. If a rung looks free, you haven't found its price. Latency, coordination, or staleness — one of them is the bill.
- Confusing "during a partition" with "on concurrent access." They're different questions. Answer both for each operation.
- Places three operations at three different rungs — and shows why each differs.
- Justifies each by what it can correctly tolerate, not by a default or by "safety."
- Names the backing system and the cost for every rung.
- Never buys a guarantee stronger than needed — and spots where predict-then-reconcile earns its keep.
That's the spectrum. You can't have the truth and the speed at once, so you decide — per operation — which to buy. Next module, the state grows past one node, and a new fact takes over: something is always failing.