Skip to content

Delete sqlite-vec vectors by rowid equality, in rowid order, which vec0 looks up instead of scanning - #1863

Draft
edwinyyyu wants to merge 2 commits into
MemMachine:mainfrom
edwinyyyu:fix/sqlite-vec-delete-by-rowid
Draft

edwinyyyu wants to merge 2 commits into
MemMachine:mainfrom
edwinyyyu:fix/sqlite-vec-delete-by-rowid

Conversation

@edwinyyyu

@edwinyyyu edwinyyyu commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of the change

SQLiteVecVectorStoreCollection.delete removed a collection's vectors with DELETE FROM [vec] WHERE rowid IN (...). sqlite-vec plans rowid IN (...) outside a KNN query as a scan of the whole vec0 table, so each delete read every vector in the collection, and took longer as the collection grew. It now deletes with rowid = :rowid, one statement per row, as the upsert path already does; sqlite-vec plans that as a lookup of one row. The rows go in rowid order: in the order the uuid index returns them, a large delete took longer than the scan it replaces.

This is the delete half of #1862. The issue's other half, _fetch_vectors, needs a choice between forms and is left to the issue.

Verification

At this PR's head 07cadc40c, on 2026-10-27: ruff check and ruff format --check clean; ty check clean as CI runs it (uv run --frozen --all-extras ty check --project packages/server); test_sqlite_vec_vector_store.py 63 passed, including its delete tests.

Measured through the store with 64-dimensional vectors, on sqlite-vec 0.1.9 and SQLite 3.50.4.

A delete of 2 records, median of 20, as the collection grows:

records in the collection main at d5a39a8 this PR
10,000 2.88 ms 1.08 ms
100,000 21.24 ms 1.04 ms

One delete from a collection of 100,000 records, median of 3, each on a fresh copy of the same database:

records deleted main at d5a39a8 per row, unsorted (e5beea591) this PR
10 0.026 s 0.006 s 0.004 s
100 0.040 s 0.017 s 0.024 s
1,000 0.090 s 0.111 s 0.071 s
10,000 0.351 s 0.830 s 0.330 s
30,000 0.824 s 1.845 s 0.678 s

At 10,000 records deleted, main and this PR are within the runs' spread (0.287–0.498 s and 0.307–0.450 s).

The query plans behind it:

statement plan
DELETE FROM vec WHERE rowid IN (5, 6) SCAN vec VIRTUAL TABLE INDEX 0:1 (full scan)
DELETE FROM vec WHERE rowid = 5 SCAN vec VIRTUAL TABLE INDEX 1:2!___ (one row)

🤖 Generated with Claude Code

`delete` removed a collection's vectors with `rowid IN (...)`, which vec0
plans as a scan of the whole table, so each delete read every vector in
the collection. It now runs `rowid = :rowid` once per row, as the upsert
path does, which vec0 plans as a lookup of one row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unsorted, the rowids come in uuid-index order, and a large delete took
longer row by row than the scan it replaced. In rowid order, a delete is
no slower than one scan of the table at every measured size, and far
faster when it removes few rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@edwinyyyu edwinyyyu changed the title Delete sqlite-vec vectors by rowid equality, which vec0 looks up instead of scanning Delete sqlite-vec vectors by rowid equality, in rowid order, which vec0 looks up instead of scanning Oct 27, 2026
@edwinyyyu

Copy link
Copy Markdown
Contributor Author

Not clear that this is worth it.

@edwinyyyu
edwinyyyu marked this pull request as draft October 30, 2026 22:59

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant