← Back to the section

People often say that Cassandra "trades consistency for availability". That is a half-truth: consistency in Cassandra is tunable, and it is tuned per request. How replication is laid out over the ring was covered in the article on architecture; this one is about the guarantees that grow out of it.

RF = 3: three copies of the row; the request picks how many answer write v2 read R1 v1 R2 v1 R3 v1 R1v2R2v2 R1v2 1. write at QUORUM: wait for 2 acks out of 3, R3 lags 2. read at QUORUM: ask R2 and R3 3. R + W = 2 + 2 = 4 > 3 — R2 is in both sets, answer v2 1. write at ONE: one ack from R1 is enough 2. read at ONE: ask a single replica, and it is R3 3. R + W = 1 + 1 = 2 < 3 — no overlap, answer v1

A quorum is the arithmetic of overlapping sets. On top the write waited for two acknowledgements, at the bottom the read asked two replicas: 2 + 2 is more than the three copies, so at least one replica that answered is bound to hold the fresh version. With ONE on both operations the overlap is not guaranteed — the read can land exactly on the copy the write has not reached yet.

Replication factor: how many copies

Replication factor (RF) is the number of copies of each partition in the cluster. RF = 3 means the data sits on three different nodes. This is the foundation: the more copies, the higher the durability and availability, but the more expensive the write (it has to be spread across more nodes).

RF is set on the keyspace together with the replication strategy. Production systems use NetworkTopologyStrategy, which places copies with datacenters in mind — three copies in each of two datacenters, for example.

Consistency level: how many replicas answer

RF is how many copies exist. The consistency level (CL) is how many of them must answer for the operation to count as successful. And it is set per request, separately for reads and for writes. The main levels:

LevelWhat it requiresWhat it means
ONEan answer from one replicafast, but the read may lag
QUORUMan answer from a majority of replicas (RF/2 + 1)a balance of speed and freshness
LOCAL_QUORUMa majority of replicas in the local datacentera quorum without crossing to another datacenter
ALLan answer from every replicamaximum consistency, minimum availability

ONE is fast and available, but a read from a replica that has not received the update yet returns stale data. ALL guarantees freshness, but one replica going down is enough for the operation to fail. The quorum sits in between.

The main rule: R + W > RF

Strict (immediate) consistency in Cassandra comes not from the levels on their own, but from how they combine across reads and writes. The rule is simple:

If the number of replicas acknowledging the write (W) plus the number of replicas answering the read (R) is greater than the replication factor, the read is guaranteed to see the last write.

Why: if R + W > RF, the set of "who acknowledged the write" and the set of "who was asked during the read" are bound to overlap on at least one replica — so the read touches a fresh copy. The classic recipe for a strict read at RF = 3 is to write and read at QUORUM (2 + 2 = 4 > 3). If both the write and the read go at ONE (1 + 1 = 2 < 3), the overlap is not guaranteed and you get eventual consistency: the data converges, just not instantly.

The main trap: treating Cassandra as "always inconsistent". At QUORUM/QUORUM it does give a strict read — the balance of speed and freshness is simply chosen per request: a quorum for an account balance, ONE is enough for an activity feed.

In the driver the level is attached to the statement itself (DefaultConsistencyLevel from com.datastax.oss.driver.api.core):

session.execute(insert.setConsistencyLevel(DefaultConsistencyLevel.QUORUM));
session.execute(select.setConsistencyLevel(DefaultConsistencyLevel.QUORUM));

The same arithmetic is easy to check by hand: the write updates the first W replicas out of three, the read asks the last R of them (the worst case) and takes the freshest answer:

live example

import java.util.Arrays;

public class QuorumDemo {
    static final int RF = 3;

    static String readAfterWrite(int w, int r) {
        String[] replicas = new String[RF];
        Arrays.fill(replicas, "stale");
        for (int i = 0; i < w; i++) {
            replicas[i] = "fresh";
        }
        String seen = "stale";
        for (int i = RF - r; i < RF; i++) {
            if (replicas[i].equals("fresh")) {
                seen = "fresh";
            }
        }
        return seen;
    }

    public static void main(String[] args) {
        int[][] levels = {{2, 2}, {1, 1}, {1, 3}};
        for (int[] level : levels) {
            int w = level[0];
            int r = level[1];
            System.out.println("W=" + w + " R=" + r + ": R+W=" + (w + r)
                    + (w + r > RF ? " > " : " <= ") + RF
                    + " -> the read sees " + readAfterWrite(w, r));
        }
    }
}
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →

How replicas catch up with each other

Since replicas drift apart temporarily at ONE, Cassandra keeps synchronising them through three mechanisms:

  • hinted handoff — if a replica is unavailable at the moment of the write, the coordinator stores a hint and replays the missed writes once that replica is back. This is how short node outages are survived: hints accumulate while the node has been down no longer than max_hint_window (three hours by default);
  • read repair — when reading from several replicas, the coordinator notices the discrepancies and updates the lagging copies with the freshest version on the fly;
  • anti-entropy repair — a scheduled background comparison of replicas (started by an operator) that finds and fixes the discrepancies accumulated during long outages. Regular repair is a mandatory part of running Cassandra.

Together they are what makes consistency "eventual": copies that drifted apart do converge sooner or later, and after a long node outage only repair brings them back together.

Lightweight transactions and their price

What about operations of the "do it only if it has not been done yet" kind — registering a user with a unique login, say? For those there are lightweight transactions (LWT) — conditional operations such as INSERT ... IF NOT EXISTS or UPDATE ... IF column = ?. Under the hood they use the Paxos consensus protocol to provide linearizability within a single partition.

That comes at a price: an LWT needs several rounds between replicas and is therefore several times slower than an ordinary write. The rule is simple: LWT is for rare operations where nothing but a strict compare-and-set will do (uniqueness, protection against a race), not for the bulk stream. If you need LWT at every step, Cassandra is probably the wrong database for the job.

What Cassandra does not guarantee

So that you do not expect from the database what is not there:

  • no transactions across partitions. You cannot change data in different partitions (let alone different tables) atomically. A BATCH looks like a transaction but is not one. Within a single partition it really is applied as a whole. Across partitions Cassandra only guarantees that it will apply every operation of the batch sooner or later: for that it first writes the batch into a system log and retries until it goes through. But there is no simultaneity — a reader may well catch a state where half of the changes are visible and half are not.
  • no rollback and no isolation like in SQL. The model is built for independent writes by key, not for complex transactional scenarios.
  • a delete is a write (a tombstone), which is why mass "delete and insert" is an antipattern.

None of this is a shortcoming — it is the direct price of write scale and constant availability. If that price is unacceptable, the task needs PostgreSQL or MongoDB rather than Cassandra.

In short

  • Replication factor (RF) is how many copies of a partition exist; the consistency level (CL) is how many replicas must answer, set per request and separately for reads and writes.
  • A strict read comes from the R + W > RF rule (the classic being QUORUM for both write and read at RF = 3); ONE/ONE gives eventual consistency. Cassandra is not "inconsistent", it is tunable.
  • Replicas are brought back together by hinted handoff, read repair and scheduled anti-entropy repair (the last one is mandatory in operation).
  • LWT (Paxos) gives a strict compare-and-set within a partition, but it is expensive — only for rare operations such as uniqueness.
  • There are no cross-partition transactions, no rollback or isolation like in SQL, and a delete is a tombstone. That is the price of scale.