Skip to the solution
MediumSystem Design13 min read

What is the difference between strong and eventual consistency?

307 views

The problem

Question presented to candidate: "What is the difference between strong consistency and eventual consistency? How does each one actually get implemented, what does the user notice, and how would you decide which one a feature needs?"

What a strong answer should cover:

  • Strong (linearizable): once a write completes, every later read anywhere returns it; the system behaves like one copy
  • Eventual: if writes stop, all replicas converge to the same value; until then a read may return any older value, with no bound on how old
  • How each is built: a leader with synchronous replication, consensus or majority quorums for strong; asynchronous replication, read-any-replica and anti-entropy for eventual
  • The visible anomalies of eventual consistency: stale reads, not seeing your own write, values going backwards in time between reads
  • The middle ground: read-your-writes, monotonic reads, causal, bounded staleness, and how a session token or sticky routing provides them cheaply
  • The cost side: strong consistency adds latency (waiting for replicas) and loses availability during partitions; eventual is fast and available but pushes complexity to the application

Clarifying questions expected:

  • Which reads must reflect the latest write (money, inventory, permissions) and which can be a little old (feeds, counters)?
  • Are replicas in one region or spread across regions (what does a synchronous round trip cost)?

Code / implementation expected: Optional β€” a model of a leader and lagging followers that measures stale reads and shows read-your-writes and monotonic-reads fixes; the example does that.

  • 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

13 min read Β· 13 sections

Read the code

Strong vs eventual reads: stale-read rates, read-your-writes, monotonic reads, convergence
// Strong vs eventual consistency on a fake clock. One leader takes every write;
// follower F1 applies each write 30 ms later, F2 applies it 120 ms later (asynchronous replication).
const LAG = { L: 0, F1: 30, F2: 120 };
const writes = []; // version k (k = 1..10) is committed on the leader at t = 100 * (k - 1) ms
for (let k = 1; k <= 10; k++) writes.push(100 * (k - 1));

function applied(r, t) { // newest version replica r has applied at time t
  let v = 0;
  for (let k = 1; k <= writes.length; k++) if (writes[k - 1] + LAG[r] <= t) v = k;
  return v;
}
const latest = (t) => applied("L", t);
const pct = (a, b) => { const x = Math.floor((a * 1000 + Math.floor(b / 2)) / b); return Math.floor(x / 10) + "." + (x % 10) + "%"; };

console.log("== 1. 143 reads, one every 7 ms from t=0 to t=994 ==");
const policies = [["strong: leader only", () => "L"], ["eventual: F1 only", () => "F1"], ["eventual: F2 only", () => "F2"],
  ["eventual: round robin F1/F2", (i) => (i % 2 ? "F2" : "F1")]];
for (const [name, pick] of policies) {
  let stale = 0, total = 0, worst = 0, leaderReads = 0;
  for (let i = 0, t = 0; t < 1000; i++, t += 7) {
    const r = pick(i), seen = applied(r, t), want = latest(t);
    total++;
    if (r === "L") leaderReads++;
    if (seen < want) { stale++; worst = Math.max(worst, t - writes[seen]); } // ms since the first write it misses
  }
  console.log(name.padEnd(28) + " stale " + String(stale).padStart(3) + " of " + total + " (" + pct(stale, total) + "), worst " +
    String(worst).padStart(3) + " ms behind, leader serves " + pct(leaderReads, total));
}

console.log("\n== 2. Read-your-writes: the writer reads 40 ms after each of its 10 writes ==");
for (const session of [false, true]) {
  let stale = 0, forwarded = 0;
  for (let k = 1; k <= 10; k++) {
    const t = writes[k - 1] + 40, r = k % 2 ? "F1" : "F2";
    let seen = applied(r, t);
    if (session && seen < k) { forwarded++; seen = applied("L", t); } // replica is behind the session token
    if (seen < k) stale++;
  }
  console.log((session ? "with session token (min version)" : "plain follower reads").padEnd(33) + ": " + stale + " of 10 missed the write just made, " + forwarded + " sent to leader");
}

console.log("\n== 3. Monotonic reads: one client reads every 20 ms ==");
for (const sticky of [false, true]) {
  let back = 0, last = 0;
  const trail = [];
  for (let i = 0, t = 0; t < 1000; i++, t += 20) {
    const r = sticky || i % 2 === 0 ? "F1" : "F2", seen = applied(r, t);
    if (seen < last) { back++; if (trail.length < 3) trail.push("t=" + t + " v" + last + "->v" + seen); }
    last = seen;
  }
  console.log((sticky ? "sticky to F1" : "round robin F1/F2").padEnd(18) + ": went back in time " + back + " times" + (trail.length ? " (" + trail.join(", ") + ")" : ""));
}

console.log("\n== 4. Convergence after the last write (t=900) ==");
for (let t = 900; t <= 1020; t += 30) {
  const vs = ["L", "F1", "F2"].map((r) => r + "=v" + applied(r, t));
  const same = new Set(["L", "F1", "F2"].map((r) => applied(r, t))).size === 1;
  console.log("t=" + t + "ms " + vs.join(" ") + (same ? "  converged" : ""));
}

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 45 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

How does S3 multipart upload make 5TB uploads resumable and fast?

Continue