Open source · Apache-2.0
One engine for transactions and analytics.
NusaDB is a relational database for a single machine. It gives you serializable transactions, a cost-based planner over a vectorized executor, and commits that are durable before they are acknowledged. All of it runs in one binary. There is no cluster to operate.
-- write and read, one engine, one connection BEGIN; UPDATE stock SET qty = qty - 1 WHERE sku = 'A-77'; INSERT INTO orders (sku, region) VALUES ('A-77', 'east'); COMMIT; SELECT region, count(*) AS n, sum(total) AS revenue FROM orders WHERE placed >= now() - INTERVAL '30 days' GROUP BY region ORDER BY revenue DESC; region | n | revenue --------+------+----------- east | 1204 | 184320.55 west | 980 | 97015.20
What the engine does today
Everything here is implemented and covered by tests. Anything planned but not built is on the limits page instead of in this list.
MVCC and snapshot isolation
Readers work from a snapshot and never block writers. All four standard isolation levels are available, up to serializable snapshot isolation, with savepoints and partial rollback.
Clustered B+tree
Rows live in the leaves of a B-link/B+tree keyed by a row id the engine mints, so reaching a row by primary key does not need a second lookup in a separate heap.
Write-ahead log
Every record carries a CRC32 and is lz4-compressed. A commit is acknowledged only once its record is durable, and recovery replays the log on start-up.
Cost-based planner
Histograms and most-common-value statistics drive join order and index choice. Plans run through a vectorized executor in batches rather than one row at a time.
A broad SQL surface
Window functions, recursive CTEs, set operations, lateral joins, grouping sets, MERGE, ON CONFLICT, views, materialized views, sequences, domains, triggers and routines.
Authentication and access control
SCRAM-SHA-256 over optional TLS, including mutual TLS. Roles, GRANT and REVOKE, and row-level security policies are enforced inside the engine.
It speaks its own protocol
The first thing to settle before planning an integration.
NusaDB does not implement another database's wire format. A client built for a different engine will not connect: the server does not answer a handshake it does not recognise, so from the client's side the socket looks dead. That is a design decision, not an unfinished feature.
The SQL dialect is a separate question. Queries written for other engines generally run unchanged once they arrive over a NusaDB connection. So porting an application means changing how it connects, not rewriting its SQL.
- Default port 5678
- Shell
nusadb-cli, interactive and batch - Drivers Rust, Java, Node, Python, Go, Ruby, PHP, .NET
- Authentication SCRAM-SHA-256, optional mutual TLS
- Bulk transfer
COPY … FROM STDIN/TO STDOUT - Observability Prometheus endpoint
- Databases Many per server, many schemas per database
Numbers from our own runs
These come from the project's test machines, not from a formal benchmark suite. They are here because a release note that only says "faster" is not worth reading.
The first pair is the one to look at. TRUNCATE used to remove rows one
at a time, so its cost grew with the table. It now takes roughly the same time
whichever size you point it at.
Release build, single node, one connection. Your hardware will not produce these exact figures. Nothing here is a comparison against another database.
| Operation | Rows | Time |
|---|---|---|
TRUNCATE | 100,000 | 28.5 ms |
TRUNCATE | 200,000 | 77.8 ms |
COUNT(*) | 1,000,000 | 249 ms |
UPDATE a filtered set | 100,000 of 1,000,000 | 6.0 s |
| Primary-key lookup, 1,000 of them | 1,000,000 in table | 569 ms |
Vector search reaches the same answers as an exact scan once
hnsw_ef_search is raised far enough. Building that index is the slow
part, and the limits page says so.
The row lives inside the index
Most engines keep rows in a heap and have the index point at them, so reading by key costs a descent and then a second fetch. Here the row is the leaf entry. The descent is the read.
- No heap fetch. The key lookup ends at the leaf
- No reader locks. Visibility comes from the stamps on the row
- No background flush to wait on. The log is what makes a commit durable
Running in about a minute
The container image needs no build step. From source you need a Rust toolchain and nothing else.
Container
docker run -d --name nusadb \ -p 5678:5678 \ -v nusadb-data:/var/lib/nusadb \ -e NUSADB_USER=app \ -e NUSADB_PASSWORD=change-me \ nusadb/nusadb docker exec -it nusadb \ nusadb-cli --user app --database nusadb
From source
git clone https://github.com/nusadb/nusadb.git cd nusadb cargo build --release ./target/release/nusadb-server \ --listen 127.0.0.1:5678 \ --data-dir ./data \ --auth-user app:change-me
With no --auth-user and no environment pair, the server accepts every
client without a password and logs a warning at start-up. That is fine on a laptop
and wrong for anything others can reach.
Read the whole thing
Six pages. They are written to be looked things up in, not skimmed once.
Getting started
Install, connect, create databases and schemas, load and export data in bulk, and run statements from scripts.
SQL reference
Types, statements and query features, plus the places where NusaDB deliberately behaves differently and why.
Transactions
Isolation levels, how conflicts surface, the server-side retry for single statements, savepoints and locking.
Clients and protocol
The official drivers, connection settings, TLS and SCRAM, and what implementing the protocol involves.
Configuration
Every server flag, the small-by-default resource limits, a systemd unit, and the metrics that are exposed.
Limits and capacity
What bounds a deployment today: memory-bound data size, log growth, restart time, and what is not built yet.
Where the edges are
NusaDB is before 1.0, and these are the constraints that change how you size a deployment. They are here rather than buried because meeting them during a data load costs far more than reading them now.
Working data must fit in memory
Table pages are held in memory and are not evicted to disk. Past the resident ceiling, inserts are refused with an error naming the limit. A dataset larger than memory does not fit at all. It is not simply slower.
The log is the durable copy
No checkpoint truncates the write-ahead log yet, so the data directory grows with write history rather than with how much data is currently live.
Restart replays the log
Because recovery reads that history, start-up time grows with it. Size restart windows against log size, not table size.
No backup or replication tooling
There is no built-in backup, point-in-time recovery or replica. You can stop and copy the data directory, or export with COPY and reload elsewhere.
Single node
One machine, so no automatic failover. The availability of the deployment is the availability of that host and its disk.
All of these are properties of this release rather than permanent limits.
Read the limits page