Guide · System design
Cassandra Consistency Explained: Levels, Quorums and Repair
19 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- Cassandra consistency is chosen per query: the consistency level says how many replicas (copies of a row on different machines) must reply before a read or write is done.
- The coordinator, the node that receives your request, sends every write to all replicas it believes are up. The level only sets how many confirmations it waits for.
- ONE waits for a single reply, so it is fast and survives failures, but a read can return old data. ALL waits for every replica, so one dead node blocks the request.
- When write replies plus read replies add up to more than the replication factor (the number of copies), every read overlaps the last acknowledged write. QUORUM on both sides, a majority each, meets that rule.
- Beginners can stop after the strong-consistency section. Engineers then get the silent LOCAL_ONE default in the Python driver, the library an app uses to talk to Cassandra. They also get the 3-hour hint window that leaves replicas stale, writes that time out yet still land, and multi-region traps.
What goes wrong when a Cassandra read returns old data?
A Cassandra read can return old data when it asks too few copies of the row, and the copy it asks missed a recent change. Nothing crashed and no error appears. The query was set up to trust a single copy, and that setting is what Cassandra consistency is about.
Picture a common setup. A farm-sensor business puts soil-moisture sensors in fields across a district in Tamil Nadu. Each sensor sends a reading every 10 minutes. The setup is made up, but the failure is real.
Each field also has a dry threshold: the moisture level below which its pump controller switches the pump on. Farmers change it from a phone app. Everything lives in Apache Cassandra, a database that keeps several copies of each row on different machines.
One Tuesday, the team takes one of its six nodes down to replace a disk. The job runs long, and the node stays down for five hours. Four hours in, a coconut farmer lowers his threshold from 35 percent to 25 percent, because rain is coming.
The change is saved on the two copies that are up. When the node returns, its copy still says 35 percent. That evening the pump controller's read happens to land on that node. It runs the pump for three extra hours, burning about Rs 1,800 of diesel.
Every query in the app used the Python driver's default level, LOCAL_ONE. It asks one copy in the local data centre (the building or cloud region where a group of nodes lives) and trusts it. This guide explains that setting, the other levels, and why the stale copy was never fixed on its own.
Which words do you need before learning Cassandra consistency?
You need five words: replica, replication factor, coordinator, consistency level and timestamp. Each comes with an example from the farm-sensor setup.
Replica. A replica is one copy of a piece of data, stored on one node, which is one machine in the cluster. The coconut field's settings row lives on three nodes, so it has three replicas.
Replication factor. The replication factor, or RF, is how many replicas each row has. The team uses RF 3. With six nodes and data spread evenly, each node holds about half of all the data.
Coordinator. The coordinator is whichever node receives your request. It finds the replicas for that row, forwards the request, collects their replies and answers your app. Any node can coordinate, so there is no single boss.
Consistency level. The consistency level is how many replica replies the coordinator must collect before it answers you. You pick it for each query, for example ONE, QUORUM or ALL.
Timestamp. Every write carries a timestamp, a time in microseconds. When two replicas hold different values for the same column of a row, the newer timestamp wins. Cassandra's docs call this rule last write wins.
What is Cassandra consistency, and how is it tuned?
Cassandra consistency is tunable: for every read and write, you choose how many replicas must reply before the request succeeds. Fewer replies mean faster requests that survive more failures. More replies mean fresher, safer answers.
A single-server database has one copy, so every read sees the latest write. Cassandra was first built at Facebook for inbox search, to run on hundreds of machines across data centres. Its docs describe a per-operation trade between consistency and availability.
Think of a boxing match with a panel of three judges. Each judge keeps a scorecard. The announcer can read out a result after one card, after two, or only after all three are in.
One card is fastest, but that judge may have looked away during a round. Two cards give a majority, so one odd card cannot decide the result. Waiting for all three is safest, but if one judge's pen runs dry, nobody announces anything.
The coordinator is the announcer, the replicas are the judges, and the consistency level is how many cards it waits for. The idea comes from Amazon's Dynamo paper, which called the counts R (replies for a read) and W (replies for a write).
Where the picture breaks: judges score the same fight at once, but replicas can be hours apart. A Cassandra replica can come back from an outage still holding an old card, and nobody tells it.
What happens during a Cassandra write at a given consistency level?
On a write, the coordinator sends the change to every replica, then waits only for as many acknowledgements as the level asks for. The level never limits where the data goes. It limits how long you wait.
This surprises many beginners. Cassandra's docs say writes are always sent to all replicas, regardless of consistency level. A write at ONE still reaches all three replicas that are up. Your app simply hears back after the first one confirms.
Here is the coconut farmer's change saved at QUORUM. Follow the arrows from the phone app to the reply. Notice that the coordinator answers after two replicas, not three.
A QUORUM write with three replicas
Drag to exploreThe change goes to all three replicas. The app hears back once two have saved it, and the third still gets it.
Diagram connections:
- Farmer saves 25 percent to Send to all 3 replicas (Write at QUORUM)
- Send to all 3 replicas to A and B confirm the save (2 of 3 needed)
- A and B confirm the save to Reply: write succeeded (Quorum reached)
- Send to all 3 replicas to C confirms a bit later (Still delivered)
- Farmer saves 25 percent: Phone app → Coordinator
- Send to all 3 replicas: Coordinator → Replicas A, B, C
- A and B confirm the save: Replicas A, B → Coordinator
- Reply: write succeeded: Coordinator → Phone app
- C confirms a bit later: Replica C → Coordinator
What if a replica is down? The coordinator checks first. If it believes too few replicas are alive to meet the level, it refuses at once with an unavailable error, without sending the write anywhere.
If one replica is down but enough are alive, the write goes ahead. For the missing replica, the coordinator stores a hint on its own disk: the missed change, kept for later, within a time limit covered below. When the node returns, the coordinator replays it. This is called hinted handoff.
What happens during a Cassandra read, and how does read repair work?
On a read, the coordinator asks only as many replicas as the level needs and returns the value with the newest timestamp. If it asked more than one replica and their answers differ, it fixes the out-of-date ones before replying. That fix is called read repair.
Go back to the bad evening. The controller read at LOCAL_ONE, and replica C answered. C held 35 percent, so the controller got 35 percent. No other replica was asked, so nothing noticed the mistake.
Now the controller reads at QUORUM. The coordinator asks two replicas, say A and C. A says 25 percent, written at 2:10 pm, and C says 35 percent, from last week. The newer timestamp wins, so the controller gets 25 percent.
Because the answers differed, the coordinator also writes 25 percent back to C, and the read waits for that repair. It happens at levels that ask several replicas, such as TWO, THREE, QUORUM and LOCAL_QUORUM. It never happens at ONE or LOCAL_ONE.
Read repair only fixes the rows a query actually reads. Cassandra's docs list it, with hinted handoff, as a best-effort technique. Neither one replaces full repair, which comes later in this guide. Cassandra 4.0 also removed an older background mode that repaired a random share of reads.
One more detail matters for speed. If a replica is slow, the coordinator may send a spare request to another replica, called a speculative retry. The default table setting, 99p, fires when a replica is slower than the table's 99th-percentile read time, meaning slower than 99 percent of its reads.
What do ONE, QUORUM and ALL mean in Cassandra?
ONE means one replica must answer, QUORUM means a majority of all replicas must answer, and ALL means every replica must answer. A majority is half the replicas, rounded down, plus one, so RF 3 gives a quorum of 2.
Cassandra 5.0.9, the latest stable release as of 19 October 2026, has a few more levels. Some are for clusters spread over several data centres, the separate buildings or cloud regions where nodes live. The table assumes RF 3 per data centre.
| Level | Replies needed | Replicas that can be down | Good use in the farm setup |
|---|---|---|---|
| ANY | 1 replica, or a hint stored by the coordinator (writes only) | All 3 | Almost never; the data may exist only as a hint |
| ONE | 1 replica | 2 | Saving soil readings |
| TWO | 2 replicas | 1 | Rare; with RF 3, QUORUM says the same more clearly |
| THREE | 3 replicas | 0 | Rare; with RF 3 it behaves like ALL |
| QUORUM | Majority of all replicas: 2 of 3 | 1 | Thresholds and pump schedules |
| ALL | All 3 replicas | 0 | Almost never; one restart blocks it |
| LOCAL_ONE | 1 replica in the coordinator's data centre | 2 local | Readings, once there are two data centres |
| LOCAL_QUORUM | Majority in the coordinator's data centre: 2 of 3 | 1 local | Settings, once there are two data centres |
| EACH_QUORUM | Majority in every data centre | 1 in each | Changes that every region must see at once |
Notice the trade in the third column. The more replies a level needs, the fewer failures it can survive. ONE keeps working with two of the row's replicas down. ALL fails the moment one is missing.
Speed follows the same pattern. At ONE, a write waits for the first replica to confirm, and a read waits for the one replica the coordinator asked. At ALL, both wait for the slowest replica, so ALL is usually the slowest level.
Two more levels, SERIAL and LOCAL_SERIAL, belong to lightweight transactions, covered later. Conditional writes use them for their Paxos step, a vote among replicas, and a read at SERIAL also runs Paxos. You cannot use them for ordinary writes.
When does Cassandra give strong consistency?
Cassandra gives strong reads when the write replies plus the read replies add up to more than the replication factor. In short, W plus R is greater than RF. Then every group of replicas you read shares at least one replica with the last acknowledged write.
Count it out for the farm setup, where RF is 3. A QUORUM write is confirmed by at least 2 replicas. A QUORUM read asks 2 replicas. Two plus two is four, more than three, so the two groups must share a replica.
Pause and predict. With RF 3, the write was confirmed by some set of replicas, and the read asks some set. Out of all possible pairs of sets, how many miss the write for ONE and ONE? And for QUORUM and QUORUM?
This short Python program counts every case instead of trusting the rule. It assumes the worst: only the replicas that confirmed the write have it so far. Run it with python3; it needs nothing installed.
from itertools import combinations
def quorum(rf):
return rf // 2 + 1
def missed_reads(rf, w, r):
replicas = range(rf)
missed = total = 0
for saved in combinations(replicas, w):
for asked in combinations(replicas, r):
total += 1
if not set(saved) & set(asked):
missed += 1
return missed, total
RF = 3
choices = [
("ONE write, ONE read", 1, 1),
("QUORUM write, ONE read", quorum(RF), 1),
("QUORUM write, QUORUM read", quorum(RF), quorum(RF)),
]
for name, w, r in choices:
missed, total = missed_reads(RF, w, r)
print(f"{name}: {missed} of {total} pairs miss the write")Expected output, checked on Python 3.13:
ONE write, ONE read: 6 of 9 pairs miss the write
QUORUM write, ONE read: 3 of 9 pairs miss the write
QUORUM write, QUORUM read: 0 of 9 pairs miss the writeWhat just happened? ONE and ONE gives 2, not more than 3, so 6 of the 9 pairs read a replica that never got the change. That is the coconut farm's evening. QUORUM writes with ONE reads still miss 3 of 9.
Only QUORUM on both sides gives 0 of 9. The same brute-force check, run for every RF from 1 to 7, agreed with W plus R greater than RF in every case.
ONE writes with ALL reads also overlap, but every read then fails if any replica is down. QUORUM on both sides survives one dead replica on either path.
There is a fine print. The docs say acknowledged writes are generally visible to later reads that overlap. The later sections show the exceptions: writes that time out, clocks that disagree, and two apps updating the same row.
How do you set the Cassandra consistency level in code?
You set it on each statement in the driver, or once as a default for the session. In cqlsh, the command-line shell, the CONSISTENCY command sets the level for the commands that follow.
Check your defaults before trusting them. In the Python driver 3.30.1, the latest as of 19 October 2026, a query with no level uses LOCAL_ONE. That default changed from ONE in driver 3.0. The driver switches its default to LOCAL_QUORUM only when it detects a hosted cloud database service.
cqlsh has its own default, ONE, so the shell and the app can behave differently. Neither default is wrong. The problem is a default nobody chose for data that must be right.
Here is the team's fix. Readings keep a fast level, and settings get QUORUM on both write and read. A keyspace is a named group of tables. The node address and the keyspace name, farm, are placeholders.
from cassandra import ConsistencyLevel
from cassandra.cluster import Cluster
from cassandra.query import SimpleStatement
session = Cluster(["10.0.0.11"]).connect("farm")
save_reading = SimpleStatement(
"INSERT INTO readings (sensor_id, read_at, moisture) VALUES (%s, %s, %s)",
consistency_level=ConsistencyLevel.ONE)
save_threshold = SimpleStatement(
"UPDATE field_settings SET dry_threshold = %s WHERE field_id = %s",
consistency_level=ConsistencyLevel.QUORUM)
get_threshold = SimpleStatement(
"SELECT dry_threshold FROM field_settings WHERE field_id = %s",
consistency_level=ConsistencyLevel.QUORUM)
row = session.execute(get_threshold, ("F-42",)).one()The consistency_level argument is the part that matters. Each SimpleStatement carries its own level, so one session mixes fast and careful queries. If the threshold read cannot reach two replicas, it raises an error instead of returning stale data.
The team also changed the pump controller to keep its last good threshold when a read fails. A failed read is safer than a wrong one, but the farm still needs a sensible fallback.
How to check the levels on your own cluster
See the shell's level
Open cqlsh and type CONSISTENCY with no argument. Expect: Current consistency level is ONE.
Raise it for a test
Type CONSISTENCY QUORUM. Expect: Consistency level set to QUORUM. Your next SELECT now needs two replies.
Find the row's replicas
Run nodetool getendpoints farm field_settings F-42; nodetool is Cassandra's admin command-line tool. It prints the address of each replica for that key, one per line, so three lines for RF 3.
Force an unavailable error
Stop one of those three nodes and wait until nodetool status shows it as DN, meaning down. Set CONSISTENCY ALL and rerun the SELECT. Expect an error containing Cannot achieve consistency level ALL.
Watch which nodes answer
Type TRACING ON and repeat a query. cqlsh prints the query's events in a table whose source column names the node that handled each step.
How do Cassandra replicas catch up after a node goes down?
Replicas catch up in three ways. Hinted handoff replays missed writes, read repair fixes rows that get read, and anti-entropy repair compares whole replicas and copies the differences. Only the last one covers everything.
Hints have a time limit, set by max_hint_window. In Cassandra 5.0.9 it defaults to 3 hours: after a node has been down that long, coordinators stop creating new hints for it. The team's node was down for five hours, and the farmer's change came in hour four.
So no hint was ever written for it. Cassandra's hints docs warn that such a replica stays out of sync until read repair or anti-entropy repair carries the change over. That was the root cause of the Rs 1,800 evening.
Anti-entropy repair is the thorough option. Each replica builds a Merkle tree, a tree of hashes where each parent summarises its children. Comparing trees from the top quickly finds which ranges of rows differ, so only those get copied.
The diagram shows what happens when a replica returns. Start at the returning node and follow both paths. Notice that only scheduled repair reaches rows nobody reads.
How replica C catches up
Drag to exploreHints are quick but time-limited. Read repair only fixes rows that are read. Scheduled repair is slow but complete.
Diagram connections:
- Replica C comes back to Down under 3 hours?
- Down under 3 hours? to Replay stored hints (Yes)
- Down under 3 hours? to Some rows stay stale (No)
- Some rows stay stale to Read repair (If read above ONE)
- Some rows stay stale to Scheduled full repair (Everything else)
- Replay stored hints to Scheduled full repair (Still run on schedule)
- Replica C comes back: Missed writes while it was down
- Down under 3 hours?: Default max_hint_window in 5.0.9
- Replay stored hints: Coordinators → Replica C
- Some rows stay stale: No hints made after the window
- Read repair: Only rows read from 2 or more replicas
- Scheduled full repair: Merkle trees find every gap
The docs are direct on this point: hints, like read repair, are best effort and not an alternative to full repair. So the team wrote four rules:
Repair after long outages
If a node was down longer than the hint window, run a repair on it as soon as it is back. Until then, its copy can be stale.
Repair on a schedule
Repair every node at least every 7 days. The gc grace period, how long a delete marker is kept, is 10 days by default. Repair inside it, or deleted data could reappear.
Check the hint window
Run nodetool getmaxhintwindow on a node. With the default, expect: Current max hint window: 10800000 ms, which is 3 hours.
Watch the hint backlog
Run nodetool listpendinghints. For each other node, it lists how many hint files this node holds and their oldest and newest times. Hints piling up for one node mean it is down or struggling.
What does Cassandra consistency not promise?
A consistency level promises how many replicas replied. It does not promise that a failed write left no trace, that clocks agree, or that two apps updating one row both win.
A write that times out may still land
A timeout means too few replicas confirmed within the write timeout, 2 seconds by default in 5.0.9. It is not a rollback. Some replicas may already hold the change, and the hints docs say coordinators also store hints for timed-out writes.
The Python driver's WriteTimeout error carries received_responses, the number of replicas that did confirm. Its default retry policy does not retry a normal timed-out write. Make retries safe to repeat, for example by writing the same full value again.
One guarantee helps here. With the default blocking read repair, the docs promise monotonic quorum reads: a second QUORUM read never returns something older than the first. That holds even after a failed write reached only a minority.
Clocks decide which write wins
Last write wins compares timestamps, so a machine with a fast clock can make an old write look new. The Python driver 3.30.1 sets the timestamp on the app side by default. Without that, the coordinator uses its own clock.
So run time sync, such as NTP, on the app servers as well as the Cassandra nodes. The Cassandra docs ask for a sync process like NTP because correctness depends on these clocks.
Two writers can still race
Strong reads are not safe concurrent updates. If two apps both read 30 percent and each writes a new value, last write wins quietly drops one change. Quorum overlap cannot stop that.
For check-then-set, Cassandra offers lightweight transactions: a write with an IF condition, such as update only if the value is still 30. It runs Paxos, a voting protocol among replicas. Its serial phase uses SERIAL or LOCAL_SERIAL.
The CQL docs warn that IF conditions carry a non-negligible performance cost, so use them sparingly. Use one only where a lost update would really hurt, such as two people booking the same pump slot.
How do you choose a consistency level across multiple data centres?
With several data centres, use LOCAL_QUORUM for important data and LOCAL_ONE for bulk data. Local levels wait only for replicas in the coordinator's own data centre, so no request waits on a far-away link.
Suppose the team adds a second data centre in Hyderabad, also with RF 3. Now each row has six replicas. Plain QUORUM counts all six, so it needs 4 replies, and at least one must come from the other city.
That cross-city wait slows every settings query. LOCAL_QUORUM needs just 2 of the 3 local replicas. The Hyderabad copies still receive every write, a little later. Cassandra's docs say all production clusters should use NetworkTopologyStrategy, which sets an RF per data centre.
The cost is a window where the other city lags. The docs call LOCAL_QUORUM a weaker guarantee: reads see the latest write from within the same data centre. A brute-force check confirms that a local write group and a remote read group can share no replica.
If a change must be visible to LOCAL_QUORUM reads in every region at once, write it at EACH_QUORUM. Every region must then confirm, so a region outage blocks those writes. Otherwise, keep each farm's apps on one data centre.
Here is the decision path the team now uses for new tables. Start at the top question and follow the answers to a level.
Picking a consistency level
Drag to exploreAsk how bad a stale answer is before asking how fast the query must be.
Diagram connections:
- Is a stale value harmful? to Use ONE or LOCAL_ONE (No)
- Is a stale value harmful? to Do two writers race? (Yes)
- Do two writers race? to Write with an IF condition (Yes)
- Do two writers race? to More than one data centre? (No)
- More than one data centre? to LOCAL_QUORUM both ways (Yes)
- More than one data centre? to QUORUM both ways (No)
- Is a stale value harmful?: Example: an old pump threshold
- Use ONE or LOCAL_ONE: Soil readings, charts, logs
- Do two writers race?: Check-then-set on one row
- Write with an IF condition: Lightweight transaction, SERIAL
- More than one data centre?: For example, a second region
- LOCAL_QUORUM both ways: Majority in the local data centre
- QUORUM both ways: 2 of 3 replicas on read and write
The same path shows the mistakes the team now looks for in code review:
- Leaving the driver default in place for data that must be right, which is what hurt the coconut farm.
- Writing at QUORUM but reading at ONE, which breaks the overlap and still gives stale reads.
- Using ALL for safety, then finding every request fails during a routine node restart.
- Using plain QUORUM across data centres, which makes every request wait on the long link.
How do you explain Cassandra consistency in a system design interview?
Explain it as a dial you set per query, then prove the overlap rule with real numbers. Interviewers want to see you match the level to the cost of a wrong answer.
State the replication setup
Say the replication factor, for example RF 3, and how many data centres there are. Every later number depends on these two.
Explain the write path
Writes go to every replica that is up. The level only sets how many acknowledgements the coordinator waits for.
Explain the read path
The coordinator asks enough replicas, keeps the newest timestamp, and repairs stale replicas it notices whenever it asked more than one.
Apply W plus R greater than RF
Show that QUORUM writes and QUORUM reads share at least one replica, while ONE and ONE do not.
Split queries by risk
Put high-volume, low-risk data at ONE and costly-if-wrong data at QUORUM or LOCAL_QUORUM. Give one example of each.
Cover failures and catch-up
Mention unavailable and timeout errors, hinted handoff and its 3-hour default window, and scheduled repair.
Questions people ask
- Is Cassandra eventually consistent or strongly consistent?
- It depends on the levels you choose. With ONE for reads and writes, replicas can disagree for a while and converge later, which is eventual consistency. With QUORUM on both sides, reads overlap the latest acknowledged write. Neither setting stops two writers racing on one row; that needs a lightweight transaction.
- What is the difference between QUORUM and LOCAL_QUORUM in Cassandra?
- QUORUM needs a majority of all replicas across every data centre, while LOCAL_QUORUM needs a majority only in the coordinator's own data centre. With RF 3 in two data centres, QUORUM needs 4 of 6 replies. LOCAL_QUORUM needs 2 of the local 3, so it never waits on another region, but other regions can briefly lag.
- What is the default consistency level in Cassandra?
- The default comes from the client, not the server. The Python driver 3.30.1 uses LOCAL_ONE when a query sets no level, and cqlsh uses ONE. Other drivers may differ, so check your own driver's docs. Set the level on purpose for any query where a stale answer costs money or trust.
- Is a QUORUM write that times out lost?
- Not necessarily. A timeout only means too few replicas confirmed in time. A replica that saved the change keeps it, and coordinators store hints for timed-out writes. The change can then spread through hints, read repair or repair. Design retries so writing the same value twice is harmless.
- Why not use consistency level ALL for everything?
- ALL makes every request depend on every replica being healthy. A single node restart, disk swap or slow replica then blocks reads or writes for all its rows. QUORUM reads paired with QUORUM writes also overlap the latest acknowledged write, while still surviving one failed replica out of three.
- What is read repair in Cassandra?
- Read repair is Cassandra fixing stale replicas during a read. When the replicas asked for a row disagree, the coordinator writes the newest value back to the older ones before replying. The read waits for this repair, and it never runs at ONE or LOCAL_ONE. It only fixes rows that are read, so scheduled repair is still needed.
Sources and further reading
- Apache Cassandra 5.0 docs: Dynamo architecture, tunable consistency and consistency levels
- Apache Cassandra 5.0 docs: Hints and hinted handoff
- Apache Cassandra 5.0 docs: Read repair
- Apache Cassandra 5.0 docs: Repair
- Apache Cassandra 5.0 docs: cqlsh CONSISTENCY and SERIAL CONSISTENCY commands
- Apache Cassandra 5.0 docs: CQL data manipulation, IF conditions and timestamps
- Apache Cassandra 5.0.9 default cassandra.yaml (max_hint_window, request timeouts)
- Apache Cassandra downloads: current release versions
- Python driver for Apache Cassandra 3.29 docs: Getting started, setting consistency levels
- Cassandra: A Decentralized Structured Storage System (Lakshman and Malik, 2009)
- Dynamo: Amazon's Highly Available Key-value Store (DeCandia et al., 2007)