HeliosDB Nano is an embedded SQL engine that, like SQLite, runs inside your process, and adds native HNSW vector search, branching and time-travel reads, plus the option to serve the same data over the PostgreSQL and MySQL wire protocols. It does not read SQLite files, and its embedded API does not give separate callers independent explicit transactions. Licensed Apache 2.0.
Last updated
Choose HeliosDB Nano when you want relational SQL and vector search in one in-process engine, with branching, time-travel reads and the option to serve the same data over the PostgreSQL and MySQL wire protocols; keep SQLite when its maturity, single-file format and tooling, FTS5 full-text search, locale-aware collation or independent embedded transactions matter more.
SQLite is the most deployed database in the world, and deservedly so: it is in-process, zero-configuration, exhaustively tested, and backed by a file format and tooling ecosystem that has been stable for two decades. For a very large number of applications it is the correct answer, and staying on it is a legitimate engineering decision rather than a failure to scale.
HeliosDB Nano is a different embedded engine with a different brief: relational SQL and native vector search in one in-process library, with the option to expose the same data directory over the PostgreSQL and MySQL wire protocols when more than one process needs to reach it. Start embedded; add server access as the application grows. This page compares the two honestly, including the places where Nano is currently the weaker choice.
Nano claims on this page are drawn from the Nano documentation and source; the known-limitations page tracks what is open in the current release. Where a capability is partial, scoped to one access path, or behind a non-default build feature, the table says so.
Evaluating HeliosDB Lite instead? Lite is a separate product tier with its own licence, feature set, and deployment model — it is not Nano with more features, and nothing on this page describes it. See HeliosDB Lite.
Nano is split into two columns because several capabilities exist on only one of its access paths. Reading the two as a single merged column will overstate what either one does.
| Capability | SQLite core | SQLite + ext | Nano embedded | Nano server |
|---|---|---|---|---|
| In-process and server access | In-process only | Third-party server wrappers | Rust API, Python, REPL | PostgreSQL, MySQL, REST |
| Reader / writer concurrency | WAL: many readers, one writer | Unchanged | MVCC reads + autocommit writes | Real per-connection concurrency |
| Independent explicit transactions | One writer at a time | Unchanged | Not safe — shared slot | Yes, per connection |
| Vector search | None | Varies by extension | HNSW built in (default) | Same engine, any client |
| Full-text search | FTS5 + BM25 + stemming | Ships in amalgamation | BM25, no stemming | Same |
| Branching & historical queries | None | File-level snapshot tools | Branches + AS OF reads | Same |
| Row-level security | None | Application-level only | Yes | Not reachable over the wire |
| Encryption at rest | None (public build) | SEE (paid) / SQLCipher | AES-256-GCM, off by default | Same |
| Replication | None built in | External WAL-level tools | Reaches logical WAL | Primary / standby / observer |
| SQL dialect, API & file format | Its own dialect, one file, huge ecosystem | Same format | PG-flavoured SQL, data directory | Existing PG/MySQL clients |
| Maturity & operational record | Decades in production | Varies by extension | Young, active correctness work | Same codebase |
Every cell keyed to a fuller note below — read the note before treating a short cell as the whole story, especially for the three rows marked not safe / not reachable / none on Nano's side.
src/storage/mvcc.rs).VECTOR(n) columns and <-> / <=> / <#> distance operators. The default path is in-process HNSW, and Product Quantization is in the default build too: add WITH (quantization='product') to an HNSW index (indexes are unquantized unless you ask). Durable, restart-surviving PQ-HNSW indexes backed by RocksDB need building with --features vector-persist, which is not in the default feature set.bm25() ranking function. Nano's PostgreSQL-shaped surface (tsvector, tsquery, @@, ts_rank / ts_rank_cd) lowercases on Unicode word boundaries and does not stem; phrase and proximity operators parse but degrade to a bag of terms; @@ is true if any query term is present; setweight() and rank weights are accepted and ignored; and CREATE INDEX ... USING gin is accepted as DDL but not consulted at runtime, so @@ walks matching rows. Nano documents this itself in its full-text search compatibility notes.MERGE DATABASE BRANCH moves rows but performs no conflict detection — it is last-writer-wins, with no warning if both sides touched the same row. Fork, validate, discard, and re-apply the validated SQL to main is the safe pattern. Time-travel reads are bounded by retained versions (by default Nano keeps all history; set storage.version_retention, e.g. "7d", to bound it, after which background version GC or VACUUM VERSIONS reclaims older versions) and are not a substitute for backups.CREATE POLICY and enforcement for REST and wire clients are planned. If your reason for looking at Nano is enforced multi-tenancy behind a network listener, this does not do it yet.fips build feature. Encryption seals values, not keys — table names, column names, row ids, and timestamps remain readable on disk, as do HNSW graph snapshots and backup dumps. Enabling it seals new writes only; it does not retroactively seal data already written..sqlite file in place — existing files come across as a one-shot conversion through the converter bundled in the Nano repository. Nano is statically typed where SQLite is dynamically typed, so inserts SQLite accepts can be rejected.The table scrolls sideways on narrow screens, so the four limits that most often decide this comparison are repeated here:
BEGIN…COMMIT blocks safely. Server mode is the supported answer.CREATE POLICY and enforcement over the PostgreSQL wire, the MySQL wire and REST are planned.MERGE DATABASE BRANCH does not detect conflicts. It is last-writer-wins and silent. Discard branches and re-apply validated SQL instead.Two cells in the table are deliberately non-specific. The SQLite + extensions entry for vector search does not name an extension or an index type, because index support in that ecosystem moves and differs by project and version — verify against the extension and release you actually plan to ship. The comparison is otherwise drawn from Nano's own source and compatibility documentation.
This is where a port from SQLite most often goes wrong, so it is worth stating plainly.
What works on the embedded API: concurrent reads, and writes issued as single autocommit statements. MVCC means readers are not blocked by a writer.
What does not work on the embedded API: two callers holding overlapping explicit BEGIN…COMMIT transactions. Explicit transaction state lives in one process-global slot per handle and is shared across every clone of that handle, so independent, overlapping transactions from separate threads are not safe. There are lower-level session primitives in the crate, but they are internal plumbing rather than a supported public API, and they are not a workaround we recommend. A public embedded session API is roadmap, not shipped.
Supported options today, in order of preference:
BEGIN and the COMMIT.One further distinction worth keeping straight: the local crash-recovery WAL is always on and is separate from the logical WAL used for replication. Embedded writes inside explicit transactions reach the logical WAL.
In these cases SQLite is the better choice. Outgrowing SQLite is not inevitable, and these are not edge cases: an SQLite deployment that meets its requirements is not a problem waiting to be solved, and the migration cost and the reliability you would trade away are both real.
.sqlite file; moving means export, reload and verify. SQLite compatibility · Migrating from SQLiteAS OF TIMESTAMP answers "what did this look like before that job ran" without restoring anything, within the retention window.psql muscle memory carry over at the connection layer. Wire-protocol compatibility is about connectivity, not full semantic parity — byte-order-only collation and embedded-only RLS are two concrete current gaps.# Rust / CLI
cargo install heliosdb-nano
# Python, in-process binding
pip install heliosdb-nano-embedded
# Container image (default-feature build)
docker pull ghcr.io/heliosdatabase/heliosdb-nano:latest
# Scratch database in memory
heliosdb-nano repl --memory
# Or initialise a persistent data directory
heliosdb-nano init ./heliosdb-data
heliosdb-nano repl --data-dir ./heliosdb-data
From Python, the binding calls the embedded engine directly — no subprocess, no wire protocol:
import heliosdb_nano
db = heliosdb_nano.EmbeddedDatabase("./heliosdb-data") # or .in_memory()
db.execute("CREATE TABLE notes (id INT, title TEXT, body TEXT)")
db.execute_many(
"INSERT INTO notes (id, title, body) VALUES ($1, $2, $3)",
[(1, "First", "hello"), (2, "Second", "world")],
)
db.query("SELECT * FROM notes WHERE title = $1", ("First",))
db.create_vector_store("emb", 3)
db.insert_vectors("emb", [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
db.vector_search("emb", [1.0, 0.0, 0.0], k=1)
# Stop the embedded process first — one directory, one process
heliosdb-nano start \
--data-dir ./heliosdb-data \
--port 5432 \
--auth scram-sha-256 --password "$HELIOSDB_PASSWORD"
# Connect with any PostgreSQL client
psql -h 127.0.0.1 -p 5432 -U postgres
# Or enable the MySQL listener alongside it
heliosdb-nano start --data-dir ./heliosdb-data --mysql
Keep the Nano version identical across the embedded-to-server switch. Changing access mode and engine version at the same time makes any resulting problem much harder to attribute.
CREATE TABLE docs (
id SERIAL PRIMARY KEY,
title TEXT,
body TEXT,
embedding VECTOR(1536)
);
CREATE INDEX ON docs USING hnsw (embedding vector_cosine_ops);
-- k-NN
SELECT title, embedding <-> '[0.15, 0.25, ...]' AS distance
FROM docs ORDER BY distance LIMIT 10;
-- Full-text, PostgreSQL-shaped (unstemmed; see the table above)
SELECT title, ts_rank_cd(to_tsvector(body), to_tsquery('heliosdb')) AS rank
FROM docs
WHERE to_tsvector(body) @@ to_tsquery('heliosdb')
ORDER BY rank DESC LIMIT 10;
There is no in-place path: Nano does not read the SQLite file format. Conversion is a one-shot export through the converter bundled in the Nano repository, followed by re-testing your queries against Nano. Budget real time for the type-affinity difference — Nano is statically typed, so values SQLite silently accepted into a column can be rejected — and do not assume SQLite-specific statements translate one-for-one. INSERT OR REPLACE and ON CONFLICT DO UPDATE in particular are not equivalent: the former deletes and reinserts the row, which resets unspecified columns to their defaults, while the latter updates in place.
See the migration guides for the current step-by-step.
We do not publish a head-to-head SQLite benchmark, and this page deliberately carries no throughput figures. Relative performance between an embedded MVCC engine and SQLite depends heavily on write pattern, transaction size, durability settings, and whether the workload touches vector or text search at all — a single number would mislead more than it informed. Measure both on your own workload. Published HeliosDB figures, with their fixtures, are on the benchmarks page.
HeliosDB Nano, built from source with these features, adds CREATE EXTENSION hdb_code for SQL-callable LSP, the WITH CONTEXT clause for grounded RAG, eight ingestion adapters (incl. Docling-backed PDF / Office / audio / image), an in-process embedder, and a native MCP server reachable from Claude Code, Cursor, Continue, Codex, and Aider. Equivalents for HeliosDB Lite and Full are planned. Learn more →
Get started with HeliosDB in minutes. Open source, free to use.