HeliosDB Nano

Nano is a single self-contained binary that you can link into your application, run as an interactive REPL, or start as a PostgreSQL- and MySQL-speaking server against the same data directory. Relational SQL and HNSW vector search share one engine, so retrieval-heavy applications do not have to run and synchronise a separate vector service.

Written in Rust, Apache 2.0 licensed. Database branching, time-travel queries, and AES-256-GCM encryption are part of the engine rather than add-ons. Nano is young — the changelog shows an active correctness campaign — so this page states the current limits alongside the capabilities.

A real embedded Nano instance per browser session — no signup, no install.

~32 MB Self-contained binary
~12 MB Compressed download
Apache 2.0 License
PG + MySQL Wire protocols

Size figures are the ones published in the Nano README for the released binary. Build conditions (feature set, target architecture, compression tool) are not yet documented alongside them — measure your own build if the exact number matters to you.

Rust
// Embedded: open a data directory in-process
use heliosdb_nano::EmbeddedDatabase;

let db = EmbeddedDatabase::new("./mydata")?;

db.execute("CREATE TABLE docs (
    id INTEGER PRIMARY KEY,
    content TEXT,
    embedding VECTOR(768)
)")?;

// Relational + vector in one query
let rows = db.query_params("
    SELECT content, embedding <=> $1::vector(768) AS dist
    FROM docs ORDER BY dist LIMIT 10
", &[query_vec])?;
Agent-Ready

Wire Nano into Claude Code & Codex CLI

Ships an AGENTS.md aggregate plus a .claude/skills/ catalogue covering the CLI, REPL meta-commands, public API, and MCP tools. Read the guide →

cargo install heliosdb-nano

Embedded or Server

Nano separates two independent choices: where the data lives (storage mode) and how callers reach it (access mode). Both are chosen when the database is opened or the server is started.

Access mode How you start it What it is for
Embedded library EmbeddedDatabase::new("./mydata") One process owns the data. No network hop, no daemon to supervise.
Interactive REPL heliosdb-nano repl --data-dir ./mydata Ad-hoc SQL, inspection, migrations. Single-user, direct access.
Foreground server heliosdb-nano start --data-dir ./mydata Several clients over PostgreSQL wire (add --mysql for MySQL). Each connection gets its own session.
Background daemon heliosdb-nano start --daemon --pid-file ./heliosdb.pid Same as above, detached. heliosdb-nano status / stop manage it.

Storage mode is a separate axis

Persistent (a data directory on disk) or in-memory (--memory, or EmbeddedDatabase::in_memory()). Any access mode can use either. In-memory can still be dumped on shutdown with --dump-on-shutdown so an ephemeral database survives as a file.

Modes are not a live cutover

One data directory is owned by one process at a time. Moving from embedded to server means stopping the embedded process and starting a server against the same directory — not running both at once. Keep the Nano version identical across the switch, and add a PostgreSQL or MySQL client to your application; the embedded calls do not carry over unchanged.

Planning that transition? See the migration guides and the concurrency and transactions deep dive.

Four Things It Does Well

Local AI memory

Relational tables and HNSW vector search live in the same engine, and the vector-search feature is in the default build — so the join between metadata and embeddings is an ordinary SQL join, with no separate vector service to run and keep in sync. Product Quantization and restart-surviving indexes are a separate opt-in build (vector-persist); the default build rebuilds the index in memory.

Agent experiments

Branching is real and cheap: CREATE DATABASE BRANCH, USE, MERGE, DROP, scoped to the whole database. MERGE moves rows but has no conflict detection — it is last-writer-wins. For anything conflict-sensitive, fork, inspect, then discard and re-apply validated SQL to main rather than merging.

Historical investigation

Any read can be anchored to a past moment with AS OF TIMESTAMP. Old versions are reclaimed by a watermark-based collector — the default minimum retention is one hour, configurable, with VACUUM VERSIONS to force a pass. Time travel answers "what did this look like then" within the retained window. It is not a backup.

Shared services

PostgreSQL and MySQL wire compatibility is about client connectivity — your existing drivers, ORMs and admin tools connect — not full semantic parity with either server. Two concrete gaps today: collation is byte-order (C) only, with no locale support; and row-level security is reachable from the embedded and REPL paths but not over the wire. See current limitations.

Native Vector Search

Vector types and operators are part of the SQL engine rather than an extension, and the same implementation serves the embedded and server paths. The vector-search feature ships in the default build; Product Quantization and on-disk index persistence are an opt-in build (vector-persist).

  • HNSW indexing with configurable M and ef_construction parameters
  • Cosine similarity, L2 distance, and inner product metrics
  • Works with any embedding model (OpenAI, Cohere, HuggingFace)

Durable PQ-HNSW (opt-in vector-persist):

  • Restart-survivable — index restores from RocksDB on open(); no rebuild after a process restart
  • ~16× less resident RAM at recall-safe defaults — PQ codes in RAM, full vectors on disk, exact two-stage rerank (measured recall@10 0.987 vs 0.989 exact)
  • Online deletes with neighbour repairremove() + compact() keep recall stable under churn
  • Filtered KNN in one traversalsearch_filtered() evaluates row predicates inside the graph walk, not after
  • F32 / F16 / I8 rerank dial — trade recall for rerank-store footprint

Throughput depends on dimensionality, index parameters and hardware — measure on your own fixtures. Deep dive: Building a Persistent, Quantized, Deletable HNSW in Rust · benchmarks: vector benchmarks · how-to: Build a RAG App on Nano.

SQL
-- Create a table with vector column
CREATE TABLE documents (
    id INTEGER PRIMARY KEY,
    title TEXT,
    content TEXT,
    embedding VECTOR(768)
);

-- Create an HNSW index (PQ requires a
-- vector-persist build)
CREATE INDEX idx_docs_embedding
ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);

