Blog
About

© 2026 Uzair Tariq

← Back to blog

How LSM-trees make writes fast without losing ordered data

August 3, 2026DatabasesStorage EnginesLsm TreesSystem DesignDistributed SystemsData Structures

The storage-engine idea that clicked for me here is simple: keep disk files sorted, but do not try to sort every write directly on disk. Let memory absorb random writes first, flush them as sorted files, and merge those files in the background. That design is the foundation of an LSM-tree.

Sorting changes what a segment file can do

An earlier append-only log stores records in write order. An SSTable, short for Sorted String Table, stores key-value pairs in key order. After compaction, each key appears once in a given file. If the same key exists in several files, the newest file holds the current value.

WRITE-ORDERED LOG
  cat -> black
  dog -> brown
  cat -> white

SSTABLE
  cat -> white
  dog -> brown

Sorting opens up efficient merging, range scans, sparse indexing, and block compression.

Sorted files are easy to merge

Two sorted SSTables can be merged by reading them forward side by side, like the merge step in merge sort. The engine writes the lower key next. When both files contain a key, it keeps the value from the newer segment. This works with files larger than RAM because it streams rather than fully loads them.

OLDER FILE             NEWER FILE
  cat -> black           cat -> white
  dog -> brown           fox -> red

MERGED OUTPUT
  cat -> white
  dog -> brown
  fox -> red

A sparse index replaces a full hash index

A hash-indexed log needs an in-memory entry for every key. An SSTable only needs offsets for occasional keys. For a lookup between two index entries, the database jumps to the earlier offset and scans the small sorted region that follows. Nearby records can be stored in compressed blocks, saving disk space and I/O bandwidth.

SPARSE INDEX IN RAM
  handbag  -> offset 0
  handsome -> offset 8 KB

LOOK UP: handiwork
  jump to handbag
  -> scan forward
  -> stop at handiwork or handsome

Memory turns random writes into sorted files

Writes arrive in arbitrary order, so the database does not insert them straight into an on-disk SSTable. It first adds them to a balanced in-memory tree called a memtable. That tree accepts arbitrary inserts but can produce its contents in sorted order.

When the memtable reaches a threshold, usually a few megabytes, the database flushes it as an immutable SSTable and continues writing to a fresh memtable.

RANDOM WRITES
  dog -> brown
  cat -> white
  fox -> red

MEMTABLE IN RAM
  cat -> white
  dog -> brown
  fox -> red

FLUSH TO DISK
  -> immutable sorted SSTable

Crash recovery needs a separate log

A memtable lives in RAM, so a crash before the flush would lose recent writes. The database also appends every write to a recovery log on disk. After a crash it replays that log to rebuild the memtable. Once a memtable is safely flushed as an SSTable, its matching recovery log can be discarded.

WRITE(key, value)
  -> append to recovery log on disk
  -> add to memtable in RAM

CRASH
  -> replay log
  -> rebuild memtable

An LSM-tree is a stack of sorted files

LSM means Log-Structured Merge-Tree. Recent writes are in a memtable, completed writes are immutable SSTables, and background compaction merges files while removing overwritten values and safe-to-delete tombstones.

Reads check the memtable first and then SSTables from newest to oldest. That preserves correctness because the newest matching value wins, but a read may have to inspect several structures.

GET key
  1. memtable
  2. newest SSTable
  3. next-oldest SSTable
  4. continue until found or exhausted

Bloom filters make absent-key lookups cheaper

A missing key can be expensive because the database may check many SSTables before proving it is absent. A Bloom filter is a compact probabilistic filter that can say a key is definitely absent from a particular file, so the database can skip that disk read. If it says the key may be present, the file still needs to be checked.

LOOK UP: unicorn

BLOOM FILTER
  definitely absent
  -> skip this SSTable

  may be present
  -> read the SSTable to confirm

Compaction decides the cost profile

Size-tiered compaction merges newer small SSTables into older larger ones. Leveled compaction organizes data into levels and key ranges, moving data incrementally into older levels. Leveled compaction generally uses less disk space, while both strategies try to limit how many files a read examines.

SIZE-TIERED
  small files -> larger files -> larger files

LEVELED
  recent level -> older levels
  key ranges kept more organized
  compaction happens incrementally

Where this design is a good fit

LSM-trees are strong for write-heavy workloads because they turn arbitrary writes into sequential SSTable writes. They also support efficient range queries because data remains sorted. The cost is that reads may inspect multiple files, and background compaction consumes resources while the database serves live traffic.

The same broad idea appears in full-text search: map each word to the IDs of documents containing it, store the mapping in sorted files, and merge files as the index grows.

LSM-TREE
  + high write throughput
  + efficient range scans
  + compact sorted files

THE COST
  - reads may inspect several files
  - absent-key lookups need care
  - compaction uses background resources

What I will carry forward

LSM-trees are not automatically the best storage engine. Their value is that sorted immutable files make merging cheap, keep indexes small, preserve range-query performance, and avoid random disk writes. The design pays for those benefits with recovery logging, multi-file reads, and continuous compaction. A storage engine is always deciding where the work should happen.

These are my personal learning notes from Designing Data-Intensive Applications by Martin Kleppmann.

 

Previous

← How databases write data: logs, indexes, and storage engines