Consistency Models and Distributed Databases Questions
Data correctness across distributed systems: strong versus eventual consistency, the CAP and PACELC trade-offs, consensus and quorum reads/writes, and consistency-versus-availability decisions. Covers how distributed databases reconcile replicas and what guarantees applications can rely on. A staple of distributed-systems and architecture interviews.
Explain eventual consistency and strong consistency with concrete examples from real distributed databases (for example, Dynamo-style stores vs Google Spanner). For each model, describe the typical latency profile, the burden it puts on application developers, and the common patterns used to handle anomalies like stale reads.
Sample Answer
Direct answer
Eventual consistency guarantees only that replicas converge given enough time with no new writes; a read taken during that window can return a stale value. Strong consistency guarantees every read reflects the latest committed write, at the cost of coordinating with enough replicas (or the current leader) on every read. Dynamo-style stores (DynamoDB, Cassandra) default to eventual consistency and let you opt into stronger reads per request; Google Spanner defaults to strong (externally-consistent) reads everywhere, using synchronized clocks to make that affordable at global scale.
Structured elaboration
- Latency profile: eventual reads are cheap, typically a single nearby replica answers with no cross-node coordination. Strong reads pay for coordination: Dynamo-style systems pay it by contacting a quorum of replicas; Spanner pays it by waiting out its clock-uncertainty bound (TrueTime) before a read can be certified as externally consistent, and by using two-phase commit across the replica groups involved in a transaction.
- Developer burden: with eventual consistency, the application must anticipate stale reads: idempotent writes, conflict-resolution logic, and UI patterns that hide or tolerate staleness. With strong consistency (Spanner), the application can mostly reason about the database as if it were a single machine, which is a large simplification, but the application still has to design around the added write latency of global coordination.
- Handling anomalies: Dynamo-style systems commonly use read-repair, anti-entropy, and vector clocks or version vectors to detect and resolve the stale-read and conflicting-write anomalies that eventual consistency permits. Spanner avoids most of those anomalies by construction (every committed transaction gets a globally-ordered timestamp), so it does not need reconciliation machinery for its core read/write path; the cost shows up instead as higher write latency, since a transaction cannot commit until TrueTime's uncertainty window has elapsed.
Worked example
A social app's "follower count" is a good fit for Dynamo-style eventual consistency: reads are extremely frequent, a few seconds of staleness is invisible, and the system stays fast and available even during a regional network hiccup. A bank's core ledger, if built on a globally-distributed SQL layer, is a good fit for Spanner-style strong consistency: a teller in one region and an ATM in another must never disagree about the current balance, and the extra tens of milliseconds a transaction pays to wait out the clock-uncertainty bound is a small price next to the cost of a wrong balance.
Trade-offs and pitfalls
The common mistake is treating this as "Dynamo is old and weak, Spanner is new and strong," rather than as a real engineering trade-off: Spanner's strong consistency requires GPS-and-atomic-clock hardware (TrueTime) that most organizations do not have and cannot easily replicate outside a hyperscaler, and it pays real write latency for the certainty. Dynamo-style eventual consistency is not "worse," it is a deliberate bet that most operations do not need that certainty, and the ones that do can be special-cased with a stronger read rather than paying the coordination cost on every single request.
Dynamo-style distributed databases typically expose more than one consistency level to the application rather than a single fixed guarantee. Name three common levels, explain what each one actually guarantees to the caller, and give one realistic use case where you would pick that level over the others.
Sample Answer
Direct answer
Dynamo-style databases commonly expose three consistency levels an application can choose per operation: strong (contact every replica / ALL), quorum (majority, R + W > N), and eventual (contact one replica / ONE). Strong consistency contacts every replica and always returns the latest committed write, at the cost of the highest latency and the lowest availability during a partition; eventual consistency returns whatever a nearby replica has, fastest and most available, but possibly stale; quorum consistency sits between the two, requiring only a majority of replicas to agree (R + W > N), which gives a strong practical guarantee (every read quorum is guaranteed to overlap every write quorum in at least one replica) without paying the cost of contacting every single replica on every operation.
Structured elaboration
- Strong (ALL) reads: the read is guaranteed to reflect the most recent successful write, as if there were only one copy of the data. Implemented by requiring every replica (R = N or W = N) to participate, or by always routing to the current write leader in systems that have one.
- Eventual (ONE) reads: the read may return an older value if it lands on a replica that has not yet received the latest write. Implemented by reading from whichever single replica is closest or least loaded, no quorum coordination required.
- Quorum (majority) reads: a read or write is acknowledged only after a majority of replicas respond (for N=3, a quorum is 2; for N=5, a quorum is 3). Choosing R and W so that R + W > N guarantees every read quorum overlaps every write quorum by at least one replica, so a quorum read is guaranteed to see the most recent quorum-acknowledged write, without the latency and availability cost of waiting on every single replica the way ALL does.
Worked example
- Strong (ALL) reads: a user checks their own account balance immediately after a transfer. They must see the transfer reflected, so the read pays the latency cost of confirming with every replica (or the leader).
- Eventual (ONE) reads: a public-facing "total likes on this post" counter. A read that is a few seconds behind is invisible to the user experience and the read stays cheap and highly available.
- Quorum reads: an inventory count during checkout, where ALL would be too slow and too fragile (any single slow replica blocks the read), but ONE risks showing stale stock and overselling the last unit. QUORUM (for N=3, R=2, W=2, so R+W=4 > N=3) gives a strong, majority-backed answer while still tolerating one replica being slow or down, which is the practical default most production Dynamo-style deployments reach for when they need "correct and fast" rather than either extreme.
Trade-offs and pitfalls
The three levels are a latency/availability-versus-freshness dial, not a correctness hierarchy where "stronger is always better." Choosing ALL for every read on a high-traffic, low-stakes field (like a like-counter) needlessly funnels all that traffic through every replica and makes the system less available during a partition, for a guarantee the product never needed. The common mistake is picking the strongest level available "to be safe" instead of matching the level to what a stale read would actually cost.
A multi-master cluster experiences a network partition resulting in split-brain: both sides keep accepting local writes. Describe the operational steps you would take to contain the problem, determine which data is authoritative, reconcile the diverged writes, and restore normal operation while minimizing data loss and customer impact.
Sample Answer
Direct answer
The operational response to a split-brain has three phases: contain (stop the divergence from getting worse), determine the authoritative state (figure out what actually happened on each side), and reconcile (merge or discard the diverged writes and get back to one consistent state). The goal throughout is to minimize both further data loss and the blast radius of whatever reconciliation you choose, and to keep a human in the loop for any conflict that automated ranking cannot resolve safely.
Structured elaboration
- Contain: as soon as the partition is detected (or heals), stop accepting new writes on at least one side, or route all traffic to a single side, so the divergence window has a hard stop instead of continuing to widen while you investigate.
- Determine authoritative data: for each diverged key, compare the two sides' versions using whatever causality information you have (vector clocks, version numbers, timestamps). Writes where one side's version strictly dominates the other (the causal "before" case) are not real conflicts, just replication lag, and resolve themselves trivially: keep the newer one. Writes where neither side's version dominates the other are true concurrent conflicts and need an explicit resolution policy.
- Reconcile: apply an automated policy (last-write-wins by timestamp, a merge function for mergeable data like a set or counter, or a business rule like "larger cart wins") to the majority of conflicts, and route anything the policy cannot resolve safely (for example, two different final states for the same financial balance) to a human for manual review rather than guessing.
- Restore: once every diverged key is resolved, resume normal write acceptance on both sides and verify convergence (a checksum or Merkle-tree comparison across the previously-diverged range) before declaring the incident closed.
A closely related but distinct failure shape is worth naming: instead of a live, ongoing partition, a primary region can go down while its secondaries keep accepting writes in its absence, then the primary returns. The reconciliation approach is the same in spirit (determine what happened while the primary was gone, then merge), but the failure looks asymmetric rather than symmetric: only one side was ever "authoritative" before the outage, so the natural default is to treat the secondaries' writes during the outage as the ones needing review against what the returning primary still has, rather than treating both sides as equally authoritative from the start.
Worked example
Using the causal-dominance check from vector clocks to separate real conflicts from mere lag:
def vc_compare(vc1, vc2):
le = all(vc1.get(k,0) <= vc2.get(k,0) for k in set(vc1)|set(vc2))
ge = all(vc1.get(k,0) >= vc2.get(k,0) for k in set(vc1)|set(vc2))
if le and not ge: return "before"
if ge and not le: return "after"
if le and ge: return "equal"
return "concurrent"
For a write on side A with clock {A:2, B:0, C:0} and a write on side C with clock {A:1, B:0, C:1}, the function returns "concurrent" (executed; confirmed): A's clock does not dominate C's and vice versa, so this is a genuine conflict, not just one side being behind, and it needs the resolution policy from step 3 rather than a simple "keep the newer one."
Trade-offs and pitfalls
The common mistake is applying an automated resolution policy (like last-write-wins) uniformly to everything, including conflicts where a wrong automated guess is expensive (a financial balance, an inventory count near zero). Route those specific cases to a human, even though it is slower, because the cost of guessing wrong there is much higher than the cost of a short manual-review delay. The other common mistake is not containing the divergence first: continuing to accept writes on both sides while you investigate only grows the set of conflicts you eventually have to reconcile.
Explain read-repair and anti-entropy (background) repair in replicated stores. Compare their roles, their performance impacts, and when you would tune one over the other. Cover the operational side too: how you would schedule and prioritize background repair at scale, how you would detect divergence cheaply across millions of keys, and what you would monitor to know it is working.
Sample Answer
Direct answer
Read-repair and anti-entropy are the two standard ways a replicated store fixes replicas that have drifted apart: read-repair is reactive, fixing divergence the moment a read happens to touch it, and anti-entropy is proactive, a background process that scans and reconciles replicas continuously, regardless of whether anyone reads that data. You tune read-repair up when correctness of frequently-read keys matters most and you can afford slightly higher read latency; you tune anti-entropy up (or its scheduling more aggressive) when data is rarely read but must still converge, or when you need a floor on staleness independent of read traffic.
Structured elaboration
- Read-repair: on a read, the coordinator queries multiple replicas, compares their values, returns the most recent one to the client, and asynchronously (or synchronously, in "read-repair-blocking" mode) writes the corrected value back to the stale replicas. Its coverage is limited to keys that actually get read; a key nobody reads never gets repaired this way.
- Anti-entropy: a background process (commonly using Merkle trees or version-vector comparisons) periodically compares whole replicas or partitions of them, independent of read traffic, and repairs whatever divergence it finds. It guarantees eventual convergence even for cold keys, at the cost of continuous background I/O and bandwidth.
Operationally, running anti-entropy well at scale requires: scheduling and staggering (so a full sweep does not hit every node's disk and network at once), prioritization (hot or business-critical keys first, so the highest-impact divergence is fixed soonest), bandwidth control (throttling so the repair traffic does not starve foreground reads and writes), verification via checksums or Merkle trees (comparing hashes of subtrees rather than every raw key, so divergence detection is cheap), and resuming cleanly after a node crash mid-sweep rather than restarting the whole comparison from scratch.
To know anti-entropy is actually working, monitor: the divergence rate found per sweep (how many keys or subtrees needed repair, which tells you how fast replicas are drifting relative to how fast you are fixing them), the age of the oldest unrepaired divergence you have detected (the real staleness bound the system is delivering in practice, not the theoretical one), sweep completion time versus the sweep interval (a sweep that takes longer to finish than the gap between sweeps means the system is falling behind, not keeping up), and the bandwidth/CPU the repair process is consuming against its throttle budget. A widening trend in any of these, more divergence found per sweep than last time, or sweeps that no longer complete inside their scheduled window, is the signal that anti-entropy is losing ground to write volume rather than keeping pace with it.
Worked example
A Merkle tree turns an O(n) "compare every key" scan into an O(log n) divergence check. With two replicas holding 3 keys, where only user:42 has diverged:
import hashlib
def h(x):
return hashlib.sha256(x.encode()).hexdigest()[:8]
replica_A = {"user:1": "v1", "user:2": "v1", "user:42": "vA-stale"}
replica_B = {"user:1": "v1", "user:2": "v1", "user:42": "vB-fresh"}
def merkle_root(replica):
keys = sorted(replica.keys())
leaves = [h(k + ":" + replica[k]) for k in keys]
level = leaves
while len(level) > 1:
nxt = []
for i in range(0, len(level), 2):
if i + 1 < len(level):
nxt.append(h(level[i] + level[i + 1]))
else:
nxt.append(h(level[i] + level[i])) # odd node: duplicate
level = nxt
return level[0]
print(merkle_root(replica_A))
print(merkle_root(replica_B))
Running this (executed; confirmed): merkle_root(replica_A) is 56c6f93d, merkle_root(replica_B) is 36243be6. Since the roots differ, the process knows immediately that something diverged without comparing all 3 keys directly. Walking down from the root to find which branch's hash differs then pinpoints exactly user:42 as the diverged key; the other two keys never need to be compared. At production scale (millions of keys per node) this is the difference between an O(log n) check most sweeps can complete cheaply and an O(n) full scan that would saturate the network.
Trade-offs and pitfalls
Read-repair alone leaves cold data permanently stale if it is never read again, which is why production systems run both together, not one instead of the other. Anti-entropy alone, run too aggressively, competes with foreground traffic for disk and network bandwidth, which is why prioritization (hot keys first) and throttling matter as much as the comparison algorithm itself. A repair sweep that dies mid-run and restarts from scratch every time is a common operational trap: track progress (a cursor or checkpoint over the key range) so a crash costs minutes of re-work, not a full re-scan.
Walk through the CAP theorem in your own words, then name a popular production distributed database that intentionally sacrifices one of the three guarantees for a specific workload. Explain which guarantee it sacrifices and why that trade-off makes sense for that workload.
Sample Answer
Direct answer
The CAP theorem states that a distributed data store that is split across a network partition can provide either Consistency (every read sees the latest write) or Availability (every request gets a response), but not both, for the duration of the partition. Partition tolerance itself is not optional in a real multi-node deployment, since the network will fail eventually, so in practice CAP is really a CP-vs-AP choice about what happens during a partition. Apache Cassandra is a well-known example that defaults to sacrificing Consistency: during a partition it keeps accepting reads and writes on both sides (AP), because for its original use case (Amazon's shopping cart) staying available mattered more than every replica agreeing instantly.
Structured elaboration
- Consistency (C): every node that receives a read returns the most recent write, or an error. No stale reads are ever served.
- Availability (A): every request that reaches a non-failed node gets a non-error response, even if it might be stale.
- Partition tolerance (P): the system keeps operating even when network messages between nodes are lost or delayed.
Because a network partition is a fact of distributed deployment rather than a design choice, CAP in practice forces a decision only about what happens while partitioned: refuse some requests to stay consistent (CP), or keep serving and reconcile afterward (AP). A single-node database that never partitions can be both C and A, which is why "CA" only makes sense for non-distributed systems.
Worked example
Cassandra's default read/write path favors availability: each node accepts writes independently and reconciles differences later through mechanisms like read-repair and anti-entropy. During a network partition between two data centers, both sides keep accepting writes to the same key. This is a deliberate trade-off: Cassandra's original design goal (from the Dynamo paper it descends from) was "the shopping cart must always accept an add-to-cart write," because losing a sale to an unavailable cart was judged worse than occasionally having to merge two divergent cart states after the fact.
Trade-offs and pitfalls
The common mistake is treating CAP as a single, permanent, whole-database choice. Real systems often make the CP-vs-AP decision per operation or per keyspace, not once for the whole deployment (Cassandra itself supports tunable consistency levels that let you dial toward the CP end for specific operations). CAP also says nothing about latency in the absence of a partition, which is why PACELC (adding "else, trade latency for consistency") is a more complete framing for day-to-day operation when the network is healthy.
Unlock Full Question Bank
Get access to all 12 Consistency Models and Distributed Databases interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.