-- Semantic similarity search.
-- A bound parameter needs an explicit dimension:
-- $1::vector(768), not a bare ::vector.
SELECT title, content,
       embedding <=> $1::vector(768) AS distance
FROM documents
ORDER BY distance
LIMIT 10;

Database Branching

Git-like branching at the storage layer. Copy-on-write branches let you test a schema change, run an A/B experiment, or give an agent a sandbox without duplicating the dataset. Branch scope is the whole database.

  • Copy-on-write branch creation
  • CREATE DATABASE BRANCH, USE, MERGE, DROP SQL syntax
  • Branch from another branch, optionally anchored with AS OF
  • Schema and migration rehearsal away from main

MERGE has no conflict detection. It moves rows, but where both sides changed the same row the result is last-writer-wins, with no conflict report. The conflict_resolution= option now fails loudly rather than silently doing nothing. For anything conflict-sensitive, treat a branch as disposable: fork, inspect, discard, then re-apply the SQL you validated to main.

A branch is not a security boundary. Branching isolates storage through copy-on-write. It does not sandbox an agent's external side effects — network calls, file writes, API requests — and nothing in the engine claims an isolation guarantee beyond storage.

SQL
-- Fork main into a disposable sandbox
CREATE DATABASE BRANCH feature_auth
FROM main;

USE DATABASE BRANCH feature_auth;

-- Try the change
ALTER TABLE users
ADD COLUMN role TEXT DEFAULT 'user';

INSERT INTO users (name, role)
VALUES ('admin', 'superuser');

-- Inspect, then throw the branch away and
-- re-apply the validated SQL to main.
-- Safer than MERGE, which is last-writer-wins.
USE DATABASE BRANCH main;
DROP DATABASE BRANCH feature_auth;

Time-Travel Queries

Anchor a read to a past moment with the AS OF clause. Useful for audit questions, for working out what changed before a bug, and for pulling back rows an UPDATE or DELETE overwrote — as long as the versions are still retained.

  • AS OF NOW, AS OF TIMESTAMP, AS OF TRANSACTION, and AS OF SCN anchors
  • Timestamp literals are read as UTC unless they carry an explicit offset
  • Watermark-based version GC — one hour minimum retention by default, configurable
  • VACUUM VERSIONS forces a reclaim pass
  • Audit and "what changed" investigation without standing up a separate history table

Time travel does not replace backups. It can only reach versions the engine still retains, and it lives inside the same store as your live data — so it does not survive losing that store. Keep a real backup using heliosdb-nano dump and restore. The two solve different problems.

SQL
-- Query data as it existed earlier today
SELECT * FROM orders
AS OF TIMESTAMP '2026-09-11 09:00:00';

-- Or with an explicit offset
SELECT * FROM orders
AS OF TIMESTAMP '2026-09-11 09:00:00 -03:00';

-- Compare current against a past snapshot
SELECT a.id, a.balance,
       b.balance AS prev_balance
FROM accounts a
JOIN (
    SELECT id, balance FROM accounts
    AS OF TIMESTAMP '2026-09-10 09:00:00'
) b ON a.id = b.id;

-- Reclaim superseded versions
VACUUM VERSIONS;

BM25 + Vector Hybrid Search

HeliosDB Nano combines keyword search (BM25 scoring) with vector similarity in a single query. Reciprocal Rank Fusion (RRF) and Maximal Marginal Relevance (MMR) merge both result sets in the engine.

  • BM25 full-text scoring — TF-IDF based relevance ranking with configurable k1 and b parameters, built into the SQL engine.
  • Hybrid search — Combine WHERE content MATCH 'query' (keyword) with embedding <=> $1 (semantic) in one query.
  • RRF / MMR fusion — Reciprocal Rank Fusion merges ranked lists. MMR maximizes diversity in results. Both available as SQL functions.
  • Compiled query plansPREPARE COMPILED pre-optimizes hot-path queries. Cached execution plans skip planning overhead on repeated calls.
