C++ Apache-2.0

vectorlite

Fast, SQL powered, in-process vector search for any language with an SQLite driver

1

1yefuwang1

Dernière activité 13 sept. 2026
1yefuwang1/vectorlite

361

étoiles

11

forks

20

issues ouvertes

cpp17hnswpython3sqlite3vector-database

Ce README est souvent en anglais.

NPM Version PyPI - Version

Overview

Vectorlite is a Runtime-loadable extension for SQLite that enables fast vector search based on hnswlib and works on Linux x64, Windows x64 and macOS Apple Silicon (arm64). It provides fast vector search capabilities with a SQL interface and runs on every language with a SQLite driver.

Rust is the main and only extension implementation. The Cargo crate owns the SQLite virtual table, scalar functions, parsers, registry and index policy. Native C++ is retained only for hnswlib, Google Highway SIMD operations, and their thin C ABI shim; the ops tests and benchmarks remain native. CMake and scikit-build-core invoke Cargo for normal source and wheel builds. Public package names and vectorlite.so / vectorlite.dylib / vectorlite.dll filenames are unchanged. The Cargo workspace uses root Cargo.toml and Cargo.lock; Rust sources and retained native ops live under vectorlite/. See the architecture and contributor guide.

For motivation and background of this project, please check here.

Below is an example of using it in sqlite CLI shell:

-- Load vectorlite
.load path/to/vectorlite.[so|dll|dylib]
-- shows vectorlite version and build info.
select vectorlite_info(); 
-- Calculate vector l2(squared) distance
select vector_distance(vector_from_json('[1,2,3]'), vector_from_json('[3,4,5]'), 'l2');
-- Create a virtual table named my_table with one vector column my_embedding with dimention of 3
create virtual table my_table using vectorlite(my_embedding float32[3], hnsw(max_elements=100));
-- Insert vectors into my_table. rowid can be used to relate to a vector's metadata stored elsewhere, e.g. another table.
insert into my_table(rowid, my_embedding) values (0, vector_from_json('[1,2,3]'));
insert into my_table(rowid, my_embedding) values (1, vector_from_json('[2,3,4]'));
insert into my_table(rowid, my_embedding) values (2, vector_from_json('[7,7,7]'));
-- Find 2 approximate nearest neighbors of vector [3,4,5] with distances
select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[3,4,5]'), 2));
-- Find the nearest neighbor of vector [3,4,5] among vectors with rowid 0 and 1. (requires sqlite_version>=3.38)
-- It is called metadata filter in vectorlite, because you could get rowid set beforehand based on vectors' metadata and then perform vector search.
-- Metadata filter is pushed down to the underlying index when traversing the HNSW graph.
select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[3,4,5]'), 1)) and rowid in (0, 1) ;

Official releases and CI support Linux x64, Windows x64 and macOS Apple Silicon (arm64) only. Python wheels and npm packages are provided for these platforms; the macOS arm64 wheel requires macOS 11.0 or newer. Intel macOS is not supported. Install with:

# For python
pip install vectorlite-py
# for nodejs
npm i vectorlite

For other languages, vectorlite.[so|dll|dylib] can be extracted from the wheel for your platform, given that a *.whl file is actually a zip archive.

Vectorlite is currently in beta. There could be breaking changes.

Highlights

  1. Fast ANN(approximate nearest neighbors) search backed by hnswlib. Vector query is significantly faster than similar projects like sqlite-vec and sqlite-vss. Please see benchmark below.
  2. Works on Linux x64, Windows x64 and macOS Apple Silicon (arm64).
  3. A fast and portable SIMD accelerated vector distance implementation using Google's highway library. On my PC(i5-12600KF with AVX2 support), vectorlite's implementation is 1.5x-3x faster than hnswlib's when dealing vectors with dimension >= 256.
  4. Supports all vector distance types provided by hnswlib: l2(squared l2), cosine, ip(inner product. I do not recomend you to use it though). For more info please check hnswlib's doc.
  5. Full control over HNSW parameters for performance tuning. Please check this example.
  6. Predicate pushdown support for vector metadata(rowid) filter (requires sqlite version >= 3.38). Please check this example;
  7. Index serde support. A vectorlite table can be saved to a file, and be reloaded from it. Index files created by hnswlib can also be loaded by vectorlite. Please check this example;
  8. Vector json serde support using vector_from_json() and vector_to_json().

API reference

Vectorlite provides the following APIs. Please note vectorlite is currently in beta. There could be breaking changes.

Free-standing Application Defined SQL functions

The following functions can be used in any context.

vectorlite_info() -- prints version info and the best SIMD target chosen by Highway at runtime.
vector_from_json(json_string) -- converts a json array of type TEXT into BLOB(a c-style float32 array)
vector_to_json(vector_blob) -- converts a vector of type BLOB(c-style float32 array) into a json array of type TEXT
vector_distance(vector_blob1, vector_blob2, distance_type_str) -- calculate vector distance between two vectors, distance_type_str could be 'l2', 'cosine', 'ip' 

In fact, one can easily implement brute force searching using vector_distance, which returns 100% accurate search results:

-- use a normal sqlite table
create table my_table(rowid integer primary key, embedding blob);

-- insert 
insert into my_table(rowid, embedding) values (0, {your_embedding});
-- search for 10 nearest neighbors using l2 squared distance
select rowid from my_table order by vector_distance({query_vector}, embedding, 'l2') asc limit 10

Virtual Table

