Keystone logo Durable ยท Ordered ยท Embedded ยท Zero dependencies

Watch a store
keep its word.

Keystone is a durable, ordered key-value store built on a log-structured merge tree, written from scratch in pure Rust with zero dependencies. You can actually watch it work.

No server. No dependencies. Real crash recovery. It lives inside your program as a library and survives a hard kill, so the data you wrote is still there when you open it again.

Open the playground View on GitHub
๐Ÿ—„๏ธ

The gap it fills

A hashmap you serialize to disk is a toy. It loses order, loses data on a crash, and rewrites the whole file on every save. Keystone is an ordered, durable store with a write-ahead log and leveled compaction, embedded with no process to run.

๐Ÿ”

Why it is different

Most stores are a black box. Keystone is a glass box. You can see the memtable fill, flush to L0, compaction merge levels, bloom filters skip tables, tombstones get dropped, and the exact path a Get walks to find an answer.

๐Ÿค–

For people and agents

An AI agent needs local memory that persists, stays sorted for range scans, and does not vanish on restart. A developer needs the same thing without standing up a database. Keystone is one file dependency that does exactly that.

The playground

A live, from-scratch model of the LSM tree. Put keys, delete them, flush the memtable, run compaction, and trace what a Get actually does. Everything below runs in your browser with no network calls.

