feat(backends): add sqlite_vec backend, fixes UAF crash on macOS/ARM64 (smaller footprint) - #1386
Open
MohamedAbdallah-14 wants to merge 1 commit into
Open
Conversation
MohamedAbdallah-14
requested review from
bensig,
igorls and
milla-jovovich
as code owners
May 6, 2026 16:01
Alternate vector backend implementing the full BaseBackend / BaseCollection contract using sqlite-vec's vec0 virtual table. Useful on platforms where chromadb_rust_bindings is unsafe — notably macOS 26 / ARM64, where the rust bindings have an intra-process UAF in the recursive segment walker (chroma-core/chroma#6852, MemPalace#1355, MemPalace#1376). Backend characteristics: - No Tokio runtime, no Rust extension, no recursive walker — the UAF cannot fire because the codepath does not exist. - Single sqlite_vec.db per palace; per-collection vec0 virtual table sized to the collection's dimension. - Chroma-style metadata filters compiled to SQL over json_extract(meta, …). Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or, plus bare-scalar equality. Where-document: $contains, $not_contains, $and, $or. Unknown operators raise UnsupportedFilterError per spec §1.4. - Implements add, upsert, query, get, delete, count, update (atomic override). update advertises supports_update via capabilities. Optional dep — opt in via pip install mempalace[sqlite-vec]. Registered through the existing mempalace.backends entry-point group, so selection goes through the standard registry. Migration: examples/migrate_chroma_to_sqlite_vec.py reads chroma.sqlite3 directly via stdlib sqlite3 (zero chromadb code involved, so the UAF cannot fire) and re-embeds via the existing get_embedding_function. Stock hnswlib cannot load chromadb's segment envelope, hence re-embed rather than vector copy. Resumable on drawer_id uniqueness — re-running picks up where it left off. Tested on a 664k-drawer palace with exact count parity to the source. Tests: 39 cases covering backend lifecycle, writes, queries, the where compiler (parametrized over every supported operator), where_document filters, get pagination, and registry-side selection. Skip cleanly when the sqlite-vec extra is not installed.
MohamedAbdallah-14
force-pushed
the
feat/sqlite-vec-backend
branch
from
May 11, 2026 16:16
cd6ed00 to
c9583de
Compare
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Alternate vector backend at
mempalace.backends.sqlite_vec.SqliteVecBackendimplementing the fullBaseBackend/BaseCollectioncontract withsqlite-vec'svec0virtual table. Targets the platforms wherechromadb_rust_bindingsis unsafe — primarily macOS 26 / ARM64 where the rust bindings have an intra-process UAF in the recursive segment walker (chroma-core/chroma#6852, #1355, #1376).The backend has no Rust extension and no chromadb dependency; this UAF vector does not exist in the call graph.
Storage
One
sqlite_vec.dbfile per palace. Per-collectionvec0virtual table sized to the collection'sdimension. Schema:PRAGMAs at open:
journal_mode=WAL,synchronous=NORMAL,foreign_keys=ON.Supported
whereoperators:$eq,$ne,$gt,$gte,$lt,$lte,$in,$nin,$and,$or, plus bare-scalar equality. Supportedwhere_documentoperators:$contains,$not_contains,$and,$or. Unknown operators raiseUnsupportedFilterErrorper RFC 001 §1.4.Selection
Optional dependency — opt in via
pip install mempalace[sqlite-vec]. Registered through the existingmempalace.backendsentry-point group, so selection goes through the standard registry:get_backend("sqlite_vec"). Themempalace.backends.chroma:ChromaBackendregistration is unchanged, so default behaviour is identical for users not opting in.Migration
examples/migrate_chroma_to_sqlite_vec.pyreadschroma.sqlite3directly via stdlibsqlite3(no chromadb code is loaded, so the UAF cannot fire even on affected platforms) and re-embeds documents via the existingget_embedding_function. Stock hnswlib cannot load chromadb's segment envelope, hence re-embed rather than vector copy.Resumable on
drawer_iduniqueness — interrupted runs pick up where they left off. Validated on a 664k-drawer palace; final counts matched the source exactly (drawers 648700/648700, closets 15368/15368). Took ~60min at ~90 records/sec on M4 Pro.Disk footprint
Side benefit on my corpus (664k drawers, 384-dim embeddings): the sqlite_vec store ended up roughly 3x smaller than the chromadb palace it replaced.
Where it comes from:
vec0does brute-force SIMD scan, which is fast enough at this scale.embedding_metadataand inside the HNSW segment.segments,segment_metadata,embeddings_queue,max_seq_id).Numbers are from one corpus; mileage will vary, especially on smaller or less-churned palaces.
Tests
39 new cases in
tests/test_sqlite_vec_backend.pycovering backend lifecycle, write paths (add / upsert / update / delete-by-ids / delete-by-where), query paths (with and without metadata filters, dimension-mismatch raises, dict-compat access), the where compiler parametrized over every supported operator, where_document filters,getpagination, and registry-side discovery. Skip cleanly when thesqlite-vecextra isn't installed.Full test suite: 904 pass / 1 pre-existing failure (
test_mcp_stdio_protection, also fails onmainwithout my changes — unrelated).Out of scope for this PR
This PR is the contract impl only. The downstream selection plumbing (
palace.py_DEFAULT_BACKENDandmcp_server.py_get_collection) is intentionally not touched. Follow-up PR if this looks good.