Skip to content

Replace a deleted DiskANN medoid with a node of the graph - #346

Open
MayCXC wants to merge 1 commit into
asg017:mainfrom
MayCXC:diskann-medoid-from-graph
Open

MayCXC wants to merge 1 commit into
asg017:mainfrom
MayCXC:diskann-medoid-from-graph

Conversation

@MayCXC

@MayCXC MayCXC commented Oct 23, 2026 •

Copy link
Copy Markdown

Deleting a DiskANN column's medoid while rows wait in its buffer leaves the graph unsearchable until the buffer is flushed.

diskann_medoid_handle_delete() picks the new medoid with SELECT rowid FROM _vectorsNN WHERE rowid != ?1 LIMIT 1. With buffer_threshold set, _vectorsNN also holds the buffered rows, which have no node yet, and the lowest rowid can be one of them. Every search then starts from a node diskann_search() cannot read: the loop skips it and ends, the graph contributes no rows, and the KNN query returns only what the buffer scan finds.

create virtual table t using vec0(emb float[8] indexed by diskann(neighbor_quantizer=int8, n_neighbors=8, buffer_threshold=10));
-- rows 100 to 109 (the tenth insert flushes them into the graph), then rows 1, 2 and 3 (buffered)
delete from t where rowid = 100;  -- the medoid
-- the new medoid is 1, which has no node
select rowid from t where emb match :vector_of_101 and k = 1;  -- returns a buffered row, not 101

On main, after that delete, none of the 9 remaining nodes is returned by a k = 1 query with its own vector, for each of eight random tables. Here, the new medoid is the first other node of _diskann_nodesNN, so it is always a node of the graph, and all 9 are found in each of them. When no node is left, the medoid is set to empty as before. A failed step of that query is now returned, where it used to set the medoid to empty as well.

test_diskann_delete_medoid_with_rows_in_buffer in test-diskann.py fails on main and passes here; the Python suite passes (240 passed, 66 skipped), test-diskann.py passes against an AddressSanitizer and UndefinedBehaviorSanitizer build, and the build's warnings are main's. The test uses the int8 quantizer: under the binary quantizer a query can also lose its own row to one whose quantized distance ties it, which #345 fixes.

It merges cleanly with every open PR that touches the DiskANN code, #344 and #345 included.

Origin. Found in the DiskANN port to photostructure/sqlite-vec, photostructure#11 (cd53413), which lists it among the port's differences from upstream; the change and its test are written here for asg017's main. No open or closed PR or issue here makes the same change.

When the medoid was deleted, diskann_medoid_handle_delete() took the
first other rowid of _vectorsNN as the new one, which can be a vector
still waiting in the buffer, with no node: every search then started
from a node it could not read, found nothing in the graph, and returned
only the buffer until the next flush. The replacement is now the first
other node of _diskann_nodesNN, and a failed step of that query is
returned instead of emptying the graph's medoid.

This branch has not been deployed

No deployments
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.

1 participant