Memtable seq 0
0 / 8 entries ยท flush at 8
    Write-ahead log
      On-disk levels
      Read path trace
      idle Run a Get to trace the read path.
      How to use this playground

      Every panel updates live after each action. Here is what each control does and what to watch for.

      1. Put a key. Type a key and a value in the first two boxes, then press Put. The pair lands at the top of the memtable, a record is appended to the write-ahead log, and the sequence number ticks up. Put the same key again with a new value to watch the newer version win.
      2. Get a key. Type a key in the third box and press Get. Follow the read path trace at the bottom right. It checks the memtable first, then each on-disk level from newest to oldest, and shows every bloom filter it consults saying "no" and skipping, or "maybe" and reading. The table that answers is highlighted. Try a key you never wrote, like ghost, to see a full miss.
      3. Delete a key. Type a key and press Delete. Nothing is erased. Keystone writes a tombstone, a marker that shadows older values. Get the same key afterward and the trace ends at the tombstone with not found.
      4. Flush the memtable. Press Flush to turn the in-memory memtable into an immutable SSTable at level 0. The memtable empties, the log rotates, and a new card appears in L0 with its key range and bloom filter. The memtable also flushes on its own once it reaches 8 entries.
      5. Compact. Press Compact to merge SSTables downward, L0 into L1, L1 into L2, and onward. Overlapping tables merge, older versions drop, and tombstones are discarded once they reach the bottom level. Compaction also runs on its own when L0 reaches 4 tables.
      6. Random workload. Press Random workload to fire a burst of puts and deletes so you can watch the levels fill and compaction kick in by itself.
      7. Reset. Press Reset to clear everything and start again from an empty store.
      A two minute tour. Put user:42 = alice, then Get user:42 and see it hit the memtable. Press Flush, then Get again and watch it now come from an L0 SSTable after the bloom filter says maybe. Put user:42 = bob, Flush, then Get once more to see the newer version win across two tables. Delete user:42, Get to see the tombstone, then Compact until it reaches the bottom level and the key is gone for good.

      The same store, from the command line

      The playground above is a teaching model. The real engine ships a keystone binary that operates over a directory: an ordered, durable store you drive with one command.

      # every command operates over a --path directory
      $ keystone --path ./data put user:1 alice
      ok
      $ keystone --path ./data put user:2 bob
      ok
      $ keystone --path ./data del user:1
      ok
      $ keystone --path ./data get user:1
      (nil)
      $ keystone --path ./data get user:2
      bob
      $ keystone --path ./data scan user:      # keys in order, by prefix
      user:2=bob
      1 pairs
      $ keystone --path ./data verify          # read every block, check its CRC
      ok: 3 tables, 3 entries verified

      How it works

      Five ideas do all the work. Written in plain terms, matched to what you just watched in the playground.

      1 The architecture

      Keystone is a log-structured merge tree. Fresh writes live in memory in a sorted memtable. When it fills, it becomes an immutable sorted file on disk called an SSTable at level 0. Compaction merges those files into larger, deeper levels over time.

      Each level holds roughly ten times the data of the one above it. New data is cheap to write at the top and slowly settles downward, so writes stay fast and disk stays tidy.

      2 The write path

      Every write first appends a record to the write-ahead log, then updates the memtable. The write is only acknowledged after the log record is safe, so a crash can never lose an acknowledged write.

      Each write also gets a monotonically increasing sequence number. A newer sequence for the same key always wins. That is how updates and deletes work without ever editing data in place.

      3 The read path

      A Get checks the memtable first, then L0 from newest table to oldest, then L1, L2 and downward. The very first hit wins, because it holds the newest sequence for that key. The search stops there.

      If that first hit is a tombstone, the answer is not found. The key was deleted, and older versions further down are ignored. Trace a Get in the playground to see this exact order.

      4 Why bloom filters matter

      Without help, a Get might open every SSTable on disk. Each table carries a bloom filter, a tiny probabilistic index that answers one question, could this key be here.

      A no means definitely absent, so the table is skipped without a disk read. A maybe means open and check. Bloom filters turn a lookup that could touch many files into one that usually touches only the few that could hold the key.

      5 Durability and crash recovery

      The write-ahead log is the safety net. On reopen, Keystone replays the log into a fresh memtable, rebuilding exactly the state that existed before the crash. Data already flushed to SSTables is durable on its own.

      A crash can leave a half-written trailing record. Keystone detects that torn record by its checksum and discards it, then keeps every complete record before it. Every durable write survives.

      6 Compaction and tombstones

      Compaction merges overlapping SSTables into the next level. Along the way it drops overwritten versions, keeping only the newest sequence per key, so space is reclaimed.

      Tombstones are kept while lower levels might still hold the deleted key. At the bottom level there is nothing below to shadow, so the tombstone and the dead key are dropped entirely. Watch the entry counts fall when you compact into the last level.

      How it differs

      Keystone is not a new storage idea. It is the readable, zero-dependency version of the design that powers the production LSM stores, small enough to read end to end.

      RocksDB, LevelDB

      The production LSM engines: battle-tested, richly configurable, and large. Reading one to learn how an LSM tree actually works means wading through years of optimizations. Keystone is the same core design, a memtable, a WAL, immutable SSTables, leveled compaction, and bloom filters, in a single crate you can read in a sitting.

      Serialize a HashMap

      The usual from-scratch store. It loses key order, loses data on a crash, and rewrites the whole file on every change. Keystone keeps keys sorted for range scans, logs every write before it acknowledges, and appends rather than rewriting.

      Embedded stores with big dep trees

      Most Rust embedded stores pull in large dependency trees. Keystone is standard library only, zero external dependencies, with every on-disk structure checksummed and every length bounds-checked, so corrupt bytes surface as a clean error rather than a wrong answer.

      Use it

      A single crate, standard library only. Open a directory as a library, or drive the same engine from the command line.

      Library API

      Db::open loads the manifest and replays the WAL; put, delete, get, and scan over any range; then flush, compact, verify, and close. Tunable Options for memtable size, block size, bloom bits per key, and sync on write.

      CLI binary

      keystone --path DIR with put, get, del, scan (all or by prefix), compact, stats, verify, and demo.

      Correctness gates

      A differential fuzz against a BTreeMap oracle, crash-recovery tests including a torn WAL tail, and a corruption sweep that bit-flips and truncates every structure. All run under cargo test.

      # build, test, and run the gates
      cargo build
      cargo test
      cargo clippy --all-targets -- -D warnings
      cargo build --release
      
      # scale the differential fuzz against the BTreeMap oracle
      KEYSTONE_FUZZ_OPS=200000 cargo test --release differential