SQL
-- Keyword search (BM25)
SELECT title, bm25_score(content, 'database')
FROM articles
ORDER BY bm25_score DESC LIMIT 10;

-- Hybrid: keyword + vector with RRF
SELECT title,
  rrf(
    bm25_score(content, 'database scaling'),
    1.0 - (embedding <=> $query_vec)
  ) AS score
FROM articles
ORDER BY score DESC LIMIT 10;

-- MMR for diverse results
SELECT * FROM mmr(
  'articles', 'embedding',
  $query_vec, 10, 0.7
);

Native Graph Queries

Query graph relationships over your relational data without standing up a separate graph database. Nano adds adjacency-list storage and graph traversal functions alongside SQL, vector and full-text in the same binary.

  • Graph adjacency lists — Store and query relationships directly.
  • Path traversal — Shortest paths, connected components, and neighborhood queries via SQL functions.
  • Multi-model in one binary — Relational SQL, HNSW vector search, BM25 full-text and graph traversal share one engine and one file format.
  • RAG + knowledge graphs — Combine semantic search with graph structure for GraphRAG without additional infrastructure.
SQL
-- Create graph edges
CREATE TABLE edges (
  src INT, dst INT,
  rel TEXT, weight FLOAT
);

-- Find neighbors
SELECT dst, rel, weight
FROM graph_neighbors('edges', 42);

-- Shortest path
SELECT * FROM graph_shortest_path(
  'edges', 1, 99
);

-- GraphRAG: semantic + structure
SELECT n.title, n.embedding <=> $q
FROM graph_neighbors('edges', 42) g
JOIN nodes n ON n.id = g.dst
ORDER BY n.embedding <=> $q
LIMIT 5;

Code-Graph — Your Source as a Queryable Database

Most code-search tools live outside the database, with their own storage to scan and re-index. Nano makes the AST a first-class table: CREATE EXTENSION hdb_code; CREATE AST INDEX ON src/, and your codebase becomes queryable in SQL. Build with --features code-graph to enable it and the heliosdb-nano code-graph subcommand.

  • SQL-callable LSPlsp_definition, lsp_references, lsp_call_hierarchy, lsp_hover, lsp_document_symbols, helios_lsp_rename_preview / helios_lsp_rename_apply. The last two land write-side refactors with sha256 conflict checking.
  • Multi-language — Rust, Python, TypeScript, TSX, JavaScript, Go, Markdown, SQL out of the box, plus a runtime grammar registry (register_grammar + register_extractor) for plugging in any tree-sitter grammar.
  • Time-travel + branch on every callON BRANCH '<name>' per-call override and AS OF combine in either order. RAII branch guard restores state on every early-return path.
  • Auto-reparse + git-hook CLI — tree stays current as you commit. hdb_code.pause() / resume() for migrations.
  • In-process embeddings — build with --features code-embed and Nano embeds locally with fastembed-rs + BGESmallENV15 (384-dim, ~30 MB model cache, no on-disk impact on the binary). External HttpEmbedder remains an option.
  • Diff helpershelios_lsp_references_diff, helios_lsp_body_diff, helios_ast_diff. Accept any AS OF ref: {"now": true}, {"commit": "sha"}, {"timestamp": "iso"}.
SQL
-- Index your repo as a queryable AST
CREATE EXTENSION hdb_code;
CREATE AST INDEX ON repo (path, lang, content);

-- Where is this function defined?
SELECT path, line FROM lsp_definition('execute_query');

-- Time-travel + branch on the same call
SELECT * FROM lsp_definition('execute_query')
  ON BRANCH 'feat/refactor'
  AS OF TIMESTAMP '2026-04-15T00:00:00Z';

-- Rename refactor with conflict check
SELECT * FROM helios_lsp_rename_preview(
  'old_name', 'new_name'
);

-- Diff helpers
SELECT * FROM helios_lsp_references_diff(
  'main', 'feat/refactor'
);

-- Languages registered (system view)
SELECT name, source FROM hdb_code_languages;

Graph-RAG — Grounded Retrieval in SQL

RAG pipelines usually glue together a vector store, a graph database, an entity linker, and a re-ranker — four services to deploy and keep in sync. Nano collapses them into one engine. WITH CONTEXT is a SQL clause; _hdb_graph.nodes and _hdb_graph.edges are queryable tables; entity linking and auto-projection happen on insert.

  • Cross-modal graph — code symbols, doc paragraphs, email threads, and issue comments live in the same node/edge tables and join naturally.
  • Centrality re-ranking + vector prefilter(1−α)·distance − α·centrality. Prefilter-aware HNSW wrapper over-fetches candidates, applies row-level prefilters, then re-scores.
  • Semantic-Merkle invalidationCREATE SEMANTIC HASH INDEX exposes subtree hashing at the SQL layer. Only re-embed what changed.
  • Six ingestion adaptersgraph_rag_ingest_docs, _email, _issues, _qa — plus Docling-backed PDF / Office / audio / image.
  • Vector-similar entity linkergraph_rag_link_vector emits MENTIONS edges with weight = similarity; threshold-gated cosine top-k.
