fenecdb and SQLite

SQLite is an embedded SQL database in one file; sqlite-vec adds vector search to it and FTS5 full-text search. fenecdb is an embedded document database with vector and full-text search built in, which also runs as a server over HTTP.

The facts about SQLite, sqlite-vec and FTS5 on this page were checked on 2026-10-02 against the pages each one links to. fenecdb is at version 0.1.8, and is alpha software.

Which to choose

Choose SQLite when

  • You want the database that is already everywhere: SQLite is "likely used more than all other database engines combined" (sqlite.org), with bindings, ORMs and tools in every language.
  • You want SQL with joins, and transactions you hold open across statements, with savepoints (transactions).
  • Readers must never wait for the writer: in WAL mode they do not (WAL).
  • You need a long track record: 100% branch coverage in its tests (testing), and an on-disk format kept backwards compatible, supported through 2050 (long-term support).
  • Exact vector search over a modest table is enough, through sqlite-vec.

Choose fenecdb when

  • You want an approximate vector index: HNSW with quantization and filtered search (The vector clause). sqlite-vec's approximate indexes are in its alpha releases.
  • You search by meaning and by words in one query, fused by rank (Hybrid search).
  • The page or app should stay in step with a server: a replica with live queries and offline writes (Sync).
  • The same file should also be served to other languages over HTTP, with replicas (Server).

Feature by feature

 fenecdbSQLite + sqlite-vec + FTS5
Where it runs Browser (WebAssembly), Rust, iOS, Android, Flutter, a server over HTTP (Where it runs) Everywhere a C library runs; the browser through the official WebAssembly build since 3.40.0 (release). No server of its own
Browser storage IndexedDB, or a file of the origin private file system in a worker (Keeping a database) kvvfs (localStorage), opfs (a worker and COOP/COEP headers), opfs-sahpool (persistence)
Data model Typed documents in collections, json fields (Objects and paths) Relational tables
Query language FenecQL; builders in JS, Python, Go, .NET, Swift, Kotlin, Dart (FenecQL) SQL
Vector index HNSW (The vector clause) sqlite-vec's vec0; ANN indexes (rescore, DiskANN) in the v0.1.10 alpha releases, "pre-v1, so expect breaking changes" (releases, README)
Quantization f16 storage; int8 and bit codes, re-ordered by the full vectors (Quantized indexes) float, int8 and binary vectors (README)
Filtered vector search Planned with the filter, an exact fallback when the filter leaves too few (A filter and a vector together) Up to 16 metadata columns and 4 partition keys, filtered inside the KNN query (vec0)
Full text BM25 (The text clause) FTS5 with bm25(); unicode61, ascii, porter and trigram tokenizers (FTS5)
Highlights and snippets highlight() and snippet(), as UTF-16 offsets or between tags, by the index's own tokenizer (Highlights and snippets) FTS5's highlight() and snippet(), the marked text between tags (FTS5)
Facets facet, each value's count over every match, beside the page (Facets) None built in; a GROUP BY a facet, a query each
Hybrid search fuse, reciprocal rank fusion (Hybrid search) None built in; FTS5's page has no vector search (FTS5)
Transactions One writer a file; a batch lands whole or not at all; no transaction held open across statements (Single writer) BEGIN … COMMIT, savepoints (transactions); one writer at a time, readers beside it in WAL mode (WAL)
Sync, live queries, offline A replica in the page or app: offline reads, queued writes, live queries (Sync) No sync; the session extension records changesets to apply to another database (sessions)
Access control On the server: a token, JWT rules added to every statement (Scoped access) Embedded: none of its own
Schema and migrations Typed schema, alter collection, a schema declared in code and checked at every open (Schema in code) SQL DDL; migrations by your own code or tools
Clients JS/TS, Python, Go, .NET, Swift, Kotlin, Dart; any language over HTTP (Your language) Bindings in nearly every language; sqlite-vec packages for Python, Node.js, Ruby, Go and Rust (README)
Browser module, brotli 236 KB (below) 341 KB, without sqlite-vec (below)
Licence Apache-2.0 Public domain (copyright); sqlite-vec Apache-2.0 or MIT (README)
Maturity Version 0.1.8, first released in 2026 Supported through 2050 (long-term support); sqlite-vec pre-v1
Details

sqlite-vec's first stable release, v0.1.0, searched by brute force (announcement). Its ANN indexes arrived in v0.1.10-alpha.1 on 2026-03-31: rescore and DiskANN, with IVF "experimental, not enabled"; the latest is v0.1.10-alpha.4, from 2026-05-18 (releases).

