Inside Cassandra: ring, gossip, replicas, quorums; HBase

After this lesson you will trace a key from its hash to the nodes that hold its copies, follow a write and a read inside a Cassandra node, tell which consistency levels guarantee a fresh read, and say what HBase does differently.

The slide "Vs. SQL" (p. 459) prints four numbers, measured "On > 50 GB data". MySQL: writes 300 ms, reads 350 ms on average — about the same. Cassandra: writes 0.12 ms, reads 15 ms. Cassandra writes 125 times faster than it reads. What did it give up to make writing so cheap?

The second puzzle comes from the company that built Cassandra. In November 2010 Facebook chose HBase, not Cassandra, for its new Messages system: "We found Cassandra's eventual consistency model to be a difficult pattern to reconcile for our new Messages infrastructure" (Facebook Engineering). Yet HBase writes with the same machinery — a log, a table in memory, sorted files on disk. Where does the different consistency come from?

Keep both questions. Section 3 answers the first, section 5 the second.

Where a key lives: the hash ring

Take the paper of 12 Sep 2025: its farmers' sales, "large-scale, continuously increasing", go to Cassandra, to be queried "by market location or product name". Partition them by market location: every Milano sale lands in the partition 'Milano'. Eight machines hold them as equals: "NO MASTER", "NO SLAVE", "PEER TO PEER" (p. 440). So who answers p. 439, "How do you decide which server(s) a key-value resides on?"

Every node, alone, with the same arithmetic. The partitioner turns the key into a number, its token: "system will use MD5(key) to distribute data across nodes" (p. 457). Tokens lie on a circle; each node owns one token, and a key belongs to the first node clockwise from its own token. The replication factor N counts the copies, and the Simple Strategy places the other N − 1 by "Clockwise placement to the next node(s)" (p. 442). A client may send a request to any node, which becomes that request's coordinator (p. 443).

Why a circle, and not the hash modulo the number of nodes? With modulo 8, a ninth machine changes the remainder of almost every key: nearly all the data moves. On the ring a new node takes one arc from one neighbour. That is consistent hashing (background: the slides draw the ring without naming it).

The ring: who owns a key, and what moves when a node joins

Twelve market locations are hashed onto a ring of tokens 0–99, read clockwise from the top. Nodes join in the order A1, B1, A2, B2, A3, B3, A4, B4; a node's letter is its rack. Pick a key: the nodes holding its copies are filled, and the arc walks clockwise from the key to them. Keys that changed node at the last change of the ring are highlighted.

  1. Keep N = 1 and 3 nodes. Predict how many of the 12 keys change node when a fourth node joins. Then move "nodes in the ring" to 4, and compare with the readout "same change with hash mod n".
    What you should see

    Four keys — Varese, Como, Cremona, Mantova — and all four come from A1. B2 (token 81) lands on the arc A1 owned, from A2's token 56 round to A1's 6, and takes the part up to 81. With hash mod n, 11 of the 12 keys would change node. That is the insight of the widget: a joining node takes one arc from one neighbour, and nothing else moves.

  2. Go to 8 nodes, then back to 7. Which keys move now, and where to?
    What you should see

    Removing B4 (token 93) moves only its own key, Padova (87), to B4's clockwise neighbour A1. Leaving is the same move backwards: the next node clockwise inherits the arc.

  3. Set 8 nodes, N = 3 and the key Varese. Which racks hold its three copies? Predict what the Network Topology Strategy changes, then switch it on.
    What you should see

    Varese (token 62) belongs to B3; the Simple Strategy adds the next two nodes clockwise, B2 and B4 — all three in rack B, so one rack failure loses the key. With the toggle on, two racks must share three copies: the walk keeps B3 and the first repeat it meets, B2, then skips B4 and continues to A1, the first node of rack A. A rack failure now leaves at least one copy.

  4. Set 3 nodes and N = 3. How much of the data does each node hold? Why does p. 458 ask for ">= three servers"?
    What you should see

    Every node holds every key: three copies need three different machines. With more nodes, each holds about 3/n of the keys — the reason a ring scales out.

Two partitioners

The "partitioner smack-down" (p. 457) sets the two side by side (Table 1). Keeping keys in order lets the order-preserving one scan a range of keys, at the price of the "'scrabble' distribution": real keys are as uneven as Scrabble letters, and the node owning the M's gets far more rows than the one owning the X's. The hash is the default (background): Cassandra gives up range scans over partition keys and keeps order inside a partition instead, with the clustering key of lesson 21.

Table 1. The "partitioner smack-down" of p. 457, in the slide's words. CF is the slides' column family, a table.
Random (hash)Order preserving
token"system will use MD5(key) to distribute data across nodes""key distribution determined by token"; "can specify the token for this node to use"
row orderhash order"lexicographical ordering"
range of keysnot scannable"required for range queries": "scan over rows like cursor in index"
load"even distribution of keys from one CF across ranges/nodes""'scrabble' distribution": hot spots

Where the copies go

"data replication" (p. 456) needs a "configurable replication factor" and a "replica placement strategy". The slide strikes out the old, more telling names: rack unaware → Simple Strategy; rack aware → Old Network Topology Strategy (since removed, background); data center shard → Network Topology Strategy. The Simple Strategy serves a "Single datacenter" (p. 442) and ignores racks. The Network Topology Strategy serves "Multiple datacenters", "Supporting local queries": a replication factor per datacenter, and inside each one the walk skips nodes of a rack that already holds a copy (Fig. 1).

