The contract every backend satisfies is
spec/storage-interface. Its Rust expression is the
NodeStore trait in
ntl-core.
Any database that satisfies it is a conforming backend — including one you
write yourself.The Matrix
“Community” means the trait is sufficient and nobody has published one yet.
Choosing, briefly. Not sure? Use SQLite — it handles both edge and
full-node deployments, and moving later is a config change plus a migration,
not a rewrite. Already run Postgres and want NTL state under the same backup
and access control? Use PostgreSQL. Is your domain data already a graph?
A graph backend lets topology queries be traversals instead of joins.
What Every Backend Must Store
Six categories, regardless of family. This is the part that makes the interface portable:
Signal bodies are optional and off by default. See
spec/storage-interface for which of these may be
memory-only per deployment class.
SQLite
Zero configuration.ntl init creates the database and applies migrations on
first run.
synchronous = "normal" trades a narrow power-loss
window for materially fewer write cycles. Use full where power loss is
likely and the hardware can take the wear — and note that only full lets
the backend honestly report Durable.
PostgreSQL
For operators who already run PostgreSQL and want NTL state where their backups, monitoring, and access control already reach. That is the case for it — an operational convenience, not a performance argument. SQLite is faster for a single node.NodeStore implementation is the remaining piece; use SQLite for a
running node until it lands.
Because NodeStore is synchronous while Postgres drivers are asynchronous,
ntl-store-postgres wraps its calls in a blocking pool at its own crate
boundary. That keeps ntl-core free of an async runtime — see
spec/storage-interface for the reasoning, and the
crate’s own docs for the two traps in that pattern.
In-Memory
MemoryStore in ntl-core is the reference implementation — the
semantics every other backend is checked against. Nothing survives process
exit, so a node re-bootstraps from its configured peers and relearns its
weights.
Graph Backends
A node’s synapse topology is a weighted, directed graph. A graph-native backend can store it as one, which turns questions like “which paths within three hops have carriedQuery signals successfully” into a native
traversal rather than a recursive join.
Where the backend supports it, the storage graph and the synapse topology can
be the same graph: signals deposit state as they propagate, and state
changes emit signals. That is a capability such backends offer, not an
assumption NTL makes.
SiafuDB
SiafuDB is an embedded property graph database for device, edge, and Web3 environments, with an offline-first design. Like NTL it is a project of The Bundu Foundation. It is one backend option among several, not a dependency. NTL’s roadmap does not wait on it, and nothing in the protocol privileges it.Status: planned, behind the
siafu feature flag. SiafuDB is itself in
development.SiafuDB-Kuzu
A high-performance C++ graph database with Cypher queries and vector search, also a Foundation project. Where SiafuDB targets the device, SiafuDB-Kuzu targets analytical work: Cypher over accumulated topology, and vector search over signal payload embeddings. The interesting fit is offline analysis of a learned network — asking why routing converged the way it did, across a fleet, after the fact.Status: planned.
Other Graph Databases
Neo4j, Memgraph, or anything else with a driver can back NTL by implementingNodeStore. Nothing in the protocol privileges one graph database over
another, and the conformance suite will tell you whether yours is correct.
KV Stores
The deduplication cache is the one piece of node state that is naturally a TTL-keyed KV workload, and Redis-style stores do it better than a relational table. A backend MAY serve the dedup cache from a KV store while keeping synapse and journal state elsewhere. The atomic check-and-set requirement in spec/storage-interface §3 still applies:SET key value NX EX ttlsatisfies it — one round trip decides presence and inserts.GETfollowed bySETdoes not. The race it admits is duplicate propagation, which is the exact failure the rule exists to prevent.
Mixing Backends
Nothing requires one backend for everything. A plausible full-node split:
This is a composition you build by implementing
NodeStore over the pieces,
not a configuration NTL ships. If you build one, the conformance suite still
applies to the composite.
Writing a Backend
ImplementNodeStore and run the conformance suite:
- Deduplication is one atomic check-and-set. A read then a write leaves a race, and that race is duplicate propagation.
- Outcome resolution is idempotent. The first receipt wins; a second must not overwrite it. At-least-once delivery makes duplicate receipts routine.
durability() honestly.
Migrating Between Backends
There is no migration tool yet. What works today:- Stop the node.
- Export with the new backend’s own tooling, or accept the relearning cost.
- Update
[storage]and restart.