A short architectural write-up on designing a disk-backed key-value storage engine using Log-Structured Merge Tree (LSM) principles.
Why LogTreeDB?
LogTreeDB started from a curiosity about what really happens below a database API.
Modern databases often feel like black boxes: data goes in, data comes out, and performance magically scales. LogTreeDB was built to peel back those layers and understand how durability, performance, and correctness are achieved at the storage-engine level.
Rather than relying on existing libraries, LogTreeDB focuses on first-principles design, closely inspired by the ideas in Designing Data-Intensive Applications (DDIA).
The Core Problem
Disks are slow at random writes.
Traditional data structures like B-Trees update data in place, which leads to:
- random I/O
- poor write throughput
- complex locking
Log-Structured Merge Trees (LSMs) solve this by never updating data in place. Instead, all writes are sequential, and data is reorganized later.
LogTreeDB is a minimal but correct implementation of this idea.
High-Level Architecture
At a high level, LogTreeDB follows a classic LSM layout:
Each stage has a single responsibility and clear invariants.
Write Path
1. Write-Ahead Log (WAL)
Every write is first appended to a Write-Ahead Log before being applied in memory.
Why this matters:
- guarantees durability
- enables crash recovery
- keeps disk writes sequential
On restart, the WAL is replayed to reconstruct in-memory state.
2. MemTable
After WAL append, writes are applied to an in-memory MemTable:
- sorted by key
- fast reads and writes
- holds tombstones for deletes
Deletes are logical — they are recorded as tombstones rather than removing data immediately.
3. Immutable MemTable
When the MemTable reaches a size threshold:
- it is frozen into an Immutable MemTable
- a new MemTable immediately takes over writes
- flushing to disk happens without blocking new writes
This separation is critical for sustained write throughput.
SSTables (Sorted String Tables)
An SSTable is an immutable, sorted, on-disk file created by flushing an Immutable MemTable.
Properties:
- written once, read many times
- sorted by key
- sequential disk writes
- no in-place updates
In Phase 1 of this project, SSTables used a simple linear scan for reads. More advanced indexing was introduced later (more on that below).
Read Path
Reads traverse layers in priority order:
- Active MemTable
- Immutable MemTable
- SSTables (newest → oldest)
The first matching key wins.
This ensures:
- newer writes override older ones
- tombstones correctly mask deleted values
Deletes & Tombstones
Deletes do not immediately remove data.
Instead:
- a tombstone is written
- it propagates through MemTables and SSTables
- actual deletion happens during compaction
This design guarantees correctness across crashes and multiple on-disk files.
WAL Truncation
Once an Immutable MemTable has been safely flushed to an SSTable and fsynced:
- the WAL is truncated
- disk usage stays bounded
- startup recovery remains fast
Crash Recovery
On startup:
- Existing SSTables are discovered
- WAL is replayed
- In-memory state is rebuilt
At no point is acknowledged data lost.
Design Trade-offs
Advantages
- high write throughput
- sequential disk I/O
- simple concurrency model
Trade-offs
- read amplification
- background compaction complexity
- multiple storage layers
These trade-offs are intentional and fundamental to LSM-based systems.
Current Project Phase
Phase 1 — Minimal & Correct LSM (Completed)
- WAL with crash recovery
- MemTable & Immutable MemTable
- SSTable flush & linear-scan reads
- Tombstones
- WAL truncation after safe flush
Phase 2 — Read Optimization (Completed)
- Sparse index per SSTable
- Binary search over sparse index
- Bounded data scans
- Per-SSTable Bloom filters
- Dynamically sized Bloom filters (bits-per-key)
- SSTable footer-based metadata discovery
Phase 3 — Compaction & Levels (Completed)
- Multi-level storage hierarchy (L0, L1, L2, …)
- Background compaction thread
- L0 → L1 leveled compaction
- K-way merge using SSTable iterators
- Newest-version wins semantics
- Tombstone cleanup during compaction
- Obsolete SSTable deletion
- Disk space reclamation
At this point, LogTreeDB is a fully functional single-node LSM storage engine, closely resembling the core architecture of LevelDB / RocksDB.
Read Optimization (Phase 2)
Phase 2 focuses on reducing read amplification, especially for negative lookups.
Sparse Index
Each SSTable contains a sparse in-memory index mapping:
key → byte offset
Instead of scanning from the beginning of the file, LogTreeDB:
- Binary-searches the sparse index
- Seeks directly to the nearest offset
- Performs a bounded linear scan
This drastically reduces disk I/O while keeping SSTables simple and immutable.
Bloom Filters
Each SSTable also owns a Bloom filter:
- built at flush time
- persisted alongside the SSTable
- loaded on SSTable open
Before any disk seek:
- the Bloom filter is checked
- if negative, the SSTable is skipped entirely
This makes most missing-key lookups complete without touching disk.
Compaction & Levels (Phase 3)
Without compaction, SSTables would grow unbounded, causing:
- high read amplification
- excessive disk usage
Phase 3 introduces leveled compaction.
Levels
Level-0 (L0)
- new SSTables from MemTable flushes
- overlapping key ranges
- read newest → oldest
Level-1+ (L1, L2, …)
- non-overlapping key ranges
- sorted SSTables
- single SSTable consulted per level for reads
Each level has a maximum size. Exceeding it triggers compaction.
Compaction Process
- Pick a compaction plan (e.g. L0 → L1)
- Select input SSTables and overlapping target SSTables
- Create iterators over all inputs
- Perform a k-way merge (newest first)
- Drop obsolete versions and tombstones
- Write new SSTable(s) to the next level
- Atomically swap metadata
- Delete old SSTables
All compaction happens in the background, without blocking reads or writes.
Correctness Guarantees
- newer versions always win
- tombstones suppress older values
- no data loss during crashes
- SSTables remain immutable
Why This Matters
At the end of Phase 3, LogTreeDB demonstrates:
- how real databases achieve high write throughput
- why immutability simplifies correctness
- how background compaction controls long-term performance
- the practical trade-offs of LSM trees
Final Thoughts
LogTreeDB is built to understand systems, not to compete with production databases.
By implementing every layer manually — WAL, MemTables, SSTables, Bloom filters, compaction — it exposes the true mechanics behind modern storage engines.
If you understand LogTreeDB, you understand the heart of LevelDB, RocksDB, and Cassandra.
Running LogTreeDB
LogTreeDB is distributed as a pre-built runnable JAR, so you can try it without cloning the repository or building from source.
Prerequisites
- Java 21+
- a local directory for data storage
Download
wget https://github.com/manojayyanavara/logtreedb/releases/download/v1.0.0/logtreedb-1.0.0.jar
Run LogTreeDB
Choose a data directory (it will be created automatically):
java -jar logtreedb-1.0.0.jar
Example Commands
# Put a key
java -jar logtreedb-1.0.0.jar put key-1 value-1
# Get a key
java -jar logtreedb-1.0.0.jar get key-1
# Delete a key
java -jar logtreedb-1.0.0.jar delete key-1
Restart Safety
You can safely stop and restart the process:
- WAL replay reconstructs in-memory state
- SSTables are rediscovered from disk
- deletes and overwrites remain consistent
This mirrors how real-world LSM-based systems guarantee durability across restarts.
LogTreeDB is intentionally designed as an embeddable storage engine. A networked API and containerized deployment are natural future extensions, but the current form keeps the focus on core storage mechanics.
This post documents the journey of building a storage engine from first principles.