data center 1 data center 2 rack 1 rack 2 rack 1 rack 2 1 2 3 4 5 6 7 8 9 10 11 12 R1 R3 R2 R4

Simple Strategy, N = 4 (computed)

data center 1 data center 2 rack 1 rack 2 rack 1 rack 2 1 2 3 4 5 6 7 8 9 10 11 12 R1 R2 R3 R4

Network Topology Strategy, 2 + 2 (p. 444)

Fig. 1. The ring of p. 444 runs clockwise 1, 7, 2, 8, 3, 9, 4, 10, 5, 11, 6, 12, and the key's token falls just before node 2. The Simple Strategy takes the next four nodes, 2, 8, 3, 9: both copies of each data center share rack 1. The Network Topology Strategy, two copies per data center, skips 3 and 9 and gives the slide's R1–R4 on 2, 4, 8, 10 — one per rack.

A Cassandra script starts by creating the keyspace, as in the exercise session (p. 1553):

-- three copies of every row
CREATE KEYSPACE population
  WITH replication = {
    'class': 'SimpleStrategy',
    'replication_factor': 3};

Per datacenter, in the syntax of the Cassandra documentation (not on the slides), for Fig. 1:

-- two copies in each datacenter
CREATE KEYSPACE population
  WITH replication = {
    'class':
      'NetworkTopologyStrategy',
    'DC1': 2, 'DC2': 2};

The hash picks the first node clockwise; the strategy picks the other N − 1: the next nodes, or the next racks of each datacenter.

Nobody in charge: gossip and failure detection

Node B3 reboots at night. With no master, nobody keeps the list of who is alive: each node keeps its own and corrects it by talking to the others. Page 440 draws a rumour — "I'm online!" — travelling from node to node as "gossip".

The "Gossip protocol" (p. 441) runs in rounds. "Each node picks its discussants (up to 3)", however large the cluster, and each conversation is a three-message handshake (Fig. 2). Afterwards both know the union of what they knew, so views "converge": the slide's anti-entropy, entropy being the disorder of nodes that disagree. Few partners per round mean "Always a constant amount of network traffic (except for gossip storms)", when many nodes change state at once.

node X node Y 1. GossipDigestSynMessage 2. GossipDigestAckMessage 3. GossipDigestAck2Message here are the versions I know this is what you lack; send me what I lack here is what you asked for
Fig. 2. One round of gossip between two nodes, with the message names of p. 441. What each message carries — versions, then the missing parts — is background from Cassandra's implementation.

"Up to 3" discussants and "three messages for each round" are two different threes: partners per round, messages per conversation. And gossip carries node state — heartbeats, tokens, versions — never rows: writes go from the coordinator to the replicas (p. 445).

The membership list

"Cluster Membership" (p. 451): each node keeps one row per node — Address, Heartbeat Counter, Time (local) — and raises only its own counter. "Nodes periodically gossip their membership list"; "On receipt, the local membership list is updated": per address the larger counter wins, stamped with the receiver's own clock, since the clocks are "asynchronous". Fig. 3 replays the slide.

node 1 sends node 2 has node counter time 11012066 21010362 31009863 41011165 node counter time 11011864 21011064 31009058 41011165 gossip node 2's own clock reads 70

Before: node 1's list arrives

node 2 after node counter time 11012070 21011064 31009870 41011165 larger: copy it, stamp 70 own row is newer: keep larger: copy it, stamp 70 equal: keep, time too counters travel, times stay

After: node 2 at time 70

Fig. 3. The membership merge of p. 451, row by row. 10120 beats 10118 and 10098 beats 10090: both rows are copied and stamped with node 2's own time, 70. Node 2's own row, 10110, is newer than the 10103 it hears about. Row 4 is equal, so even its time stays 65.

From silence to suspicion

A silent node may be dead or only slow, and a fixed timeout errs one way or the other. The accrual failure detector of p. 452 "outputs a value (PHI) representing suspicion", "Apps set an appropriate threshold", and "PHI = 5 => 10-15 sec detection time". PHI, φ, grows with the silence, measured against the "Inter-arrival times for gossip messages":

  1. Let \(\Delta t = t_{now} - t_{last}\) be the silence and F the distribution of past gaps. A live node stays silent this long with the tail probability

    \[ P_{\text{later}}(\Delta t) = 1 - F(\Delta t) \]
  2. φ = 1 means a 10 % chance that a live node is this late; 2 means 1 %, 5 means 0.001 %:

    \[ \varphi = -\log_{10} P_{\text{later}}(\Delta t) \]
  3. Background: Cassandra takes the gaps as exponential with mean μ, so \(P_{\text{later}} = e^{-\Delta t / \mu}\) and

    \[ \varphi = \frac{\Delta t}{\mu \ln 10} \]
  4. With gossip about once a second (μ ≈ 1 s, background), φ = 5 after 5 ln 10 ≈ 11.5 s — inside the slide's 10–15 s. A slower network has a larger μ and a later alarm: the slide's "sensitive to actual inter-arrival time variations".

A write is an append; a read is a merge

A sale arrives for 'Milano'. "Writes" (p. 445): the client "sends write to one front-end node in Cassandra cluster (Coordinator)", "Which (via Partitioning function) sends it to all replica nodes responsible for key".

