Stele
A from-scratch, append-only, bitemporal analytical database in Rust. Every row records when it was true in the world and when the system learned it, so time-travel and audit are first-class instead of bolted on.
Why I built it
Most databases treat an UPDATE as destruction. The old value is gone, and reconstructing what the data looked like at some past moment — and what you believed at that moment — turns into a pile of audit tables, triggers, and slowly-changing-dimension bookkeeping. For anything where history is the point (finance, audit, regulated data), that’s backwards. I wanted history to be the default, not a thing you bolt on later.
What it does
- Bitemporal core. Two independent time axes: valid time (when a fact is true in the world) and system time (when the database learned it).
AS OFqueries travel on either axis, and nothing is ever destructively overwritten. - Postgres-compatible wire protocol. It speaks the PostgreSQL protocol on port 5454, so existing
psql, drivers, ORMs, and BI tools connect with no special client. - A real query engine. Multi-statement transactions with snapshot isolation, joins, aggregates, and a SQL layer with documented temporal extensions.
- Append-only columnar storage with object-storage tiering and system-time-driven archival.
- Audit-native features: lineage and provenance, tamper-evident audit, and hash-keyed
MERGEfor stable identity across repeated loads.
Decisions I’d defend
- Correctness before speed, and before real data. There’s an explicit “trust gate” — a defined testing bar the engine has to clear before it’s allowed to hold real data. The project has no deadline on purpose.
- Differential testing against DuckDB. A separate oracle crate runs the same queries through both Stele and DuckDB and compares results, so the query engine earns trust instead of asserting it.
- Borrow the protocol, build the engine. Reusing the Postgres wire protocol buys instant tooling compatibility; the effort goes where the novelty is — bitemporal storage and execution.
- Say what it is. Source-available under BSL 1.1 (it converts to Apache-2.0 four years after each release), and I call it source-available rather than pretending it’s OSI open-source.
The shape of the work
Fourteen separable crates (storage, catalog, txn, sql, exec, engine, pgwire, lineage, server, client, cli, and more), roughly 112k lines of Rust, a formal bitemporal-semantics spec, ADRs for the decisions, and signed multi-arch binaries and Docker images on every release.
The four-statement example below is the whole thesis in miniature: create a system-versioned account, update its balance, then read the balance as of one second ago and get the old value back — because history is never destroyed.
CREATE TABLE account (id INT PRIMARY KEY, balance INT) WITH SYSTEM VERSIONING;
INSERT INTO account VALUES (1, 100);
UPDATE account SET balance = 250 WHERE id = 1;
SELECT balance FROM account FOR SYSTEM_TIME AS OF (now() - interval '1 second') WHERE id = 1;
-- → 100
That an account balance can be read at any past point, exactly as it stood and as it was understood, is the kind of primitive an audit, ledger, or revenue system would otherwise spend a lot of engineering to fake.