Repository navigation
Conversation
edwinyyyu
force-pushed
the
fix/sqlite-collection-generation
branch
from
August 31, 2026 20:15
46b3de2 to
1518b2b
Compare
Both SQLite-backed vector stores named a collection's native resources from (namespace, name) alone, so deleting a collection and creating one with the same name rebuilt resources under the same names. A handle opened on the deleted collection then addressed the collection that replaced it: in SQLiteVecVectorStore its writes were immediately visible there, and in SQLiteVectorStore its row landed in the new records table while _maybe_save_index rewrote the replacement's index file from the dead engine, so the record became queryable after a restart. The stale handle could also delete the replacement's applied pending operations and flip its index_saved. An incarnation, minted per creation and stored on the collection row, is now part of the resource names, the index path, the search engine cache key, and the pending operation rows. A stale handle addresses dropped tables and fails loudly instead of writing into the replacement. Existing databases are not migrated: collections created by an earlier version keep resources this version does not address. Migration is deferred with the wider question of where the server runs DDL. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Edwin Yu <[email protected]>
edwinyyyu
force-pushed
the
fix/sqlite-collection-generation
branch
from
September 1, 2026 20:17
1518b2b to
7bd964c
Compare
This was referenced Sep 2, 2026
Contributor
Author
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.
Purpose of the change
Both SQLite-backed vector stores let a collection handle reach the collection that replaces it. Native resource names derive from (namespace, name) alone, so deleting a collection and creating one with the same name rebuilds resources under the same names, and a handle opened before the deletion keeps addressing them.
Measured before this change, single-process, no concurrency involved:
SQLiteVecVectorStoreSQLiteVectorStoreQdrantVectorStoreMilvusVectorStoreSQLiteVectorStoreis the worse case.SQLiteVectorStoreCollectioncapturesindex_pathandsearch_engineat open time and_maybe_save_indexwrites that engine to that path, so a handle to a deleted collection rewrites the live index file of its replacement:The stale write's row also lands in the replacement's records table, and its
_PendingOperationRowcarries the same (namespace, name) — and those rows are replayed on startup "applied or not", which is how the record survives a restart._save_collection_indexwas likewise unscoped, so a stale handle could delete the replacement's applied pending operations and flip itsindex_saved, which marks the on-disk index as part of the durable contract.Description
An
incarnationis minted per creation, stored on_CollectionRow, and made part of everything that was previously keyed by (namespace, name):_collection_prefix, and so the records table and (for sqlite-vec) the vec0 virtual table_search_enginescache key_PendingOperationRow.incarnation, the save-threshold count, the "mark applied" updates, and the startup replay, which now skips operations whose incarnation is not the collection's current one_save_collection_index, so its pending-operation delete andindex_savedupdate touch only the incarnation that savedA stale handle therefore addresses dropped tables and fails loudly rather than writing into the replacement.
This deliberately does not take a
CollectionRegistrydependency. The registry exists to give a backend a transactional metadata authority it does not have; these stores already are one —_CollectionRowlives in the same engine as the per-collection tables andcreate_collectiondoes the DDL and the metadata write in one transaction. Delegating would split one authority into two and reintroduce a crash window that the registry-backed stores accept only because they have no alternative.Breaking changes
Existing databases are not migrated. Collections created by an earlier version keep resource names this version does not address, so their data is not reachable after upgrade. That is deliberate: migration is deferred along with the wider question of where the server runs DDL, and older data stays with older server versions for now.
Fixes/Closes
Fixes #1536.
Type of change
How Has This Been Tested?
Seven new tests across the two stores: a stale handle cannot write to a recreated collection; a stale handle does not clobber the recreated collection's index file, verified by bytes before and after and by reopening the database without a clean shutdown (a clean shutdown rewrites the index from the live engine and hides the damage); a database predating the column is migrated on startup and stays usable; and the unsuffixed name form is pinned as a compatibility contract.
Test Results: full server suite 1876 passed, 3 skipped; vector store suite 300 passed;
ruff check,ruff format --checkclean;ty check packagesunchanged at 20 baseline diagnostics, none in the touched files.Checklist
Further comments
Independent of the collection-registry stack (#1526, #1527, #1530, #1531, #1533) and based on
mainso it can merge on its own. #1531 states the invariant this restores as aVectorStoreCollectioncontract and currently names these two stores as known non-conformance; whichever lands second should drop that note.Terminology follows #1545, which introduces the same concept in the segment store: "incarnation" rather than "generation", since the value is a random uuid and
generation/epochinvite the assumption that incarnations are ordered and comparable. The branch name still saysgeneration; renaming it would break this PR's head ref, so it stays.Two adjacent items are deliberately left out.
SQLiteVecVectorStore.create_collectionstill reads, checks, then inserts although its primary key could arbitrate directly, which is a separate defect from this one. And a shared conformance test running this sequence against everyVectorStoreimplementation would be worth having, but it needs the container-backed suites and belongs in its own change.