SQL + ingestion
-- Ingest a PDF, an Office doc, audio, an image
SELECT graph_rag_ingest_pdf('/docs/handbook.pdf');
SELECT graph_rag_ingest_office('/contracts/2026-Q1.docx');
SELECT graph_rag_ingest_audio('/calls/standup.m4a');
SELECT graph_rag_ingest_image('/diagrams/topology.png');

-- Ground an LLM query with subgraph context
SELECT answer
FROM ask_llm('What changed in the auth flow last quarter?')
WITH CONTEXT (
    seed_text   = 'auth flow',
    seed_kinds  = ['function', 'doc'],
    edge_kinds  = ['IMPORTS', 'REFERENCES', 'MENTIONS'],
    hops        = 3,
    rerank      = 'centrality'
);

-- Incremental re-embed via semantic-Merkle
CREATE SEMANTIC HASH INDEX IF NOT EXISTS
  graph_rag_chunks_hash ON _hdb_graph.nodes;

Native MCP Server for Coding Agents

Coding agents — Claude Code, Cursor, Continue, Codex, Aider — speak Model Context Protocol. Nano serves it from the same process: tool discovery (tools/list), execution (tools/call), and a JSON-RPC 2.0 handshake, without a wrapper process or glue server. Build with --features mcp-endpoint.

  • Three transportsPOST /mcp (HTTP JSON-RPC), /mcp/ws (WebSocket), /mcp/sse (Server-Sent Events). Plus stdio for sandboxed agents.
  • Hardened defaults — JWT auth, Unix-socket transport for sandboxed agents, public-bind opt-in only.
  • Auto-registered tool cataloguemcp_tool! macro registers via the inventory crate. BM25, hybrid search, graph operations, and helios_graphrag_search surface automatically.
  • Streaming progressnotifications/progress messages flow over WS and stdio when the client opts in via _meta.progressToken.
  • Single-shot discoverytools/list?verbose=true, helios/info JSON-RPC method, GET /mcp/info route. One round-trip for serverInfo + capabilities + verbose tool catalogue + resource list.
  • HTTP POST + SSE pairing — process-static session table keyed by Mcp-Session-Id. POSTs paired with an open SSE channel get progress events forwarded while the POST returns the final response.
MCP transports
# Discover tools (verbose)
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list",
       "params":{"verbose":true},"id":1}'

# helios/info: one-shot discovery
GET http://localhost:8080/mcp/info

# Streaming progress over WebSocket
ws://localhost:8080/mcp/ws
> {"jsonrpc":"2.0","method":"tools/call",
   "params":{"name":"helios_graphrag_search",
             "arguments":{"seed_text":"auth flow"},
             "_meta":{"progressToken":42}},"id":2}
< notifications/progress {"token":42, "message":"seeding"}
< tools/call response <final>

# Or via Claude Code / Cursor MCP config
{
  "mcpServers": {
    "heliosdb-nano": {
      "url": "http://localhost:8080/mcp",
      "auth": {"type":"bearer","token":"$HDB_TOKEN"}
    }
  }
}

Built-in Backend-as-a-Service

Nano includes a BaaS layer in the same binary that runs the database: auth, a REST API, realtime subscriptions and file storage, with no external service to deploy.

  • REST API with 19 PostgREST-compatible filter operators (eq, like, in, gt, ...)
  • Built-in Auth: email/password signup, JWT sessions, token refresh
  • OAuth2: Google and GitHub login with PKCE and automatic user creation
  • Realtime WebSocket: subscribe to INSERT/UPDATE/DELETE on any table
  • Argon2id password hashing, signed URLs, API key authentication
  • Swagger UI at /docs + OpenAPI 3.0 spec at /openapi.json

Row-level security policies are enforced on the REST endpoints via JWT claims. Note that RLS is not currently enforced for clients arriving over the PostgreSQL or MySQL wire protocols — see current limitations.

Terminal
# REST API
$ curl localhost:8080/rest/v1/users?role=eq.admin&select=id,name

# Auth — built-in signup and JWT
$ curl -X POST localhost:8080/auth/v1/signup \
  -d '{"email":"alice@example.com","password":"s3cret"}'

# OAuth — redirect to Google login
$ curl localhost:8080/auth/v1/authorize?provider=google