The core of vectorlite is the virtual table module, which is used to hold vector index and way faster than brute force approach at the cost of not being 100% accurate. A vectorlite table can be created using:

SQL vector inputs and outputs are little-endian float32 blobs. The declared storage type can be float32, float16 or bfloat16; half-precision tables quantize on insert/query and dequantize when reading the vector column. Supported metrics are l2 (squared L2), ip and cosine (normalized inner-product distance).

-- Required fields: table_name, vector_name, dimension, max_elements
-- Optional fields:
-- 1. distance_type: defaults to l2
-- 2. ef_construction: defaults to 200
-- 3. M: defaults to 16
-- 4. random_seed: defaults to 100
-- 5. allow_replace_deleted: defaults to true
-- The index is always held in memory. Persist or restore it explicitly with the
-- operation/path commands shown below.
create virtual table {table_name} using vectorlite({vector_name} float32[{dimension}] {distance_type}, hnsw(max_elements={max_elements}, {ef_construction=200}, {M=16}, {random_seed=100}, {allow_replace_deleted=true}));

Breaking change: explicit index persistence

The optional third file-path argument to CREATE VIRTUAL TABLE has been removed. Existing SQL using that argument is rejected. Indexes are no longer automatically loaded when a table is created or automatically saved when the connection closes.

To migrate, remove the path argument, then issue an explicit load command after creating the table when you want to restore an existing index:

-- Old API (no longer supported):
CREATE VIRTUAL TABLE my_table USING vectorlite(
    embedding float32[128], hnsw(max_elements=10000), '/path/to/index.bin'
);

-- Current API:
CREATE VIRTUAL TABLE my_table USING vectorlite(
    embedding float32[128], hnsw(max_elements=10000)
);
INSERT INTO my_table(operation, path) VALUES ('load', '/path/to/index.bin');

For a new index, skip load. Explicitly save any changes you want to retain before closing the connection; closing without saving loses the in-memory changes. If an existing SQLite database stores a virtual-table declaration with the old third argument, that declaration also needs migration; changing only new table-creation SQL is not sufficient. Legacy index-file compatibility is described below.

Persist an index to disk, or restore a saved index into an in-memory table:

-- Save the current in-memory index to a file (overwrites if it exists).
insert into {table_name}(operation, path) values ('save', '/path/to/index.bin');
-- Load a saved index into a freshly created table. Loading replaces the table's
-- current in-memory index; on any error the existing index is left unchanged.
insert into {table_name}(operation, path) values ('load', '/path/to/index.bin');

New saves use a versioned envelope recording the vector dimension, element type (float32, float16 or bfloat16), distance metric, normalization policy and native word size/endianness. These must match the receiving table on load. A successful save atomically replaces the destination; a failed load leaves the live index unchanged.

Loading also accepts legacy raw HNSW files from older Vectorlite builds or hnswlib. Raw files have no Vectorlite schema descriptor, so the receiving table's declaration is authoritative. The per-vector byte size and native layout must match, but equal-width types or dimensions with the same total byte size cannot be distinguished. Loading does not convert or re-normalize vectors or rebuild the graph: declare the intended schema. To upgrade, load the raw file and save again to write a versioned envelope.

The receiving table's max_elements and allow_replace_deleted control capacity and deleted-slot reuse after loading. Capacity is at least the loaded element count; use a larger max_elements to allow growth. Other graph-construction parameters come from the saved graph.

The in-memory index is held per database connection and survives schema reparses (e.g. VACUUM, ALTER TABLE, or DDL from other connections). It is lost when the connection closes unless you explicitly save it. On SQLite 3.31 or newer, vectorlite tables are direct-only: application SQL can access them, but views and triggers cannot.

Note: operation, path, and distance are reserved column names and cannot be used as the vector column name.

You can insert, update and delete a vectorlite table as if it's a normal sqlite table.

-- rowid is required during insertion, because rowid is used to connect the vector to its metadata stored elsewhere. Auto-generating rowid doesn't makes sense.
insert into my_vectorlite_table(rowid, vector_name) values ({your_rowid}, {vector_blob});
-- Note: update and delete statements that uses rowid filter require sqlite3_version >= 3.38 to run.  
update my_vectorlite_table set vector_name = {new_vector_blob} where rowid = {your_rowid};
delete from my_vectorlite_table where rowid = {your_rowid};

The following functions should be only used when querying a vectorlite table

-- returns knn_parameter that will be passed to knn_search(). 
-- vector_blob: vector to search
-- k: how many nearest neighbors to search for
-- ef: optional, positive HNSW speed/accuracy parameter. Defaults to 10.
-- An override applies only to this query; later queries without ef use the default.
knn_param(vector_blob, k, ef)
-- Should only be used in the `where clause` in a `select` statement to tell vectorlite to speed up the query using HNSW index
-- vector_name should match the vectorlite table's definition
-- knn_parameter is usually constructed using knn_param()
knn_search(vector_name, knn_parameter)
-- An example of vector search query. `distance` is an implicit column of a vectorlite table.
select rowid, distance from my_vectorlite_table where knn_search(vector_name, knn_param({vector_blob}, {k}))
-- An example of vector search query with pushed-down metadata(rowid) filter, requires sqlite_version >= 3.38 to run.
select rowid, distance from my_vectorlite_table where knn_search(vector_name, knn_param({vector_blob}, {k})) and rowid in (1,2,3,4,5)

Benchmark

