Skip to content

fix(storage): no orphan vector index entry when the label-add tag write is refused - #5081

Open
andrejtonev wants to merge 2 commits into
fix/vector-abort-undo-nothrowfrom
fix/vector-index-add-label-orphan-key
Open

andrejtonev wants to merge 2 commits into
fix/vector-abort-undo-nothrowfrom
fix/vector-index-add-label-orphan-key

Conversation

@andrejtonev

Copy link
Copy Markdown
Contributor

Stacked on #5077; review only the commits after it.

When a label is added, VectorIndex::ApplyAddLabel adds the vertex to usearch first and then writes the vector-index tag into the property store. Both steps run under AddLabel's OutOfMemoryExceptionEnabler. If the memory limit refuses the tag write, the transaction aborts with a usearch entry but no tag. The ADD_LABEL undo keys off the tag, so it skips the vertex and the entry stays behind as an orphan. After that, the index's search returns a vertex its label filter does not admit, and the index count is off by one. The entry is cleared only when the label is re-added, the vertex is deleted, the index is dropped or the server restarts.

The window predates #4992: master has the same add-then-tag order under the same enabler. #4992 widens it. With that PR the usearch tape allocation is no longer refusable and may overshoot the limit, so the very next fresh-extent allocation, the tag write, gets refused.

Changes

  • ApplyAddLabel builds the new id list and the tag value before the usearch add. Nothing has been mutated yet at that point, so a refusal there is harmless.
  • It then stores the tag under an OutOfMemoryExceptionBlocker. The write is still tracked but can no longer be refused, which is the same rule as AtomicMemoryBlock. The overshoot is bounded by the vertex's own property buffer. The abort path (restore=true) already ran under UndoNoThrow's blocker, so it behaves as before.
  • Writing the tag first was rejected. A later throw from the add would leave a tag with no entry, and the abort would then lose the embedding.

Result

  • New unit test VectorIndexTest.AddLabelTagWriteRefusedLeavesNoOrphanEntryAfterAbort. It puts a vertex in index A with a 3 MiB exact-size property buffer. Adding label B on the same property grows the tag by 8 bytes, which forces a fresh-extent reallocation right after the usearch add. A tight graph memory limit is set. Without the fix, the write is refused, and after the abort idx_B still holds the vertex (size 1, search hit), reproducing in 3 of 3 runs. With the fix, the write succeeds above the limit and the abort leaves idx_B empty.
  • vector_index, vector_edge_index, storage_v2 and storage_v2_indices pass.

@andrejtonev andrejtonev added bug bug CI -build=coverage -test=core Run coverage build and core tests on push CI -build=coverage -test=clang_tidy CI -build=debug -test=core Run debug build and core tests on push CI -build=debug -test=integration Run debug build and integration tests on push CI -build=release -test=e2e Run release build and e2e tests on push CI -build=release -test=stress Run release build and stress tests on push CI -build=release -test=benchmark Run release build and benchmark on push Docs - changelog only Docs - changelog only labels Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

This PR has potential conflicts with the following other open pull requests which modify the same files:

@sonarqubecloud

sonarqubecloud Bot commented Oct 9, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug bug CI -build=coverage -test=clang_tidy CI -build=coverage -test=core Run coverage build and core tests on push CI -build=debug -test=core Run debug build and core tests on push CI -build=debug -test=integration Run debug build and integration tests on push CI -build=release -test=benchmark Run release build and benchmark on push CI -build=release -test=e2e Run release build and e2e tests on push CI -build=release -test=stress Run release build and stress tests on push Docs - changelog only Docs - changelog only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant