john-mlika opened a new issue, #16419:
URL: https://github.com/apache/lucene/issues/16419

   ### Description
   
   On `branch_10x`, `KnnVectorsReader#finishMerge()` is dead code: nothing in 
the branch calls it.
   The revert half of the merge read-advice machinery is disconnected, so every 
merge of a vector
   format that propagates `getMergeInstance()` — plain float/HNSW fields via
   `Lucene99FlatVectorsReader`/`Lucene99HnswVectorsReader` — flips the 
**shared, search-visible** raw
   `.vec` `IndexInput` to `DataAccessHint.SEQUENTIAL` 
(`Lucene99FlatVectorsReader#getMergeInstance` →
   `vectorData.updateIOContext(...)`, which mutates and returns `this`) and 
**nothing ever flips it
   back**. The advice sticks until the segment reader is closed.
   
   This is not a bug on `main`.
   
   ### How the branch got here
   
   - #14977 (2025-09-17) fixed exactly this problem on `main` — its own 
rationale was *"ReadAdvice not
     being reset properly"* — by moving the cascade initiation into 
`SegmentMerger#cleanupMerge()`,
     invoked from both IndexWriter call sites inside `finally` blocks (on main:
     `IndexWriter.java:3544-3545` addIndexes path, `:5356-5357` merge path; 
`SegmentMerger.java:331-337`
     iterates `mergeState.knnVectorsReaders` calling `finishMerge()`). #14977 
was never backported to
     `branch_10x`.
   - Commit `d1c4db43036` ("Prevent writing vectors twice during merging HNSW 
graphs", 2026-04-28 — the
     `branch_10x` backport of #15732) **deleted the previous caller**: the 
private
     `finishMerge(MergeState)` helper and its `reader.finishMerge()` loop in 
`KnnVectorsWriter#merge()`,
     without the #14977 replacement existing on the branch. It is an ancestor 
of the
     `releases/lucene/10.5.0` tag, so 10.5.0 shipped with it.
   - Net: since `d1c4db43036`, `branch_10x` has the advice-flip side 
(`getMergeInstance`) fully wired
     and the advice-revert side (`finishMerge`) unreachable. Verified against 
the released
     `lucene-core-10.5.0.jar`: a jar-wide search finds `finishMerge` only on 
`KnnVectorsReader` and its
     implementors (`Lucene99FlatVectorsReader`, `Lucene99HnswVectorsReader`,
     `PerFieldKnnVectorsFormat$FieldsReader`) — no call site.
   
   ### Impact
   
   For any 10.5.x deployment using mmap and float vector fields (the advice 
machinery is a no-op on
   Directory implementations that ignore read advice):
   
   - **During a merge** (intended, same as main): concurrent searches on the 
merging segments read raw
     vectors with SEQUENTIAL advice — readahead-friendly, random-access-hostile.
   - **After the merge** (the regression): the flip is never reverted, so 
SEQUENTIAL advice persists on
     the *pooled* segment readers that searches share, for as long as those 
readers live — until the
     source segments are dropped *and* every NRT reader referencing them cycles 
out. For merges that
     fail or abort, and for segments that survive as sources of aborted merges, 
the mis-advice persists
     until segment close.
   - Downstream, Elasticsearch 9.x ships Lucene 10.5.0, so mmap-served float 
vector fields there
     inherit this today.
   
   One related note: quantized readers are currently shielded from this by a 
separate gap — they don't
   override `getMergeInstance` at all, so the flip never reaches them. I have a 
change ready that closes
   that gap; it seems worth resolving this issue on `branch_10x` before that 
propagation is backported,
   otherwise quantized fields would join the regression.
   
   ### Proposed fix
   
   Backport #14977 to `branch_10x` (restore `SegmentMerger#cleanupMerge()` plus 
the two
   `finally`-covered IndexWriter invocations), or fold an equivalent 
restoration into the next vector
   backport touching the area. By #14977's own framing this is a bug fix, so it 
seems to qualify for a
   10.5.x patch release independently of any optimization work.
   
   One reason this hasn't shown up in CI: the test framework's own invariant is 
weaker on this branch.
   `AssertingKnnVectorsFormat` closes with
   `assert finishMergeCount.get() <= 0 || mergeInstanceCount.get() == 
finishMergeCount.get();` on
   `branch_10x`, whereas `main` tightened it to a strict `assert 
mergeInstanceCount.get() ==
   finishMergeCount.get();` in the very commit that fixed this (`085a0ae054b`, 
#14977). So on
   `branch_10x` the `AssertingCodec` cannot catch the regression either.
   
   ### Reproducing it
   
   I wrote a detector (`TestMergeReadAdviceRevert`, identical source on both 
branches) that indexes two
   segments, holds an NRT reader open across `forceMerge(1)`, and records every 
`updateIOContext` per
   file through a recording `FilterDirectory`. It asserts that each `.vec` 
switched to SEQUENTIAL during
   the merge is switched back before its input closes. On `branch_10x` it fails:
   
   ```
   The following .vec files were switched to DataAccessHint.SEQUENTIAL for the 
merge and were
   never switched back (KnnVectorsReader#finishMerge() was never invoked):
     _0_Lucene99HnswVectorsFormat_0.vec [ADVICE:SEQUENTIAL]
     _1_Lucene99HnswVectorsFormat_0.vec [ADVICE:SEQUENTIAL]
   ```
   
   One flip, no revert, and both source inputs are still open (pinned by the 
NRT reader) when the
   assertion runs. The same test passes on `main`.
   
   Three constraints matter, or the detector silently false-passes:
   
   - the field must be a **plain float HNSW** field — on `branch_10x` the 
quantized readers don't
     override `getMergeInstance` at all, so a quantized detector fails for the 
wrong reason;
   - an **NRT reader must be held open across the merge** — a merge-created 
pooled reader is opened
     with `IOContext.merge(...)`, whose `withHints` is a no-op, so the flip 
never becomes observable;
   - the recording `IndexInput` must explicitly delegate `updateIOContext`, 
`clone()` and both `slice`
     overloads (`FilterIndexInput` forwards none of them).
   
   Happy to open the backport PR and contribute the test if it would help.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to