Route every Qdrant collection reference through a single helper so that,
when registry.enable_online_feature_view_versioning is set, each feature
view version gets its own collection (driver_stats_v2) instead of all
versions sharing one.
The helper is built on compute_versioned_name rather than
compute_table_id, which the other stores use. Qdrant collections have
always been named by the bare feature view name, with no {project}_
prefix, so adopting compute_table_id would rename every collection in
every existing deployment. With versioning disabled the collection name
is byte-identical to today's.
Covered by write, update, teardown and document-retrieval paths.
online_read is intentionally left out of the versioned-read allowlist:
it has a separate pre-existing defect and cannot serve a versioned read
correctly yet. See the PR description for a runnable reproduction.
Part of feast-dev#2728. Closes feast-dev#6179
Signed-off-by: arose26 <145766958+arose26@users.noreply.github.com>
Closes #6179. Part of #2728.
What this does
Routes every Qdrant collection reference through one helper, so that with
registry.enable_online_feature_view_versioning: true each feature view version gets
its own collection (driver_stats_v2) instead of all versions sharing one. Applied to
the write, create, update, teardown and document-retrieval paths.
One deliberate deviation from the FAISS/Milvus precedent
Those stores use compute_table_id(project, table, versioning), which yields
{project}_{name}[_v{N}]. This PR uses compute_versioned_name instead, which yields
{name}[_v{N}] with no project prefix.
The reason is that Qdrant collections have always been named by the bare table.name —
unlike Milvus, which already had the {project}_ prefix before versioning was added to it,
so gaining the flag there was non-breaking. Switching Qdrant to compute_table_id would
rename every collection in every existing deployment and orphan their data. With versioning
disabled the collection name here is byte-identical to today's, which
test_unversioned_store_still_round_trips pins.
Happy to switch to the project-prefixed form if you'd rather have consistency across
stores and want to handle the migration — just say so.
Why online_read is not added to the versioned-read allowlist
I did not add QdrantOnlineStore to OnlineStore._is_versioned_read_supported(), because
QdrantOnlineStore.online_read cannot currently serve any read, versioned or not. It looks
like it has never been exercised — there is no unit test for it, and it fails on two
independent counts before reaching Qdrant:
pydantic rejects (Input should be a valid string), and
QdrantOnlineStoreConfig nor VectorStoreConfig defines collection_name, so that
line raises AttributeError.
It also returns one entry per stored point holding a base64 str, where the
OnlineStore.online_read contract (see sqlite.py) is one entry per requested entity key,
in order, holding ValueProto values, with (None, None) for a miss.
Reproduction, stock main, no external service — an in-process Qdrant is enough:
Fixing that properly means deciding how entity_key should be stored — it is currently
written into the payload as raw bytes from serialize_entity_key, which MatchAny cannot
filter on, and which retrieve_online_documents then reads back through
str(payload.get("entity_key")), producing a bytes repr rather than the value
_build_retrieve_online_document_record expects. That is a storage-format call I did not
want to make unilaterally inside a versioning PR, so I have kept it out of scope. Happy to
open a separate issue, or to take it in a follow-up once you've said which encoding you want.
So this PR delivers the versioned collection namespace; versioned scalar reads stay
correctly gated behind VersionedOnlineReadNotSupported until online_read works.
Tests
New sdk/python/tests/unit/infra/online_store/test_qdrant_online_store.py, 6 tests, running
against an in-process Qdrant (location=":memory:") — no service required, and skipped via
importorskip when qdrant-client is absent.
point each, no cross-contamination
Verified red-before/green-after: with the change reverted, the two versioning behaviour
tests fail while the unversioned control still passes, so they are testing the change rather
than the setup.
Regression check across the 56 unit test files touching online stores: the set of failing
and erroring tests is identical with and without this change (diff of the sorted
FAILED/ERROR lines is empty). Those pre-existing failures are missing optional
dependencies in my environment. ruff check, ruff format and mypy are clean on both
files.
🤖 Written with Claude Code (Claude Opus 5), reviewed by @arose26.