Here’s a puzzle that trips up a lot of people the first time they meet it. A database that stores your data on a spinning or flash disk has to do two things that pull in opposite directions: write new data quickly, and find old data quickly. The classic design — the B-tree, the thing under most traditional databases — is organized for finding. It keeps everything sorted in place, so a lookup is a few confident hops. But that sorted-in-place order is exactly what makes writing slow: to insert one new record in the right spot, you often have to go edit a page sitting at some random location on disk, and random edits are the slowest thing storage does.
So a different family of storage engines — the one under RocksDB, LevelDB, Cassandra, and a lot of what you’re using without knowing it — makes the opposite trade. It is built around a simple refusal: never update data in place. Only ever append.
How that actually works
When a write comes in, it doesn’t go hunt for the record’s home on disk. It does two cheap things: it appends the change to a log file (so nothing is lost if the power dies — the same durability trick a write-ahead log gives you), and it drops the new value into a sorted table held in memory. Memory is fast and the log is a pure append, so the write returns almost immediately. No seeking, no editing a page in place.
That in-memory table fills up. When it does, the engine writes it out to disk in one go as an immutable, sorted file — and then leaves it alone forever. It never edits that file. More writes arrive, fill a new in-memory table, and get flushed to a new file beside the old one. Over time you accumulate a stack of these sorted files, newest on top.
Where the bill comes due
Nothing is free, and the appender’s bill arrives at read time. To read a key, the engine checks the in-memory table first, then the newest file on disk, then the next, working down until it finds the value — because a newer file might hold an updated version of a key an older file still has. Left unchecked, that pile would grow forever and every read would get slower. Two things save it. First, each file carries a compact in-memory summary (a Bloom filter) that can say “this key is definitely not in me” cheaply, so most files get skipped without being touched. Second, a background job called compaction quietly merges the small files into larger sorted ones, throwing away superseded values and deleted keys as it goes — which is also, incidentally, how a “delete” actually happens: you append a little tombstone marker now, and the data is really dropped later, during a merge.
So the whole design is one trade stated honestly: writing is fast because you never go back and fix anything; reading and disk cost stay bounded because a background process goes back and fixes everything, later, in bulk. You moved the expensive, in-place work off the critical path and batched it. That’s the same instinct behind a lot of good engineering — and a lot of good product decisions, come to think of it: don’t make the user wait on cleanup that can happen after they’ve moved on.
The one thing worth internalizing if you build on top of these engines: a sudden write spike doesn’t just cost you at write time. It schedules compaction work that lands on the system minutes later, competing for the same disk your reads need. The speed was borrowed. Compaction is when you pay it back.
Sources
- the log-structured merge tree (O'Neil, Cheng, Gawlick & O'Neil, 1996)
- SSTables and memtables (Google Bigtable, LevelDB)
- Bloom filters (Burton Bloom, 1970)
- the RUM conjecture (Athanassoulis et al., 2016)
Liked this? Get the next one in Working Theory.
Going weekly in August (it's in beta now). One genuinely interesting read on building, the brain, and the science most people missed.
Subscribe →