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.
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.
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.
Design a key-value service API and internal architecture that supports configurable per-key consistency levels: linearizable, causal, and eventual. Requirements: handle 10,000 requests per second per region, replicate across multiple regions, let a caller tune consistency per key, and keep the client-library ergonomics simple. Describe the components, client behavior, failure handling, and how you route or replicate data for each consistency tier.
Sample Answer
Direct answer
The core idea is that consistency becomes a parameter on each request rather than a fixed property of the whole database: the service stores each key with N replicas, and a client picks linearizable, causal, or eventual per key (or even per call) by choosing how many replicas the coordinator must contact and what ordering metadata it must check before returning. Linearizable calls pay a quorum round-trip and a version check; causal calls pay a smaller round-trip to confirm the client's own prior writes are visible; eventual calls pay almost nothing, a single nearby replica answers immediately.
Structured elaboration
- Components: a stateless coordinator layer that receives requests and fans them out to the replica set for a key; a replica store (each replica holds the key's value plus version metadata); a client library that lets the caller specify the desired consistency level per request and hides the fan-out/quorum logic behind a simple
get(key, consistency)/put(key, value, consistency)API. - Client behavior per tier:
- Linearizable: the coordinator contacts a write quorum on
putand a read quorum onget, sized so that R + W > N, guaranteeing every linearizable read sees the latest linearizable write. - Causal: the client library attaches a small causal-context token (a version vector covering the keys that client has previously read or written) to each request; the coordinator ensures the read reflects at least that context before returning, without requiring a full quorum.
- Eventual: the coordinator returns whatever the nearest reachable replica has, no version check, no quorum.
- Linearizable: the coordinator contacts a write quorum on
- Failure handling: if a linearizable request cannot reach a quorum (say, during a partition), it must fail loudly rather than silently downgrading to a weaker guarantee the caller did not ask for; a causal or eventual request degrades more gracefully, since a caller who already accepted weaker guarantees can tolerate serving from fewer replicas.
- Routing and replication: replicate asynchronously to all N regions for eventual/causal reads to stay fast everywhere; for linearizable operations, route the quorum check to whichever replicas are needed to satisfy R or W, which usually means at least one round trip leaves the client's local region if the quorum cannot be satisfied locally.
- Meeting the 10,000-requests-per-second-per-region target: the coordinator layer is stateless, so it scales horizontally behind a load balancer, independent of the consistency tier a given request uses. The eventual and causal tiers carry almost none of the quorum-coordination cost, so most of the traffic (whichever tiers the workload's key mix favors) never becomes a bottleneck; the linearizable tier is the one that needs headroom planning, since every linearizable call consumes capacity on W replicas at once, so the replica set's write capacity, not the coordinator, becomes the limiting factor if a large share of traffic requests the strongest tier.
Worked example (executed quorum arithmetic for the linearizable tier)
For N=5 replicas and a fixed read quorum R=2, what is the minimum write quorum W that still guarantees linearizable reads, and what happens to that guarantee if W is set one lower?
def strongly_consistent(N, R, W):
return (R + W) > N
min_W = N - R + 1 # = 5 - 2 + 1 = 4
Running this: with N=5, R=2, the minimum W is 4 (executed; confirmed). At W=4, strongly_consistent(5, 2, 4) returns True; at W=3 (one less), it returns False (executed; both confirmed). That single-replica difference is the entire gap between a linearizable guarantee and none: at W=4 the read quorum (2) and write quorum (4) are guaranteed to overlap in at least one replica, so any read is guaranteed to see the latest write; at W=3, R+W=5 equals N rather than exceeding it, so a read quorum and a write quorum can land on entirely disjoint sets of replicas and a client can read a stale value. Under a single node failure, this linearizable configuration (W=4 of 5) can still accept writes (4 of the remaining 4 healthy replicas can still form the quorum only if exactly one has failed; a second simultaneous failure would block writes entirely), which is the concrete availability cost of choosing the strongest tier.
Trade-offs and pitfalls
The main pitfall is letting a caller request "linearizable" and silently getting something weaker during a partition, which is worse than an honest failure, because the caller has no way to know their guarantee was violated. The API must make that distinction explicit (an error, not a downgraded success) for the linearizable tier specifically, while causal and eventual tiers can be designed to degrade gracefully since the caller already accepted a weaker guarantee going in. A second pitfall is ergonomics: if choosing the wrong tier for a given field is easy to do by accident (say, a default that quietly falls back to eventual), the client library should make the strongest-needed tier the explicit default for anything touching money or safety, and require an opt-in to relax it, rather than the reverse.
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.
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 15 Consistency Models and Distributed Databases interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.