Read the documentation · Browse the source
Most applications do not stay “a database and an API” for long. A document changes, so a worker must run. The worker produces an embedding, so a search index must catch up. A browser expects the result immediately. A scheduled task retries after a deploy. A cache coordinates rate limits and short-lived state. Before long, one application has accumulated a database, Redis, a queue, a scheduler, a realtime service, and a search system—each with its own failure model and operational vocabulary.
I built SubstrateDB to explore a different boundary: one independently deployable runtime for the application beyond CRUD. It is written in Rust and combines JSON documents, durable jobs, cache primitives, vector and keyword search, cron schedules, and browser WebSockets. It does not run application code. Servers and workers remain ordinary external processes; Substrate stores and coordinates their work.
That distinction matters. This is not an attempt to put an entire application inside a database. It is an attempt to make the state transitions between the application’s supporting systems explicit, durable, and inspectable.
The seam that kept causing trouble
The motivating problem was not the number of vendors by itself. It was the gap between them. Consider a familiar flow: save an uploaded document, enqueue extraction, create an embedding, make it searchable, then update the browser. With separate systems, the application becomes the transaction coordinator. If it writes the document but crashes before publishing the job, the upload exists without work. If the worker retries, an older embedding can arrive after a newer document revision. If the search index is asynchronous, “accepted” and “searchable” become different states that the product must somehow explain.
Substrate gives a namespace and logical partition a concrete atomic boundary. A document write and job enqueue can commit together within that boundary. A stable request ID makes an uncertain retry detectable for a bounded retention period. This is not permanent exactly-once execution: workers receive jobs at least once, and external side effects still need idempotency. But the runtime can remove one important ambiguity—the job and the state that created it do not have to diverge.
One binary, separated responsibilities
The server uses Tokio and Axum for asynchronous transport, redb for embedded transactional storage, and OpenRaft for replicated consensus. These are modules in one distribution rather than miniature products that happen to share a logo. The command layer routes a namespace and partition to a deterministic state machine. Documents, indexes, jobs, events, schedules, cache values, and deduplication records can be changed in one redb transaction. In clustered operation, the same command boundary is replicated through a shard’s Raft group.
The application side stays intentionally outside. Connected workers claim jobs with expiring receipts, heartbeat while they work, and acknowledge or reject with the current receipt. An expired receipt prevents a stale acknowledgement, but it cannot stop code already running. If a worker charges a card or sends an email, the protected system must still enforce an idempotency key or fencing token. Bounded concurrent commands, delivery limits, queue pause controls, and explicit overload responses provide backpressure rather than pretending capacity is infinite.
Cron lives in the runtime because schedule ownership and job creation are durable coordination problems. Execution still happens in an external worker or a signed HTTP handler. Timezones, daylight-saving behavior, bounded catch-up, and durable occurrence identities are part of the schedule contract. A lease timeout is not magical cancellation, so overlap prevention remains cooperative when external code continues running.
A concrete upload-to-browser path
Here is how the pieces fit together for a document product:
- The API saves
upload-42 and enqueues embed-42 in one namespace/partition command.
- An external worker claims the job with a lease receipt, reads the document revision, and calls an embedding model outside Substrate.
- The worker submits the dense vector with that source revision. Substrate durably accepts it and returns a visibility sequence.
- The worker waits for that sequence before treating vector search as current. If the document changed while the embedding was in flight, the stale vector is rejected instead of replacing newer work.
- A browser holding a short-lived scoped capability receives the indexed document change over WebSocket and refreshes the relevant view.
The browser credential can subscribe, publish to a permitted channel, or participate in presence; it cannot administer jobs or write documents. Filtered live subscriptions require a covering index and establish an atomic snapshot/cursor handoff before changes begin. Reconnect takes a new snapshot, and a history gap is reported rather than silently skipped. The operator dashboard is a deliberate local exception: an administrator can enter the admin token into the same-origin UI, where it remains in memory, to inspect documents, jobs, cache, vectors, schedules, events, and raw commands.
Search is asynchronous on purpose
Substrate stores caller-supplied named dense or sparse vectors; it does not generate embeddings. Dense fields support cosine, dot-product, and Euclidean scoring. Text queries use BM25, and hybrid queries combine independent vector and lexical rankings with reciprocal-rank fusion. Equality metadata filters are checked against current authoritative documents.
The important contract is visibility. A successful vector submission is durable but not immediately searchable. Every submission receives a sequence, background indexing resolves sequences in order, and search.wait exposes the barrier. Source revisions connect derived vectors to the document version that produced them. Deletes invalidate queued work so a later flush cannot resurrect an old vector.
Exact search is the default. There is also a persisted deterministic neighbor graph, but it is experimental and is not a production-proven HNSW implementation. The repository records a small 2,000-vector development measurement and a tiny smoke run; neither establishes production latency, sustained mixed-load behavior, or million-vector capacity. The one-million single-node and ten-million cluster procedures in the search documentation are experiments still to be run, not achievements.
Cache and durability without protocol cosplay
Cache primitives include TTL values, counters, hashes, lists, sets, sorted sets, fixed-window rate limits, and leases with monotonically increasing fencing tokens. They are durable and replicated through the same command machinery. That is useful when cache-like coordination should survive a restart, but it is not a claim of in-memory Redis performance, and Substrate does not speak the Redis wire protocol.
The same honesty applies to the rest of the API. Substrate has its own HTTP/JSON protocol and typed TypeScript SDK. It is not wire-compatible with MongoDB, RabbitMQ, AMQP, RESP, or ZeroMQ, and its APIs are not fully equivalent replacements. Aggregations and joins are deferred. Exact contracts are more useful here than a broad compatibility promise.
Standalone today, fixed shards when replicated
One process is enough for local development and standalone deployments. Standalone mode provides local durability, not protection from losing the machine. In cluster mode, independent fixed shard groups use explicit replica placement. Quorum writes and reads, leader failover, learner catch-up, and operator-controlled membership movement are implemented. A node outside a shard group can forward to it.
This is not elastic serverless infrastructure. The shard count and discovery peers are configured ahead of time; there is no automatic shard splitter or cluster autoscaler. Cross-partition vector scatter can merge comparable results from an explicit partition list, but there is no global snapshot, and lexical/hybrid ranking stays within one partition. A hosted service and Vercel Marketplace integration are future product directions. Vercel currently hosts the documentation—not the database runtime.
What the evidence says—and does not say
The verification record documents 45 Rust tests after the dashboard work, TypeScript SDK tests, standalone and three-process end-to-end checks, Chromium dashboard workflows, and Linux Docker build/smoke passes. The fault checks include leader shutdown, minority-write rejection, replica restart and catch-up, and explicit placement movement. That is meaningful development evidence.
It is not production-proven scale. The implementation status still lists the hard gates: sustained mixed workloads, production ANN/storage selection, one-million and ten-million vector qualification, fleet-wide resource isolation, automated cluster backup orchestration, upgrade and migration tests, security review, and a longer fault campaign. I would not describe the current build as mission-critical infrastructure.
Run it locally
Rust 1.87 or newer is required. The compiled dashboard assets are embedded in the binary, so Node is not needed for a normal build.
git clone https://github.com/Metatransformer/SubstrateDB.git
cd SubstrateDB
cargo build --release --bin substrate
./target/release/substrate --dev
Then open http://127.0.0.1:7432/dashboard. Development mode binds to loopback and uses the documented local token. For any non-development deployment, configure strong credentials, persistent storage, a trusted internal Raft network, and TLS at the ingress. The operations guide covers durability, limits, backup/restore, security boundaries, and known qualifications.
SubstrateDB is the system I wanted when application state began spilling across too many coordinators. The current implementation proves that the pieces can share a coherent runtime and testable contracts. The next phase is less glamorous and more important: qualify those contracts under sustained load, larger indexes, failures, upgrades, and real application pressure.
Read the SubstrateDB documentation · Inspect the Apache-2.0 source