AAI Agentic Handbook
Problem 98 · Data Engineering & Streaming

LSM Tree: Memtable Flush and SSTable Compaction

HardClassic DSA & systems
01Problem

Build a tiny LSM store: writes go to a memtable that flushes to immutable sorted SSTables when full; reads check newest first; deletes write tombstones; compaction merges SSTables keeping the newest version and dropping tombstones.

02Key insight

LSM trades read amplification for sequential writes. Correctness hinges on recency ordering: memtable > newer SSTables > older ones, and tombstones must shadow older values until a full compaction reaches the bottom level, where they can be dropped. Compaction is a k-way merge keyed by (key, -age).

03Solution

Try it first.

Timeput O(1) amortized (+O(M log M) flush); get O(T log N); compact O(N log T)
SpaceO(N)
tests.py · 4 passing
tests.pypython12 lines
db = LSMTree(memtable_limit=2)
db.put("a", 1); db.put("b", 2)       # flush -> sst0
db.put("a", 10); db.delete("b")      # flush -> sst1 (newer)
db.put("c", 3)
assert (db.get("a"), db.get("b"), db.get("c"), len(db.sstables)) == (10, None, 3, 2)
db.flush(); db.compact()
assert len(db.sstables) == 1 and db.sstables[0] == (["a", "c"], [10, 3])
assert db.get("b") is None and db.get("a") == 10
db2 = LSMTree(memtable_limit=2)
db2.put("a", 1); db2.put("b", 2)     # flush: a=1 on disk
db2.put("a", None)                   # stored None in memtable shadows a=1
assert db2.get("a") is None and db2.get("zz") is None
04Staff-level follow-up

How do you make reads fast and writes durable? Per-SSTable Bloom filters skip tables that can't contain the key, a WAL makes the memtable crash-safe, and leveled (vs size-tiered) compaction trades write amplification for bounded read/space amplification.