DBRaven
Pattern · data storage

LSM-Tree Storage

mature

Summary

Store data as an in-memory memtable backed by a write-ahead log, periodically flushed to immutable sorted-string-tables (SSTables) on disk and merged by background compaction, trading point-read latency and background I/O for sequential-only writes and sustained high write throughput.

Problem

A B-Tree's in-place updates require random I/O and occasional page splits, which caps sustained write throughput well below what sequential I/O can deliver. Workloads that need to absorb a high, continuous write rate (ingestion, time-series, event logs) need a write path that never does a random write, even at the cost of a more expensive read path.

Description

A log-structured-merge-tree (LSM-tree) is the storage engine underneath Cassandra, ScyllaDB, and RocksDB-backed stores, and it answers the write-path question differently than a B-Tree does. A B-Tree updates a row in place: it finds the page holding the row, modifies it, and writes that page back, which means random I/O and occasional page splits when a page overflows. An LSM-tree never modifies data in place. Every write (insert, update, or delete) is first appended to a write-ahead log for durability (Cassandra calls this the commit log), then inserted into an in-memory, sorted structure called the memtable, commonly a skip list. Both steps are sequential appends: no seek, no page split, no read-before-write.

When the memtable reaches a configured size threshold, it is flushed to disk as an SSTable (sorted-string table): an immutable, sorted, sequential file. Because SSTables are immutable, a later write to the same key does not touch the old SSTable; it lands in the current memtable and eventually a newer SSTable. A delete is represented the same way, as a tombstone marker written like any other value, not as an in-place removal, because the row being deleted may already live in an SSTable the write path cannot touch.

This immutability is what makes the read path harder than a B-Tree's. A point read for key K may need to check the memtable, then potentially every SSTable on disk from newest to oldest, since K's most recent version could be in any of them and older SSTables are never rewritten purely because a newer value exists. Two structures keep this bounded in practice. A Bloom filter is a per-SSTable probabilistic structure that can say "this SSTable definitely does not contain K" and let the read skip it entirely; it can also say "K might be present," which costs a real disk read that may turn out to be a false positive, but it can never produce a false negative. A sparse index within each SSTable (an entry roughly every few kilobytes rather than every key, since the SSTable is already sorted) narrows a positive hit to a small byte range instead of a linear scan of the file.

Background compaction is what keeps the SSTable count, and therefore both the Bloom filter checks and the read amplification, bounded. Compaction merges several SSTables into fewer, larger sorted SSTables, discarding obsolete versions of overwritten keys and physically dropping tombstoned rows once they are safe to remove. Cassandra exposes this as a per-table compaction strategy: SizeTieredCompactionStrategy merges similarly-sized SSTables and favors write throughput and space at the cost of read amplification, while LeveledCompactionStrategy organizes SSTables into non-overlapping levels and favors bounded read amplification at the cost of more total compaction I/O. When sustained write volume outpaces compaction throughput, SSTable count grows unbounded and both read amplification and space amplification climb with it; this is the operational risk the lsm_compaction_debt failure mode describes, and it is the primary way an LSM-tree deployment gets into trouble in production.

Tradeoffs

Write amplification (foreground write path)
+0.7

Writes are sequential appends to the WAL and an in-memory memtable: no seek, no in-place page rewrite, no page split. This is favorable relative to a B-Tree's random-write cost on the foreground write path.

Write amplification (background compaction)
-0.3

Every key is rewritten each time compaction merges the SSTable holding it into a new one, potentially several times over the key's lifetime. Total bytes written to disk per byte of user data is frequently higher for an LSM-tree in steady state than for a B-Tree; this is the cost of turning random writes into sequential ones.

Read amplification
-0.4

A point read may need to check the memtable plus multiple SSTables, since a key's current value could be in any of them. Bloom filters and sparse indexes cut this down but do not remove it, and the cost climbs directly with SSTable count, which is unbounded once compaction falls behind (lsm_compaction_debt).

Space amplification
-0.3

Overwritten keys and tombstoned deletes remain physically present on disk until compaction merges the SSTables that hold them. Space amplification is bounded and temporary when compaction keeps pace with writes, and grows without bound when it does not.

Write throughput under high write load
+0.8

This is the pattern's core strength: the memtable absorbs write bursts in memory, the WAL append is O(1) sequential I/O, and there is no read-before-write or page-split cost, so sustained write throughput scales with I/O bandwidth rather than with in-place update cost.

Point-lookup read latency (worst case)
-0.5

Without a well-tuned Bloom filter (low false-positive rate) and a bounded SSTable count, a worst-case point lookup checks many SSTables sequentially, producing tail latency that can be materially worse than a B-Tree's guaranteed single-structure O(log n) lookup.

When to use

Write-heavy workload with sustained high insert or update rate

The memtable and WAL are sequential-only; there is no in-place random write or page-split cost regardless of write volume

Time-series or append-heavy data, where most writes are new keys rather than in-place updates to hot keys

Compaction has less overwritten and tombstoned data to reclaim, keeping read and space amplification closer to their best case

Workload can tolerate the read-amplification cost of checking multiple SSTables per read

Bloom filters and sparse indexes reduce but do not eliminate this cost; a workload that cannot absorb it at all is a poor fit

When not to use

Workload needs consistently low point-read latency with unpredictable key access

Worst case, a point read touches the memtable plus every SSTable that has not yet been compacted away, which a B-Tree's single O(log n) structure does not incur

Deployment cannot tolerate periodic, sustained background compaction I/O

Compaction competes with foreground reads and writes for disk bandwidth; on I/O-constrained hardware this shows up as latency spikes correlated with compaction runs

Operational Requirements

mandatory

Monitor SSTable count and compaction backlog per table

Rising SSTable count is the leading indicator of compaction falling behind write volume, ahead of any read-latency symptom

mandatory

Choose a compaction strategy that matches the workload (size-tiered for write-heavy and space-tolerant, leveled for read-heavy and read-latency-sensitive)

The strategy trades write amplification, read amplification, and space amplification against each other; there is no setting that minimizes all three at once

recommended

Tune Bloom filter false-positive rate for the read workload

A lower false-positive rate reduces wasted SSTable reads on point lookups at the cost of more memory per SSTable

recommended

Monitor tombstone accumulation and the delete-to-compaction reclamation window

Tombstones inflate read amplification and space usage until compaction physically removes them; a high tombstone-to-live-row ratio on a table is an early warning sign

Characteristics

Scales on
writestorage
Implementation complexitymedium
Operational complexityhigh
Scaling ceilingWrite throughput scales with memtable flush rate and available sequential I/O bandwidth, and is largely decoupled from dataset size. The real ceiling is compaction throughput: if sustained write volume exceeds what background compaction can process, SSTable count grows without bound, and both point-read latency and space usage degrade progressively until compaction catches up or is given more I/O and CPU headroom.

Technologies

Canonical

cassandrascylladb

Relationships

Complements

consistent hashingshardingtime series rollup

Basis

Memtable/WAL/SSTable/compaction mechanics are well documented across Cassandra and ScyllaDB engineering references and Database Internals; the write-amplification split between the sequential foreground path and the compaction background cost is the specific nuance most likely to be collapsed into one generic bucket, so confidence is held slightly below patterns with a single unambiguous cost story.

Sources & Claims

LSM-tree writes are first appended to a write-ahead log and inserted into an in-memory memtable (commonly implemented as a skip list), and are only written to disk as an immutable SSTable when the memtable is flushed at a size threshold.

pending

ddia or accepted reference · Petrov, Database Internals, Part I, Chapter 7, Log-Structured Storage

storage-engine-internals-spine batch 4

A Bloom filter lets an LSM read skip an SSTable that provably does not contain a key; a false positive costs one extra disk read, and a false negative is impossible by the data structure's construction.

pending

ddia or accepted reference · Kleppmann, Designing Data-Intensive Applications, Chapter 3, Making an LSM-Tree out of SSTables

storage-engine-internals-spine batch 4

Apache Cassandra exposes compaction strategy as a per-table setting, including SizeTieredCompactionStrategy and LeveledCompactionStrategy, which trade write amplification, read amplification, and space amplification differently.

pending

official documentation · Apache Cassandra documentation: Compaction

storage-engine-internals-spine batch 4

Cassandra represents a deleted row as a tombstone marker rather than an immediate physical removal; the tombstoned data is only reclaimed once compaction merges the SSTables that contain it.

pending

official documentation · Apache Cassandra documentation: About deletes

storage-engine-internals-spine batch 4