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.

Every Nano claim below was checked against the v4.31.1 release (tagged 2026-09-07). 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.


Capability comparison

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 (v4.8.0+) 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.

Notes, row by row

In-process and server access
Access mode is chosen when the database is opened or the server is started. Moving from embedded to server means stopping the embedded process and starting a server on the same data directory — not a live cutover, and not two processes sharing one directory.
Reader / writer concurrency
Nano's storage layer is MVCC (src/storage/mvcc.rs).
Independent explicit transactions
Explicit transactions on an embedded Nano handle share a single process-global slot, shared across clones of the handle. This is the single most important line in the table for anyone porting a multi-threaded SQLite application. On the server, each connection carries its own transaction state (since v3.38.0). See Concurrency and transactions below.
Vector search
SQLite extensions add vector search; index types, maturity, and API differ per extension and per version — check the specific one you intend to use before comparing capabilities. Nano ships VECTOR(n) columns and <-> / <=> / <#> distance operators. The default path is in-process HNSW; product quantization and RocksDB-backed persistent HNSW require building with --features vector-persist, which is not in the default feature set.
Full-text search
SQLite has the more complete text engine today. FTS5 is a persisted inverted index with prefix, phrase, and NEAR queries, pluggable tokenizers including Porter stemming, and a 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 docs/compatibility/fts.md.
Branching and historical queries
Branch scope is the whole database. MERGE 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 (1 hour minimum retention by default, configurable, garbage-collected by VACUUM VERSIONS) and are not a substitute for backups.
Row-level security
Nano's RLS is real — tenant isolation and RLS policies via the embedded and REPL path. But no release to date exposes it over the PostgreSQL wire, the MySQL wire, or REST. If your reason for looking at Nano is enforced multi-tenancy behind a network listener, this does not do it yet.
Encryption at rest
Nano's AES-256-GCM transparent encryption is compiled in by default, but disabled at runtime by default and requires a key (environment variable, file, or cloud KMS). A separate zero-knowledge mode exists, and FIPS 140-3 mode is available via the 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.
Replication
Writes made inside explicit embedded transactions reach the logical WAL (fixed in v4.8.0; they did not before). The first HA tier is compiled in by default but needs runtime configuration. Known limitation: a standby can transiently expose a partially applied transaction, because atomic-apply markers are not emitted yet.
SQL dialect, API, and file format
SQLite has its own dialect and one self-contained database file, with an enormous and mature ecosystem built around that format. Nano speaks PostgreSQL-flavoured SQL plus a SQLite-dialect compatibility layer for common patterns; its store is a data directory, not a single file, and is not SQLite-format compatible. Nano never opens a .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.
Maturity and operational track record
SQLite has decades of production use at enormous scale, with a test suite far beyond what most databases carry — a real, current reason to choose it. Nano is young and moving quickly, with a visible correctness campaign in the changelog; v4.31.1 itself fixes a data-loss-on-upgrade bug that affected 4.30.0 and 4.31.0. Read the changelog before any upgrade, and rehearse upgrades on a copy. Nano is not production-mature in the sense SQLite is, and we are not going to claim otherwise.

The table scrolls sideways on narrow screens, so the four limits that most often decide this comparison are repeated here:

  • Embedded explicit transactions are not independent. Two in-process callers cannot hold overlapping BEGINCOMMIT blocks safely. Server mode is the supported answer.
  • Row-level security is embedded-only. It is not enforced over the PostgreSQL wire, the MySQL wire, or REST in any release so far.
  • MERGE BRANCH does not detect conflicts. It is last-writer-wins and silent. Discard branches and re-apply validated SQL instead.
  • SQLite's full-text search is more capable than Nano's. No stemming, no phrase queries, and no runtime inverted index on the Nano side today.

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.


Concurrency and transactions

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 BEGINCOMMIT 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:

  1. Run the daemon and give each caller its own PostgreSQL connection, over TCP or a Unix socket. Per-connection transaction state is real here, and this is the only path that gives genuinely independent sessions.
  2. If the application must share one embedded handle, it has to coordinate around the entire transaction lifetime — including the error and rollback paths, not just the BEGIN and the COMMIT.
  3. Use single-statement autocommit writes where the workload allows it.

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 have reached the logical WAL since v4.8.0; before that they did not.


When SQLite is the better fit

Outgrowing SQLite is not inevitable, and these are not edge cases.

  • Your application is already well served by it. A working SQLite deployment that meets its requirements is not a problem waiting to be solved. Migration cost is real and the reliability you would be trading away is real.
  • You depend on SQLite-specific extensions or tooling. The format's ecosystem — inspection tools, backup tooling, language bindings, extensions — is a genuine asset, and none of it carries over. Nano cannot open a .sqlite file.
  • You need a full-featured text search engine now. FTS5's persisted index, stemming, and phrase and proximity queries are ahead of Nano's current full-text surface. If search quality is a product requirement rather than a convenience, this alone can settle it.
  • Multiple in-process callers need independent explicit transactions. Nano's embedded API cannot do this today, and the fix is to restructure the application around the server — a larger change than swapping a library.
  • Locale-aware collation matters. Nano compares text by byte order only; there is no locale-aware collation support. If your sorting or comparison semantics depend on a collation, SQLite with ICU is the shorter path.
  • You value a long reliability record over new capability. SQLite has decades of production evidence behind it. Nano has months, and a changelog that still contains upgrade-path fixes.
  • You are extremely constrained. SQLite's footprint is far smaller than a self-contained Nano binary.

When to evaluate Nano

  • You want relational data and vector search in one in-process engine. HNSW is built in on the default build, so there is no separate vector service to run, sync, or keep consistent with the rows it describes.
  • You expect to need network access later. The same data directory can be served over the PostgreSQL and MySQL wire protocols, so growing past a single process does not mean changing engines — though it does mean stopping the embedded process and writing real client code.
  • Multiple clients need independent transactions. Server mode gives you that today; the embedded API does not.
  • You want disposable database sandboxes. Branching is a SQL statement, forks are cheap, and fork-validate-discard is a good fit for migration rehearsals and agent runs — provided you discard rather than merge, given the merge caveat above.
  • You want historical reads as part of the query language. AS OF TIMESTAMP answers "what did this look like before that job ran" without restoring anything, within the retention window.
  • Existing PostgreSQL or MySQL clients are an advantage. Your drivers, ORMs, and 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.

Getting started

Install

# Rust / CLI
cargo install heliosdb-nano

# Python, in-process binding
pip install heliosdb-nano-embedded

Embedded

# 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)

Server

# 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.

Vector and full-text queries

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;

Bringing a SQLite database across

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.


Performance

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.


Further reading

Now also: code-graph + graph-RAG + native MCP

HeliosDB Nano 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. Learn more →

Ready to try HeliosDB?

Get started with HeliosDB in minutes. Open source, free to use.

Get Started Contact Sales