Historical results: the figures, raw tables and performance summaries below were recorded before the Rust-primary migration. They are preserved as measurements of that earlier build, not newly verified Rust results. Recorded build paths are provenance, not a requirement to build an old C++ extension. Use the current benchmark instructions to measure a release build of the Rust implementation.

Please note only small datasets(with 3000 or 20000 vectors) are used because it would be unfair to benchmark against sqlite-vec using larger datasets. Sqlite-vec only uses brute-force, which doesn't scale with large datasets, while vectorlite uses ANN(approximate nearest neighbors), which scales to large datasets at the cost of not being 100% accurate.

How the benchmark is done:

  1. Insert 3000/20000 randomly-generated vectors of dimension 128,512,1536 and 3000 into a vectorlite table with HNSW parameters ef_construction=100, M=30.
  2. Randomly generate 100 vectors and then query the table with them for 10 nearest neighbors with ef=10,50,100 to see how ef impacts recall rate.
  3. Calculate recall rate by comparing the result with the neighbors calculated using brute force.
  4. vectorlite_scalar_brute_force(which is just inserting vectors into a normal sqlite table and do select rowid from my_table order by vector_distance(query_vector, embedding, 'l2') limit 10) is benchmarked as the baseline to see how much hnsw speeds up vector query.
  5. hnswlib is also benchmarked to see how much cost SQLite adds to vectorlite. The benchmark is run in WSL on my PC with a i5-12600KF intel CPU and 16G RAM.

TL;DR:

  1. Vectorlite's vector query is 3x-100x faster than sqlite-vec at the cost of lower recall rate. The difference gets larger when the dataset size grows, which is expected because sqlite-vec only supports brute force.
  2. Surprisingly, vectorlite_scalar_brute_force's vector query is about 1.5x faster for vectors with dimension >= 512 but slower than sqlite-vec for 128d vectors. vectorlite_scalar_brute_force's vector insertion is 3x-8x faster than sqlite-vec.
  3. Compared with hnswlib, vectorlite provides almost identical recall rate. Vector query speed with L2 distance is on par with 128d vectors and is 1.5x faster when dealing with 3000d vectors. Mainly because vectorlite's vector distance implementation is faster. But vectorlite's vector insertion is about 4x-5x slower, which I guess is the cost added by SQLite.
  4. Compared with brute force baseline(vectorlite_scalar_brute_force), vectorlite's knn query is 8x-80x faster.

The benchmark code can be found in benchmark folder, which can be used as an example of how to improve recall rate for your scenario by tuning HNSW parameters.

3000 vectors

When dealing with 3000 vectors(which is a fairly small dataset):

  1. Compared with sqlite-vec, vectorlite's vector query can be 3x-15x faster with 128-d vectors, 6x-26x faster with 512-d vectors, 7x-30x faster with 1536-d vectors and 6x-24x faster with 3000-d vectors. But vectorlite's vector insertion is 6x-16x slower, which is expected because sqlite-vec uses brute force only and doesn't do much indexing.
  2. Compared with vectorlite_scalar_brute_force, hnsw provides about 10x-40x speed up.
  3. Compared with hnswlib, vectorlite provides almost identical recall rate. Vector query speed is on par with 128d vectors and is 1.5x faster when dealing with 3000d vectors. Mainly because vectorlite's vector distance implementation is faster. But vector insertion is about 4x-5x slower.
  4. vectorlite_scalar_brute_force's vector insertion 4x-7x is faster than sqlite-vec, and vector query is about 1.7x faster when dealing with vectors of dimension >= 512.

vecter insertion vector query

Check raw data
Using local vectorlite: ../build/release/vectorlite/vectorlite.so
Benchmarking using 3000 randomly vectors. 100 10-nearest neighbor queries will be performed on each case.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┓
┃ distance ┃ vector    ┃ ef           ┃    ┃ ef     ┃ insert_time ┃ search_time ┃ recall ┃
┃ type     ┃ dimension ┃ construction ┃ M  ┃ search ┃ per vector  ┃ per query   ┃ rate   ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━┩
│ l2       │ 128       │ 100          │ 30 │ 10     │ 62.41 us    │ 12.96 us    │ 56.40% │
│ l2       │ 128       │ 100          │ 30 │ 50     │ 62.41 us    │ 42.95 us    │ 93.30% │
│ l2       │ 128       │ 100          │ 30 │ 100    │ 62.41 us    │ 62.06 us    │ 99.40% │
│ l2       │ 512       │ 100          │ 30 │ 10     │ 146.40 us   │ 38.05 us    │ 46.60% │
│ l2       │ 512       │ 100          │ 30 │ 50     │ 146.40 us   │ 95.96 us    │ 86.50% │
│ l2       │ 512       │ 100          │ 30 │ 100    │ 146.40 us   │ 148.46 us   │ 96.70% │
│ l2       │ 1536      │ 100          │ 30 │ 10     │ 463.56 us   │ 124.51 us   │ 38.10% │
│ l2       │ 1536      │ 100          │ 30 │ 50     │ 463.56 us   │ 355.70 us   │ 78.50% │
│ l2       │ 1536      │ 100          │ 30 │ 100    │ 463.56 us   │ 547.84 us   │ 92.70% │
│ l2       │ 3000      │ 100          │ 30 │ 10     │ 1323.25 us  │ 391.57 us   │ 36.60% │
│ l2       │ 3000      │ 100          │ 30 │ 50     │ 1323.25 us  │ 1041.37 us  │ 78.60% │
│ l2       │ 3000      │ 100          │ 30 │ 100    │ 1323.25 us  │ 1443.10 us  │ 93.10% │
│ cosine   │ 128       │ 100          │ 30 │ 10     │ 59.75 us    │ 15.27 us    │ 58.30% │
│ cosine   │ 128       │ 100          │ 30 │ 50     │ 59.75 us    │ 36.72 us    │ 94.60% │
│ cosine   │ 128       │ 100          │ 30 │ 100    │ 59.75 us    │ 63.67 us    │ 99.30% │
│ cosine   │ 512       │ 100          │ 30 │ 10     │ 148.19 us   │ 36.98 us    │ 51.00% │
│ cosine   │ 512       │ 100          │ 30 │ 50     │ 148.19 us   │ 102.46 us   │ 88.10% │
│ cosine   │ 512       │ 100          │ 30 │ 100    │ 148.19 us   │ 143.41 us   │ 96.90% │
│ cosine   │ 1536      │ 100          │ 30 │ 10     │ 427.21 us   │ 106.94 us   │ 42.10% │
│ cosine   │ 1536      │ 100          │ 30 │ 50     │ 427.21 us   │ 285.50 us   │ 83.30% │
│ cosine   │ 1536      │ 100          │ 30 │ 100    │ 427.21 us   │ 441.66 us   │ 95.60% │
│ cosine   │ 3000      │ 100          │ 30 │ 10     │ 970.17 us   │ 289.00 us   │ 42.20% │
│ cosine   │ 3000      │ 100          │ 30 │ 50     │ 970.17 us   │ 848.03 us   │ 83.90% │
│ cosine   │ 3000      │ 100          │ 30 │ 100    │ 970.17 us   │ 1250.29 us  │ 95.60% │
└──────────┴───────────┴──────────────┴────┴────────┴─────────────┴─────────────┴────────┘
Bencharmk hnswlib as comparison.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┓
┃ distance ┃ vector    ┃ ef           ┃    ┃ ef     ┃ insert_time ┃ search_time ┃ recall ┃
┃ type     ┃ dimension ┃ construction ┃ M  ┃ search ┃ per vector  ┃ per query   ┃ rate   ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━┩
│ l2       │ 128       │ 100          │ 30 │ 10     │ 12.84 us    │ 12.83 us    │ 56.90% │
│ l2       │ 128       │ 100          │ 30 │ 50     │ 12.84 us    │ 41.93 us    │ 93.60% │
│ l2       │ 128       │ 100          │ 30 │ 100    │ 12.84 us    │ 65.84 us    │ 99.40% │
│ l2       │ 512       │ 100          │ 30 │ 10     │ 29.34 us    │ 47.37 us    │ 47.00% │
│ l2       │ 512       │ 100          │ 30 │ 50     │ 29.34 us    │ 126.29 us   │ 86.40% │
│ l2       │ 512       │ 100          │ 30 │ 100    │ 29.34 us    │ 198.30 us   │ 96.80% │
│ l2       │ 1536      │ 100          │ 30 │ 10     │ 90.05 us    │ 149.35 us   │ 37.20% │
│ l2       │ 1536      │ 100          │ 30 │ 50     │ 90.05 us    │ 431.53 us   │ 78.00% │
│ l2       │ 1536      │ 100          │ 30 │ 100    │ 90.05 us    │ 765.03 us   │ 92.50% │
│ l2       │ 3000      │ 100          │ 30 │ 10     │ 388.87 us   │ 708.98 us   │ 36.30% │
│ l2       │ 3000      │ 100          │ 30 │ 50     │ 388.87 us   │ 1666.87 us  │ 78.90% │
│ l2       │ 3000      │ 100          │ 30 │ 100    │ 388.87 us   │ 2489.98 us  │ 93.40% │
│ cosine   │ 128       │ 100          │ 30 │ 10     │ 10.90 us    │ 11.14 us    │ 58.10% │
│ cosine   │ 128       │ 100          │ 30 │ 50     │ 10.90 us    │ 37.39 us    │ 94.30% │
│ cosine   │ 128       │ 100          │ 30 │ 100    │ 10.90 us    │ 62.45 us    │ 99.40% │
│ cosine   │ 512       │ 100          │ 30 │ 10     │ 25.46 us    │ 38.92 us    │ 50.70% │
│ cosine   │ 512       │ 100          │ 30 │ 50     │ 25.46 us    │ 109.84 us   │ 87.90% │
│ cosine   │ 512       │ 100          │ 30 │ 100    │ 25.46 us    │ 151.00 us   │ 97.10% │
│ cosine   │ 1536      │ 100          │ 30 │ 10     │ 77.53 us    │ 119.48 us   │ 42.00% │
│ cosine   │ 1536      │ 100          │ 30 │ 50     │ 77.53 us    │ 340.78 us   │ 84.00% │
│ cosine   │ 1536      │ 100          │ 30 │ 100    │ 77.53 us    │ 510.02 us   │ 95.50% │
│ cosine   │ 3000      │ 100          │ 30 │ 10     │ 234.79 us   │ 453.12 us   │ 43.20% │
│ cosine   │ 3000      │ 100          │ 30 │ 50     │ 234.79 us   │ 1380.79 us  │ 83.80% │
│ cosine   │ 3000      │ 100          │ 30 │ 100    │ 234.79 us   │ 1520.92 us  │ 95.70% │
└──────────┴───────────┴──────────────┴────┴────────┴─────────────┴─────────────┴────────┘
Bencharmk vectorlite brute force(select rowid from my_table order by vector_distance(query_vector, embedding, 'l2')) as comparison.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ distance ┃ vector    ┃ insert_time ┃ search_time ┃ recall  ┃
┃ type     ┃ dimension ┃ per vector  ┃ per query   ┃ rate    ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ l2       │ 128       │ 2.38 us     │ 299.14 us   │ 100.00% │
│ l2       │ 512       │ 3.69 us     │ 571.19 us   │ 100.00% │
│ l2       │ 1536      │ 4.86 us     │ 2237.64 us  │ 100.00% │
│ l2       │ 3000      │ 7.69 us     │ 5135.63 us  │ 100.00% │
└──────────┴───────────┴─────────────┴─────────────┴─────────┘
Bencharmk sqlite_vss as comparison.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ distance ┃ vector    ┃ insert_time ┃ search_time ┃ recall  ┃
┃ type     ┃ dimension ┃ per vector  ┃ per query   ┃ rate    ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ l2       │ 128       │ 395.24 us   │ 2508.52 us  │ 99.90%  │
│ l2       │ 512       │ 2824.89 us  │ 1530.77 us  │ 100.00% │
│ l2       │ 1536      │ 8931.72 us  │ 1602.36 us  │ 100.00% │
│ l2       │ 3000      │ 17498.60 us │ 3142.38 us  │ 100.00% │
└──────────┴───────────┴─────────────┴─────────────┴─────────┘
Bencharmk sqlite_vec as comparison.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ distance ┃ vector    ┃ insert_time ┃ search_time ┃ recall  ┃
┃ type     ┃ dimension ┃ per vector  ┃ per query   ┃ rate    ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ l2       │ 128       │ 10.21 us    │ 202.05 us   │ 100.00% │
│ l2       │ 512       │ 14.43 us    │ 989.64 us   │ 100.00% │
│ l2       │ 1536      │ 31.68 us    │ 3856.08 us  │ 100.00% │
│ l2       │ 3000      │ 59.94 us    │ 9503.91 us  │ 100.00% │
└──────────┴───────────┴─────────────┴─────────────┴─────────┘

20000 vectors

When dealing with 20000 vectors,

  1. Compared with sqlite-vec, vectorlite's vector query can be 8x-100x faster depending on vector dimension.
  2. Compared with vectorlite_scalar_brute_force, hnsw provides about 8x-80x speed up with reduced recall rate at 13.8%-85% depending on vector dimension.
  3. Compared with hnswlib, vectorlite provides almost identical recall rate. Vector query is on par with 128d vectors and can be 1.5x faster with 3000d vectors. But vector insertion is 3x-9x slower.
  4. vectorlite_scalar_brute_force's vector insertion is 4x-8x faster than sqlite-vec. sqlite-vec's vector query is 1.5x faster with 128d vectors and 1.8x slower when vector dimension>=512.

Please note:

  1. sqlite-vss is not benchmarked with 20000 vectors because its index creation takes so long that it doesn't finish in hours.
  2. sqlite-vec's vector query is benchmarked and included in the raw data, but not plotted in the figure because it's search time is disproportionally long.

vecter insertion vector query

Check raw data
Using local vectorlite: ../build/release/vectorlite/vectorlite.so
Benchmarking using 20000 randomly vectors. 100 10-neariest neighbor queries will be performed on each case.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┓
┃ distance ┃ vector    ┃ ef           ┃    ┃ ef     ┃ insert_time ┃ search_time ┃ recall ┃
┃ type     ┃ dimension ┃ construction ┃ M  ┃ search ┃ per vector  ┃ per query   ┃ rate   ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━┩
│ l2       │ 128       │ 100          │ 30 │ 10     │ 187.41 us   │ 46.58 us    │ 29.10% │
│ l2       │ 128       │ 100          │ 30 │ 50     │ 187.41 us   │ 95.16 us    │ 70.20% │
│ l2       │ 128       │ 100          │ 30 │ 100    │ 187.41 us   │ 179.51 us   │ 85.70% │
│ l2       │ 512       │ 100          │ 30 │ 10     │ 820.80 us   │ 105.80 us   │ 18.10% │
│ l2       │ 512       │ 100          │ 30 │ 50     │ 820.80 us   │ 361.83 us   │ 50.40% │
│ l2       │ 512       │ 100          │ 30 │ 100    │ 820.80 us   │ 628.88 us   │ 67.00% │
│ l2       │ 1536      │ 100          │ 30 │ 10     │ 2665.31 us  │ 292.39 us   │ 13.70% │
│ l2       │ 1536      │ 100          │ 30 │ 50     │ 2665.31 us  │ 1069.47 us  │ 42.40% │
│ l2       │ 1536      │ 100          │ 30 │ 100    │ 2665.31 us  │ 1744.79 us  │ 59.50% │
│ l2       │ 3000      │ 100          │ 30 │ 10     │ 5236.76 us  │ 558.56 us   │ 13.80% │
│ l2       │ 3000      │ 100          │ 30 │ 50     │ 5236.76 us  │ 1787.83 us  │ 39.30% │
│ l2       │ 3000      │ 100          │ 30 │ 100    │ 5236.76 us  │ 3039.94 us  │ 56.60% │
│ cosine   │ 128       │ 100          │ 30 │ 10     │ 164.31 us   │ 25.35 us    │ 34.70% │
│ cosine   │ 128       │ 100          │ 30 │ 50     │ 164.31 us   │ 78.33 us    │ 71.20% │
│ cosine   │ 128       │ 100          │ 30 │ 100    │ 164.31 us   │ 133.75 us   │ 87.60% │
│ cosine   │ 512       │ 100          │ 30 │ 10     │ 711.35 us   │ 100.90 us   │ 19.00% │
│ cosine   │ 512       │ 100          │ 30 │ 50     │ 711.35 us   │ 406.08 us   │ 51.10% │
│ cosine   │ 512       │ 100          │ 30 │ 100    │ 711.35 us   │ 582.51 us   │ 71.50% │
│ cosine   │ 1536      │ 100          │ 30 │ 10     │ 2263.96 us  │ 283.88 us   │ 22.60% │
│ cosine   │ 1536      │ 100          │ 30 │ 50     │ 2263.96 us  │ 919.98 us   │ 54.50% │
│ cosine   │ 1536      │ 100          │ 30 │ 100    │ 2263.96 us  │ 1674.77 us  │ 72.40% │
│ cosine   │ 3000      │ 100          │ 30 │ 10     │ 4541.09 us  │ 566.31 us   │ 19.80% │
│ cosine   │ 3000      │ 100          │ 30 │ 50     │ 4541.09 us  │ 1672.82 us  │ 49.30% │
│ cosine   │ 3000      │ 100          │ 30 │ 100    │ 4541.09 us  │ 2855.43 us  │ 65.40% │
└──────────┴───────────┴──────────────┴────┴────────┴─────────────┴─────────────┴────────┘
Bencharmk hnswlib as comparison.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┓
┃ distance ┃ vector    ┃ ef           ┃    ┃ ef     ┃ insert_time ┃ search_time ┃ recall ┃
┃ type     ┃ dimension ┃ construction ┃ M  ┃ search ┃ per vector  ┃ per query   ┃ rate   ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━┩
│ l2       │ 128       │ 100          │ 30 │ 10     │ 23.06 us    │ 39.96 us    │ 29.60% │
│ l2       │ 128       │ 100          │ 30 │ 50     │ 23.06 us    │ 75.02 us    │ 69.80% │
│ l2       │ 128       │ 100          │ 30 │ 100    │ 23.06 us    │ 160.01 us   │ 85.40% │
│ l2       │ 512       │ 100          │ 30 │ 10     │ 146.58 us   │ 167.31 us   │ 18.10% │
│ l2       │ 512       │ 100          │ 30 │ 50     │ 146.58 us   │ 392.12 us   │ 50.80% │
│ l2       │ 512       │ 100          │ 30 │ 100    │ 146.58 us   │ 781.50 us   │ 67.20% │
│ l2       │ 1536      │ 100          │ 30 │ 10     │ 657.41 us   │ 298.71 us   │ 12.70% │
│ l2       │ 1536      │ 100          │ 30 │ 50     │ 657.41 us   │ 1031.61 us  │ 40.60% │
│ l2       │ 1536      │ 100          │ 30 │ 100    │ 657.41 us   │ 1764.34 us  │ 57.90% │
│ l2       │ 3000      │ 100          │ 30 │ 10     │ 1842.77 us  │ 852.88 us   │ 13.80% │
│ l2       │ 3000      │ 100          │ 30 │ 50     │ 1842.77 us  │ 2905.57 us  │ 39.60% │
│ l2       │ 3000      │ 100          │ 30 │ 100    │ 1842.77 us  │ 4936.35 us  │ 56.50% │
│ cosine   │ 128       │ 100          │ 30 │ 10     │ 19.25 us    │ 23.27 us    │ 34.20% │
│ cosine   │ 128       │ 100          │ 30 │ 50     │ 19.25 us    │ 72.66 us    │ 71.40% │
│ cosine   │ 128       │ 100          │ 30 │ 100    │ 19.25 us    │ 134.11 us   │ 87.60% │
│ cosine   │ 512       │ 100          │ 30 │ 10     │ 112.80 us   │ 106.90 us   │ 22.70% │
│ cosine   │ 512       │ 100          │ 30 │ 50     │ 112.80 us   │ 341.23 us   │ 54.20% │
│ cosine   │ 512       │ 100          │ 30 │ 100    │ 112.80 us   │ 609.93 us   │ 72.40% │
│ cosine   │ 1536      │ 100          │ 30 │ 10     │ 615.04 us   │ 268.00 us   │ 22.50% │
│ cosine   │ 1536      │ 100          │ 30 │ 50     │ 615.04 us   │ 898.82 us   │ 54.00% │
│ cosine   │ 1536      │ 100          │ 30 │ 100    │ 615.04 us   │ 1557.51 us  │ 71.90% │
│ cosine   │ 3000      │ 100          │ 30 │ 10     │ 1425.49 us  │ 546.18 us   │ 20.60% │
│ cosine   │ 3000      │ 100          │ 30 │ 50     │ 1425.49 us  │ 2008.53 us  │ 49.20% │
│ cosine   │ 3000      │ 100          │ 30 │ 100    │ 1425.49 us  │ 3106.51 us  │ 65.00% │
└──────────┴───────────┴──────────────┴────┴────────┴─────────────┴─────────────┴────────┘
Bencharmk vectorlite brute force(select rowid from my_table order by vector_distance(query_vector, embedding, 'l2')) as comparison.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ distance ┃ vector    ┃ insert_time ┃ search_time ┃ recall  ┃
┃ type     ┃ dimension ┃ per vector  ┃ per query   ┃ rate    ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ l2       │ 128       │ 0.93 us     │ 2039.69 us  │ 100.00% │
│ l2       │ 512       │ 2.73 us     │ 7177.23 us  │ 100.00% │
│ l2       │ 1536      │ 4.64 us     │ 17163.25 us │ 100.00% │
│ l2       │ 3000      │ 6.62 us     │ 25378.79 us │ 100.00% │
└──────────┴───────────┴─────────────┴─────────────┴─────────┘
Bencharmk sqlite_vec as comparison.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ distance ┃ vector    ┃ insert_time ┃ search_time ┃ recall  ┃
┃ type     ┃ dimension ┃ per vector  ┃ per query   ┃ rate    ┃
┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ l2       │ 128       │ 3.49 us     │ 1560.17 us  │ 100.00% │
│ l2       │ 512       │ 6.73 us     │ 7778.39 us  │ 100.00% │
│ l2       │ 1536      │ 17.13 us    │ 26344.76 us │ 100.00% │
│ l2       │ 3000      │ 35.30 us    │ 60652.58 us │ 100.00% │
└──────────┴───────────┴─────────────┴─────────────┴─────────┘