"Always writable: Hinted Handoff": "If any replica is down, the coordinator writes to all other replicas, and keeps the write until down replica comes back up" — the kept write is a hint — and "When all replicas are down, the Coordinator (front end) buffers writes (for up to an hour)". The slide adds "Atomicity for a given key (i.e., within ColumnFamily)": one write to one partition is applied whole, with no multi-key transactions. And "One ring per datacenter": the coordinator "can also send write to one replica per remote datacenter".

At the replica: commit log, memtable, SSTable

Writes "Need to be lock-free and fast (no reads or disk seeks)". At the replica (p. 446): "1. log it in disk commit log" — an append, so no seek, and the write survives a crash; "2. Make changes to appropriate memtables" — a memtable is an "In-memory representation of multiple key-value pairs", sorted by key. Then the replica acknowledges (Fig. 4).

client write Milano 1 · commit log (disk) 2 · add value to memtable Como 7 Lodi 9 Milano 41 memtable memory, sorted ack no read, no seek, no lock later: flush index file + Bloom filter SSTable 1 SSTable 2 SSTable 3 disk
Fig. 4. The "write op" of p. 454 with the steps of p. 446. The write costs one append to the commit log and one insert into the memtable; the flush to an SSTable happens later, once per full memtable, again as one sequential write.

"Later, when memtable is full or old, flush to disk": once, in key order, as an SSTable ("Sorted String Table – list of key value pairs, sorted by key"), with an index of "(key, position in data sstable) pairs" "And a Bloom filter". SSTables are never modified: newer versions of 'Milano' land in newer files, and compaction merges SSTables, keeping each key's newest version ("Merge key updates, etc.").

The Bloom filter: a cheap "no"

To skip the SSTables that cannot hold a key, each has a Bloom filter (p. 447), a "Compact way of representing a set of items": a bit array and k hash functions. "On insert, set all hashed bits." "On check-if-present, return true if all hashed bits set." One 0 proves absence; all 1s prove nothing — other keys may have set them. Hence "Some probability of false positives" but "Never false negatives".

A Bloom filter of 32 bits

The twelve market keys are inserted in a fixed order (Milano first, Rimini last). Each key sets its k bits. Choose a probe: arrows show its k bits; a 0 among them, underlined, proves the key absent. The plot shows how often the filter says "maybe" for keys that were never inserted, as it fills.

  1. With k = 3 and n = 4, probe Roma, which is never inserted. Predict the answer before you look.
    What you should see

    "Definitely not here": Roma's bits are 27, 13 and 6, and bits 27 and 6 are still 0. One zero is enough — the read never opens this SSTable.

  2. Probe Milano, then other inserted keys, at any k and any n that includes them. Can you ever get "definitely not here"?
    What you should see

    Never. An inserted key set all its bits, and bits are never cleared: no false negatives. That is the insight of the widget. A key not inserted yet — Rimini with k = 3 and n = 4 — rightly gets "definitely not here".

  3. Probe Napoli with k = 3 and slide n from 8 to 12. Predict when the answer changes.
    What you should see

    Up to n = 8 at least one of Napoli's bits is 0. At n = 9 Cremona sets bits 0 and 24, and with bit 10 already set by Varese all three bits are 1: "maybe here" for a key that was never inserted — a false positive. The fuller the filter, the more often it is wrong this way; the formula climbs to about 31 % at n = 12.

  4. Keep n = 12 and try k from 1 to 6. Do more hash functions always help?
    What you should see

    No. The formula readout gives 31.3, 27.8, 30.8, 36.4, 43.5 and 51.3 %: with 32 bits for 12 keys, extra hash functions fill the array faster than they help (the measured crosses wobble around the curve). The slide's filter has 32 bits per key, where k = 4 gives 0.02 %.

The slide's example — "k=4 hash functions", "100 items", "3200 bits", "FP rate = 0.02%" — checks out with the standard formula (background): a bit stays 0 with probability about \(e^{-kn/m}\), so

\[ p_{FP} \approx \left(1 - e^{-kn/m}\right)^{k} \] \[ \left(1 - e^{-400/3200}\right)^{4} = 0.1175^{4} \] \[ 0.1175^{4} \approx 0.019\,\% \]
\(m\)
bits in the array (3200)
\(n\)
keys inserted (100)
\(k\)
hash functions (4)

32 bits per key, whatever the key: a "no" costs k bit tests in memory, not a disk read.

Deletes write a tombstone

Immutable files cannot erase a row. "Delete: don't delete item right away" — "add a tombstone to the log"; "Compaction will remove tombstone and delete item" (p. 448). A tombstone is a timestamped delete marker written like any write; it hides older values from reads (Fig. 5). Until compaction a delete adds data (background: a tombstone outlives a grace period, ten days by default, so that replicas that were down hear of it).

SSTable 1 · oldest Bergamo12t1 Milano40t2 SSTable 2 Como7t3 Milano41t4 SSTable 3 · newest Bergamotombstonet5 Lodi9t6 versions pile up

Before: three SSTables

SSTable 4 · after compaction Como7t3 Lodi9t6 Milano41t4 dropped: Milano 40, an older version dropped: Bergamo 12 and its tombstone one file to read

After: one SSTable

Fig. 5. Compaction merges sorted files in one pass, keeping the newest timestamp of each key; a tombstone and the value it hides are dropped once its grace period is over. Before it, a read of Milano opens two files and a read of Bergamo must notice that the tombstone (t5) is newer than the value (t1).

