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.
Compare ACID guarantees with the BASE model (Basically Available, Soft state, Eventually consistent) used by many distributed and NoSQL systems. Discuss the trade-offs in latency, availability, and developer complexity, and give examples of applications that can tolerate eventual consistency along with techniques to manage the resulting complexity.
Sample Answer
Direct answer
ACID (Atomicity, Consistency, Isolation, Durability) is the guarantee model of traditional relational databases: every transaction leaves the data in a valid state, transactions do not interfere with each other, and once committed a write survives failures. BASE (Basically Available, Soft state, Eventually consistent) is the looser model many distributed and NoSQL systems adopt instead: the system stays available even during faults, its state may be in flux, and it only promises replicas will converge eventually, not immediately. The trade is availability and latency now, correctness later, versus correctness now, at the cost of availability and latency.
Structured elaboration
| ACID | BASE | |
|---|---|---|
| Core promise | Transaction is atomic, isolated, and durable the instant it commits | System stays available; data converges over time |
| Typical cost | Coordination (locking, quorum, or consensus) on every write | Little to no coordination on writes |
| Write latency | Higher, pays for coordination | Lower, writes accepted locally and propagated async |
| Availability under partition | Lower (may refuse writes to stay correct) | Higher (keeps accepting writes on both sides) |
| Developer burden | Lower (the database enforces correctness) | Higher (application must handle stale reads and conflicting writes) |
Worked example
A banking ledger needs ACID: if a transfer debits one account and credits another, both must happen together or not at all, and a concurrent read must never see the money "missing" between the two steps. Losing that guarantee for lower latency is not an acceptable trade for money movement.
A social-media "like count" or a product's "recently viewed" list can run on BASE: if a like posted a moment ago has not yet propagated to every replica, the count is off by one for a few seconds and nobody is harmed. The application gets a large availability and latency win in exchange for tolerating that brief inconsistency, and it can hide the seam entirely from the user (a like button that instantly shows "liked" locally, regardless of what the aggregate counter currently displays).
Trade-offs and pitfalls
BASE does not mean "no guarantees," it means the guarantees are weaker and the application must compensate for the gap: idempotent writes so a retry under uncertain state does not double-apply, conflict-resolution logic (last-write-wins, CRDTs, or application-level merge rules) for when two replicas disagree, and UI or business-process design that tolerates a visible staleness window. The common mistake is picking BASE for latency reasons without budgeting for that compensating logic, which produces silent correctness bugs (double-counted actions, lost updates) rather than the loud failures ACID would have produced instead.
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.
What is eventual consistency? Using a food-delivery-style app as your running example, describe one workflow where eventual consistency is acceptable (for example, order-history or delivery-analytics replication) and one where it is not (for example, capturing a payment). Explain what you would actually do to reduce the business risk created by the gap between when a write happens and when every reader sees it.
Sample Answer
Direct answer
Eventual consistency means that after a write stops happening, all replicas of the data will eventually converge on the same value, but there is no guarantee about how long that takes or what a reader sees in the meantime. It trades a temporary window of staleness for lower write latency and higher availability, and it is the right default for data where a slightly-stale read is harmless, and the wrong default where a stale read causes real damage.
Structured elaboration
Whether eventual consistency is acceptable comes down to one question: what does the application actually do with a stale read?
- Tolerant workloads: anything the user does not act on financially or safety-critically in the moment. Order history, delivery-tracking analytics, recommendation feeds, and dashboard counters are all fine to serve slightly stale, because a few seconds of lag has no real consequence.
- Intolerant workloads: anything where a stale read causes an incorrect real-world action. Capturing a payment, decrementing the last unit of inventory, or checking an account balance before a withdrawal are all cases where a stale read can produce double-charges, oversells, or overdrafts.
The dividing line is not the technology, it is the cost of being wrong for a few hundred milliseconds to a few seconds.
Worked example
Picture a food-delivery app.
- Acceptable: the "your driver is 4 stops away" tracker and the "orders this month" analytics dashboard read from an asynchronously-replicated read replica. If that replica is a second behind, the customer sees the driver's position update a second late, which nobody notices.
- Not acceptable: the moment a customer taps "place order" and their card is charged. If two replicas of the payment-capture record briefly disagree about whether the charge already happened, a naive retry can charge the card twice. This path needs a strongly-consistent read (or an idempotency key tied to the order, so a retry is safe regardless of replication lag).
A second, different domain shows the same trade-off with a different shape of consequence. Picture a social-feed app instead: a user posts a photo and immediately likes their own post. Because "post visible to followers" and "like count" are two independently-replicated pieces of data, a reader can briefly see a user-visible anomaly: the poster's own like counted in the total but the post itself not yet visible in a follower's feed, or the reverse, the post visible but the like count still showing the pre-like value. Nobody's money or safety is at stake here, so full strong consistency for every post and every counter would be a wildly expensive fix for a cosmetic problem. The mitigation is much cheaper than moving to strong consistency everywhere: have the poster's own client apply an optimistic local update (show "liked", show the post as posted, immediately, from the write they just issued) regardless of what the shared aggregate view currently shows, while everyone else's feed is allowed to catch up asynchronously over the next second or two. This is the same "read-your-writes for the writer only" idea as the food-delivery payment case, just applied to a cosmetic anomaly instead of a financial one, which is the point: the fix pattern generalizes across very different domains and severities.
Trade-offs and mitigations
You rarely need to make the whole system strongly consistent to fix this. Options, cheapest first:
- Read-your-writes for the writer only: route the customer's own immediate post-order reads (or, in the social-feed case, the poster's own view of their own post) to the primary or a replica guaranteed to have applied their write, while everyone else's dashboard or feed keeps reading from a lagging replica.
- Idempotency keys on the write path itself, so even if a client retries under uncertainty, the payment is captured at most once regardless of what any read shows.
- Reserve strong consistency for the specific field that matters (payment status, inventory count for the last few units) rather than promoting the entire order record, or the entire social graph, to strong consistency, which would slow down the majority of reads that never needed it.
The common mistake is treating "eventual consistency" as a single global switch. In practice it is a per-field decision: most of an application, whether it is a checkout flow or a social feed, can tolerate staleness, and only the handful of fields tied to money, safety, or the acting user's own immediate perception of their own action need the latency cost of strong consistency.
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.
Deep-dive: discuss how different consistency models (strong consistency, read-your-writes, eventual consistency, monotonic reads) affect the correctness and perception of aggregated BI metrics. For each model, give a concrete example scenario where it could mislead users, and propose mitigation approaches (UI annotations, reconciliation windows, read-model choices) a BI team can actually implement.
Sample Answer
Direct answer
Different consistency models distort aggregated BI metrics in different, specific ways: strong consistency shows the truth but can be slow or unavailable to compute at scale; eventual consistency can undercount or overcount recent activity depending on which replica the aggregation reads from; read-your-writes without the rest of the system catching up can make a single user's own dashboard look right while everyone else's looks wrong; and monotonic reads violations can make a metric appear to go backward between two consecutive dashboard refreshes, which reads to a business user as "the data is broken" even when the underlying numbers are technically converging correctly.
Structured elaboration
- Strong consistency: an aggregation query reads a single, definitive snapshot. Misleading scenario: none in terms of correctness, but a strongly-consistent aggregation over a large, actively-written dataset can be slow enough that a BI team is tempted to switch to an eventually-consistent read path without adjusting their reporting language, reintroducing the problems below. Mitigation: if you need strong consistency for a report, budget for its latency explicitly (a nightly batch snapshot rather than an ad hoc live query).
- Eventual consistency: an aggregation reads from a replica that has not yet caught up. Misleading scenario: a "signups today" counter that reads from a lagging replica can show fewer signups than actually occurred, and a business user refreshing the dashboard mid-afternoon might reasonably (and wrongly) conclude signups slowed down. Mitigation: UI annotations showing data freshness ("as of 2 minutes ago") so the business user can calibrate their trust in the number, rather than presenting a stale count as if it were current truth.
- Read-your-writes: an analyst who just imported a correction sees it reflected immediately (because their session is pinned to a replica with their own write), while a colleague viewing the same dashboard from a different session does not yet see it. Misleading scenario: two people in the same meeting, looking at what they believe is the same report, disagreeing about a number. Mitigation: a visible "last updated" reconciliation window agreed for the whole team's dashboards, so everyone knows to wait for that window before treating a number as final.
- Monotonic reads: without this guarantee, a metric can appear to decrease between two consecutive reads even though nothing was actually removed, simply because the second read happened to land on a replica that is further behind than the one the first read landed on. Misleading scenario: a revenue dashboard that appears to drop between two refreshes a minute apart, triggering a false alarm. Mitigation: pin a given viewing session to the same replica for its duration (session affinity), so at minimum a single user's sequence of reads never goes backward, even if it may still be behind the true latest value.
Worked example
A retail dashboard shows "orders in the last hour." If it reads from a replica lagging by 90 seconds, refreshing the page every 30 seconds can show the count staying flat or even ticking down slightly as the lagging replica catches up unevenly across regions, which a business stakeholder reasonably reads as "orders stopped." The actual orders never stopped; the read path's freshness did.
Trade-offs and pitfalls
The mitigations above are all UI or read-model choices, not database changes, which is the point: a BI team rarely controls the underlying database's consistency model, but can still design dashboards that are honest about what they are showing (freshness timestamps, session-pinned reads, monotonic read guarantees where the platform supports them) rather than presenting a possibly-stale or possibly-inconsistent number as unambiguous truth. The common mistake is a dashboard that shows a single number with no indication of its freshness or consistency guarantee, which invites a business user to over-trust a number the underlying system never promised was exact.
Unlock Full Question Bank
Get access to all 15 Consistency Models and Distributed Databases interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.