# Realtime — WebSocket subscription
wscat -c ws://localhost:8080/realtime/v1/websocket
> {"event":"phx_join","topic":"realtime:public:orders"}

Encryption & Security

Encryption is compiled into the default build, but it is off at runtime until you configure a key. Installing Nano does not encrypt your data on its own — that is a deliberate activation step.

  • Transparent Data Encryption (TDE) with AES-256-GCM — compiled in by default, disabled at runtime by default, activated by supplying a key
  • Key sources — environment variable, key file, or a cloud KMS
  • Zero-Knowledge Encryption (ZKE) — a separate mode using client-held keys
  • FIPS 140-3 mode — opt-in via the fips compile-time feature
  • Authentication — trust, password, md5, and SCRAM-SHA-256 for the PostgreSQL listener; mysql_native_password and caching_sha2_password for MySQL
  • TLS--tls-cert / --tls-key, shared by both listeners
  • Row-Level Security — real, via the tenant manager, but reachable only from the embedded, REPL and REST paths today
Terminal
# Encryption is compiled in, but inactive
# until a key is supplied. Nothing is
# encrypted at rest by default.

# Supply a key, then start the server
$ export HELIOS_ENCRYPTION_KEY="$(cat ./tde.key)"
$ heliosdb-nano start --data-dir ./mydata \
    --auth scram-sha-256 --password "$PGPASS" \
    --tls-cert ./server.crt --tls-key ./server.key

# FIPS 140-3 mode is a build-time choice
$ cargo install heliosdb-nano --features fips

PostgreSQL and MySQL Wire Protocols

Nano implements both wire protocols natively, so existing clients, drivers, ORMs and admin tools connect to it directly. This is client connectivity, not full semantic parity with PostgreSQL or MySQL — expect to test your queries rather than assume every behaviour matches.

  • PostgreSQL wire protocol over TCP and Unix socket, with trust / password / md5 / SCRAM-SHA-256 auth
  • MySQL wire protocol behind --mysql, with 5.7/8.0 compatibility modes and prepared statements
  • Works with psql, DBeaver, pgAdmin, DataGrip, and the standard MySQL clients
  • Driver and ORM support: psycopg2, SQLAlchemy, Diesel, SQLx, Prisma, GORM, node-postgres, JDBC
  • SHOW DATABASES / TABLES / COLUMNS / VARIABLES and information_schema for tool compatibility
  • JSONB document type with GIN indexes; window functions and CTEs
  • TLS (STARTTLS) using the same certificates for both listeners

For the current state of SQL-surface compatibility, see the compatibility documentation rather than a single headline percentage.

Terminal
# Start one server with both listeners
$ heliosdb-nano start --data-dir ./mydata --mysql

# Connect with a PostgreSQL client
$ psql -h 127.0.0.1 -p 5432

# Connect with a MySQL client — same server,
# same tables, a different protocol
$ mysql -h 127.0.0.1 -P 3306

mysql> SELECT * FROM users;
+----+-------+-------------------+
| id | name  | email             |
+----+-------+-------------------+
|  1 | Alice | alice@example.com |
+----+-------+-------------------+

One Server, Many Drivers

A single running Nano server accepts PostgreSQL and MySQL clients at the same time, against the same tables. That is genuinely useful for mixed-language teams — and it is a different thing from running the embedded API alongside a server.

  • Mixed-stack teams — a Python service on psycopg2 and a PHP service on mysqli can talk to one server concurrently, each with its own session.
  • CMS ecosystem — WordPress, Drupal, Joomla, Laravel, Symfony, Rails and Django all speak MySQL or PostgreSQL, so they connect without middleware.
  • Changing driver is not a data migration — but it is not free either: SQL dialect differences still surface, so re-test your queries rather than assuming a connection-string swap is the whole job.
  • Embedded is an alternative, not an addition — the embedded API and a running server are two ways to reach one data directory, used one at a time. Two processes opening the same directory is a locking hazard, not a supported topology.
One server, two protocols
# One server process owns ./mydata
$ heliosdb-nano start --data-dir ./mydata --mysql

# Concurrent clients, independent sessions
$ psql  -h 127.0.0.1 -p 5432
$ mysql -h 127.0.0.1 -P 3306

# The embedded API is the OTHER way to open
# the same directory — stop the server first.
let db = EmbeddedDatabase::new("./mydata")?;

Measured on Fixtures

Single-machine, release build, criterion methodology. These are acceptance thresholds from the Graph-RAG work, not a competitive benchmark — reproduce them on your own hardware and report variance.

Workload Target Measured
WITH CONTEXT mean (10k-node fixture, 100 queries) ≤ 500 ms 62 ms
Entity linker precision (hand-labelled fixture) ≥ 80 % 100 %

Source: tests/with_context_bench.rs, tests/linker_precision.rs.

WordPress Without a Separate Database Server