Reads merge

A read merges 'Milano' from the memtable and the SSTables whose filter says "maybe", keeping the newest timestamp: reads "May be slower than writes" (p. 446).

Page 455 adds the "Staged Event-Driven Architecture": "A general-purpose framework for high concurrency & load conditioning" built of "stages separated by queues". The slide leaves the link implicit (background): in a Cassandra node writes, reads, gossip and compaction are such stages, so overload shows as a growing queue, and a slow compaction does not stop gossip.

Back to the first puzzle

So the 0.12 ms against 15 ms of p. 459: a write is one log append and one memory insert, with no read, seek or lock; a read merges several files, often on several replicas. Cassandra gave up cheap reads and in-place updates, and pays later instead: flushes, compactions, tombstones. The slide names no hardware or workload: read a ratio, not a benchmark.

How many copies must answer: quorums

Three copies of 'Milano': how many must answer a write, and how many a read? The client chooses (p. 449): "R = read replica count, W = write replica count", "N = total number of replicas for this key".

Reads "Wait for R replicas (R specified by clients)" and check the "remaining N-R replicas" in the background. The coordinator "can contact closest replica (e.g., in same rack)", "also fetches from multiple replicas", and starts a read repair "if any two values are different" (p. 448). Writes "come in two flavors": "Block until quorum is reached", or "Async: Write to any node". A quorum is a majority of the N copies.

\[ Q = \left\lfloor \frac{N}{2} \right\rfloor + 1 \]
\(N\)
replication factor: copies of the key
\(\lfloor \cdot \rfloor\)
round down

"For N = 3, the slide's 'Quorum Q = N/2 + 1' (p. 449) is 2.5 replicas." No: the division rounds down, as in the Cassandra documentation — 2 of 3, 3 of 4, 3 of 5 — and so does the "(N /2) + 1" of p. 453. Write the floor.

Why W + R > N is enough

The slide: "If W+R > N and W > N/2, you have consistency", and "Allowed (W=1, R=N) or (W=N, R=1) or (W=Q, R=Q)". The first half does the work:

  1. The write was acknowledged by a set A of W replicas; a later read hears from a set B of R replicas, out of the same N:

    \[ |A \cup B| \le N \]
  2. So the two sets share at least

    \[ |A \cap B| = W + R - |A \cup B| \] \[ |A \cap B| \ge W + R - N \]
  3. If W + R > N, a replica answering the read holds the write, and the coordinator returns the most recent timestamp: "Return most recent TS" (p. 453).

  4. The slide's corners pass: 1 + N, N + 1 and 2⌊N/2⌋ + 2 all exceed N.

The corners pay differently: (1, N) has fast writes, but a read needs every replica up; (N, 1) has fast reads, but one replica down stops the writes; (Q, Q) balances both and survives N − Q failures — one of three, two of five.

"W > N/2 is required too." Not by the slide's own list: (W=1, R=N) is "Allowed", and 1 is not more than N/2. W > N/2 keeps two writes from being accepted by disjoint sets; Cassandra settles concurrent writes by timestamp, and its documentation states the rule as W + R > N alone.

Quorum explorer: a write, then a read

The client writes v2 over an old v1, then reads. Filled replicas acknowledged the write; dashed outlines are the replicas the read asks. Drawn is the worst case — the read asks the replicas the write reached last — so a highlighted replica is one the inequality forces into both sets.

  1. Start with N = 3, write ONE, read ONE. Predict: can a read right after the write miss it?
    What you should see

    Yes. 1 + 1 = 2 ≤ 3: r1 acknowledged the write, the read asks r3, which still holds v1, and the coordinator returns v1. Read repair fixes r3 only afterwards.

  2. Raise only the read to QUORUM. Enough? Then raise the write to QUORUM too.
    What you should see

    1 + 2 = 3 is still not greater than 3: the read set r2, r3 misses r1. With QUORUM on both sides, 2 + 2 = 4 > 3 and r2 is in both sets. That is the insight: when W + R > N, there is no room left for two disjoint sets.

  3. Keep QUORUM and QUORUM and set N = 5. How many replicas does each operation wait for, how many may be down, and what does it cost against N = 3?
    What you should see

    ⌊5/2⌋ + 1 = 3 for both; 3 + 3 = 6 > 5. Two replicas may be down instead of one, but each operation waits for the third-fastest answer (9 ms) instead of the second (6 ms) — illustrative times.

  4. Try write ALL with read ONE, then write ANY with read ALL. What does each buy, and what does it cost?
    What you should see

    ALL/ONE: reads are fast and always fresh (3 + 1 > 3), but a write waits for the slowest replica and fails if any replica is down. ANY/ALL: the write may exist only as a hint on the coordinator, so even reading every replica can miss it — no guarantee. ANY buys availability, not consistency.

Consistency levels

"In reality, a client can choose one of these levels for a read/write operation" (p. 450; Table 2). The older tables of p. 453 use old names: ZERO ("Good luck with that") is gone, and "DCQ for RackAware" is today's LOCAL_QUORUM. In cqlsh the level is set for the statements that follow (documentation syntax, not on the slides):

