LSM Tree: Memtable Flush and SSTable Compaction
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.
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).
Try it first.
tests.py · 4 passing
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 NoneHow 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.