WordPress expects to talk to MySQL. Because Nano serves the MySQL wire protocol from the same binary that holds the data, a WordPress install can run against it without a separate MySQL server: 37/37 tests pass, no db.php drop-in, standard wpdb.

One less moving part

Deploy WordPress with one database process instead of a separate MySQL instance to provision, credential and supervise.

Branching for staging

CREATE DATABASE BRANCH staging gives a copy-on-write clone for testing plugins and themes without a mysqldump-and-restore cycle. Discard it when you are done — remember MERGE is last-writer-wins.

Time-travel investigation

SELECT * FROM wp_posts AS OF TIMESTAMP '...' reads a post or setting as it was, within the retention window. Useful for working out what a plugin changed — not a substitute for backups.

Semantic content search

Native HNSW vector search means WordPress content can be searched by meaning without adding Elasticsearch or Algolia to the stack.

Encryption at rest

AES-256-GCM TDE is handled by the database, so no WordPress plugin is involved. It is off until an operator supplies a key — it is not automatic for every install.

Backup with dump / restore

Back up with heliosdb-nano dump (zstd by default, --append for incrementals, or --dump-schedule on a cron expression) and recover with restore --verify. Copying a live data directory is not a consistent backup — use the dump path.

Frameworks That Connect

Any framework that speaks MySQL or PostgreSQL can point its connection string at a Nano server.

Framework Protocol HeliosDB Nano
WordPress / Drupal / JoomlaMySQL✓ Native
Laravel / Symfony (PHP)MySQL / PG✓ Both
Ruby on RailsMySQL / PG✓ Both
Django (Python)MySQL / PG✓ Both
Express / Fastify (Node.js)PG / MySQL✓ Both
Rust (SQLx / Diesel)PG / Embedded✓ Both

Nano vs SQLite

SQLite is an excellent, extraordinarily well-tested embedded database, and for a great many applications it is the right answer. Here is the short version of how Nano differs; the full comparison, including when SQLite is the better fit, is on its own page.

Capability SQLite HeliosDB Nano
Server access No built-in network listener; third-party wrappers exist Embedded, plus built-in PostgreSQL and MySQL wire servers
Independent explicit transactions One write transaction at a time; concurrent readers in WAL mode Per-connection in server mode. Not on the plain embedded API — see concurrency
Vector search Not in core; extensions exist — check the specific extension and version for its index type HNSW in the engine, in the default build, same in both modes. Product Quantization and index persistence are an opt-in build
Branching & historical queries Not built in Copy-on-write branches (MERGE is last-writer-wins) and AS OF within the retention window
File format & ecosystem Its own dialect and a mature, ubiquitous file format with decades of tooling PostgreSQL-flavoured SQL with a SQLite-dialect shim. Not file-format compatible — import via an external converter
Maturity Decades of production use across an enormous install base — a real reason to choose it Young, with an active correctness campaign visible in the changelog. Evaluate it on your workload
Full comparison, including when to keep SQLite →

Two Ways to Start

Embedded first, server when you need it. Both examples use the shipped CLI and the published binding — run them against your own install and tell us if anything differs.

A. Embedded — SQL + vector search in-process

The published Python binding is heliosdb-nano-embedded, which imports as heliosdb_nano. It is version-locked to the engine release. No server, no wire protocol — the engine runs inside your process.

Python
# pip install heliosdb-nano-embedded
import heliosdb_nano

db = heliosdb_nano.EmbeddedDatabase("./mydata")

db.execute("""
    CREATE TABLE IF NOT EXISTS docs (
        id        INTEGER PRIMARY KEY,
        title     TEXT,
        embedding VECTOR(4)
    )
""")

db.execute_many(
    "INSERT INTO docs (id, title, embedding) VALUES ($1, $2, $3)",
    [
        (1, "vector search", "[1.0,0.0,0.0,0.0]"),
        (2, "backups",       "[0.0,1.0,0.0,0.0]"),
        (3, "hnsw tuning",   "[0.9,0.1,0.0,0.0]"),
    ],
)

# A bound parameter needs an explicit dimension
rows = db.query("""
    SELECT id, title, embedding <=> $1::vector(4) AS distance
    FROM docs ORDER BY distance LIMIT 3
""", ("[1.0,0.0,0.0,0.0]",))

for r in rows:
    print(r)

# {'id': 1, 'title': 'vector search', 'distance': 0.0}
# {'id': 3, 'title': 'hnsw tuning', 'distance': 0.0061162710}
# {'id': 2, 'title': 'backups', 'distance': 1.0}

Executed against heliosdb-nano-embedded 4.31.1 from PyPI; the output above is the real result. The equivalent Rust API is EmbeddedDatabase::new(path) / in_memory() with execute, execute_params, query_params.

B. Server — start it, then connect with psql

When callers need independent sessions or network access, run the same binary as a server. Each connection gets its own transaction state.

