Lesson 03 · The write-optimized rival

LSM trees: compaction and the amplification triangle

A series of progressively larger storage boxes or filing bins being compacted and consolidated into fewer bigger ones

The field guide's wide-column lesson had you drive the LSM basics: writes land in a memtable, flush as immutable sorted SSTables, compaction merges them later. This lesson is about the part that runs production: the debt. Every LSM design is a position in a three-way trade the literature calls the RUM/amplification triangle: and understanding it turns "Cassandra is being weird" into "compaction chose this, and here's the dial."

The three amplifications

Write amplification (WA): bytes physically written ÷ bytes logically written. Every compaction re-reads and re-writes data that was already "written": a key may be rewritten 10–30× over its life as it migrates down levels. Read amplification (RA): places checked per read: memtable plus every SSTable that might hold the key. Space amplification (SA): disk used ÷ live data: old versions and tombstones squat until compaction reclaims them. The theorem you can't escape: improve any one and at least one other degrades. Prove it to yourself:

Pick a compaction strategy, read the invoice

Same workload (steady writes, point reads, some overwrites) under three strategies. Watch all three meters: no strategy wins all three, ever.

write amp
:
read amp (sstables/read)
:
space amp
:
:

Real engines expose exactly this dial: Cassandra ships STCS (write-friendly default), LCS (read-optimized, ~10× the compaction I/O), and TWCS (time-windowed: for time-series data, where "compact only within a time window, then drop whole windows at expiry" makes retention free, as the field guide's specialists lesson promised). RocksDB: the LSM engine inside dozens of other databases: makes leveled-with-tiered-L0 the default and exposes dozens of tuning knobs that are all, ultimately, positions on this triangle. And when compaction falls behind the write rate, all three amplifications grow together: files pile up, reads slow, disk fills: the LSM failure mode, and the first graph to check on a struggling cluster.

Bloom filters: the read-amp painkiller

With a key possibly in any of 10 SSTables, point reads should cost 10 disk probes. They don't, because each SSTable carries a bloom filter: a bit array that answers "is key K possibly in this file?" with no false negatives and a tunable false-positive rate, in nanoseconds, from RAM. Build one:

16 bits, 2 hash functions, zero lies about absence

Add keys: each sets 2 bits. Then probe: all bits set = "maybe here" (go read the SSTable); any bit clear = "definitely not" (skip the file, free). The last probe is engineered to embarrass the filter.

The asymmetry is the whole design: "no" is guaranteed, "yes" is probabilistic. A false positive costs one wasted disk probe: annoying, correct. A false negative would return wrong query results: impossible by construction, since adding a key can only set bits, never clear them. Production filters use ~10 bits per key and ~7 hash functions for a ~1% false-positive rate, so a read across 10 SSTables does ~10 RAM checks + 1 real disk read + 0.1 wasted ones. This is why LSM point reads stay competitive with B-trees despite the multi-file layout. (The same "cheap probabilistic no" trick shows up all over systems: cache admission, distributed joins, malware scanning: worth having in your pocket.)

So: B-tree or LSM?

write path B-tree: read-modify-write pages, in place, random I/O LSM: append to memtable+WAL; sorting deferred & batched read path B-tree: one structure, 3-4 page hops, predictable LSM: memtable + N files (blooms absorb most of N) space B-tree: ~fill-factor overhead (70-100%), stable LSM: dead versions await compaction; fluctuates deletes B-tree: remove in place LSM: tombstones: a delete is a WRITE that must out-survive every older copy of the key the bet B-tree: reads dominate, latency must be predictable LSM: writes dominate, throughput is king

One more honest note: SSDs shrank the random-write penalty that motivated LSM, but didn't kill the trade: flash has its own write-wear economics (SSD controllers run their own internal LSM-ish log!), and the CPU cost of compaction vs page management keeps the rivalry alive. Modern practice: PostgreSQL/MySQL (B-tree) for the OLTP core; RocksDB-based engines (MyRocks, Cassandra, Scylla) where write volume or space efficiency wins. Same conclusion as the whole field guide: physics, workload, pick.

Check yourself

1. A Cassandra cluster's disks show 10× more bytes written than clients send. The new SRE suspects a bug. What's the diagnosis?

The triangle's write corner, measured. Logical write → memtable → flush (×1) → merged in L0→L1 (×2) → L1→L2 (×3)... Leveled compaction multiplies by ~level-count × fanout-factor. It's also why LSM stores are gentle on ingest latency but demand disk bandwidth headroom: the compaction I/O is the payment plan for every byte accepted cheaply at the front door.

2. Why can a bloom filter never produce a false negative, no matter how overloaded it gets?

Monotonicity is the proof: the bit array only gains 1s. The failure mode of an under-sized filter is saturation (all bits set → every probe says "maybe" → every SSTable gets read → read amplification returns in full). This is also why classic blooms can't support deletion: clearing a bit might erase evidence of another key: and why counting blooms and cuckoo filters exist for stores that need it (tombstoned keys, for instance).

3. A team runs mass deletions to shrink a Cassandra table, and reads get SLOWER and disk usage barely drops for days. Explain.

The LSM delete paradox: in an append-only world, removal is information that must be written, propagated, and preserved until every replica and every older SSTable has been overruled (the grace period ties into the replication course's hinted-handoff machinery: drop the tombstone too early and a recovering node resurrects the data: "zombie rows"). Mass-delete-then-read workloads are the documented LSM anti-pattern; the escape hatches are TTLs, whole-partition deletes, or TWCS's drop-the-window trick.