-- next statements: wait for a majority
CONSISTENCY QUORUM
Table 2. The levels of p. 450, with the older tables of p. 453 next to them.
LevelWaits for (p. 450)Write (p. 453)Read (p. 453)
ANY"any node (may not be replica)""1 replica (hints count)""Try ONE instead"
ONE"at least one replica""1 replica. read repair in bkgnd""1 replica"
QUORUM"quorum across all replicas in all datacenters""(N /2) + 1""Return most recent TS after (N /2) + 1 report"
LOCAL_QUORUM"in coordinator's DC""DCQ for RackAware" in the old tables
EACH_QUORUM"quorum in every DC"—
ALL"all replicas all DCs""N = replication factor""N = replication factor"

"The level ANY works for reads too." Page 450 lists it for "a read/write operation", but a hint cannot be read: p. 453 answers a read at ANY with "Try ONE instead".

With three copies in each of two datacenters (computed): QUORUM waits for 4 of the 6, wherever they are; LOCAL_QUORUM for 2 in the coordinator's datacenter, with no trip to the other — what "Supporting local queries" buys; EACH_QUORUM for 2 in each, the strictest and slowest.

The trade, and when it pays

"Cassandra Summary" (p. 460): BASE, "Basically Available Soft-state Eventual Consistency"; Cassandra "Prefers Availability over Consistency", HBase "Prefers (strong) Consistency over Availability" — the CAP corners of lesson 5. Yet the choice is per operation: ONE and ANY keep accepting writes while replicas are down, QUORUM and ALL refuse rather than answer stale — "Eventual consistency, tunable per query" (p. 1550). Table 3 says when it pays.

Table 3. "is cassandra a good fit?" (p. 458): the two columns of the slide.
It pays when……and the team can take
"you need really fast writes""your programmers can deal": documentation, complexity, consistency model, change, visibility tools
"you need durability""your operations can deal": hardware considerations, can move data, JMX monitoring
"you have lots of data": > GBs, >= three servers
"your app is evolving": startup mode, fluid data structure
"loose domain data": "points of interest"

A read is sure to see the latest acknowledged write when W + R > N; a quorum is ⌊N/2⌋ + 1, chosen per operation.

HBase: the same engine, one owner per row

"HBase" (p. 463) brings Google's Bigtable design into Hadoop: "Major Apache project today, part of Hadoop"; "Facebook uses HBase internally". Its API (p. 464): Get(row), Put(row), Scan(row range, filter) ("range queries") and MultiPut(rows). The scan is the first difference: rows stay sorted by row key, as with the order-preserving partitioner, so a range of rows is one sequential read. (Data model: lesson 32.)

The architecture of p. 465 (Fig. 6) has two floors. Upstairs, HBase: a client asks ZooKeeper which HRegionServer serves its rows, then talks to it directly; the HMaster assigns regions and watches the servers. Downstairs, Hadoop: every byte lives in HDFS DataNodes, which keep the copies.

client ZooKeeper HMaster which server? get / put / scan assigns regions HRegionServer HRegion · rows A–M Store · family 1 MemStore HFile HFile Store · family 2 MemStore HFile more HRegions HLog · one per server HDFS h1 l1 h2 l1 h1 h2 h2 h1 l1 DataNode DataNode DataNode
Fig. 6. HBase redrawn from p. 465. One server owns each region; inside it, one Store per column family, each a MemStore plus HFiles; one HLog per server. The files are written to HDFS as blocks (h for HFile, l for HLog), and HDFS keeps several copies of each, three by default: solid the first, dashed the others.

The "HBase Storage hierarchy" (p. 466), outside in: a table splits into regions, ranges of row keys; a region has "One Store per ColumnFamily (subset of columns with similar query patterns)" — columns read together share a family, stored and read as one Store; a Store has a "Memstore" ("in-memory updates to Store; flushed to disk when full") and "StoreFiles … where the data lives", in blocks. A StoreFile is an HFile, the "SSTable from Google's BigTable": data blocks, indexes and a trailer, every key-value record carrying its whole key (p. 467, Table 4).

Table 4. One key-value record of an HFile (p. 467), with the slide's census example.
FieldCensus example
Key Length, Value Lengthlengths of the two parts
Row Length, RowSSN:000-00-0000 (15 characters)
Column Family Length, Column FamilyDemographic (11 characters)
Column QualifierEthnicity
Time Stamp, Key Typeversion of the cell; a value or a delete marker (background)
Valuethe stored bytes

Write-ahead log and replay

"Strong Consistency: HBase Write-Ahead Log" (p. 468): a client's put(), delete() and incr() go "to HLog before writing to MemStore", so the server "Can recover from failure". The HLog is HBase's commit log, forced to disk by a Log Flusher (sync()) and cut into files by a Log Roller (rollWriter()). "Log Replay" (p. 469): "After recovery from failure, or upon bootup", "Replay any stale logs (use timestamps to find out where the database is w.r.t. the logs)" — "add edits to the MemStore". One HLog per HRegionServer, not per region, "Avoids many concurrent writes, which on the local file system may involve many disk seeks".

Across datacenters

"Cross-data center replication" (p. 470) reuses the log: each region server of the master cluster ships its HLog edits, in batches, to slave clusters ("Synchronous Call"). ZooKeeper, "actually a file system for control information", keeps /hbase/replication/state, /hbase/replication/peers/<peer cluster number> and /hbase/replication/rs/<hlog>: per region server, the HLogs to ship and the offset reached.

