Definition:
LSM Trees are a data structure designed for write-heavy workloads. Instead of updating data in place (like a B-tree), they just append every change to a log, then lazily sort and merge things in the background. The mantra: write fast now, organize later.
In one sentence: Turn random writes into sequential writes, then periodically tidy up.
Analogy β The Receptionistβs Logbook Picture a busy doctorβs office. Patients arrive constantly. The receptionist has two tools:
Tool What happens LSM Equivalent π Logbook on the desk Scribbles every arrival instantly, in order of arrival. Super fast β just append to the page. Memtable + WAL (in-memory + append-only log) ποΈ Filing cabinet During quiet moments, she takes names from the logbook and files them alphabetically into patient folders. SSTables (sorted files on disk) β»οΈ Merging old folders Eventually, she merges duplicate folders, keeping only the latest record for each patient. Compaction Writing (new patient arrives): Just scribble in the logbook. Zero searching. Lightning fast. Reading (find patient info): Check the logbook first, then check the filing cabinet. Slightly more work for reads, but writes fly.
How It Works
WRITE βββΆ [ Memtable (sorted, in RAM) ] β fastest
β
β (when full, flush to disk)
βΌ
[ SSTable (sorted file) ] β disk, immutable
[ SSTable (sorted file) ]
[ SSTable (sorted file) ]
β
β (background compaction)
βΌ
[ Merged SSTable ] β fewer files, garbage collected
Example:
Simplified LSM Key-Value Store in Python This is a dramatically simplified version to illustrate the mechanics. Real LSM engines use skip lists, bloom filters, binary search, and much more.
import bisect
import json
import os
class MiniLSM:
def __init__(self, db_path="minilsm_data"):
self.db_path = db_path
os.makedirs(db_path, exist_ok=True)
self.memtable = {} # in-memory sorted dict
self.sstables = [] # list of filenames on disk
self.memtable_limit = 3 # flush after 3 entries (tiny for demo)
def put(self, key, value):
"""Just write to memtable. Super fast!"""
self.memtable[key] = value
print(f"βοΈ memtable β {key}: {value}")
if len(self.memtable) >= self.memtable_limit:
self._flush()
def get(self, key):
"""Read path: check memtable first, then SSTables (newest first)."""
# 1. Check memtable (fastest, in RAM)
if key in self.memtable:
print(f"π Found '{key}' in memtable")
return self.memtable[key]
# 2. Check SSTables, newest to oldest
for sst_filename in reversed(self.sstables):
with open(sst_filename, 'r') as f:
data = json.load(f)
if key in data:
print(f"π Found '{key}' in {sst_filename}")
return data[key]
return None
def _flush(self):
"""Sort memtable, write to disk as a new SSTable, then clear memtable."""
sst_name = f"{self.db_path}/sst_{len(self.sstables)+1:03d}.json"
with open(sst_name, 'w') as f:
# Sort by key before writing (real SSTables are always sorted)
sorted_data = dict(sorted(self.memtable.items()))
json.dump(sorted_data, f)
self.sstables.append(sst_name)
print(f"π¦ Flushed β {sst_name} ({len(self.memtable)} entries)")
self.memtable.clear()
def compact(self):
"""Merge all SSTables into one, keeping only the latest value per key."""
if len(self.sstables) < 2:
return
merged = {}
# Read from oldest to newest, so newer values overwrite older
for sst_filename in self.sstables:
with open(sst_filename, 'r') as f:
data = json.load(f)
merged.update(data) # newer overwrites older
os.remove(sst_filename) # delete old file
# Write the single merged file
new_sst = f"{self.db_path}/sst_merged.json"
with open(new_sst, 'w') as f:
json.dump(dict(sorted(merged.items())), f)
self.sstables = [new_sst]
print(f"β»οΈ Compacted β {new_sst} ({len(merged)} keys)")
# --- Usage ---
db = MiniLSM()
db.put("name", "Alice") # memtable β
db.put("city", "Paris") # memtable β
db.put("name", "Bob") # overwrites Alice in memtable
db.put("age", "30") # triggers flush! (limit=3 reached)
print("\nReading...")
print(db.get("name")) # checks memtable first
print(db.get("city")) # checks SSTable
db.compact() # merge everything (real DBs do this in background)
Output:
βοΈ memtable β name: Alice
βοΈ memtable β city: Paris
βοΈ memtable β name: Bob
π¦ Flushed β minilsm_data/sst_001.json (3 entries)
βοΈ memtable β age: 30
Reading...
π Found 'name' in memtable
π Found 'city' in minilsm_data/sst_001.json
β»οΈ Compacted β minilsm_data/sst_merged.json (4 keys)
Notice: name: Bob overwrites name: Alice because it was written later β no in-place update, just a newer entry that takes precedence.