Skip to content

vector-store: fix index size counter desync#447

Merged
swasik merged 1 commit into
scylladb:masterfrom
m-szymon:count-fix
May 13, 2026
Merged

vector-store: fix index size counter desync#447
swasik merged 1 commit into
scylladb:masterfrom
m-szymon:count-fix

Conversation

@m-szymon

Copy link
Copy Markdown
Collaborator

usearch's remove() returns Ok(usize) where the value is the number of entries actually removed (0 if the key was not found). The previous wrapper converted this to Ok(()) unconditionally, causing size.fetch_sub(1) to be called even when nothing was removed from the index.

This manifests when an item is first seen during the range scan with an invalid vector (e.g. wrong dimensionality), causing the usearch add() to return Err. The item is still recorded in primary_ids and vector_timestamps with ETValue::Some — intentionally, to advance the timestamp for CDC deduplication. When a later valid update arrives via CDC, the Entry::Occupied path emits RemoveBeforeAddVector (because vector_already_exists=true) followed by AddVector. The remove hits a key that was never in usearch, returns Ok(0), but the size counter was decremented anyway. The subsequent successful add only brought it back to the pre-update value, so the count never advanced.

Fix: UsearchIndex::remove() now returns Ok(bool) and size is decremented only when an entry was actually removed.

usearch's remove() returns Ok(usize) where the value is the number of
entries actually removed (0 if the key was not found). The previous
wrapper converted this to Ok(()) unconditionally, causing size.fetch_sub(1)
to be called even when nothing was removed from the index.

This manifests when an item is first seen during the range scan with an
invalid vector (e.g. wrong dimensionality), causing the usearch add() to
return Err. The item is still recorded in primary_ids and
vector_timestamps with ETValue::Some — intentionally, to advance the
timestamp for CDC deduplication. When a later valid update arrives via
CDC, the Entry::Occupied path emits RemoveBeforeAddVector (because
vector_already_exists=true) followed by AddVector. The remove hits a
key that was never in usearch, returns Ok(0), but the size counter was
decremented anyway. The subsequent successful add only brought it back to
the pre-update value, so the count never advanced.

Fix: UsearchIndex::remove() now returns Ok(bool) and size is decremented
only when an entry was actually removed.
@m-szymon

Copy link
Copy Markdown
Collaborator Author

Initially it was part of #392 (as some tests added there would fail).
But it deserves separate PR.

Answering remarks from that review:
@ewienik

Are you sure that usearch can file silently because of dimension? We should check this before calling usearch add().

Initially I wrote that "usearch add() fail silently." I used wrong wording. Usearch returns error. And it is handled and generates warn. I meant that such vector in general is ignored (not indexed) and considered it a correct behavior.

What about converting the result into an error if result == 0?

It is not an error. And it would trigger warn, when we actually fix the invalid entry.
Unless we keep information with timestamp that vector is invalid and was not added to index, in which case we will not need to try to remove it. But I think it is overkill.

@QuerthDP
I used bool as a return type as you suggested.

@m-szymon
m-szymon requested review from QuerthDP and ewienik May 12, 2026 08:19
@m-szymon
m-szymon marked this pull request as ready for review May 12, 2026 08:21
@swasik
swasik added this pull request to the merge queue May 13, 2026
Merged via the queue into scylladb:master with commit 67f31cc May 13, 2026
30 checks passed
@m-szymon
m-szymon deleted the count-fix branch May 14, 2026 10:18
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.

3 participants