Architecture
vectlite is an embedded vector database written in Rust with first-class bindings for Python and Node.js.
Rust Core
The database engine is implemented entirely in Rust (vectlite-core). This provides:
- Memory safety without garbage collection
- Predictable, low-latency performance
- A single codebase shared by all language bindings
- Small binary size with no runtime dependencies
Language Bindings
| Language | Bridge | Package |
|---|---|---|
| Python | PyO3 | pip install vectlite |
| Node.js | napi-rs | npm install vectlite |
| Rust | Direct crate | vectlite-core |
PyO3 and napi-rs generate native extensions that call directly into the Rust core with minimal overhead. There is no IPC, no serialization layer, and no separate server process.
Storage Format
Each vectlite database consists of several files on disk:
| File | Description |
|---|---|
<name>.vdb | Main database file containing records, metadata, and sparse indexes. |
<name>.vdb.wal | Write-ahead log for uncommitted writes. |
<name>.vdb.ann | HNSW approximate nearest-neighbor index sidecar. |
<name>.vdb.lock | Advisory file lock for concurrency control. |
See Storage Format for details.
Indexing
Dense Vectors (HNSW)
vectlite uses the HNSW (Hierarchical Navigable Small Worlds) algorithm for approximate nearest-neighbor search.
- Auto-build threshold: The HNSW index is built automatically once a database reaches 128 or more records. Below this threshold, search falls back to exact brute-force scan, which is faster for small collections.
- Incremental updates: New records are added to the graph incrementally. A full rebuild occurs during
compact()if the index has drifted significantly. - Cosine similarity: All dense search uses cosine similarity as the distance metric. Vectors are normalized internally.
Sparse Vectors (BM25)
Sparse keyword search uses an inverted index with BM25 scoring:
- Terms are extracted using a configurable text analyzer (see
vectlite.analyzers). - Each term maps to a posting list of record IDs and term frequencies.
- At query time, BM25 scores are computed from term frequencies, document frequencies, and average document length.
Fusion Strategies
When both dense and sparse scores are available, vectlite combines them using one of two strategies:
Linear Combination (default)
final_score = dense_weight * dense_score + sparse_weight * sparse_score
Both scores are normalized to [0, 1] before combination. Adjust dense_weight and sparse_weight to control the balance.
Reciprocal Rank Fusion (RRF)
final_score = sum(1 / (rrf_k + rank_i))
RRF combines the rank positions from each retrieval method rather than raw scores. The rrf_k constant (default 60) controls smoothing. RRF is more robust when dense and sparse scores are on different scales.
File Locking and Concurrency
vectlite uses advisory file locks via the .lock file to manage concurrent access:
- Single writer: Only one process can open a database for read-write at a time. Attempting to open a second writer raises
VectLiteError. - Multiple readers: Any number of processes can open the same database in
read_onlymode simultaneously. Read-only mode acquires a shared lock. - In-process concurrency: Within a single process, the
Databaseobject is not thread-safe. Use a mutex or confine access to a single thread. Searches on a read-only database are safe to run concurrently from multiple threads.
Repository Layout
vectlite/
core/ Rust core library (vectlite-core crate)
src/
storage/ File format, WAL, compaction
index/ HNSW and inverted index implementations
search/ Query planning, fusion, MMR
ffi/ Shared FFI helpers
bindings/
python/ PyO3 bindings and Python package
node/ napi-rs bindings and npm package
tests/ Integration tests
The Rust core is the single source of truth. The binding layers are thin wrappers that translate language-native types to and from Rust structures.