Terminal
# cargo install heliosdb-nano

# Start a server on the loopback interface
$ heliosdb-nano start --data-dir ./mydata \
    --listen 127.0.0.1 --port 5432

# Or detached, managed by the CLI
$ heliosdb-nano start --data-dir ./mydata --daemon
$ heliosdb-nano status
$ heliosdb-nano stop

# Connect with a stock PostgreSQL client
$ psql -h 127.0.0.1 -p 5432 -U postgres -d heliosdb

heliosdb=> CREATE TABLE docs (
             id INTEGER PRIMARY KEY,
             title TEXT,
             embedding VECTOR(4));
CREATE TABLE

heliosdb=> SELECT id, title,
             embedding <=> '[1,0,0,0]'::vector AS dist
           FROM docs ORDER BY dist LIMIT 3;
 id | title         |    dist
----+---------------+-------------
  1 | vector search |         0.0
  3 | hnsw tuning   | 0.006116271
  2 | backups       |         1.0
(3 rows)

# Back it up — dump, don't copy a live data dir
$ heliosdb-nano dump --data-dir ./mydata \
    --output ./backup.heliodump --compression zstd
$ heliosdb-nano restore --input ./backup.heliodump \
    --target ./restored --verify

Exercised end-to-end with a stock psql client against a locally started server, and the dump/restore round-trip verified into a fresh data directory. A literal vector string can use a bare ::vector cast; a bound parameter needs ::vector(N).

Concurrency and Transactions

Nano's embedded and server paths have genuinely different transaction models. Picking the wrong one is the most common way to be surprised by this engine, so here is the whole picture.

What works embedded

Storage is MVCC, so concurrent reads are fine, and single-statement autocommit writes are fine. For the large class of applications that read a lot and write one statement at a time, the embedded API is straightforward.

What does not work embedded

Independent, overlapping explicit transactions are not safe on the plain embedded API. Explicit transaction state lives in a single process-global slot per handle, shared across clones — so a second caller that issues BEGIN while another transaction is open does not get its own transaction.

What works in server mode

The PostgreSQL and MySQL wire daemon keeps real per-connection transaction state. Two clients can hold two independent explicit transactions at the same time, each committing or rolling back on its own. This is the supported way to get independent sessions today.

What to do about it

  1. Run the daemon and give each caller its own connection. Unix socket or TCP, PostgreSQL or MySQL — each connection is an independent session. This is the recommended answer whenever more than one caller needs its own transaction.
  2. If the application must share one embedded handle, it has to coordinate around the entire transaction lifetime — from BEGIN through every commit, rollback and error path — not just around the statement that opens it. A lock that covers only the start or only the end of a transaction will not make this safe.
  3. Use single-statement autocommit where that is sufficient. It avoids the problem entirely and is the simplest embedded pattern.

Raw per-session primitives do exist inside the engine, but they are internal building blocks rather than a documented, supported public API — they are not a workaround, and you should not build on them. A proper public embedded Session API is roadmap, not shipped.

Two different things called "WAL"

The local crash-recovery WAL is always on, backed by RocksDB, and is what makes an unclean shutdown recoverable. The logical WAL is the replication stream that feeds standbys. They are separate mechanisms, and only the second one is about replication.

Writes made inside explicit transactions do reach the logical WAL and replicate — this was broken before v4.8.0 and is fixed. One known limitation remains: a standby can transiently observe a partially-applied transaction, because atomic-apply markers are not emitted yet. If a standby is serving reads that must never see a half-applied transaction, account for that.

Full detail, including worked examples: Concurrency and transactions →

Compatibility and Current Limitations

These are the current constraints as of v4.31.1. We would rather you hit them on this page than in production.

Collation is byte-order only

Sorting and comparison use C / byte-order semantics everywhere. There is no locale-aware collation. If your application depends on linguistic sort order, this will be visible.

RLS is not enforced over the wire

Row-level security is real and works from the embedded, REPL and REST paths — but it is not reachable for clients connecting over the PostgreSQL or MySQL wire protocols in any release to date. Do not treat it as a wire-facing tenancy boundary today.

Embedded explicit transactions

Independent overlapping explicit transactions are not safe on the plain embedded API. Use server mode for independent sessions. See concurrency and transactions.

Branch MERGE has no conflict detection

MERGE moves rows, but concurrent edits to the same row resolve last-writer-wins with no conflict report. Prefer discard-and-reapply for anything conflict-sensitive.

No direct SQLite file access

Nano does not open .sqlite files, and the format is not shared. It accepts many SQLite SQL idioms through a dialect compatibility layer, so moving an existing database means exporting it and reloading it into Nano, then verifying the result yourself. See the migration guides.

Binary size is not yet methodical

