Visitar URL original
LSM_VECTOR PRODUCT: a reopen with nothing deleted logs "Deleted vectors detected" and skips the #8852 ordinal-map path, so the first search re-reads every vector's document · Issue #10568 · ArcadeData/arcadedb · GitHub
Skip to content

LSM_VECTOR PRODUCT: a reopen with nothing deleted logs "Deleted vectors detected" and skips the #8852 ordinal-map path, so the first search re-reads every vector's document #10568

Closed

Description

@tae898

After #8852 (fixed in #8863), a reopened LSM_VECTOR index with quantization NONE loads its graph through the ordinal map recorded next to it and skips the per-vector document read. A PRODUCT-quantized index with nothing deleted still takes the old walk on every reopen, and it also logs a line that says vectors were deleted.

What happens

On reopen with getDeletedCount() == 0, loadPersistedGraphOrDecidePrefix calls reusePersistedGraphDespiteDeletions(gf, true) (the #8852 path). That method returns null at once for PRODUCT and, before it does, logs:

Deleted vectors detected in index %s, which uses PRODUCT quantization: its PQ codes are addressed by the same ordinals the rebuild reassigns, so the graph is rebuilt from scratch rather than reused (issue #7842)

Nothing was deleted, and nothing is rebuilt either: the caller falls through to gf.loadGraph() plus the walk that re-reads every vector's document (the cost #8852 describes: 2.8 s at 1M vectors, 17.9 s at 10M). So the log line is false, and the first search after every reopen pays the O(N) read that #8852 removed for NONE.

The cause is the PRODUCT refusal that #7842 / #7844 added for the deletion case, which is right there: after a deletion the ordinal space has a hole and the PQ codes were built over a dense one. With no deletion the recorded map is exactly the array the PQ codes were built with. The refusal sits above both calls, so it also applies to the no-deletion call, and the writer does not record a map for PRODUCT at all (writeGraphManifest: "a file nothing will ever read", from the #7844 review), which #8852 later made untrue for the no-deletion case.

Measured

Untimed (laptop, not a controlled benchmark), Python bindings on engine 7f65a6e (upstream main of 2026-10-22), SIFT 100k, EUCLIDEAN, pqSubspaces 32, pqClusters 256, build_graph_now(), close, then open in a fresh process and run one vectorNeighbors (k 10, efSearch 100):

index line logged on the first search first search after reopen
PRODUCT Deleted vectors detected ... 0.51 s
NONE (same data) no 0.32 s

An idle check of 25 s after that first search showed no further CPU (0.13 s PRODUCT, 0.10 s NONE) and no changed index file, so no rebuild actually happens; the extra 0.19 s is most likely the walk (inferred from the difference, not profiled). I will add a 1M measurement to the fix PR.

Proposed fix

  • record the ordinal map for PRODUCT too;
  • let the exact-cover (no deletion) call reuse it for PRODUCT, when the PQ codes are loaded and are one per mapped ordinal;
  • keep the PRODUCT refusal, and its log line, only for the call that runs because something was deleted;
  • an index persisted by an older build has no map for PRODUCT: it keeps taking the walk, without the false line.

I will send the PR for this.

Two smaller things in the same area

Not part of the fix above, seen in my hunt on the same engine commit through SQL (not re-measured for this issue):

  • the vectorNeighbors query option rerank is read with opts.getInt("rerank", -1) (SQLFunctionVectorNeighbors), so rerank: 2.5 is accepted, while index METADATA refuses rerankOversample 2.5;
  • vectorNeighbors(..., -1, ...) returns 0 rows without an error.

Activity

  1. added
    engineCore engine: document/schema API
    severity:minorCosmetic, documentation, edge case, or a workaround exists
    vectorVector index and embedding search
    in progressThe team is actively working on this issue
    on Oct 22, 2026
  2. added this to the 26.11.1 milestone on Oct 22, 2026
  3. added a commit that references this issue on Oct 22, 2026
  4. robfrank commented on Oct 22, 2026

    @robfrank
    Collaborator

    Fixed by #10571. A PRODUCT-quantized LSM_VECTOR index with nothing deleted now records its ordinal map next to the graph, so a reopen takes the fast ordinal-map path instead of re-reading every vector's document, and it no longer logs "Deleted vectors detected" when nothing was deleted. In the 1M-vector measurement in the PR, the first search after a reopen went from about 2.65 s to about 0.70 s, in line with an unquantized index. After a real deletion, a PRODUCT index still rebuilds as before, and an index persisted by an older build (with no recorded map) keeps the slower path, without the false log line, until it is rebuilt.

    Still open: the rerank: 2.5 and vectorNeighbors(..., -1, ...) observations from this issue were not addressed by #10571. A review follow-up is tracked in #10584 (the reopen reuse checks the PQ code count, not that the codes belong to the recorded graph).


    ❤️ Thanks for helping make ArcadeDB better! ArcadeDB is free and open source, sustained by its community. If it's useful to you or your company, please consider becoming a sponsor to keep development going.

  5. removed
    in progressThe team is actively working on this issue
    on Oct 22, 2026
  6. added 2 commits that reference this issue on Oct 22, 2026
  7. added a commit that references this issue on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugengineCore engine: document/schema APIperformanceseverity:minorCosmetic, documentation, edge case, or a workaround existsvectorVector index and embedding search

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions