alexgarcia-xyz
Joined 5 years ago
Joined 5 years ago
You mentioned "VSS" in another comment, which if that was my sqlite-vss extension, I more-or-less abandoned that extension for sqlite-vec last year. The VSS extension was more unstable and bloated (based on Faiss), but the new VEC extension is much more lightweight and easy to use (custom C vector functions). The new metadata column support is also pretty nice, and can be used for push-down filtering.
That being said, sqlite-vec still doesn't have ANN yet (but can brute-force ~10k's-100k of vectors quick enough), and I've fallen behind on updates and new releases. Hoping to push out a desperately-needed update this week
link: https://alexgarcia.xyz/sqlite-vec/
And there are a few other SQLite-based vector search extensions/tools out there, so the ecosystem is general might be worth another look.
In sqlite-vec, there's only a flat brute-force index (though DiskANN/IVF will be coming soon). But we do have a concept of partition keys[0], which allows you to "internally shard" the vector index based on a user_id or any other value.
create virtual table vec_documents using vec0(
document_id integer primary key,
user_id integer partition key,
contents_embedding float[1024]
)
Then at query time, any WHERE constraints on a partition key are pushed-down, so only matching vectors are searched instead of the entire index. select
document_id,
user_id,
distance
from vec_documents
where contents_embedding match :query
and k = 20
and user_id = 123; -- only search documents from user 123
Definitely not as performant as a proper vector index, but a lot of real-world application have these natural groups anyway. Like "search only English documents" or "search entries in this workspace only", or even "search comments only from the past 30 days."Internally sqlite-vec stores vectors in "chunks", so when partition keys are definds, every chunk is associated with a unique combination of all partition keys. Kinda hard to describe, but if you create a vec0 virtual table and insert some values, you can inspect the internal "shadow tables" in the SQLite database to see how it's all stored.
[0] https://alexgarcia.xyz/sqlite-vec/features/vec0.html#partiti...
the cvec0.all file can be ignored. i was debugging a issue and uploaded a mscv compiled version as that, but its not part of the official release. prefer the tar.gz version and let me know if it doesn't work
I wouldn't completely replace keyword search with vector search, but it's a great addition that essentially lets you perform calculations on text
sqlite-vec works on MacOS, Linux, Windows, Raspberry Pis, in the browser with WASM, and (theoretically) on mobile devices. I focused a lot on making it as portable as possible. It's also pretty fast - benchmarks are hard to do accurately, but I'd comfortable saying that it's a very very fast brute-force vector search solution.
One experimental feature I'm working on: You can directly query vectors that are in-memory as a contiguous block of memory (ie NumPy), without any copying or cloning. You can see the benchmarks for that feature here under "sqlite-vec static", and it's competitive with faiss/usearch/duckdb https://alexgarcia.xyz/blog/2024/sqlite-vec-stable-release/i...
The libsql vector feature only works in libsql, sqlite-vec works in all SQLite versions. The libsql vector feature works kindof like pgvector, while sqlite-vec works more like the FTS5 full text SQLite extension.
I'd say try both and see which one you like more. sqlite-vec will soon be a part of Turso's and SQLite Cloud's products.
Turso's version: https://turso.tech/vector
That being said, I find WASM projects that are written entirely in Rust to be pleasant, more pleasant than C WASM projects. But for SQLite extensions specifically, Rust/WASM gets a bit harder
Also one note: load_extension isn't required, you can statically compile this and other extensions into an application. That being said, load_extension itself doesnt make your application 'vulnerable.' SQLite offers many features to limit and control dynamically loadable extensions, like sqlite3_enable_load_extension() and SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION. Of course it depends what your definition of "security" is, but load_extension() doesn't need to be dangerous.
That being said, it depends what the extension does - a "hello world" extension mainly just calls the same SQLite C APIs over and over again, so the small Rust layer makes it a bit slower. However, my Rust extensions for regex[1] and CSV parsing[2] are usually faster than the C counterparts, mostly due to less memory allocations and batching. It's not a 1:1 comparison (both extensions have slightly different APIs and features), but I'd say a lot of "real world" features available in Rust can be faster than what's available in C.
That being said, I'm sure someone could write their own faster CSV or regex extension in C that is faster than the Rust ones. But it's a ton of work to do that from scratch, and I find wrapping a pre-existing Rust implementation to be much easier
[0] https://github.com/asg017/sqlite-loadable-rs?tab=readme-ov-f... [1] https://github.com/asg017/sqlite-regex [2] https://github.com/asg017/sqlite-xsv
I'm curious - do you find yourself constrained by the default macOS build often? Typically I `brew install sqlite` and use that CLI to get around extension loading issues (and for more modern SQLite versions). Same with Python's default MacOS build, which I avoid at all costs. Though very curious to hear more about times where that isn't a viable option
This extension isn't the best example, since it's a thrown-together demo, but sqlite-ulid is a similar extension written in Rust that could be run anywhere, not just Rust
That being said, writing in Rust instead of C has many drawbacks (slightly slower, cross compiling, larger binary sizes, WASM is more difficult, statically compiling is complex, etc.). But for cases like this, many SQLite extensions I write in Rust are just light wrappers around extremely high quality Rust crates (like jiff), which makes my life easier and it "good enough"
SELECT
rowid,
distance
FROM vec_items
WHERE rowid IN (SELECT rowid FROM ...)
AND title_embeddings MATCH ?
ORDER BY distance
LIMIT 10
^ This would do a KNN style query, k=10, but only include results where the item's rowid is included in the (...) subquery. This is a half-measure for metadata filtering, by pre-filtering which items should be considered in the KNN search.It's currently a bit slow, and not the best solution. Eventually there will be support for metadata column in vec0 tables, which with trickery should be much faster, but isn't planned for v0.1.0.