Skip to main content

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​

LanguageBridgePackage
PythonPyO3pip install vectlite
Node.jsnapi-rsnpm install vectlite
RustDirect cratevectlite-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:

FileDescription
<name>.vdbMain database file containing records, metadata, and sparse indexes.
<name>.vdb.walWrite-ahead log for uncommitted writes.
<name>.vdb.annHNSW approximate nearest-neighbor index sidecar.
<name>.vdb.lockAdvisory 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_only mode simultaneously. Read-only mode acquires a shared lock.
  • In-process concurrency: Within a single process, the Database object 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.