The published ~32 MB / ~12 MB compressed figures come from the README without stated build conditions, and the CI size guard is not currently operational. Treat them as indicative and measure your own build if it matters.

Replication has a visibility caveat

Explicit-transaction writes replicate (fixed in v4.8.0), but a standby can transiently see a partially-applied transaction because atomic-apply markers are not emitted yet.

Upgrade carefully

v4.31.1 fixes a data-loss-on-upgrade bug that affected opening a pre-4.31.1 store on 4.30.0 or 4.31.0. Take a dump before any version change, and do not combine a version bump with a mode switch.

For the current SQL-surface compatibility detail, see the Nano documentation.

Straight Answers

Is Nano based on SQLite?

No. It is an independent engine written in Rust. It includes a SQL-dialect compatibility layer that accepts many SQLite idioms, which is a different thing from sharing SQLite's code or file format.

Can Nano open an existing SQLite file?

No. There is no direct .sqlite file access and the two file formats are unrelated. Moving an existing database is a one-time migration — export the schema and data from SQLite, reload it into Nano, and verify row counts and checksums on both sides independently. After that the SQLite file is no longer the live database. See the migration guides for the current recommended workflow.

Is Nano multi-user?

Over the wire, yes — the PostgreSQL and MySQL server listeners handle multiple clients, each with its own session and its own transaction state. Embedded is different: explicit transactions are not independent across callers sharing a handle. See concurrency and transactions.

Can embedded threads run independent transactions?

No — not on the plain embedded API. Explicit transaction state is a single process-global slot per handle, shared across clones, so two threads cannot each hold their own open transaction. Concurrent reads and single-statement autocommit writes are fine. For independent transactions, use server mode.

When should I use server mode?

When callers need independent transaction sessions, or when they need to reach the database over a network. The daemon gives you both today. Remember it is a stop-then-start handoff onto the same data directory, not a live switch.

Does PostgreSQL wire compatibility mean full PostgreSQL compatibility?

No. It means your PostgreSQL clients, drivers and tools can connect. Two concrete gaps today: collation is byte-order only with no locale support, and row-level security is not enforced for wire clients. Test your queries rather than assuming parity.

Can embedded writes replicate?

Yes. Writes inside explicit transactions reach the logical WAL and replicate — this was broken before v4.8.0 and has been fixed. The remaining caveat is that a standby can transiently observe a partially-applied transaction, because atomic-apply markers are not emitted yet.

Are branches a security boundary?

No. Branching isolates storage through copy-on-write and nothing more. It does not sandbox an agent's external side effects — network calls, file writes, API requests all still happen. If you need a containment boundary for agent actions, build it outside the database.

Does time travel replace backups?

No. Time travel only reaches versions the engine still retains — the default minimum retention is one hour, and version GC reclaims older ones — and it lives inside the same store as your live data, so it does not survive losing that store. Use heliosdb-nano dump and restore for real backups. Copying a live data directory is not a consistent backup.

When should I keep SQLite?

Often. SQLite's maturity, ecosystem and file format are real advantages, and plenty of applications need nothing Nano adds. Rather than compress that into a sentence here, we wrote it out properly: see when SQLite is the better fit →

Who is Nano For?

Applications that want a real SQL engine and vector search close to the code, without running a database server for it — and the option to add one later.

Desktop Applications

Embed a full SQL engine with vector search and branching directly in a desktop app, shipped as part of your binary with no install step for the user.

Edge & IoT

Run on edge devices and constrained hosts with offline-first behaviour. Check the binary size against your own target — measure the build you actually ship.

AI Prototypes & Agent Memory

Build RAG pipelines, semantic search and agent memory without provisioning a vector service. Metadata and embeddings join in ordinary SQL.

Single-Server Deployments

Run a workload on one host with MVCC, crash-recovery WAL and optional encryption, without cluster overhead. Evaluate it against your workload first.

W

WordPress & CMS

Serve a PHP CMS over the MySQL wire protocol from the same process that holds the data, removing the separate MySQL server from the deployment.

Source, Releases & Docs

Nano is Apache 2.0 and developed in the open. The changelog is the most honest record of where the engine is — including its bug fixes.

Source & releases

github.com/HeliosDatabase/HeliosDB-Nano — source, tagged releases and the full CHANGELOG. Current release: v4.31.1.

Install

Rust / CLI: cargo install heliosdb-nano (or cargo add heliosdb-nano to embed it). Python embedded binding: pip install heliosdb-nano-embedded, imported as heliosdb_nano.

Compare & migrate

Nano vs SQLite for the full comparison, comparisons across the tier range, and the migration guides for moving an existing application.

Ready to try HeliosDB?

Start embedded, add server access when you need it. Apache 2.0, source on GitHub.

Or ask your AI coding agent to install it for you

Copy this into Claude Code, Codex CLI, ChatGPT, Gemini, OpenCode, or any agent with shell access.