-
Notifications
You must be signed in to change notification settings - Fork 33
Implement SVSIndex::getDataByLabel (MOD-17706) #1033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
713635e
af46163
c2b763f
d45ce88
ee9745e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ | |
| #include <shared_mutex> | ||
|
|
||
| #if HAVE_SVS | ||
| // For the SVS special case in getDataByLabel; remove with it (MOD-17706). | ||
| // For the compressed-backend check in getDataByLabel. | ||
| #include "VecSim/algorithms/svs/svs.h" | ||
| #endif | ||
|
|
||
|
|
@@ -118,9 +118,7 @@ class VecSimTieredIndex : public VecSimIndexInterface { | |
| * @brief Get the vector elements stored under a label, in insertion order. | ||
| * | ||
| * Contract on `VecSimIndexAbstract::getDataByLabel`, including that `vectors_output` arrives | ||
| * empty, with two caveats a tiered index cannot | ||
| * avoid, both of which only ever make an equality-testing caller answer "different": | ||
| * | ||
| * empty, with two caveats: | ||
| * - The vectors are the buffer's followed by the backend's, which for a multi-value label | ||
| * split across the tiers is not insertion order. | ||
| * - An ingest job inserts into the backend before removing from the buffer, so a vector | ||
|
|
@@ -132,13 +130,9 @@ class VecSimTieredIndex : public VecSimIndexInterface { | |
| * the backend, as this used to, reports nothing for a vector written recently enough to | ||
| * still be buffered -- which is exactly when a document is most likely to be written again. | ||
| * | ||
| * `flatIndexGuard` is held across both reads, in the order `relabelVector` and | ||
| * `acquireSharedLocks` take: it cannot prevent a duplicate, but it does stop the buffer's | ||
| * copy being removed between them. The backend's own data guard is deliberately not taken | ||
| * here -- its `getDataByLabel` takes it, because a shared main lock does not exclude an | ||
| * ingest mutating under `indexDataGuard`. Same division as | ||
| * `computeUnifiedIndexLabelsSetUnsafe`, which holds the outer locks and lets `getLabelsSet` | ||
| * take the inner one. | ||
| * A compressed SVS backend cannot report its stored vectors as values. Hence, for | ||
| * a multi-value label that already got a buffer contribution, we check `isLabelExists` | ||
| * in the backend before trusting the buffer alone. | ||
| */ | ||
| void getDataByLabel(labelType label, std::vector<std::vector<DataType>> &vectors_output) const { | ||
| #ifdef BUILD_TESTS | ||
|
|
@@ -147,37 +141,34 @@ class VecSimTieredIndex : public VecSimIndexInterface { | |
| assert(vectors_output.empty() && "getDataByLabel expects an empty output vector"); | ||
| #endif | ||
|
|
||
| // A quantized backend cannot report its stored vectors as values -- the stored form is | ||
| // compression plus metadata, and nothing here dequantizes -- so it would append nothing. | ||
| bool backend_can_report = true; | ||
| #if HAVE_SVS | ||
| // TODO(MOD-17706): remove once SVSIndex::getDataByLabel reports real data. Removing it | ||
| // means deleting this block, the `backend_can_report` flag, and the guarded include of | ||
| // svs.h, then unwrapping the body below. | ||
| // | ||
| // Until then nothing is read at all for an SVS backend: the buffer alone would be a | ||
| // partial answer for a multi-value label split across the tiers, and a caller cannot tell | ||
| // a subset from the whole. Skipping the reads also avoids waiting on `mainIndexGuard` | ||
| // behind an SVS batch update to be told nothing. | ||
| // | ||
| // Here rather than as an override in TieredSVSIndex because this method is not virtual: | ||
| // `VecSimTieredIndex` derives from `VecSimIndexInterface`, which does not declare it, and | ||
| // callers reach it through a `VecSimTieredIndex *` (RediSearch dynamic_casts to exactly | ||
| // that), so a derived override would simply not be found. The type test is deliberately | ||
| // explicit rather than dressed up as a capability: it is a special case, not | ||
| // architecture. | ||
| backend_can_report = dynamic_cast<const SVSIndexBase *>(this->backendIndex) == nullptr; | ||
| const auto *svs_backend = dynamic_cast<const SVSIndexBase *>(this->backendIndex); | ||
| const bool backend_cannot_report = svs_backend && svs_backend->isCompressed(); | ||
| #endif | ||
|
|
||
| std::shared_lock<std::shared_mutex> flat_lock(this->flatIndexGuard); | ||
| const size_t before_flat = vectors_output.size(); | ||
| this->frontendIndex->getDataByLabel(label, vectors_output); | ||
| #if HAVE_SVS | ||
| // no relevant data in flat and backend cannot function | ||
| if (backend_cannot_report && vectors_output.size() == before_flat) { | ||
| return; | ||
| } | ||
| #endif | ||
| if (backend_can_report) { | ||
| std::shared_lock<std::shared_mutex> flat_lock(this->flatIndexGuard); | ||
| const size_t before_flat = vectors_output.size(); | ||
| this->frontendIndex->getDataByLabel(label, vectors_output); | ||
| // Whether the buffer held it, measured rather than read off emptiness, so the tier | ||
| // decision does not depend on an assertion that only exists in test builds. | ||
| if (this->backendIndex->isMultiValue() || vectors_output.size() == before_flat) { | ||
| std::shared_lock<std::shared_mutex> main_lock(this->mainIndexGuard); | ||
| this->backendIndex->getDataByLabel(label, vectors_output); | ||
| // continue to look the data in the backend | ||
| if (this->backendIndex->isMultiValue() || vectors_output.size() == before_flat) { | ||
| std::shared_lock<std::shared_mutex> main_lock(this->mainIndexGuard); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For a compressed SVS backend with no buffer contribution, this acquires mainIndexGuard even though the result must be empty. It can wait behind a batch’s exclusive addVectors lock, then immediately return nothing from SVSIndex::getDataByLabel. While waiting, it also holds flatIndexGuard shared, potentially delaying buffer writes.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| #if HAVE_SVS | ||
| if (backend_cannot_report && vectors_output.size() > before_flat && | ||
| svs_backend->isLabelExists(label)) { | ||
| // The buffer's contribution alone would look like the whole answer; report | ||
| // nothing instead, the same rule `SVSIndex::getDataByLabel` applies to a | ||
| // single tier. | ||
| vectors_output.resize(before_flat); | ||
| return; | ||
| } | ||
| #endif | ||
| this->backendIndex->getDataByLabel(label, vectors_output); | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.