Skip to the solution
MediumSystem Design14 min read

What is a read/write quorum?

113 views

The problem

Question presented to candidate: "In a replicated key-value store like Dynamo or Cassandra, each key lives on N replicas, and you configure how many must acknowledge a write (W) and answer a read (R). What is a quorum, why does W + R > N matter, and how would you set W and R for different workloads?"

What a strong answer should cover:

  • Definitions: N replicas per key, a write succeeds after W acknowledgements, a read waits for R answers and returns the newest version
  • The overlap rule: W + R > N forces every read set to share at least one replica with every write set, so a read meets the latest acknowledged write
  • The trade-off: lower W or R means faster, more available operations and possible stale reads; the common default is a majority for both (N=3, W=2, R=2), which survives one failure
  • How replicas converge: read repair, anti-entropy, and versions (timestamps or vector clocks) to pick the winner
  • Limits: sloppy quorums with hinted handoff break the overlap guarantee; quorums alone are not linearizable under concurrent or partially failed writes
  • Real knobs: Cassandra ONE, QUORUM (floor of RF/2, plus 1), LOCAL_QUORUM, ALL

Code / implementation expected: Optional โ€” the overlap rule and availability table matter most; the example enumerates every write and read set for N=3.

  • quorum
  • consistency

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

14 min read ยท 13 sections

Read the code

N=3 quorums: stale reads for every W and R, availability under failures, read repair, sloppy quorum
// Read/write quorums on N = 3 replicas: when W + R > N a read always meets the newest write.
// Replicas are in-process objects {version, value}; "down" and "missed the write" are flags.
const N = 3;
const names = ["r1", "r2", "r3"];
function subsets(k) { // every set of k replica indexes, in lexicographic order
  const out = [];
  const go = (start, cur) => { if (cur.length === k) { out.push(cur.slice()); return; } for (let i = start; i < N; i++) { cur.push(i); go(i + 1, cur); cur.pop(); } };
  go(0, []); return out;
}
function read(reps, set) { // newest version among the replicas that answered
  let best = null;
  for (const i of set) if (best === null || reps[i].version > best.version) best = reps[i];
  return best;
}
const configs = [[1, 1], [1, 2], [2, 1], [2, 2], [3, 1], [1, 3]];

console.log("1) All replicas hold v1=blue. Then v2=green is written to exactly W replicas.");
console.log("   Every possible write set x every possible read set:");
console.log("W  R  W+R>N  stale reads");
for (const [W, R] of configs) {
  let stale = 0, total = 0;
  for (const ws of subsets(W)) for (const rs of subsets(R)) {
    const reps = names.map((_, i) => (ws.includes(i) ? { version: 2, value: "green" } : { version: 1, value: "blue" }));
    total++; if (read(reps, rs).version !== 2) stale++;
  }
  console.log(`${W}  ${R}  ${String(W + R > N).padEnd(5)}  ${stale} of ${total}`);
}

console.log("\n2) Availability: can the operation still reach enough replicas?");
console.log("W  R  1 down: write/read   2 down: write/read");
for (const [W, R] of configs) {
  const ok = (need, down) => (N - down >= need ? "ok" : "FAIL");
  console.log(`${W}  ${R}  ${(ok(W, 1) + "/" + ok(R, 1)).padEnd(19)}  ${ok(W, 2)}/${ok(R, 2)}`);
}

const show = (reps) => reps.map((r, i) => `${names[i]}=v${r.version}:${r.value}`).join(" ");
console.log("\n3) N=3, W=2, R=2 with read repair.");
let reps = names.map(() => ({ version: 1, value: "blue" }));
reps[0] = { version: 2, value: "green" }; reps[1] = { version: 2, value: "green" }; // r3 was partitioned
console.log("   after write v2 (r3 missed it): " + show(reps));
const got = read(reps, [1, 2]);
console.log(`   read from r2,r3 -> v${got.version}:${got.value} (r3 answered v1, newer version wins)`);
for (const i of [1, 2]) if (reps[i].version < got.version) reps[i] = { ...got };
console.log("   after read repair:              " + show(reps));

console.log("\n4) Sloppy quorum: r1 and r2 are down, W=2.");
reps = names.map(() => ({ version: 2, value: "green" }));
const up = [2], W4 = 2;
console.log(`   strict quorum write v3: ${up.length >= W4 ? "ok" : "FAIL"}, only ${up.length} of the 3 home replicas is up`);
for (const i of up) reps[i] = { version: 3, value: "red" };
const hint = { version: 3, value: "red", for: "r1" };
console.log("   sloppy quorum write v3: ok, stored on r3 and on stand-in node h4 with a hint for r1");
console.log(`   r1 and r2 recover; read from r1,r2 -> v${read(reps, [0, 1]).version}:${read(reps, [0, 1]).value} (stale: the overlap was on h4, not a home replica)`);
reps[0] = { version: hint.version, value: hint.value };
console.log(`   hinted handoff h4 -> ${hint.for}; read from r1,r2 -> v${read(reps, [0, 1]).version}:${read(reps, [0, 1]).value}`);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 50 of 106 in System design

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

What indexing strategies improve database performance?

Continue