Quick Start

The quickest way to get started is to install vectorlite using python.

# Note: vectorlite-py not vectorlite. vectorlite is another project.
pip install vectorlite-py numpy

Use a Python build with loadable SQLite extensions enabled. Vectorlite requires SQLite >= 3.20; rowid lookups and metadata filtering require SQLite >= 3.38. Python 3.14 with a recent bundled SQLite is recommended. On SQLite >= 3.31, access vectorlite tables directly from application SQL, not through views or triggers. Below is a minimal example of using vectorlite. It can also be found in the examples folder.

import vectorlite_py
import sqlite3
import numpy as np
"""
Quick start of using vectorlite extension.
"""

conn = sqlite3.connect(':memory:')
conn.enable_load_extension(True) # enable extension loading
conn.load_extension(vectorlite_py.vectorlite_path()) # load vectorlite

cursor = conn.cursor()
# check if vectorlite is loaded
print(cursor.execute('select vectorlite_info()').fetchall())

# Vector distance calculation
for distance_type in ['l2', 'cosine', 'ip']:
    v1 = "[1, 2, 3]"
    v2 = "[4, 5, 6]"
    # Note vector_from_json can be used to convert a JSON string to a vector
    distance = cursor.execute(f'select vector_distance(vector_from_json(?), vector_from_json(?), "{distance_type}")', (v1, v2)).fetchone()
    print(f'{distance_type} distance between {v1} and {v2} is {distance[0]}')

