Cruxly

Cruxly › System design › Databases: Choosing One

Consistency models, for the person choosing

name the guarantee a user needs, pick the weakest model that gives it, and say what it costs

Open the interactive lesson

Consistency is not one dial. It is a ladder of promises about which copy you read and in what order you see things, from strict serializable at the top to eventual at the bottom. Isolation is a different question, about overlapping transactions. A good answer names the guarantee each piece of data needs, the mechanism that provides it, what that costs in latency and availability, and how stale everything else may be.

Isolation and consistency are two different axes

The word "consistency" covers at least three things in interviews: the C in ACID (your application's invariants hold), the C in CAP (linearizability), and the freshness and ordering of replicated copies. This lesson is about the last two. First, separate them from isolation.

IsolationConsistency (replicas and order)
QuestionWhen transactions overlap, which interleavings are allowed?After a write completes, who sees it, from which copy, in what order?
Lives inThe transaction manager of one databaseReplicas, caches, regions, the client
Vocabularyread committed, snapshot, serializablelinearizable, causal, read your writes, eventual
A failure looks likeLost update, write skewA stale read, a comment that vanishes
FixA stronger isolation level, locks, retriesRead the primary, wait for a replica, carry a token

Two quick tests. If the bug needs two actors at the same moment, it is isolation. If one actor writes, then someone reads an old copy, it is consistency. A balance of 100 that two transactions both read and both subtract from is isolation. A balance of 70 on the primary that a replica still shows as 100 is consistency, and no isolation level changes that.

They get confused because both are fixed by "make it stronger", and because a single node hides the second axis completely. A replica read can also be perfectly isolated and still old: a transfer of 30 leaves the primary at 70 / 130 while a replica serves the earlier snapshot of 100 / 100. The total is right, nothing is torn, the data is simply behind.

Strict serializability is the model that covers both axes. Jepsen defines it as serializability's total order of multi-object transactions plus linearizability's real-time constraint: if A completes before B begins, A is ordered first. Spanner's documentation gives the banking example: a deposit commits, then a debit starts. A merely serializable system may reorder them, so the debit hits insufficient funds.

:::key Name the axis first Before you answer a "consistency" question, say "that is an isolation problem" or "that is a replica freshness problem". Many interview mistakes come from answering the wrong one. :::

The ladder, strongest to weakest

Jepsen's consistency map has many models, and it is a partial order, so a single ladder is a simplification. These are the ones that matter in products, with one example each and what the user sees when it breaks. The Alice and Bob example in the linearizable row is Kleppmann's (his 2015 post, set at the 2014 World Cup final); the lunch chat used later for causal consistency is Jepsen's.

ModelPromiseViolation looks likeUnder a partition (Jepsen)
Strict serializableTransactions in one order that respects real time, across many objectsReordered transactions: the debit ran "before" the deposit it followedCannot be totally or sticky available
LinearizableOne object. If A finishes before B starts, B sees A or newerAlice sees the final score and tells Bob; Bob reloads a lagging replica and the game is still onCannot be totally or sticky available
SequentialOne total order everyone agrees on, consistent with each client's own order, but not tied to the clockTwo users see events in different orders. (A stale read alone is allowed.)Cannot be totally or sticky available
CausalCause before effect everywhere. Unrelated events may differA reply visible before the question it answersSticky available
Session guaranteesPer client: read your writes, monotonic reads, monotonic writes, writes follow readsYour own edit disappears; a comment appears then vanishesSee next section
EventualIf writes stop, copies converge. Nothing is promised in betweenAny old state, in any order, for an unspecified timeAvailable (no coordination needed)

Three notes that interviewers probe.

:::key Read the table as a menu with prices Each step up buys a promise a user can notice, and costs coordination: latency on the read or write path, and availability when the network splits. :::

Session guarantees: the cheap ones users actually notice

Session guarantees constrain what one client sees, not what everyone sees. That is why they are cheap and why they are the guarantees real users complain about.

GuaranteeMeaningViolation the user seesTypical fix
Read your writesAfter my write, my reads reflect itI save my name, reload, the old name is backRead the primary briefly, or carry a version token
Monotonic readsMy reads never go back in timeA comment appears, then vanishes on refreshPin the session to one replica
Monotonic writesMy writes are applied in the order I issued themI set "draft" then "published", a replica applies them in reverse and stays on draftOrder writes per client at the replica
Writes follow readsA write I make after reading lands after what I readMy reply shows up where the post it answers does not exist yetShip the dependency with the write

Availability is what makes this group attractive. Jepsen classes monotonic reads, monotonic writes and writes follow reads as totally available: every node can keep serving during a partition. Read your writes is sticky available: it holds as long as the client keeps talking to the same server. Jepsen's causal page says it plainly: if you need total availability you give up causal consistency and read your writes, but you can still have the other three.

One trap: monotonic reads does not give read your writes. A session pinned to a replica that is consistently ten seconds behind never goes backwards, yet it never sees the write it just made either.

MongoDB's docs list exactly these four as the causal consistency guarantees of a client session, and state that they hold together only with majority read concern and majority write concern. That is a good example of a vendor selling "causal" as a bundle of session guarantees.

Quorum stores: DynamoDB and Cassandra

DynamoDB (AWS docs, read October 2026). Reads of tables and local secondary indexes are eventually consistent by default; setting ConsistentRead to true returns data reflecting all earlier successful writes. Reads from global secondary indexes and streams are always eventually consistent; strong reads there are not supported. Eventual reads cost half: a read unit covers one strong read or two eventual reads per second of up to 4 KB. Example (an assumption): 400 reads per second of 3 KB items need 200 read units eventually consistent and 400 strongly consistent.

Global tables add a second dimension. Multi-Region eventual consistency (MREC, the default) replicates typically within a second, and a strongly consistent read returns the latest only if the item was last written in that Region. Multi-Region strong consistency (MRSC) replicates synchronously to at least one other Region before the write returns, so a strong read on any replica is current, at higher write and strong-read latency. The docs list limits for MRSC tables: exactly three Regions (three replicas, or two plus a witness), no transactions, no TTL, no local secondary indexes.

Cassandra (docs read October 2026). You pick a consistency level per request. With replication factor RF = 3:

Write atRead atW + R > 3?Reads see the last acked write?Copies you can lose
ONEONEno (1+1)No, can miss it2 for either
QUORUM (2)QUORUM (2)yes (2+2)Yes, the sets overlap1 for either
QUORUM (2)ONEno (2+1)No1 write, 2 read
ONEALLyes (1+3)Yes2 write, 0 read
ALLONEyes (3+1)Yes0 write, 2 read

Quorum is a majority, floor(RF/2) + 1. Writes are always sent to all replicas; the level only decides how many replies the coordinator waits for. LOCAL_QUORUM limits the wait to the local datacenter, which the docs describe as enough for clients homed in one datacenter to read their own writes. The Dynamo paper's common setting was (N, R, W) = (3, 2, 2).

Overlap is not linearizability. Kleppmann warns against relying on it: sloppy quorums and read repair create edge cases where deleted data returns or the replica count drifts from what the quorum assumed. Cassandra itself separates the two: writes to a table are eventually consistent, and lightweight transactions (Paxos) give linearizable compare-and-set on a single partition. Use them for the few operations that need a single winner, such as claiming a username.

Primary and replica systems: PostgreSQL, MySQL, MongoDB, Redis

Most production systems are one writer plus replicas. The default is almost always: the primary is current, replicas are stale by some amount. What each product lets you change:

SystemDefaultTurn it upPrice
PostgreSQLStreaming replication is asynchronous. Docs: a small delay, typically under a second if the standby keeps up.Set synchronous_standby_names and synchronous_commit = remote_apply: commit waits until the standby has replayed it and made it visibleAt least one round trip added to each commit; the setting can be chosen per transaction
MySQL 8.4Asynchronous.Semi-sync: the source waits for a replica to receive and log the eventsAcknowledges receipt, not apply, so replica reads can still be stale. If no replica answers in time the source falls back to async
MongoDBWrite concern majority; read concern local. Secondary reads can lag but are never out of orderCausal session with majority read and write concern for all four session guarantees; linearizable read concern for single-document recencyLinearizable reads can be much slower; set maxTimeMS so the read errors instead of blocking when a majority is unavailable
RedisAsynchronous replicationWAIT n timeout blocks until n replicas acknowledgeLowers loss risk, but Redis states WAIT does not make it a strongly consistent store: acknowledged writes can still be lost at failover

Details worth saying aloud:

What strength costs, and buying it back with bounded staleness

Strength is paid in two currencies. Latency: a write or a fresh read must wait for coordination, and across regions that is a speed-of-light round trip. Availability: Jepsen classes linearizable, sequential and strict serializable as unable to be totally or sticky available, so some or all nodes cannot make progress during a partition.

Abadi's PACELC states it without the CAP slogans: if there is a partition (P), trade availability against consistency (A and C); else (E), trade latency against consistency (L and C). The "else" half is the one that matters daily: any replicated system faces it when nothing is broken, and over a wide-area network there is no way around it. The paper puts the default Dynamo, Cassandra and Riak in the PA/EL group. Kleppmann argues that CAP labels are too coarse, since many systems are neither linearizable nor CAP-available, and says to state the concrete guarantee and failure behaviour. In an interview, say "reads go through the leader; during a partition the minority side rejects them; each fresh read costs a cross-region round trip".

One data point on cost: in the Spanner paper's 2012 microbenchmark (OSDI 2012, Table 3; one replica), commit wait was about 5 ms, Paxos about 9 ms, and a 4 KB write took about 14 ms in total, while read-only transactions and snapshot reads took about 1.3 to 1.4 ms. Clients and servers sat less than 1 ms apart and reads came from memory, so these show software overhead, not cross-region cost; treat them as a 2012 data point, not a current benchmark.

Bounded staleness buys the latency back.

SystemStale optionDetail from the docs
SpannerStrong (default), bounded staleness, exact stalenessStale reads help most at long staleness; the timestamp-bounds page suggests a minimum of 10 seconds (the reads page calls 15 seconds a reasonable value)
CockroachDBExact staleness via follower_read_timestamp(); bounded via with_max_staleness or with_min_timestampExact reads should be at least about 4.2 s in the past; bounded reads must be a single statement on one row and keep serving even when the leaseholder is unreachable, with answers growing older
CockroachDBStrong follower reads on GLOBAL tablesFast reads from every region, slower writes
Postgres, MySQLNo built-in bounded-staleness readYou measure replica lag and enforce the bound in your router

How to state a bound. A usable sentence has a number, a percentile and a fallback: "Replica reads may be at most 5 seconds behind (an assumed figure) at p99; the router skips any replica past that lag, and reads then go to the primary." Anything less, such as "mostly fresh", cannot be tested or alerted on.

Client-side techniques and what they cost

When the database default is "replica reads may be stale", the application restores a guarantee for the users who notice. Three techniques, from blunt to precise.

TechniqueGivesHowCost
Primary after writeRead your writesFor N seconds after a user writes, send that user's reads to the primaryExtra primary load; N is a tuned number (choose it longer than normal replica lag); a lag spike beyond N leaves a gap
Version tokenRead your writes, causal orderThe write returns a log position or timestamp; the next read carries it; a replica answers only if it has reached it, otherwise it waits briefly or the read goes to the primaryThe token travels with every request; replicas must expose their position
Sticky routingMonotonic readsPin a session to one replicaUneven load. It does not guarantee the replica has your write. If the replica dies, the session lands on one that may be further behind

Real systems implement the token idea. MongoDB causal sessions carry operation and cluster time between operations. CockroachDB's with_min_timestamp exists, in its docs' words, to enforce causality between an operation and the reads that depend on it. Jepsen notes read your writes is sticky available only if the client never changes servers, which is the sticky routing trade in one line.

Two details separate good answers from adequate ones.

Mapping product requirements to the weakest safe model

The rule: pick the weakest model whose violation your users cannot see, or do not mind. Ask three questions out loud for each piece of data: who sees it, what do they do with it, and what does a stale value cost?

FeatureWeakest safe modelReasoning to sayMechanism
Profile editRead your writes for the editor; eventual for everyone elseOnly the editor can tell; others can lag secondsPrimary after write or a token
Feed / timelineEventual with a bound, plus monotonic readsLate posts are invisible; posts that appear then vanish are notReplicas with a pinned session
InventoryLinearizable on the decrement; eventual on the "3 left" badgeA wrong badge is harmless; selling the last item twice costs moneyConditional update on the primary
Payments / ledgerSerializable transactions on the primary; strict serializable if real-time order must hold across regionsA stale balance can approve a second spendPrimary reads, idempotency keys
Chat read receiptsCausal order within a conversation, receipts that only move forwardA reply before its question confuses people; a receipt must never un-readPer-conversation ordering, max-merge receipts
LeaderboardEventual, with a stated boundNobody can tell it is five seconds oldAny replica or cache; final results from the record

The inventory row shows the habit worth practising: split one feature into its stale-safe part and its strict part. Spending strength only on the buy step keeps the strong path small, cheap and fast, and leaves the rest of the system free to be replicated, cached and available.

If an interviewer pushes ("why not make everything strong?"), the answer has three parts: latency on every read, lost availability during partitions, and wasted money on data nobody can see is stale.

Interview: common wrong statements and the right answers

Wrong statementWhy it is wrongSay instead
"Eventual consistency means the data may be wrong."It means copies converge once writes stop. In between, reads can be old. Conflicting writes need a resolution rule, which is a separate issue"Reads may be up to N seconds old, and replicas converge."
"Strong consistency means ACID."ACID describes transactions. Linearizability is a recency and ordering guarantee on replicated copies"ACID for the transaction, linearizable reads for freshness."
"SQL is strongly consistent, NoSQL is eventual."DynamoDB offers ConsistentRead, MongoDB has read concerns, and a PostgreSQL async replica is stale"It depends on the read path and settings."
"Quorum reads and writes are linearizable."Overlap shows a recent write, not a real-time order; sloppy quorums and read repair break the assumption"Quorum gives fresh reads in normal operation; use lightweight transactions for compare and set."
"Reading the leader is always fresh."A former primary cut off by a partition can still answer, which is why MongoDB has a linearizable read concern"Fresh reads need confirmation, not just a role."
"Synchronous replication means replicas are always fresh."MySQL semi-sync acknowledges receipt, and PostgreSQL's default on waits for the standby's flush, not apply"Use remote_apply, or a token."
"This database is CP / AP."Kleppmann: CAP's terms are narrow, and many systems are neither. Settings change the answer"Name the guarantee and what happens in a partition."
"Redis WAIT makes it consistent."Redis says WAIT does not make Redis a strongly consistent store; a write replicated to several replicas can still be lost"WAIT reduces the loss window."

The four-part answer for any piece of data:

  1. The guarantee it needs (linearizable, read your writes, causal, eventual with a bound).
  2. The mechanism that provides it (primary read, quorum, token, remote_apply, lightweight transaction).
  3. The price, in latency and in availability during a partition.
  4. The staleness you accept elsewhere, as a number, so the strong path stays small.

Likely follow-ups: "What happens during a partition?" (the minority side of a strong system rejects; an eventual one answers with older data), "How do you detect staleness?" (measure replica lag and enforce a bound), "What would you change at ten times the traffic?" (more replicas make staleness a bigger share of reads, so the token and fallback matter more).

How to say it in the interview

"Which consistency, for which data? The balance and the last item in stock need a read that cannot be stale, so they go to the primary or a linearizable path. The profile page needs read your writes for the editor and seconds of staleness for everyone else. The feed and the leaderboard can be eventual with a stated bound. I'd pay for strength only where an old read costs money."

Checklist

Practice this interactively
Share this lessonWhatsAppLinkedInX

Related lessons