Postgres uses B-trees. RocksDB, Cassandra, and LevelDB use LSM-trees. They’re solving the same problem — store key-value data on disk and find it again — but they make opposite bets about which operation deserves to be fast.
The B-Tree: Update in Place
A B-tree keeps keys sorted in fixed-size pages, with a shallow, high-fanout tree on top. A lookup walks from root to leaf — a handful of page reads even for huge datasets:
[ 25 | 50 | 75 ] ← root
/ | | \
[..20] [30..45] [55..70] [80..] ← leaves hold the dataReads are excellent: the tree is shallow and a key lives in exactly one place. The cost is writes. To insert, you find the right leaf and modify it in place — a random write. Pages split when they fill, and the write-ahead log has to durably record the change first. Lots of small random I/O.
The LSM-Tree: Never Modify, Always Append
A log-structured merge tree refuses to do random writes. New data goes into an in-memory table; when it fills, it’s flushed to disk as an immutable sorted file (an SSTable). Writes are sequential and fast:
writes ──▶ [ memtable ] (in RAM, sorted)
│ flush when full
▼
[ SSTable L0 ] [ SSTable L0 ] ...
│ background compaction merges + sorts
▼
[ SSTable L1 .................. ]The catch is reads. A key might be in the memtable, or any of several SSTables. A lookup may have to check multiple files — softened by Bloom filters and an index per file, but still more work than a B-tree’s single path.
The Amplification You’re Trading
Three costs frame the whole decision:
- Read amplification — extra reads per lookup. B-tree: low. LSM: higher (multiple SSTables).
- Write amplification — bytes actually written per byte of data. B-tree: page rewrites + WAL. LSM: compaction rewrites data repeatedly across levels.
- Space amplification — disk used per byte of live data. LSM keeps stale copies until compaction; B-trees leave half-empty pages.
reads writes space
B-tree fast slower fragmented
LSM-tree slower fast stale until compactedPicking a Side
- Read-heavy, point lookups, transactional → B-tree. This is most relational workloads, which is why Postgres and MySQL’s InnoDB use them.
- Write-heavy, high ingest, time-series, logs → LSM-tree. Sequential writes and cheap appends win when you’re firing data in faster than you read it.
Neither is “better.” Compaction tuning is the LSM’s eternal headache; write amplification and vacuum are the B-tree’s. You’re choosing which problem to own.
Takeaways
- B-trees update in place: great reads, random-write cost.
- LSM-trees append immutable sorted files: great writes, multi-file read cost plus background compaction.
- The real axis is read vs write amplification — pick the engine that matches whether your workload reads or writes more.