# generate some test data
DIM = 32 # dimension of the vectors
NUM_ELEMENTS = 10000 # number of vectors
data = np.float32(np.random.random((NUM_ELEMENTS, DIM))) # SQL inputs use float32 blobs; stored types may also be float16/bfloat16.

# Create a virtual table using vectorlite using l2 distance (default distance type) and default HNSW parameters
cursor.execute(f'create virtual table my_table using vectorlite(my_embedding float32[{DIM}], hnsw(max_elements={NUM_ELEMENTS}))')
# Vector distance type can be explicitly set to cosine using:
# cursor.execute(f'create virtual table my_table using vectorlite(my_embedding float32[{DIM}] cosine, hnsw(max_elements={NUM_ELEMENTS}))')

# Insert the test data into the virtual table. Note that the rowid MUST be explicitly set when inserting vectors and cannot be auto-generated.
# The rowid is used to uniquely identify a vector and serve as a "foreign key" to relate to the vector's metadata.
# Vectorlite takes vectors in raw bytes, so a numpy vector need to be converted to bytes before inserting into the table.
cursor.executemany('insert into my_table(rowid, my_embedding) values (?, ?)', [(i, data[i].tobytes()) for i in range(NUM_ELEMENTS)])

# Query the virtual table to get the vector at rowid 12345. Note the vector needs to be converted back to json using vector_to_json() to be human-readable. 
result = cursor.execute('select vector_to_json(my_embedding) from my_table where rowid = 1234').fetchone()
print(f'vector at rowid 1234: {result[0]}')

# Find 10 approximate nearest neighbors of data[0] and there distances from data[0].
# knn_search() is used to tell vectorlite to do a vector search.
# knn_param(V, K, ef) is used to pass the query vector V, the number of nearest neighbors K to find and an optional ef parameter to tune the performance of the search.
# If ef is not specified, ef defaults to 10. For more info on ef, please check https://github.com/nmslib/hnswlib/blob/v0.8.0/ALGO_PARAMS.md
result = cursor.execute('select rowid, distance from my_table where knn_search(my_embedding, knn_param(?, 10))', [data[0].tobytes()]).fetchall()
print(f'10 nearest neighbors of row 0 is {result}')

# Find 10 approximate nearest neighbors of the first embedding in vectors with rowid within [1000, 2000) using metadata(rowid) filtering.
rowids = ','.join([str(rowid) for rowid in range(1000, 2000)])
result = cursor.execute(f'select rowid, distance from my_table where knn_search(my_embedding, knn_param(?, 10)) and rowid in ({rowids})', [data[0].tobytes()]).fetchall()
print(f'10 nearest neighbors of row 0 in vectors with rowid within [1000, 2000) is {result}')

conn.close()

More examples can be found in examples and bindings/python/vectorlite_py/test folder.

Build Instructions

The source build uses CMake -> Cargo for the extension library and retains CMake's native ops tests and benchmarks. Python source installs and wheels use scikit-build-core -> CMake -> Cargo. No separate C++ extension build or Rust redeployment step is required. Prebuilt wheels and npm packages do not require Rust on the user's machine.

Prerequisites

  1. The latest stable Rust toolchain (rustup update stable); the repository selects stable in rust-toolchain.toml.
  2. CMake >= 3.22 and Ninja.
  3. C and C++ compilers in PATH with C++17 support (MSVC on Windows).
  4. Git and the initialized vcpkg submodule. CMake configuration installs hnswlib and Highway; BUILD_TESTING=ON selects the vcpkg tests feature for Google Test and SQLite headers/ABI checks. Wheel builds do not require those test dependencies.
  5. Python 3.14 or newer with loadable SQLite extensions enabled, plus pytest and NumPy, to run integration tests.

Build

Build and test the SQLite extension

git clone --recurse-submodules git@github.com:1yefuwang1/vectorlite.git
cd vectorlite
# Also needed when cloning without --recurse-submodules:
git submodule update --init --recursive
python3 bootstrap_vcpkg.py

# Install dependencies for the Python integration suites.
python3 -m pip install -r requirements-dev.txt

sh build.sh         # Debug build and tests
sh build_release.sh # Release build and tests

These scripts build the Rust extension via CMake, run CTest (Rust unit tests, SQLite ABI checks and native ops tests), and run both Python integration suites (bindings/python/vectorlite_py/test/ and vectorlite/tests/) against the built library. CMake deploys the library into the Python package automatically. The public vectorlite.[so|dll|dylib] artifact is also available under build/dev/vectorlite or build/release/vectorlite.

For build-only iteration:

cmake --preset release
cmake --build build/release -j8

Build a wheel or install from source

After initializing and bootstrapping vcpkg, use the same prerequisites above:

python3 -m pip install .
# Or build a wheel without installing it:
python3 -m pip wheel . --wheel-dir dist
# Alternatively, with the Python build frontend installed:
python3 -m build -w

The vectorlite_py wheel contains the same Rust-built extension with unchanged public filenames. Source archives omit Git submodules; when the local vcpkg toolchain is absent, CMake fetches and bootstraps the checkout pinned by vcpkg.json (Git and network access are required). See the Rust contributor guide for direct Cargo commands, native dependency selection and optional SQLite binding regeneration.

Roadmap

  • SIMD support for ARM platform
  • Support user defined metadata/rowid filter
  • Support Multi-vector document search and epsilon search
  • Support multi-threaded search
  • Release vectorlite to more package managers.
  • Support more vector types, e.g. int8.

Known limitations

  1. On a single query, a knn_search vector constraint can only be paired with at most one rowid constraint and vice versa. For example, The following queries will fail:
select rowid from my_table where rowid in (1,2,3) and rowid in (2, 3, 4) # multiple rowid constraints

select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[1,2,3]'), 10)) and rowid in (1,2,3) and rowid in (3, 4) # multiple rowid constraints

select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[1,2,3]'), 10)) and knn_search(my_embedding, knn_param(vector_from_json('[1,2,3]'), 10)) # multiple vector constraints

However, multiple constrains can be combined with or, because the query will search the underlying hnsw index multiple times. The results will be conbined by sqlite. The following queries will work.

select rowid from my_table where rowid in (1,2,3) or rowid in (2, 3, 4)

select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[1,2,3]'), 10)) and rowid in (1,2,3) or rowid in (3, 4)

select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[1,2,3]'), 10)) or knn_search(my_embedding, knn_param(vector_from_json('[1,2,3]'), 10)) 
  1. Stored vectors may be float32, float16 or bfloat16, but SQL vector inputs and outputs are always little-endian float32 blobs.
  2. SIMD is only enabled on x86 platforms. Because the default implementation in hnswlib doesn't support SIMD on ARM. Vectorlite is 3x-4x slower on MacOS-ARM than MacOS-x64. I plan to improve it in the future.
  3. rowid in sqlite3 is of type int64_t and can be negative. However, rowid in a vectorlite table should be in this range [0, min(max value of size_t, max value of int64_t)]. The reason is rowid is used as labeltype in hnsw index, which has type size_t(usually 32-bit or 64-bit depending on the platform).
  4. Transaction is not supported.
  5. Metadata filter(rowid filter) requires sqlite3 >= 3.38. Python 3.14's built-in sqlite3 module bundles SQLite 3.50.4 (>= 3.38) to use it. knn_search() without rowid fitler still works for old sqlite3.
  6. The vector index is held in memory.
  7. Deleting a row only marks the vector as deleted and doesn't free the memory. The vector will not be included in later queries. However, if another vector is inserted with the same rowid, the memory will be reused.
  8. A vectorlite table can only have one vector column.

Acknowledgement

This project is greatly inspired by following projects

Projets similaires

A vector search SQLite extension that runs anywhere!

Csqlitesqlite-extension
Aasg017
8,1 k étoiles349

SQLite-Vector is a cross-platform, ultra-efficient SQLite extension that brings vector search capabilities to your embedded database.

C
Ssqliteai
1,2 k étoiles51

A lightweight, lightning-fast, in-process vector database

C++agent-skillsdbembedded
Aalibaba
16 k étoiles1 k