Bedrock — a relational database engine
A single-node relational database engine, built in Rust from the storage layer up — buffer pool, indexes, query execution, transactions, and recovery, each implemented from the paper rather than imported from a crate. The target is a real, Postgres-shaped engine: durable, concurrent, and fast, built one rigorously-tested layer at a time.
Why from scratch
Reference implementations explain what a database does. Porting every algorithm to Rust by hand — with no query engine crate, no storage-engine crate, not even a hashing crate — is what forces understanding why each piece exists and where it actually breaks under concurrency.
Target architecture
Components
How it's being built
The first project is a probabilistic data structure, not a database component — deliberately so. Understanding a data structure's *error bound* rather than just its API is the same discipline the rest of the engine demands, just in a smaller, self-contained problem first.
A Count-Min Sketch trades exact frequency counts for a fixed-size matrix and a provable one-sided error bound — it never undercounts, only ever overcounts within a known probability. That asymmetry, and why it's acceptable, is the actual lesson.
Plain LRU is vulnerable to sequential flooding: one full table scan evicts every genuinely hot page in the pool. ARC's two ghost lists let it adapt toward whichever access pattern — recency or frequency — is actually winning, without a fixed policy choice baked in ahead of time.
`Replacer` (`record_access`, `set_evictable`, `evict`, `remove`, `size`) is implemented by both ARC and LRU-K, so the buffer pool manager depends on the trait, not a concrete policy — and the two can be benchmarked against each other on an identical Zipfian access pattern.
The disk scheduler owns a dedicated OS thread and an mpsc queue; buffer-pool code enqueues a request and awaits its completion instead of blocking on the read/write syscall directly. This is the seam where request batching or read-ahead would attach later without touching the buffer pool at all.
A page guard pins its page on construction and unpins — flushing first if dirty — when it drops. Every code path that can return early (an error, a panic during a test) still releases the pin correctly, because it's not a step anyone has to remember.
The next layer is the index: a disk-backed B+Tree with search, insert, and delete, then concurrent access via latch crabbing (and later, optimistic latch coupling) so multiple threads can traverse and modify the tree safely at once.
Each relational operator (scan, filter, join, aggregate) will be its own executor implementing a common `next()` iterator interface — the classic Volcano model — so a query plan is a tree of operators pulled one tuple at a time, the same shape as Postgres's own executor. Above it, a cost-based optimizer picks join order and access paths from table statistics instead of executing whatever order the query happened to be written in.
Lock manager and MVCC-style concurrency control give isolation; write-ahead logging and ARIES-style recovery give atomicity and durability. Together they're what turns a fast storage engine into an actual ACID database — correct under crashes and concurrent writers, not just fast on a single thread.