This project implements a key-value storage engine in Rust. Based on Build Your Own Key-Value Storage Engine by The Coder Cafe.
- Keys are lowercase ASCII strings.
- Values are ASCII strings.
- Implementation is single-threaded (Will be revisited later).
NOTE: Assumptions persist for the rest of the series unless explicitly discarded.
The StorageEngine struct provides the core key-value storage functionality:
- WAL: Every write is first appended to a write-ahead log (
wal.db) and synced to disk for durability. On startup, unflushed WAL entries are replayed into the memtable to recover the last session - Memtable: Writes are buffered in an in-memory
HashMap<String, MemtableEntry>for fast key-value operations. Deletions are represented as tombstones (MemtableEntry::Deleted) rather than immediate removals - SST files: When the memtable reaches 2000 entries, it is flushed to a sorted JSON SST file on disk and the WAL is cleared. Tombstones are written to SST files so deletions survive a restart
- Compaction: Every 10,000 writes, all existing SST files are merged into a minimal set of new files. Duplicate keys are resolved by keeping the newest value; tombstones are dropped entirely. Old SST files are removed and the manifest is updated atomically
- Manifest: Tracks all SST files written; updated atomically via a temp file rename on each flush and compaction
- Negative cache: An LRU cache of recently queried absent or deleted keys to avoid redundant SST scans
- Thread-safe access: Wrapped in
Arc<Mutex<>>for concurrent access across HTTP handlers - Simple interface: Provides
get(),set(), anddelete()methods for basic operations
new(path)- Creates a storage engine rooted atpath, recovering the SST counter from the manifest and replaying any unflushed WAL entries into the memtableset(key: String, value: String) -> Result<()>- Appends to the WAL, inserts into the memtable, and flushes to disk if the memtable is fullget(key: &str) -> Option<String>- Retrieves a value by key; checks memtable, negative cache, then SST files. ReturnsNonefor missing or deleted keysdelete(key: &str) -> Result<()>- Appends a delete record to the WAL and inserts a tombstone into the memtable
The server exposes a REST API on 127.0.0.1:8080 with the following endpoints:
Retrieves the value associated with the given key.
Response:
200 OK- Returns the value as plain text404 Not Found- Key does not exist
Example:
curl http://127.0.0.1:8080/mykeySets or updates the value for the given key.
Response:
200 OK- Value successfully stored500 Internal Server Error- Failed to write to WAL or flush memtable to disk
Example:
curl -X PUT http://127.0.0.1:8080/mykey \
-H "Content-Type: text/plain" \
-d 'Hello, World!'Marks the given key as deleted.
Response:
202 Accepted- Key successfully deleted500 Internal Server Error- Failed to write to WAL
Example:
curl -X DELETE http://127.0.0.1:8080/mykey-
Start the server:
cargo run
-
Store a value:
curl -X PUT http://127.0.0.1:8080/greeting \ -H "Content-Type: text/plain" \ -d 'Hello, World!'
-
Retrieve the value:
curl http://127.0.0.1:8080/greeting # Output: Hello, World! -
Try to get a non-existent key:
curl http://127.0.0.1:8080/nonexistent # Output: 404 Not Found
- Week 1: In-Memory Store
- Week 2: LSM Tree Foundations
- Week 3: Durability with Write-Ahead Logging
- Week 4: Deletes, Tombstones, and Compaction
- Week 5: Leveling and Key-Range Partitioning
- Week 6: Block-Based SSTables and Indexing
- Week 7: Bloom Filters and Trie Memtable
- Week 8: Concurrency