← Back to the section

The key to understanding Cassandra is its topology. PostgreSQL and MongoDB have a primary node that every write goes through. Cassandra has no such node: all cluster nodes are equal, and any of them accepts a write. That single fact is where both its strengths (write scalability, no single point of failure) and its limits come from.

any node accepts the write; the token decides where it lands 0 25 50 75 A B C D B CD D AB 1. partition key: order-42 2. hash of the key → token 4 3. range (0..25] — node B 4. copies around the ring: B → C → D 1. partition key: user-7 2. hash of the key → token 70 3. range (50..75] — node D 4. copies around the ring: D → A → B RF = 3: three copies on three ring nodes coordinator — the node that took the request vnodes: each node owns many tick marks

Nodes are tick marks on a ring of tokens. The partition key is hashed into a number, the number falls between two marks, and the range belongs to the nearest mark clockwise. Copies go to the next nodes around the ring — and after the last mark the circle closes back on the first one.

The ring and token partitioning

Cassandra nodes form a logical ring. The whole space of possible hash values is split into ranges, and each node is responsible for its own slice of the ring.

When you write a row, Cassandra takes its partition key, runs it through a hash function (the partitioner, usually Murmur3) and gets a number — the token. The token points at a spot on the ring, and therefore at the node responsible for that range. So the partition key deterministically decides where the data lives. This is consistent hashing: adding or removing a node redistributes only a small share of the data, not the whole ring.

You can check the layout by hand — nodes here are just tick marks, and the owner of a key is the first mark not smaller than the token:

live example

public class TokenRing {
    record Node(String name, long token) {}

    static long token(String key) {
        long hash = 0xcbf29ce484222325L;
        for (int i = 0; i < key.length(); i++) {
            hash = (hash ^ key.charAt(i)) * 0x100000001b3L;
        }
        return Math.floorMod(hash, 100);
    }

    public static void main(String[] args) {
        Node[] ring = {new Node("A", 0), new Node("B", 25),
                       new Node("C", 50), new Node("D", 75)};
        for (String key : new String[]{"order-42", "user-7", "cart-9"}) {
            long token = token(key);
            int owner = 0;
            while (owner < ring.length && ring[owner].token() < token) {
                owner++;
            }
            String copies = "";
            for (int i = 0; i < 3; i++) {
                copies += ring[(owner + i) % ring.length].name() + " ";
            }
            System.out.println(key + ": token " + token + " -> copies " + copies.trim());
        }
    }
}
Run

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

In practice each physical node is responsible not for one large slice but for many small ones — these are virtual nodes (vnodes). They even out the load: when a node fails, its data is picked up not by a single neighbour but by many nodes at once, so recovery is faster and more evenly spread.

Gossip: how nodes know about each other

If there is no leader node, who keeps the cluster map — who is alive, who is responsible for which ranges? Nobody, centrally. Nodes exchange state through the gossip protocol: once a second every node "gossips" with a few random neighbours, passing on what it knows about the state of everyone else. After a few rounds, news about a new or a failed node spreads across the whole cluster.

Any node that accepts a client request becomes the coordinator of that request: from the token it knows which nodes hold the partition, sends them the operation and collects the answers. Any node can be the coordinator — hence the absence of a single point of failure.

The write path: why writes are so fast

Cassandra's write speed is not magic, it follows from how the write path is built. Once the coordinator has delivered a write to a replica node, two things happen at the same time:

  1. the write is appended to the commit log — an on-disk journal, append-only (a sequential write, which is why it is fast); it exists for durability, and data is recovered from it after a crash;
  2. the write is put into the memtable — a structure in RAM.

At that point the write is done — nothing had to be found and overwritten in an on-disk data structure. One caveat about the journal: by default (commitlog_sync: periodic) it is flushed to disk not on every write but once every 10 seconds, and writes acknowledged inside that window are lost if the node loses power. What saves you is not the journal but replication: copies of the same data sit on other nodes. Hence the speed: no in-place updates. When the memtable fills up, it is flushed to disk as a whole into a new file — an SSTable (Sorted String Table). An SSTable is immutable: it is never edited again, only read.

An important consequence: updates and deletes are writes too, not edits. Update a row and a new version is written into a fresh SSTable. Delete a row and a special "deleted" marker is written, called a tombstone. Old versions and tombstones coexist in different SSTables for a while.

Compaction and tombstones

Since data is only ever appended, over time many SSTables pile up, with the same row present in different versions. So that reads do not slow down and disk space is not wasted, a background process called compaction merges several SSTables into one, keeping only the current version of each row and dropping the shadowed ones.

This is also where tombstones are finally applied: deleted data physically disappears only after compaction, and not immediately — a tombstone is kept for a fixed period (gc_grace_seconds, 10 days by default) so that the delete has time to reach every replica. Hence a classic trap: mass deletes create masses of tombstones, and they slow reads down (the engine has to skip past the markers) until compaction removes them. Cassandra dislikes being used as a queue with a constant "push and delete" cycle.

Reading in such a design is a merge: the engine assembles the row from the memtable and several SSTables, picking the freshest version. Helper structures speed this up (Bloom filters, partition indexes) so that SSTables which certainly do not hold the row are never opened.

Replication and multiple datacenters

Each partition is stored not on one node but on several — their number is set by the replication factor (RF). RF = 3 means three copies on three different ring nodes. This is the basis of both durability (a node dies, the data is on the others) and availability (any live replica can serve the request). For how RF and consistency levels add up to actual guarantees, see the article on consistency and replication.

A particular strength of Cassandra is multiple datacenters. With the right replication strategy (NetworkTopologyStrategy) copies are spread across several datacenters, and the cluster runs active-active: each datacenter accepts both reads and writes locally, and data propagates asynchronously between them. The application reads and writes to the nearest datacenter with low latency, and losing a whole datacenter does not stop the service.

In short

  • Cassandra has no leader node: nodes form a ring, any of them accepts a write (becoming the coordinator), and data is distributed by the token of the partition key (consistent hashing, vnodes).
  • Nodes learn the topology through gossip — exchanging state with random neighbours, without a central dispatcher.
  • The write path is commit log (durability) + memtable (RAM) → flush into an immutable SSTable. No in-place edits, which is why writes are fast; updates and deletes are new writes (a delete is a tombstone).
  • Compaction merges SSTables, keeping current versions and removing tombstones; mass deletes breed tombstones and slow reads down.
  • The replication factor sets the number of copies; NetworkTopologyStrategy spreads them across multiple datacenters in active-active mode — hence the geo-distribution and constant availability.