Repository navigation
Conversation
edwinyyyu
force-pushed
the
feat/qdrant-sql-registry
branch
from
August 26, 2026 22:02
2658fa0 to
7c925ad
Compare
edwinyyyu
force-pushed
the
feat/qdrant-sql-registry
branch
3 times, most recently
from
August 27, 2026 17:27
d3a5e5f to
3ce47ae
Compare
12 of 13 tasks
edwinyyyu
force-pushed
the
feat/qdrant-sql-registry
branch
11 times, most recently
from
August 28, 2026 00:25
4a5521d to
23b62ae
Compare
This was referenced Aug 28, 2026
edwinyyyu
force-pushed
the
feat/qdrant-sql-registry
branch
2 times, most recently
from
August 31, 2026 22:00
a9493a5 to
395cf5c
Compare
…tion Signed-off-by: Edwin Yu <[email protected]>
…registry Signed-off-by: Edwin Yu <[email protected]>
edwinyyyu
force-pushed
the
feat/qdrant-sql-registry
branch
from
August 31, 2026 22:44
395cf5c to
7eddf95
Compare
This was referenced Sep 1, 2026
Closed
Contributor
Author
|
Superseded by a reworked stack. The premise this PR was built on has changed in four ways, each now filed separately:
The defects this PR did fix are still fixed by the replacement, and are now filed on their own so they do not get lost: #1562 (native name re-derived on open) and #1563 (stale handles resurrecting records). Closing rather than force-pushing, so the review discussion here stays attached to the design it was about. Replacement PRs to follow. |
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
Horizontal scalability: let multiple MemMachine server processes work with the same logical collections on one Qdrant cluster (#1525). Strict sharding of collection names across processes — what the
VectorStorecontract requires today — has always worked; this PR is the redesign that lifts the restriction for collection lifecycle management, by moving QdrantVectorStore's collection metadata out of Qdrant onto the SQL-backed config registry from #1526.Stacked on #1526; only the last commit is new to this PR.
Qdrant offers no conditional writes, transactions, or unique constraints, so the current
<ns>__registrybookkeeping (dummy-vector points keyeduuid5(name)) makescreate_collection/open_or_create_collection/delete_collectionnon-atomic read-check-write sequences, guarded only by a per-process asyncio lock. The moment two processes manage the same name:VectorStoreCollectionAlreadyExistsErrorstops firing — two concurrent creates both pass the absence check and last-writer-wins the registry point.open_or_create_collectioncalls with different configs each create a different native collection (native names aresha256(config)while registry points are keyed by name), one silently wins the registry, and records written through the loser's handle become permanently unreachable.VectorStoreCollectionConfigMismatchErrornever fires.With the registry's unique-constraint CAS as the commit point, both guarantees hold across processes sharing the same registry database, and generationed partition keys make
delete_collectionsafe against handles held in other processes. Scope: lifecycle/metadata management — data-path read-after-write visibility tuning remains a separate concern.Description
qdrant_vector_store.py: all__registrymachinery is deleted (_REGISTRY_*constants, uuid5 point ids,_ensure_namespace_registry_collection,_get_registry_entry,_parse_entry,_register_collection,_is_not_found_error,registry_replication_factor).QdrantVectorStoreParamsgains a requiredregistry: CollectionRegistry.Registry entries store resolved identity, not just config (
CollectionRegistryEntryfrom #1526):config,native_collection_name, and a generation-scopedpartition_key(minted at creation; also the shard key in distributed mode).sha256(config)on every open, so a later change to the config model's serialization (any added field re-hashes every config) cannot silently repoint existing collections at new empty native collections. The naming scheme for new collections is untouched.delete_collectionremoves that generation's data and the entry; a handle held across the deletion keeps writing into the dead generation — invisible to every reader, storage-only garbage — and a re-creation mints a fresh generation, so stale records are never resurrected. No locks and no per-write round trips; the residual cost is bounded garbage under dead generations (a future GC can sweep partition keys absent from the registry).Lifecycle ordering, chosen for crash windows:
open_collectionbuilds handles straight from the entry, so a crash between claim and native create would hand out handles to a nonexistent native collection.) Invariant: registry entry implies native collection exists.delete_collection); retrying the delete completes it._name_locksstays, with its comment updated: it now only serializes lifecycle operations within a process (preserving today's single-process open-vs-delete interleaving semantics and avoiding redundant idempotent round trips); cross-process correctness comes from the registry CAS.Wiring (
database_manager.py):async_get_qdrant_clientbuilds and starts aSQLAlchemyCollectionRegistrynamedqdrant_<conf name>on the engine named by the newQdrantConf.registry_database, passing it into the store — QdrantVectorStore holds a registry dedicated to it and cannot reach any other. One registry per Qdrant conf entry, so two Qdrant instances sharing one registry database get separate tables. Misconfiguration (unknown database name, or a conf entry name that cannot form a registry table name) raisesQdrantConfigurationErrorwith the fix spelled out.Configuration (
database_conf.py, samples, docs):QdrantConf.registry_database(required) names a configured relational database (postgresorsqliteentry);registry_replication_factoris removed — it existed to give the Qdrant-hosted registry read-your-writes, which is moot now. Sample configs and the configuration docs gained the new field; the installation wizard points Qdrant at its existing SQLite entry.Design doc:
design/collection_registry_backends.md.Breaking changes
QdrantConf.registry_databaseis required: existing Qdrant users add one line of YAML (single-node setups can point at asqliterelational entry). There is deliberately no per-host auto-SQLite fallback — divergent per-host registries would look consistent while reproducing exactly the races this PR removes. Staleregistry_replication_factorYAML keys are silently ignored (extra="ignore").<ns>__registryQdrant collections are no longer read, and there is no data migration. This is mostly self-healing: native collection names are unchanged, so the firstopen_or_create_collectionwith an unchanged config re-registers it and all data is reachable again. Caveat: paths that re-create with a currently derived schema (e.g. the episodic event backend) will orphan old partitions if that schema drifted since original creation — the old registry would have preserved the original config. Stale__registrycollections can be deleted manually.Fixes/Closes
Fixes #1525
Type of change
How Has This Been Tested?
Unit Test
Integration Test
Existing QdrantVectorStore suite unchanged in behavior, now parametrized with a registry over the full client matrix: in-memory (unit) plus HTTP, gRPC, and distributed-cluster containers (
integration).New
TestRegistryIntegration: no__registry-suffixed collections exist in Qdrant after creation; two stores sharing one registry+client see each other's collections (create raisesAlreadyExistscross-store, delete in one is observed by the other); concurrentopen_or_create_collectionwith mismatched configs yields exactly one winner and oneVectorStoreCollectionConfigMismatchError;("a__b", "c")and("a", "b__c")are distinct collections (key-ambiguity regression); re-creation mints a fresh generation over the same native collection; records upserted through a handle held across a deletion are unreachable from the re-created collection (resurrection regression).test_database_manager.py: registry built on the named engine and passed into the store; unknownregistry_databaseand un-usable conf entry names raiseQdrantConfigurationErrorand close the client.test_database_conf.py: field swap, required-field validation.Test Results: full server suite 1901 passed locally; targeted integration run (qdrant containers + PostgreSQL registry) 452 passed;
ruff check,ruff format --check,ty check packagesclean (no new diagnostics).Checklist
Further comments
The same swap for
MilvusVectorStorelands later in this stack.