ACID transactions
Pessimistic and optimistic, across read-committed, snapshot and serializable.
Regolith is an ACID, performance oriented, embedded key-value engine for edge systems. An LSM-tree in pure Rust, with a read path that takes no locks and a scan that streams instead of materializing.
cargo add regolith · no C toolchain · no FFI · no async runtime
The LevelDB design done properly: a write-ahead log, a lock-free skip-list memtable, leveled SSTables, compaction on an ordinary OS thread. Small enough to learn in an afternoon, wide enough to build a database product on.
Pessimistic and optimistic, across read-committed, snapshot and serializable.
A snapshot captures a sequence number and ignores anything newer.
One transaction shared across threads, no mutex around it.
A valid prefix of the write history. No gaps, no half-applied batch.
Separate keyspaces, own options, one WAL, one atomic batch.
Full backups, and hardlinked checkpoints that cost almost nothing.
Fold an update in without reading the value first.
Drop or rewrite entries as compaction passes over them.
Build an SSTable outside the database and ingest it whole.
Entries expire on their own, reclaimed during compaction.
Follow a keyspace as it changes.
get_slice borrows the bytes the database already holds.
A miss is refused before it touches disk.
Tickers, histograms, and callbacks on flush and compaction.
A token bucket with priorities, so compaction cannot starve writes.
Level, FIFO and universal.
wasm32-wasip1, and wasm32-unknown-unknown through OPFS.
Options::embedded() targets a 1 to 4 MiB working set.
The same range, read three ways. The only thing that changes is whether the scan is held in memory or streamed through.
peak memory, materialized against streamed
-0.00x
smaller peak. Streaming the same scan holds 792 KiB instead of 34.7 MiB.
collect the whole scan before reading it
one entry held at a time
scan_page, explicit page-sized reads
Open a database, write a batch atomically, take a snapshot, walk a range. The parts that cost you something say so in the type.
use regolith::{Db, Options, WriteBatch}; let db = Db::open("/tmp/my_db", Options::default())?; db.put(b"hello", b"world")?; let mut batch = WriteBatch::new(); batch.put(b"a", b"1"); batch.put(b"b", b"2"); db.write(batch)?; // A snapshot is a point in time. let snap = db.snapshot(); db.put(b"a", b"changed")?; assert_eq!(snap.get(b"a")?.as_deref(), Some(&b"1"[..]));
use regolith::StreamOptions; let mut writer = db.streaming_writer(StreamOptions { max_buffered_bytes: 1 << 20, ..Default::default() }); for (key, value) in huge_source { // takes the buffer, does not copy it writer.put_owned(&key, value)?; } let sequence = writer.finish()?; // Peak is the budget plus one operation, // whatever the stream's length. Each flush // is atomic; the stream as a whole is not.
Pure Rust throughout. Compaction runs on an ordinary OS thread; where there are no threads, set max_background_compactions to 0 and it runs on the calling thread.
| target | status |
|---|---|
| linux, macos (x86_64, aarch64) | full |
| wasm32-wasip1 | full, through a preopened directory |
| wasm32-unknown-unknown | full, through OPFS with Options::wasm() |
| embedded linux | Options::embedded(), 1 to 4 MiB working set |