"'Synchronous Call' means a client's write waits for the other datacenter." No: each shipping call is synchronous, but replication runs behind the client — a write is acknowledged once it is in the local HLog and MemStore (background: the HBase Reference Guide calls the replication asynchronous).

Back to the second puzzle

Table 5. The same engine, two owners.
CassandraHBase
who serves a rowN equal replicas, no masterone HRegionServer per region
finding itthe hash ring, known to all by gossipZooKeeper; HMaster assigns regions
log, memory, diskcommit log, memtable, SSTableHLog, MemStore, HFile
copiesN replicas on N nodesHDFS copies of the files
row orderhash order; order inside a partitionsorted by row key: Scan(row range)
p. 460"Prefers Availability over Consistency""Prefers (strong) Consistency over Availability"

Same engine, different owner. Cassandra lets any of N equal replicas take a write, so writes never wait for one machine, and replicas may disagree for a while. HBase gives each row one server, so a read sees every acknowledged write of the row, with no quorum arithmetic; but while a crashed server's regions are reassigned and its HLog replayed, those rows cannot be served. Facebook chose that simpler consistency for Messages.

The other columnar: storing each column apart

The deck closes with "Other Columnar DBs" (p. 471) — another meaning of "columnar". Cassandra and HBase keep a row's columns together (HBase within each column family); MonetDB and LucidDB store each column apart, the column storage of lesson 20, for queries that read a few columns of many rows.

"MonetDB" (p. 472) runs an SQL front-end (relational model), an ODMG front-end (object-oriented model), an OLAP tool and a data-mining tool on one "Monet back-end" through the MIL Language, with "full vertical fragmentation": on p. 473 (Fig. 7) Order(id, day, discount) becomes order_id, order_day and order_discount, each pairing an object id, the oid, with one value.

Order id day discount 104/4/980.175 119/4/980.065 121/2/980.175 139/4/980.000 147/2/980.000 151/2/980.065 order_id order_day order_discount oidint 10010 10111 10212 10313 10414 10515 oiddate 1004/4/98 1019/4/98 1021/2/98 1039/4/98 1047/2/98 1051/2/98 oidfloat 1000.175 1010.065 1020.175 1030.000 1040.000 1050.065 a query on discount reads this
Fig. 7. The Order table of p. 473 split column by column. The six orders get the oids 100–105 in every piece, so a row is rebuilt by taking the same position in each piece; a query on the discount touches one narrow table instead of three columns.

"LucidDB" (pp. 474–476) keeps "column store tables" in one file, db.dat. A table "consists of set of clusters", by default one per column ("Each column maps to single cluster"), and a cluster page "stores the values for a specific set of rowIDs". A B-tree per cluster "maps rid values to pageIds" by each page's first rid: page 49 starts at rid 984 and page 50 at 1248, so page 49 holds rids 984–1247. Inside a page (p. 477) LucidDB stores "just the unique column values", each with "a bit-encoded vector" (Table 6).

Table 6. The discounts of p. 473 as one page, stored the LucidDB way (computed): each distinct value once, one bit per row.
Distinct valuerid 012345
0.175101000
0.065010001
0.000000110

Hence the "Conclusion" (p. 478): "Column architecture doesn't read unnecessary columns"; "Avoids decompression costs and performs operations faster" — discount = 0.000 is answered by the vector 0 0 0 1 1 0, decompressing nothing; "Use of compression schemes allow us to lower our disk space requirements".

Exam-style questions

Five theory questions in the forms of the theory part — open answers of a few sentences, and closed ones where a wrong tick may cost points — then two exercises: a quorum computation and a deployment question on the ER scenario of a past paper.

Describe step by step what a Cassandra replica node does when it receives a write, and explain why reads can be slower than writes.

Show model answer

The replica first appends the write to the commit log on disk, a sequential append that makes it durable, and then applies it to the memtable, an in-memory structure of key-value pairs sorted by key; then it can acknowledge. No read, no disk seek and no lock are needed. When the memtable is full or old, it is flushed to disk as an SSTable (Sorted String Table): an immutable file of key-value pairs sorted by key, with an index file of (key, position) pairs and a Bloom filter. Because SSTables are never modified, new versions and deletes (tombstones) accumulate in newer SSTables, and compaction periodically merges them, keeping the newest version of each key and removing deleted items. A read, instead, must look in the memtable and possibly in several SSTables — skipping those whose Bloom filter says the key is absent — and merge the versions by timestamp; the coordinator may also contact several replicas and start a read repair. This is why reads may be slower than writes.

A complete answer gives the order (commit log, then memtable), the words SSTable, flush, compaction and Bloom filter, and the reason for the asymmetry. The slide of p. 446 says reads "touch log and multiple SSTables": write memtable, and mention the log only for restarts.

A keyspace has replication factor N = 3. Which write/read consistency-level pairs guarantee that a read returns the most recent acknowledged write? Select all that apply.

  • W = ONE, R = ONE 1 + 1 = 2 ≤ 3: the read may ask a replica the write has not reached.
  • W = QUORUM, R = QUORUM 2 + 2 = 4 > 3: the two majorities share at least one replica.
  • W = ANY, R = ALL ANY can be satisfied by a hint on a coordinator that is not a replica: until the hint is delivered, no replica has the value, so even reading all three can miss it.
  • W = ALL, R = ONE 3 + 1 = 4 > 3: every replica has the value.
  • W = ONE, R = ALL 1 + 3 = 4 > 3: the read asks every replica, including the one that acknowledged.
  • W = ONE, R = QUORUM 1 + 2 = 3, not greater than 3: the two replicas read may both be stale.
Show model answer

QUORUM/QUORUM, ALL/ONE and ONE/ALL. The condition is W + R > N (here 2 + 2, 3 + 1 and 1 + 3, all greater than 3): it forces the replicas that acknowledged the write and the replicas that answer the read to overlap in at least one node, and the coordinator returns the value with the most recent timestamp. ONE/ONE and ONE/QUORUM fail the inequality; ANY guarantees no replica at all, because a hint stored on the coordinator counts as a successful write.

Three correct options out of six. ANY/ALL is the trap: it looks like the "read everything" corner, but W counts only real replicas. Wrong ticks may be penalised, so tick only what the inequality proves.

True or false: a Bloom filter may report that a key is absent from an SSTable although the key is stored in it.

  • True That would be a false negative, which a Bloom filter never gives: the bits set when the key was inserted are never cleared.
  • False No false negatives; only false positives — an absent key whose bits were all set by other keys.
Show model answer

False. When a key is inserted, all its k hashed bits are set, and bits are never reset, so a stored key always finds its bits set and the filter answers "maybe present". The filter can only err the other way: an absent key may find all its bits set by other keys (a false positive). The rate is low; the lecture's example has k = 4 hash functions, 100 items and 3200 bits, with a false-positive rate of 0.02 %.

A true/false question that tests the direction of the error. Write the reason, not just the word: "bits are never cleared".

Explain how the nodes of a Cassandra cluster learn which nodes are alive without a master. Describe the membership list and how a node decides that another node is down.

Show model answer

Cassandra is peer to peer: no master knows the membership, so each node keeps its own membership list with, for every node, its address, the latest heartbeat counter heard and the local time at which that counter was received. Each node increments its own counter and periodically gossips its list to a few peers (up to three per round), with a three-message exchange (GossipDigestSyn, GossipDigestAck, GossipDigestAck2). On receipt, for every address the node keeps the larger counter and stamps it with its own local time, because the clocks are not synchronised: node 2 at local time 70 receives 10120 and 10098 for nodes 1 and 3, larger than its 10118 and 10090, and stores (10120, 70) and (10098, 70), while it keeps its own entry and the equal one. The traffic per node stays constant and the views converge (anti-entropy). A node whose counter stops increasing becomes suspect: the accrual failure detector computes a suspicion value PHI, minus the base-10 logarithm of the probability that a heartbeat arrives later than the silence observed so far, from the past inter-arrival times; the application sets the threshold (on the slide, PHI = 5 gives a detection time of 10–15 seconds).

A complete answer has three parts: no master and gossip with few peers; the merge rule with local times; suspicion as a value with a threshold rather than a fixed timeout. The numbers of p. 451 make the merge rule concrete.

Cassandra "prefers availability over consistency", while HBase "prefers (strong) consistency over availability". Explain where this difference comes from in the two architectures.

Show model answer

In Cassandra every row has N equal replicas on a masterless ring. Any node can coordinate a write and sends it to all replicas; with levels such as ONE or ANY, and with hinted handoff, a write is accepted even while replicas are down, so the replicas may hold different versions for a while; they are reconciled later by timestamps, read repair and compaction (eventual consistency, BASE). In HBase a table is split into regions, and each region is served by exactly one HRegionServer at a time, assigned by the HMaster through ZooKeeper. Every write of a row goes through that single server, which appends it to its write-ahead log (HLog) before applying it to the MemStore, so every read of the row sees the latest write. The copies of the data are kept by HDFS at the file level, not by several servers answering for the same row. When a region server fails, its regions are unavailable until the HMaster reassigns them and the HLog is replayed into the new MemStores: HBase gives up availability during recovery to keep strong consistency.

The core is "N equal replicas" against "one owner per region". The write-ahead log alone gives durability; consistency comes from the single owner. Mention BASE for Cassandra, and the unavailability during recovery for HBase.

A keyspace uses the Simple Strategy with replication factor 4. Writes are performed at consistency level QUORUM. What is the smallest number of replicas a read must wait for, so that it is guaranteed to return the latest acknowledged write?

Show model answer

QUORUM with N = 4 is ⌊4/2⌋ + 1 = 3 replicas, so W = 3. A fresh read needs W + R > N, that is R > 4 − 3 = 1, so R = 2 replicas. Of the lecture's levels, ONE is not enough and QUORUM (3) is more than needed.

The two steps are the quorum size and the inequality. With N = 4 the slide's N/2 + 1 gives 3 with or without rounding; with an odd N only the rounded-down form is a whole number, so always write the floor.

ER scenario of the exam of 12 Sep 2025 (the exercise itself is exam-style, not from the paper): a data structure for tracking farmers and their sales at local markets. Market (Name, Location, Opening Time, Closing Time, Size) and Product (Product ID, Name, Quantity, Expiration Date) are linked by the relationship Sell, with cardinality (1, N) on both sides.

The sales are stored in Cassandra, partitioned by market location, on one datacenter of six nodes n1–n6 with tokens 10, 25, 40, 55, 70 and 85; the token of the partition 'Milano' is 62. Write the CQL statement that creates the keyspace markets so that every row is stored three times. Say which nodes hold the copies of 'Milano', and choose consistency levels for writes and reads so that a read always returns the latest acknowledged sale while one replica may be down.

