An LSM tree moves work from scattered updates toward buffered writes and later merging. That is a design trade, not a promise that every disk operation becomes sequential or that random writes always beat a B-tree. The useful question is whether foreground writes, background compaction, reads, and retained versions fit the same resource budget.
Buffering changes when work happens
RocksDB normally inserts writes into a memtable and can record them in a write-ahead log. WAL enablement and synchronization are separate durability choices; merely using an LSM layout does not promise that every acknowledged write survives power loss. A full memtable becomes an immutable sorted table. Flushes create files in level zero, while application writes continue through other available buffers.
With leveled compaction, level-zero files may overlap in key range. Deeper levels normally form one sorted run per level, divided among multiple nonoverlapping SST files. This is not one file per level. File boundaries help a lookup select candidates; compaction merges overlapping ranges into replacement files. Dynamic level sizing can also leave intermediate levels empty, so a fixed picture of every level being populated is misleading.
Reads still pay for versions
A key may have older values in several runs. A lookup must choose the visible version, and a deletion marker must stop an older value from reappearing. Bloom filters can reject candidate files that definitely lack the key; a positive answer still requires further checking. They can help successful overall lookups by skipping unrelated files before reaching the file containing the answer. They are not exclusively useful for absent keys.
Compaction rewrites data to reduce this search and space burden. Leveled and universal policies commonly trade write, read, and space amplification differently, but workload, compression, caching, and configuration determine the result. Compare like accounting: SST bytes written, WAL bytes, device writes, and logical application bytes are different quantities. A ratio without its numerator and denominator cannot size storage bandwidth reliably.
When it breaks
When flush or compaction work cannot keep up, immutable buffers and level-zero files can accumulate, reads can become more expensive, and the engine may slow or stop foreground writes. A nonzero historical stall counter alone does not diagnose today’s bottleneck. Inspect recent stall time, pending work, file counts, storage latency, and application throughput together. Raising a threshold may defer pressure while increasing the eventual recovery cost.
Deletes do not instantly reclaim every older value. A tombstone cannot safely disappear while a lower run can still expose the value it hides, or a retained snapshot requires relevant history. Reclaiming space depends on the compaction coverage and version-retention rules. Dividing physical bytes by zero live bytes also produces no finite space-amplification ratio; report absolute retained bytes for an entirely deleted dataset.
Check it quickly
Save this as lsm_model.py and run Python 3. The standard-library program is an explicit in-memory model of three flushed runs and complete-history compaction. It does not execute RocksDB, create SST files, perform I/O, simulate its scheduler, or measure latency. None of the printed counts should be described as production benchmark results.
from pprint import pprint
# A semantic model: sorted in-memory runs, not RocksDB or a disk benchmark.
runs = [] # newest run first
writes = 0
sequence = 0
def flush(operations):
global writes, sequence
memtable = {}
for key, value in operations:
sequence += 1
memtable[key] = (sequence, value)
run = dict(sorted(memtable.items()))
runs.insert(0, run)
writes += len(run)
def get(key, tables):
for run in tables:
if key in run:
return run[key][1] # None is a tombstone: stop searching.
return None
flush([("a", "one"), ("b", "old")])
flush([("a", "two"), ("c", "three")])
flush([("b", None)])
assert [get(k, runs) for k in "abc"] == ["two", None, "three"]
assert writes == 5 and sequence == 5
# Dropping a tombstone from only the newest run resurrects old b.
unsafe = [{k: v for k, v in runs[0].items() if v[1] is not None}] + runs[1:]
assert get("b", unsafe) == "old"
# Merge ALL history, with no retained snapshots, before dropping tombstones.
latest = {}
for run in reversed(runs):
latest.update(run)
merged = {k: v for k, v in sorted(latest.items()) if v[1] is not None}
writes += len(merged)
assert [get(k, [merged]) for k in "abc"] == ["two", None, "three"]
assert (len(merged), writes, sequence) == (2, 7, 5)
pprint({"live_keys": list(merged), "logical_operations": sequence,
"table_records_written": writes, "record_write_ratio": writes / sequence,
"unsafe_partial_merge_resurrects": get("b", unsafe)})
The first flush stores a and b; the second replaces a and adds c; the third deletes b. Reading across all runs returns the newer a, no b, and c. Deliberately dropping only the newest tombstone resurrects the older b, demonstrating why a locally empty-looking deletion record cannot simply be discarded.
The safe model merge examines every run and assumes no snapshots. It retains two values and writes two more table records, giving seven record writes for five logical operations. This is a record-count ratio of 1.4, not byte-based write amplification: variable value sizes, the WAL, compression, metadata, and intermediate real compactions are absent.
For an actual engine, reproduce representative inserts, updates, deletes, and reads in an isolated database, record options and hardware, then inspect its measured counters. An SQLite timing comparison cannot establish RocksDB behavior, and a backlog bandwidth difference cannot directly predict disk-fill time without a model of retention, reclamation, throttling, and available capacity.
Sources
- RocksDB overview, memtables, optional WAL, persistence and compaction, checked 2026-10-15.
- RocksDB leveled compaction, sorted runs, files, level sizing and compaction pressure, checked 2026-10-15.
- RocksDB Bloom filters, candidate filtering and false positives, checked 2026-10-15.
- RocksDB snapshots, version retention during compaction, checked 2026-10-15.