FTS5's bm25() has its constants fixed at k1 = 1.2 and b = 0.75 (FTS5). fenecdb's @text also splits Chinese, Japanese, Korean and Thai text into character runs and can index word prefixes (The text clause).

Of the official build's browser storage, opfs-sahpool needs no COOP/COEP headers but "does not support multiple simultaneous connections to the same database" (persistence). fenecdb's openFile also holds its file for one worker; other tabs reach it through the one that holds it, or share a synced replica (Multiple tabs).

Measured

SQLite against fenecdb in one process, 100 000 rows of 128 dimensions, SQLite with no vector extension: its rows are read back and scored by fenecdb's own code. We measured these on one laptop; the method is below.

100 000 × 128fenecdbSQLite
data write, no index936 k rows/s430 k/s
disk size59.3 MB59.8 MB
scalar filter, indexed1.25 ms14.1 ms
vector top-10, exact2.9 ms30.2 ms
vector top-10, approximate0.091 msnot measured
reopen9 ms0.9 ms

Writers and readers at once, a row a write:

 fenecdbSQLite
durable writes/s, 1 / 4 / 16 writers252 / 487 / 516248 / 248 / 258
reads/s from 4 threads, beside 4 writers701 k, p99 75 µs344 k, p99 79 µs
… beside blocks of 1 000 writes213 k, the longest 4.8 ms632 k, the longest 0.6 ms

YCSB's core workloads over a million records, operations a second, the median of three runs (every cell):

YCSB, 1 000 000 recordsfenecdbSQLite
B, 95% reads, 16 threads, buffered545 k47.9 k
A, 50% updates, 16 threads, durable3.22 k0.45 k
C, reads, one thread, after the updates438 k279 k
  • fenecdb writes a record again at its file's end on every update, where SQLite updates its page in place; the file compacts itself once half of it is dead, beside the reads. Before it did, C at one thread read 30.1 k after the updates, where SQLite read 279 k.
  • SQLite opens faster and its readers never wait for a block of writes; fenecdb's do, while the block runs.
  • Not measured yet: sqlite-vec, by brute force or with its alpha indexes; FTS5; and either in the browser.
Method and details

The numbers are make compare's and make concurrency-bench's, as Against SQLite and pgvector and Writers and readers at once report them, on an Apple M-series laptop.

make compare
make concurrency-bench
  • SQLite 3.53, in WAL mode with synchronous=NORMAL, the rows written in one transaction and the index built afterwards. The first table is the median of three runs.
  • SQLite had no vector index. Its exact search reads every row and scores it with the same kernel fenecdb uses, so the difference is in reading the rows, not in the arithmetic.
  • Writers and readers ran in one process, after idle minutes, once: fenecdb's threads share one database behind a lock, SQLite's hold a connection each with a 30 s busy timeout. Durable is an fsync a commit, F_FULLFSYNC for both.

What the browser downloads

The WebAssembly engine alone, as each project publishes it, compressed the way a static file is served.

 brotligzip
fenecdb's module, fenec.wasm 0.1.8236 KB285 KB
SQLite's official build, sqlite3.wasm 3.53.4-build2341 KB393 KB
Method and details

Measured on 2026-10-02: fenecdb's module as make wasm builds it, and dist/sqlite3.wasm from the npm package @sqlite.org/sqlite-wasm 3.53.4-build2, each compressed with brotli -q 11 and gzip -9. A KB is 1 024 bytes. The JavaScript beside each is left out of both. sqlite-vec's WebAssembly size is not published, and is not in the figure.

Moving from SQLite

fenec import reads a SQLite file directly, with no SQLite library installed, and makes a collection of a table.

fenec import data.sqlite --table docs --into articles \
    --vector embed:384 --index "embed@hnsw(cosine)" --dry-run
  • --dry-run prints the schema it would make and writes nothing.
  • Vectors stored as BLOBs of little-endian f32s become vector<N> with --vector.
  • INTEGER PRIMARY KEY becomes the document id.

A search and its filter, in each:

-- sqlite-vec, year a metadata column of the vec0 table
SELECT rowid, distance FROM vec_articles
WHERE embed MATCH ?1 AND k = 10 AND year >= 2024;
get articles select title
  where year >= 2024
  near embed $1
  limit 10
Details

The import reads the file format itself, and resolves untyped columns from the first 1 000 rows. A table with no equivalent type is refused until --cast says what to do (Type mapping). A join becomes lookup or in (get …) (The lookup clause), and a transaction becomes a batch that lands whole (Batches).