Show model answer
CREATE KEYSPACE markets
  WITH replication = {
    'class': 'SimpleStrategy',
    'replication_factor': 3};

With the Simple Strategy the first copy goes to the first node clockwise from the token 62, that is n5 (token 70), and the other two to the next nodes clockwise, n6 (85) and n1 (10, after wrapping around the ring). Writes and reads at QUORUM wait for ⌊3/2⌋ + 1 = 2 replicas each: 2 + 2 > 3, so every read overlaps the last acknowledged write, and either operation still succeeds with one replica down. (ONE/ALL or ALL/ONE would also be consistent, but a single replica down would block the reads or the writes.)

The keyspace line opens every Cassandra script of the exercise session (p. 1553); here it is joined to the placement and quorum rules of this lesson. The wrap-around from n6 to n1 is where answers usually slip.

Check yourself

In the ring widget, five nodes hold the keys with N = 1. B3 joins with the token 68, between A2 (56) and B2 (81). Which keys change node?

  • About five sixths of all keys, because the number of nodes changed That is what hash modulo n does. On the ring a key changes owner only if the new token is now the first one clockwise from it.
  • Only the keys with tokens from 57 to 68 — Varese and Como — which move from B2 to B3 B3 takes over the part of B2's arc that now ends at its own token; every other key keeps its owner. Try it: 5 to 6 nodes.
  • A sixth of the keys of every node, so that the load is even again Spreading the load over all nodes is what virtual nodes add (background); on the slides' ring, one arc moves from one neighbour.
  • None until the next compaction Compaction merges SSTables inside a node; which node owns a key is decided by the tokens.

Which statements about gossip in Cassandra are true? Select all that apply.

  • In each round a node talks to a few peers, up to three "Each node picks its discussants (up to 3)" (p. 441).
  • Gossip carries the rows written by clients to the replicas Writes go from the coordinator to the replicas directly (p. 445); gossip carries heartbeats and node state.
  • The network traffic per node stays about the same as the cluster grows "Always a constant amount of network traffic": the partners per round do not grow with the cluster.
  • A received entry replaces the local one only if its heartbeat counter is larger The merge rule of p. 451; the time is then the receiver's own.
  • A master node decides which nodes are dead and tells the others There is no master: each node's own accrual detector turns silence into suspicion.

Gossip spreads state about nodes, cheaply and repeatedly; the decisions — dead or alive — are taken by each node for itself.

Node 3 is at local time 42. Its list holds the row 1: (counter 500, time 30). A gossip message from node 4 carries 1: (498, 41). What does node 3 store for node 1 afterwards?

  • (498, 41) The received counter is smaller: it is older news about node 1. And node 4's times mean nothing on node 3's clock.
  • (500, 42) The time is restamped only when a larger counter arrives; here nothing changed.
  • (500, 30) The larger counter wins, and it is the one node 3 already has: the row stays as it was, time included.
  • (498, 42) This takes the older counter and stamps it as new — both halves are wrong.

You delete the partition 'Lodi' and look at a replica's disk right away. What do you find?

  • More data: a tombstone was written, and the old rows stay until compaction "Delete: don't delete item right away"; "add a tombstone to the log" (p. 448). The SSTables holding 'Lodi' are immutable.
  • Less data: the rows of 'Lodi' were removed from the SSTables SSTables are never modified; nothing is erased in place.
  • The same data, minus the commit-log entry of 'Lodi' The commit log is append-only: the delete is appended to it like any other write.

N = 3. A client writes a new price at ONE and, a millisecond later, another client reads it at ONE. What can happen?

  • The read always returns the new price, because read repair runs first Read repair fixes stale replicas after a read has noticed them, and with R = 1 the read may compare nothing at all.
  • The read may return the old price; the stale replica is fixed later 1 + 1 is not greater than 3: the two sets may not overlap. Eventual consistency, as in the widget's first task.
  • The read fails, because ONE cannot be used for reads ONE is a read level ("1 replica", p. 453); ANY is the one that is not.

Which statements about HBase are correct? Select all that apply.

  • Each region is served by one HRegionServer at a time The single owner is the source of HBase's strong consistency.
  • Every edit is appended to the HLog before it reaches the MemStore "Write to HLog before writing to MemStore" (p. 468): the log makes the edit survive a crash.
  • Each region is served by three region servers, like Cassandra's three replicas This is the reading that p. 466's "replicated across servers" invites, and it is wrong: copies are kept by HDFS, at the file level.
  • HDFS keeps several copies of the HFiles and the HLog The lower floor of p. 465: blocks on several DataNodes.
  • Scan cannot read a range of rows, because the rows are hashed HBase keeps rows sorted by row key: Scan(row range, filter) is in the API (p. 464).

In LucidDB's rid-to-pageId B-tree (p. 476), page 49 starts at rid 984 and page 50 at rid 1248. On which page is rid 1247?

  • Page 50 Page 50 starts at 1248; 1247 is the last rid before it.
  • It cannot be told without reading the pages Avoiding that is the job of the B-tree: it maps the first rid of each page to the page.
  • Page 49 The B-tree finds the largest start rid not above 1247 — 984 — so page 49, which holds rids 984–1247.

Key take-aways

What to watch and read