NusaDB

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.

Written from scratch in Rust · runs as a single binary or a container image

nusadb-cli
-- 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
Capabilities

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.

transactions

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.

storage

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.

durability

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.

queries

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.

sql

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.

security

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.

Integration

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.

Clients and protocol

  • 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
Measured

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.

OperationRowsTime
TRUNCATE100,00028.5 ms
TRUNCATE200,00077.8 ms
COUNT(*)1,000,000249 ms
UPDATE a filtered set100,000 of 1,000,0006.0 s
Primary-key lookup, 1,000 of them1,000,000 in table569 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.

Storage

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.

root branch branch leaf two comparisons, then the row LEAF ENTRY ROW ID CREATED BY REMOVED BY COLUMNS 4181txn 902 txn 918'A-77', 12, 'east' 4181txn 918 still live 'A-77', 11, 'east' Two versions of one row. A reader compares its own snapshot against these stamps, so it never waits for the writer. Write-ahead log One record per change, on disk before the commit returns. This is the durable copy.
  • 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
Install

Running in about a minute

The container image needs no build step. From source you need a Rust toolchain and nothing else.

Container

shell
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

shell
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
Set a credential before exposing the port

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.

Before you commit data

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

Try it against your own schema

The container image starts in one command, and the getting-started page takes you from an empty database to loaded data.