A kNN query asks every segment for its k nearest vectors. Since 9.10.0 the unfiltered query builds each segment's collector first, with a heap of k + 1 longs allocated up front, and only then looks at how many vectors the segment holds. Nothing caps k at the segment's size.
- Index 3 documents with a float vector (one segment, default codec) and search
new KnnFloatVectorQuery("v", target, k)with no filter, on a plainIndexSearcher. - k = 1000 returns all 3 documents, as the javadoc ("the number of documents to find") and the upstream
testFindAllpromise. You expect the same for k =Integer.MAX_VALUE, the usual "all" value; 9.9.2 gives exactly that for the unfiltered query. - Instead the query builds the segment's collector first, with a heap of k + 1 longs, and only then reads the segment's vector count. k =
Integer.MAX_VALUEthrowsIllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 2147483647. - Smaller huge k values allocate instead: k = 2^24 reserves 128 MiB to return 3 hits, and k = 2^28 throws
OutOfMemoryErrorwith a 2 GiB heap. Four segments cost about 9 times one. - With a filter that matches every document, the same k =
Integer.MAX_VALUEreturns the 3 hits: that path caps its queue atmin(k, matches)(since 9.11.0). Lucene capped k at the segment's vector count until 9.9.2; #12962 dropped the cap. The joinDiversifyingChildren*KnnVectorQueryhas the same flaw since 9.8.0, andSeededKnnVectorQueryalso sizes its seed collector by k.
Anyone on 9.10.0 to 10.5.2 or main who runs KnnFloatVectorQuery or KnnByteVectorQuery (also seeded or patience variants) without a filter with a very large k, such as Integer.MAX_VALUE for "all" or a client's k forwarded unchecked; the join DiversifyingChildren*KnnVectorQuery since 9.8.0. Default codec and searcher. Ordinary k (thousands) wastes only kilobytes; no wrong hits were seen.
Mechanism, terms and numbers858 words
KnnFloatVectorQuery accepts any k >= 1 ("the number of documents to find"), and a k larger than the index returns every vector: k = 1000 over a 3-document index gives 3 hits. Since 9.10.0 the unfiltered (approximate) path builds each segment's collector with a heap sized from k (k + 1 longs on a single segment) before it looks at how many vectors the segment has. So on a single-segment index k = Integer.MAX_VALUE throws IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 2147483647 from an internal heap (with several segments it runs out of memory instead), k = 2^28 over the same 3 documents throws OutOfMemoryError with a 2 GiB heap, and k = 2^24 allocates about 128 MiB to return 3 hits. On main the same k works on that index with a filter that is not match-all (the exact path caps its queue at Math.min(k, cost), since 9.11.0) and through LeafReader#searchNearestVectors(..., int k, ...), which still caps k at the vector count. Up to 9.9.2 the query went through that method, and the readers before it carried the comment "bound k by total number of vectors to prevent oversizing data structures"; #12962 (9.10.0) made the query build its own collector and the cap was lost. The join DiversifyingChildren{Float,Byte}KnnVectorQuery has the same shape and has been unbounded since it was added in 9.8.0, and SeededKnnVectorQuery also sizes its seed collector by k. No wrong hits were seen: every search either returned the expected hits or threw.
- Segment collector: for each segment, the kNN query creates a
KnnCollectorthrough aKnnCollectorManager. The default one,TopKnnCollector, keeps the best hits in aNeighborQueue, which is aTernaryLongHeap. - Approximate vs exact path: without a filter, every segment takes the approximate path (
approximateSearch). With a filter, a segment whose matching documents are no more than its share of k takes the exact path (exactSearch), which scores the matches directly.
- The constructor rejects only k < 1 (
AbstractKnnVectorQuery.java:67-68). Without a filter (or with one that rewrites toMatchAllDocsQuery, which is dropped,:87-103),getLeafResultscallsapproximateSearch(:252-254). KnnFloatVectorQuery.approximateSearchcallsknnCollectorManager.newCollector(...)first (KnnFloatVectorQuery.java:107-108), then reads the vector values (:110) and checksMath.min(knnCollector.k(), floatVectorValues.size()) == 0(:115): the remnant of the old cap, after the allocation.KnnByteVectorQueryis the same (:106-107,:114).OptimisticKnnCollectorManagerturns k intoperLeafTopKfrom the segment's share of the index (AbstractKnnVectorQuery.java:319-347); for one segment that is k itself (computed in float, so a k above 2^24 is rounded to the nearest float).TopKnnCollectorManagerpasses it on unchanged:new TopKnnCollector(k, visitedLimit, searchStrategy)(knn/TopKnnCollectorManager.java:46,:52).TopKnnCollectorbuildsnew NeighborQueue(k, false)(TopKnnCollector.java:45), andTernaryLongHeap(k)throwsIllegalArgumentExceptionfor k >=ArrayUtil.MAX_ARRAY_LENGTH("Throw exception to prevent confusing OOME") or allocatesnew long[k + 1]at once (util/TernaryLongHeap.java:56-69).- With several segments, the optimistic re-entry pass builds two more full-k collectors per re-entered segment (
ReentrantKnnCollectorManager,AbstractKnnVectorQuery.java:128-130,:433-476). - Join:
DiversifyingNearestChildrenKnnCollectorManagerbuildsnew DiversifyingNearestChildrenKnnCollector(k, ...)(:65-66,:77-78), whoseNodeIdCachingHeap(k)allocates three arrays of k + 1 entries and a k-sized hash map (DiversifyingNearestChildrenKnnCollector.java:61,:140-153). - Seeded:
SeededKnnVectorQueryfirst builds a seed collector,new TopScoreDocCollectorManager(k, null, Integer.MAX_VALUE).newCollector()(SeededKnnVectorQuery.java:315-316), with the query's full k on every segment; itsTopScoreDocCollectorallocatesnew TernaryLongHeap(numHits, ...)at once (TopScoreDocCollector.java:44).
Where Lucene does cap the same request: the exact path, Math.min(k, Math.toIntExact(acceptIterator.cost())) (AbstractKnnVectorQuery.java:373, #13184, from 9.11.0); LeafReader#searchNearestVectors(..., int k, ...), k = Math.min(k, ...size()) (index/LeafReader.java:288, :332, :376); regular top-N search, IndexSearcher#searchAfter caps numHits at max(1, maxDoc) (IndexSearcher.java:587-597).
| 3 documents, 1 segment, default codec | expected | main a9779ef3c193 |
|---|---|---|
| k = 1000, no filter | 3 hits | 3 hits |
k = Integer.MAX_VALUE, TermQuery filter matching all 3 | 3 hits | 3 hits |
k = Integer.MAX_VALUE, no filter | 3 hits | IllegalArgumentException from TernaryLongHeap |
| k = 2^28, no filter | 3 hits | OutOfMemoryError with a 2 GiB heap |
| k = 2^24, no filter | 3 hits | 3 hits, 134,224,064 bytes allocated (14,336 for k = 1000) |
| the unfiltered rows on 9.9.2 | 3 hits | 3 hits (5,000 bytes for k = 2^24) |
| the filtered row on 9.9.2 and 9.10.0 | 3 hits | IllegalArgumentException from the exact path's HitQueue(k) (capped from 9.11.0 by #13184) |
join query, 2 parents, k = Integer.MAX_VALUE | 2 parents | IllegalArgumentException from NodeIdCachingHeap (also on 9.8.0, 9.9.2, 9.12.3 and 10.5.2) |
The k = 2^24 rows come from runs on main ea57360912ed, whose query path is the same as a9779ef3c193's. On 12 vectors, k = 2^22 allocates 33,561,432 bytes in 1 segment and 302,491,608 bytes in 4 segments (9.01x); one k = 2^26 search over 3 vectors runs out of a 512 MiB heap. On a9779ef3c193, k = Integer.MAX_VALUE over 4 segments of 3 documents runs out of a 2 GiB heap instead of throwing IllegalArgumentException, and the same k also fails for KnnByteVectorQuery, KnnFloat16VectorQuery, PatienceKnnVectorQuery, SeededKnnVectorQuery and the byte join query. The filtered vs unfiltered contrast exists only from 9.11.0.
Exposure and likelihood268 words
- Who is hit. Unfiltered
KnnFloatVectorQuery/KnnByteVectorQuery(or with a filter that rewrites toMatchAllDocsQuery) on 9.10.0 through 10.5.2 and main;DiversifyingChildrenFloatKnnVectorQuery/DiversifyingChildrenByteKnnVectorQueryon 9.8.0 and later. Default codec, defaultIndexSearcher, no setting.SeededKnnVectorQueryandPatienceKnnVectorQuery(since 10.2.0) andKnnFloat16VectorQuery(main only) fail the same way on main (executed on main only). - Trigger. k =
Integer.MAX_VALUEto mean "all" (an idiomIndexSearchersupports:TestIndexSearcher.testHugeN), a k derived from the index size, or a server that forwards a client's k unchecked. - Effect. On a single-segment index, k at or above
ArrayUtil.MAX_ARRAY_LENGTHalways throwsIllegalArgumentExceptionon the first search; with several segments the first pass sizes each heap by the segment's share of k, so it runs out of memory instead (4 segments, 2 GiB heap). Below that, a single-segment search reserves about 8 bytes per unit of k before it collects anything: 128 MiB for k = 2^24, a 512 MiB heap exhausted at k = 2^26, a 2 GiB heap at k = 2^28. More segments (about 9x for 4: first-pass heaps by each segment's share, plus two full-k collectors per re-entered segment) and concurrent searches add up. After a caughtOutOfMemoryErrora follow-up search on the same searcher worked; with-XX:+CrashOnOutOfMemoryErrorthe JVM aborted. - Not affected. No wrong hits were seen; on the 3-document index a filter that is not match-all takes the bounded exact path; ordinary k values cost kilobytes.
- Scale. None: 3 documents in one segment.
- How likely. Rare: only a very large k fails, and the
Integer.MAX_VALUEcase is loud on the first search; nothing was reported since 9.10.0 (2024-02).
The test shows that KnnFloatVectorQuery without a filter, k = Integer.MAX_VALUE, over a 3-vector single-segment index sizes the per-segment collector heap from k (TestKnnQueryKMaxValue#testKMaxValue).
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.search;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.KnnFloatVectorField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestKnnQueryKMaxValue extends LuceneTestCase {
public void testKMaxValue() throws Exception {
try (Directory dir = new ByteBuffersDirectory()) {
try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
for (int i = 0; i < 3; i++) {
Document doc = new Document();
doc.add(new KnnFloatVectorField("v", new float[] {i, 1}));
w.addDocument(doc);
}
}
try (DirectoryReader reader = DirectoryReader.open(dir)) {
Query query = new KnnFloatVectorQuery("v", new float[] {1, 1}, Integer.MAX_VALUE);
try {
assertEquals(3, new IndexSearcher(reader).search(query, 10).scoreDocs.length);
} catch (IllegalArgumentException e) {
// expected: all 3 vectors (k is "the number of documents to find"); actual: this exception
throw new AssertionError("expected 3 hits for k = Integer.MAX_VALUE, but got " + e, e);
}
}
}
}
}
# in a copy of this finding's repro folder (repro/LFV-454/, which holds src/)
# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar # JUnit Platform console launcher (Maven Central)
javac -d classes -cp "$CP" $(find src -name '*.java')
# run 1: assertions on, seed 5EED0454
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=5EED0454 \
-cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
execute --select-class org.apache.lucene.search.TestKnnQueryKMaxValue
- Expected vs actual
- Expected 3 hits (k is "the number of documents to find"); actual IllegalArgumentException 'initialCapacity must be > 0 and < 2147483630; got: 2147483647' from TernaryLongHeap, with -ea and -da.
More files, environment, full output66 words
| lucene | Lucene main e1faeba08bde (2026-11-02) |
|---|---|
| jdk | 25.0.3+9-LTS |
# Lucene main 645d4fe14a20, assertions on (-ea), seed 5EED0454: 1 test(s) run, 1 failed
=> java.lang.AssertionError: expected 3 hits for k = Integer.MAX_VALUE, but got java.lang.IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 2147483647
org.apache.lucene.search.TestKnnQueryKMaxValue.testKMaxValue(TestKnnQueryKMaxValue.java:45)
Caused by: java.lang.IllegalArgumentException: initialCapacity must be > 0 and < 2147483630; got: 2147483647
org.apache.lucene.util.TernaryLongHeap.<init>(TernaryLongHeap.java:59)
org.apache.lucene.util.hnsw.NeighborQueue.<init>(NeighborQueue.java:60)
org.apache.lucene.search.TopKnnCollector.<init>(TopKnnCollector.java:45)
org.apache.lucene.search.knn.TopKnnCollectorManager.newOptimisticCollector(TopKnnCollectorManager.java:52)
org.apache.lucene.search.AbstractKnnVectorQuery$OptimisticKnnCollectorManager.newCollector(AbstractKnnVectorQuery.java:330)
[...]- The javadoc accepts any k >= 1 ("the number of documents to find"), and
testFindAllexpects a k above the document count to return every document. - Until 9.9.2 Lucene capped k at the segment's vector count "to prevent oversizing data structures"; #12962 dropped it, and no review comment mentions it.
- Since 9.11.0 a non-match-all filter makes the same k work.
The full argument297 words
- Contract.
KnnFloatVectorQuery: "@param k the number of documents to find", "@throws IllegalArgumentException if <code>k</code> is less than 1" (KnnFloatVectorQuery.java:60-61,:73-75;KnnByteVectorQuery.java:59-60,:72-74). These constructors are not@lucene.experimental. The heap's "initialCapacity must be > 0 and < 2147483630" for an accepted k is not the documented failure. - Upstream test.
BaseKnnVectorQueryTestCase.testFindAll: "Tests that a AbstractKnnVectorQuery whose topK >= numDocs returns all the documents in score order" (k = 10 over 3 documents,:178-194). - A deliberate cap, lost silently. 9.0-9.7 readers: "// bound k by total number of vectors to prevent oversizing data structures" /
k = Math.min(k, fieldEntry.size());(9.7.0Lucene95HnswVectorsReader.java:288-289, from LUCENE-9614 / #413). 9.8-9.9: the cap lived inLeafReader#searchNearestVectors(..., int k, ...)(9.9.2LeafReader.java:253), which the query called (9.9.2KnnFloatVectorQuery.java:82); main still has it (LeafReader.java:288). #12962 (9.10.0) made the query build its own collector; reviewers asked to keep the int-kLeafReadermethod unchanged, and none of the 22 conversation or 40 review comments discusses the cap. - Siblings are capped. #13184 (9.11.0) capped the exact path of the same class (
AbstractKnnVectorQuery.java:373) because "the number of candidates is actually lower than k". Until then that path failed for a huge k too (9.9.2, 9.10.0), so the project has bounded this kind of k-sized preallocation before. LUCENE-4938 extendedIndexSearcher'smin(maxDoc, n)cap to the sorted searches and addedTestIndexSearcher.testHugeN("should not throw exception"), which runssearch(query, Integer.MAX_VALUE). - Inconsistent (from 9.11.0). On one index, k =
Integer.MAX_VALUEand 2^28 return all hits with a filter matching every document and throughLeafReader#searchNearestVectors(int k), but throw without the filter (this doc's test; a verifier's contrast probe). - The manager API speaks of per-leaf sizing.
KnnCollectorManager#newOptimisticCollector: "generally with a specific k value, scaled per leaf statistics".
The devil's-advocate check209 words
The strongest case for "intended": top-k naturally needs O(k) storage; TopKnnCollector is @lucene.experimental; the heap's IllegalArgumentException is deliberate ("to prevent confusing OOME"); maintainers have said Lucene targets "some reasonable value of k" (#9588), that k = Integer.MAX_VALUE "will not only run in O(maxDoc) time but also use O(maxDoc) memory" (#12679), and, on dev@ (2022-11), of a very large k, "something that I would like to be documented as not recommended"; downstream engines bound k; SECURITY.md leaves bounding resource-consuming input to the application.
Why it fails: here the storage does not grow with the index at all, because it is reserved per segment before the segment's size is known; 3 documents need 2 GiB. Lucene capped exactly this on purpose until 9.9.2 and still caps it on the filtered path and in LeafReader, and the remarks above were made while the cap existed, so they assume O(maxDoc). The heap's guard only turns an OOME into an exception; it does not make k = Integer.MAX_VALUE a documented failure of the query. Every reviewer rejected the intended-behaviour argument; the latest cross-model fact check rated it moderate, mainly as an argument about which fix to choose (a documented high upper bound would not contradict testFindAll, which uses k = 10).
Recommended Cap the per-segment k at the segment's maxDoc in both collector managers and the seeded query's seed collector
Cap each segment's collector at min(k, maxDoc) in TopKnnCollectorManager, in the join DiversifyingNearestChildrenKnnCollectorManager and in SeededKnnVectorQuery's seed collector (+22/-5 lines in three files). Validated with gradle on main: the repro tests and 8 large-k variant tests pass with assertions on and off, the 13 closest kNN suites (323 tests) pass, and format and lint pass.
All options and trade-offs4 options · 867 words
One fix was built and validated, first in two files and then completed with the seeded query; the alternatives are untested.
Cap the per-segment k at the segment's maxDoc in both collector managers and the seeded query's seed collector (validated by the checks) (recommended)
A segment cannot return more hits than it has documents. TopKnnCollectorManager.newCollector and newOptimisticCollector pass perLeafK(k, context) instead of k, DiversifyingNearestChildrenKnnCollectorManager does the same in both methods, and SeededKnnVectorQuery's collector manager sizes its seed collector the same way. The TopKnnCollectorManager change covers KnnFloatVectorQuery, KnnByteVectorQuery, KnnFloat16VectorQuery, the optimistic re-entry pass and PatienceKnnVectorQuery; SeededKnnVectorQuery builds its seed collector from the full k on every segment, so it needs the third change (executed).
// TopKnnCollectorManager: newCollector and newOptimisticCollector
return new TopKnnCollector(perLeafK(k, context), visitedLimit, searchStrategy);
/** A leaf cannot return more than {@code maxDoc} hits, so do not size its collector by a larger k. */
private static int perLeafK(int k, LeafReaderContext context) {
return Math.max(1, Math.min(k, context.reader().maxDoc()));
}
// DiversifyingNearestChildrenKnnCollectorManager: the same helper, in both methods
return new DiversifyingNearestChildrenKnnCollector(
perLeafK(k, context), visitedLimit, searchStrategy, parentBitSet);
// SeededKnnVectorQuery.SeededCollectorManager.newCollector: the seed collector
// a leaf cannot return more than maxDoc seeds, so do not size the collector by a larger k
int seedK = Math.max(1, Math.min(k, ctx.reader().maxDoc()));
TopScoreDocCollector seedCollector =
new TopScoreDocCollectorManager(seedK, null, Integer.MAX_VALUE).newCollector();Trade-offs
- Same hits. A segment has at most
maxDocvectors, somin(k, maxDoc)slots hold every hit. The reader's choice between graph search and an exhaustive scan (knnCollector.k() < numVectors,Lucene99HnswVectorsReader.java:373) does not change: if k >= numVectors, thenmin(k, maxDoc) >= numVectorstoo. A segment cannot hold more thanmaxDocseed hits either.Math.max(1, ...)keeps k >= 1 for an empty segment. - Bound is maxDoc, not the vector count as before 9.10.0 (the managers do not know the field), so a large segment can still reserve 8 bytes per document for a huge k: O(maxDoc), the cost maintainers described in #12679.
- Custom
KnnCollectorManagerimplementations are not covered. No benchmark was run; the change adds oneMath.minper segment. Add a CHANGES entry under Bug Fixes. - Backports (read): branch_10x and branch_10_6 have the same manager code as main. Older lines also allocate a global
BlockingFloatHeap(k)per multi-segment query (TopKnnCollectorManagerfrom 9.10.0, the join manager in 9.12.3, both up to 10.2.2), which a backport must bound too; 9.8.x and 9.9.x build the join collector directly in the query;SeededKnnVectorQueryexists from 10.2.0.
What the checks validated
- Two-manager fix (
fix.diff), clean scratch worktree ata9779ef3c193, gradle, seedDEADBEEF,-Ptests.heapsize=2g. Stock:TestKnnQueryLargeK2 of 4 fail,TestDiversifyingKnnLargeK2 of 3 fail. With the fix: both pass (4 of 4, 3 of 3), with assertions on and with-Ptests.asserts=false; the 13 closest suites pass; format and lint pass. - The seeded gap (standalone runner,
a9779ef3c193): the 8 variant tests fail 6 of 6 and 2 of 2 on stock; withfix.diff4 of 6 pass and both seeded tests still throwIllegalArgumentExceptionfromTopScoreDocCollector; withfix-complete.diffall pass, assertions on and off. - Complete fix (
fix-complete.diff), gradle on a source extraction ofa9779ef3c193with only the patch and the four test files added: with assertions on 338 tests, 0 failures (the 15 repro and variant tests plusTestKnnFloatVectorQuery(43),TestKnnByteVectorQuery(40),TestKnnByteVectorQueryMMap(40),TestSeededKnnFloatVectorQuery(39),TestSeededKnnByteVectorQuery(39),TestPatienceFloatVectorQuery(38),TestPatienceByteVectorQuery(38),TestKnnGraph(6),TestTopKnnResults(1), joinTestParentBlockJoinFloatKnnVectorQuery(12),TestParentBlockJoinFloat16KnnVectorQuery(12),TestParentBlockJoinByteKnnVectorQuery(11),TestToParentJoinKnnResults(4)); with-Ptests.asserts=falsethe 15 tests pass;checkGoogleJavaFormat,ecjLintMain,ecjLintTest,forbiddenApisMainandforbiddenApisTestpass for core and join. - Not validated with the fix: byte and float16 queries on several segments, seeded queries with a filter, executor-backed searchers.
In approximateSearch, read the vector values first and size the collector by min(k, values.size()), in the core and the join queries.
Trade-offs
The pre-9.10 behaviour of the core queries (the join queries were unbounded before 9.10 too) and the tightest bound, but KnnCollectorManager#newCollector has no k parameter, so it needs an API change (or routing a bounded k through newOptimisticCollector).
Start NeighborQueue / TernaryLongHeap small and grow it as hits arrive, up to k.
Trade-offs
Also helps large segments with a huge k, but the heap's capacity is its overflow bound today (TernaryLongHeap.java:93-103), so it needs a separate maximum, and it touches the hot path of every kNN search; needs benchmarks. The heap's growth has its own limit (TernaryLongHeap stops growing at 715,827,883 elements, registry LFV-069).
Validate k against an upper bound in the constructor, or document one.
Trade-offs
The query does not know the index when it is built, and a bound would not fit the filtered path, LeafReader's int-k method and IndexSearcher (testHugeN), which all accept any k (testFindAll uses only k = 10, so a high documented bound would not contradict it). Until a fix ships, applications can cap k themselves, for example at reader.maxDoc() (read, not executed); that caps by the whole index, so on several segments each segment's collectors can still be sized by the index's maxDoc.
Downstream: Elasticsearch and OpenSearch cap k at 10,000, while Solr's kNN parser reads topK with no upper bound (source reading only), so an application that forwards a client's k should cap it, for example at the index size.
A hunt for accepted-parameter boundaries scanned the argument checks of Lucene's public APIs and probed the ranges they accept: the kNN query constructors reject only k < 1. End-to-end runs with very large k then hit the failure, and a JUnit test reproduced it on the stock jars.
The method in full152 words
Method: a static and dynamic boundary scan (the finder's label: semi-formal; no model checker, solver or prover found it).
- A static guard inventory of the real jars: a JDK ClassFile API pass over 27 core and non-sandbox module jars listed 1,231 public entry points whose bytecode reaches an
IllegalArgumentException, with each guard's parameters and constants. - The kNN query constructors reject only k < 1 (
AbstractKnnVectorQuery.java:67-68), so every k up toInteger.MAX_VALUEis accepted. - A scratch driver (its output was not saved) then ran
KnnFloatVectorQuery,KnnByteVectorQueryand the twoDiversifyingChildren*queries end to end on the stock jars with k in {5, 1000, 2^20, 2^27, 2^28,Integer.MAX_VALUE- 9,Integer.MAX_VALUE}, and a public-API JUnit test witnessed the failure (2026-10-26). - The same scan found the pattern at
LatLonPoint.nearest(..., n = Integer.MAX_VALUE)andRescoreTopNQuery(..., n = Integer.MAX_VALUE)(anIllegalArgumentExceptionfromPriorityQueue); those are separate sites and were not filed.
No prior report found (2026-10-26, re-checked 2026-10-28) in Lucene's GitHub issues, PRs and commits, the mailing lists, JIRA, four downstream projects or the web. Nearest: #13184 (capped only the exact path) and #11387 (the k = 0 case on all-deleted segments).
Searches4 searches
Verdict new: 29 hits were classified and none is this bug by anyone. Context, not reports: #12962 / #13086 introduced it; #413 (LUCENE-9614) added the cap; #9588 (LUCENE-8542, open) asks to size regular per-slice collectors by the slice; #12679, #11946 and a 2022-11 dev@ thread call a very large k costly or not recommended, while the cap still existed. No open PR bounds k by the segment. Gaps: GitHub search is token-based and ran near the rate limit; JIRA search covers the LUCENE archive only; private security reports cannot be searched.
| Where | When | How | Found |
|---|---|---|---|
| apache/lucene issues, PRs and commits; dev@, java-user@, issues@ and commits@; JIRA archive; the web | 2026-10-26 | the hunt's prior-report tool: 20 GitHub issue/PR, 3 commit, 2 downstream, 20 list, 4 JIRA and 6 web queries (TopKnnCollectorManager, TernaryLongHeap, "maxSize must be", "initialCapacity must be", "bound k", "oversizing data structures", perLeafTopK, ...) | no report; nearest #13184, #11387 / LUCENE-10351, #9588 |
| GitHub: apache/lucene and four downstream projects (Elasticsearch, OpenSearch, OpenSearch k-NN, Solr) | 2026-10-26 | 23 manual searches (exception texts, large-k OOM, NodeIdCachingHeap, BufferAllocationException), plus the finder's 45 and a verifier's 43 queries | no report; downstream hits unrelated |
| Open PRs, upstream history, CHANGES, PR comments | 2026-10-26 | the changed files of 129 of the 408 open apache/lucene PRs; history of the 9 affected files; the comments of #12962, #13086 and #12794; one dev@ thread; 3 JIRA entries | 10 open PRs touch the files, none caps k; #12962's review never discusses the cap |
| GitHub issues and PRs (apache/lucene); upstream commit lists | 2026-10-28 | "initialCapacity must be"; TopKnnCollector updated since 2026-10-26; commits of the affected files | nothing new; no affected file changed after a9779ef3c193 (head 20ced71c1e6e) |
Introduced in 9.10.0 by #12962 (main d095ed02a2a; backport #13086, a6c45ebe432), which stopped routing the query through LeafReader#searchNearestVectors and its cap. 9.9.2 passes; 9.10.0 through 10.5.2 and main fail. The join queries fail in every release since they were added in 9.8.0.
Versions tested11 rows
Introduced by #12962, "Speedup concurrent multi-segment HNWS graph search" (main d095ed02a2a, merged 2024-02-06), and its branch_9x backport #13086 (a6c45ebe432, 2024-02-07), first released in 9.10.0. It made approximateSearch build the collector through the new TopKnnCollectorManager instead of calling LeafReader#searchNearestVectors(field, target, int k, ...), which caps k. Attribution rests on the diff, the release boundary (9.9.2 passes, 9.10.0 fails) and the only other query-path commit in 9.9.2..9.10.0 (#13058, unrelated); no commit-level bisect build was run.
The cap itself came from LUCENE-9614 (#413, 2021, before 9.0.0) in the readers, and #12434 (9.8.0) moved it into LeafReader. The join queries were added by #12434 (1f25c68cf03, 9.8.0) without it. #15163 (10.4.0) switched NeighborQueue to TernaryLongHeap, which changed the message from maxSize must be ... to initialCapacity must be .... #13184 (9.11.0) capped the filtered/exact path, which also failed on 9.9.2 and 9.10.0. Not fixed upstream as of 20ced71c1e6e (2026-10-27).
| Version or commit | Verdict | Result |
|---|---|---|
main a9779ef3c193 | affected | this doc's test fails 2 of 4 and the join test 2 of 3 (gradle and standalone, assertions on and off) |
main a9779ef3c193, variants | affected | k = Integer.MAX_VALUE fails for byte, float16, patience and seeded queries, on 4 segments (OutOfMemoryError), and for the byte and 2-segment join queries: 8 of 8 tests |
main ea57360912ed | affected | original repro 2 of 3, a verifier's tests 4 of 6; k = 2^24 allocates 134,224,064 bytes |
| 10.5.2, 10.0.0, 9.12.3 | affected | fail 4 of 5 (TernaryLongHeap on 10.5.2, LongHeap on 10.0.0 and 9.12.3) |
| 9.11.0, 9.12.0 | affected | fail (a reviewer's 9.x port of the repro) |
| 9.10.0 | first release | fails 4 of 5; the filtered path fails too there (capped from 9.11.0 by #13184) |
| 9.9.2 | last before (core, unfiltered) | unfiltered: every k returns 3 hits, 5,000 bytes for k = 2^24; the filtered k = Integer.MAX_VALUE throws from HitQueue(k) (capped from 9.11.0) and the join query fails |
| 9.5.0, 9.7.0, 9.8.0, 9.9.0 | not affected | core queries pass |
| join: 9.8.0, 9.9.2, 9.12.3, 10.5.2, main | join queries | k = Integer.MAX_VALUE and 2^28 fail since the queries were added |
| join: 9.7.0 | API absent | no DiversifyingChildrenFloatKnnVectorQuery (compile error) |
fix, a9779ef3c193 | not affected | two-manager fix: both repro tests and the 13 closest suites pass, but the seeded variant still fails; complete three-file fix: the repro tests, 8 variant tests and the 13 closest suites (338 tests) pass, format and lint pass |
The finder's repro, an independent reproducer's own tests, a cross-model review (OpenAI GPT-5.6), a devil's-advocate check, an adjudication, a credibility audit and fact checks found it real; a second cross-model review said not real only because it could not run code. The fact checks corrected the text and found that the fix missed the seeded query.
Checks and verdicts458 words
The checks below ran on main ea57360912ed unless stated; the kNN query path is the same on a9779ef3c193.
| Check | Verdict | What it found |
|---|---|---|
| finder | candidate | A public-API JUnit fails 2 of 3 on main, 10.5.2, 10.0.0 and 9.12.3, assertions on and off; a core-only cap passes it and TestKnnFloatVectorQuery. |
| cross-model review (refute lens) | not real (low), on procedure only | OpenAI GPT-5.6, reading only: it could not run Java, so it voted not real on procedure only; it agreed with the root cause and rated the intended-behaviour argument weak. A separate run re-ran the repro: fails 2 of 3. |
| independent reproducer | real | Own tests fail 4 of 6 on main, 10.5.2 and 9.12.3, 5 of 6 on 9.10.0 and 1 of 6 on 9.9.2 (on the last two also the filtered control, before #13184); the unfiltered tests pass on 9.9.2. Found the join variant (failing since 9.8.0) and measured 134,224,064 bytes for k = 2^24. |
| cross-model review (intended lens) | real (high) | OpenAI GPT-5.6, reading only (no Java run): the intended-behaviour argument is weak; the core-only fix sketch is incomplete without the join manager. (The release runs that show 9.7.0-9.9.0 passing and 9.10.0-9.12.0 failing were run separately) |
| devil's advocate | real (high) | Built the works-as-designed case and showed why it fails; its contrast probe shows the same k working with a filter and through LeafReader#searchNearestVectors. |
| adjudication | confirmed (high) | Re-ran the reproducer's tests: 4 of 6 fail, both controls pass. Corrected the versions (core 9.10.0, join 9.8.0) and the framing (an exception for an accepted k and a lost cap, not a memory contract). |
| credibility audit | keep | Re-ran the original repro with a fresh seed: 2 of 3 fail, assertions on and off. |
| independent fact check | real; 4 text errors, fixed | Re-ran the gradle repro (assertions on and off), the release jars and a multi-segment probe on a9779ef3c193: the bug, root cause and fix hold. Corrected the 9.9.2 rows (only unfiltered queries pass there), the single-segment scope of the IllegalArgumentException, a commit-range description and a release number. |
| cross-model fact check | real; fix incomplete (seeded), fixed | OpenAI GPT-5.6, reading only: found that SeededKnnVectorQuery's seed collector is still sized by k with the two-manager fix (then executed and fixed here), plus over-broad filter, per-segment and backport claims (corrected). |
| diagram check | one wrong line range, fixed | |
| this doc | reproduced; fix validated | Gradle on a9779ef3c193: stock, the new test fails 2 of 4 and the join test 2 of 3; with the two-manager fix both pass and the 13 closest suites pass; with the complete three-file fix also the 8 variant tests (seeded included) pass, 338 tests in all, and lint passes. |
The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.
- PNG 530 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,634 words
- Registry title
- kNN vector queries size each segment's result heap from the requested k, not from what the segment can return, so k = Integer.MAX_VALUE throws IllegalArgumentException and k = 2^28 runs out of a 2 GiB heap on a 3-document index (core KnnFloat/KnnByteVectorQuery without a filter: regression in 9.10.0; join DiversifyingChildren*KnnVectorQuery: since 9.8.0)
- Area
- search / kNN vector queries (per-segment collector sizing: AbstractKnnVectorQuery, TopKnnCollectorManager, TopKnnCollector; join DiversifyingNearestChildrenKnnCollectorManager)
- Priority
- Low, rank 390: Core default kNN queries throw for the k=Integer.MAX_VALUE 'all' idiom and allocate memory in proportion to k, a loud MAX_VALUE-idiom core failure that is more reachable than LFV-394, so it heads that group.
- Impact class
- crash
- Impact note
- Applications on 9.10.0 through 10.6.0 (10.6.0 by reading) or main that run a KnnFloatVectorQuery or KnnByteVectorQuery without a filter (default codec, IndexSearcher and query, no setting needed), or on 9.8.0 and later a DiversifyingChildrenFloat/ByteKnnVectorQuery, with a very large k get an IllegalArgumentException from an internal heap for k = Integer.MAX_VALUE (the usual Java 'all' value), and each search reserves heap in proportion to k before any segment's size is consulted (about 8 bytes per unit of k on a single-segment index; four segments cost about 9 times as much), so one k = 2^24 search over 3 vectors allocates 128 MiB and k = 2^28 runs out of a 2 GiB heap; ordinary k values (up to the thousands) waste only kilobytes, results are never wrong, no index size is needed, Elasticsearch and OpenSearch cap k at 10,000, and Solr's kNN parser does not bound topK (read in source only, not run).
- Summary
- You run a KnnFloatVectorQuery with a k larger than the index, for example k = Integer.MAX_VALUE to mean 'all vectors', and expect every vector back, as k = 1000 over 3 vectors gives, and as 9.9.2 gives for any k. Instead each segment's collector is built with a heap sized from k (k + 1 longs on a single-segment index) before the segment's vector count is looked at: k = Integer.MAX_VALUE throws IllegalArgumentException('initialCapacity must be > 0 and < 2147483630; got: 2147483647') from TernaryLongHeap, and k = 2^28 over a 3-document index throws OutOfMemoryError with a 2 GiB heap. Lucene used to cap k at the segment's vector count ('bound k by total number of vectors to prevent oversizing data structures'); a search speed-up change dropped that cap in 9.10.0, and the join DiversifyingChildren*KnnVectorQuery never had it.
- Introduced
- d095ed02a2a7742235e73320fa992eabfeecbecc on main (GITHUB#12962 'Speedup concurrent multi-segment HNWS graph search' by mayya-sharipova, merged 2024-02-06) and its branch_9x backport a6c45ebe4321cacfac27380d41125ed6ae7a8dee (PR #13086, merged 2024-02-07), which first shipped in 9.10.0 [read: the diff plus the executed release boundary (9.9.2 passes, 9.10.0 fails); the only other query-path commit in 9.9.2..9.10.0 is c059819a59a (#13058, filtered-path visit limit, unrelated); no commit-level bisect build]: approximateSearch stopped calling LeafReader.searchNearestVectors(field, target, int k, ...), which caps k at the vector count, and instead builds the per-segment collector from k through the new TopKnnCollectorManager; the old cap survives only as the emptiness check 'Math.min(knnCollector.k(), size()) == 0', evaluated after the heap is allocated. Join DiversifyingChildren{Float,Byte}KnnVectorQuery: unbounded since the API was added (#12434, branch_9x 1f25c68cf03, 2023-08-07, first released in 9.8.0) [read + exec] (2024-02-06 (main, d095ed02a2a); 2024-02-07 (branch_9x backport a6c45ebe432); join path 2023-08-07 (1f25c68cf03))
- First affected version
- 9.10.0 [exec] for the core KnnFloat/KnnByteVectorQuery (tag 2024-02-11, from the branch_9x backport; 9.5.0, 9.8.0 and 9.9.2 pass [exec]; the first main-line release containing d095ed02a2a is 10.0.0); 9.8.0 [exec] for the join DiversifyingChildren*KnnVectorQuery (since the API was added; 9.7.0 has no such class [exec: compile error])
- Affected versions
- core KnnFloatVectorQuery/KnnByteVectorQuery without a filter (the approximate path): 9.10.0, 9.11.0, 9.11.1, 9.12.0-9.12.3, 10.0.0, 10.1.0, 10.2.0-10.2.2, 10.3.0-10.3.2, 10.4.0, 10.5.0-10.5.2, 10.6.0 (by reading: same collector-manager code as main) and main ea57360912ed [exec: 9.10.0, 9.12.3, 10.0.0, 10.5.2 and main (-ea and -da) in the follow-up history runs; 9.11.0 and 9.12.0 by the cross-model review; the rest by source check at each tag]; join DiversifyingChildren{Float,Byte}KnnVectorQuery: 9.8.0 through main [exec: 9.8.0, 9.9.2, 9.12.3, 10.5.2, main]; not affected: core 9.0.0-9.9.2 [exec: 9.5.0, 9.8.0, 9.9.2, and 9.7.0 and 9.9.0 by the cross-model review; read for the rest]; before 9.0.0 there is no kNN query; KnnFloat16VectorQuery (main only, unreleased) has the same approximateSearch shape [read, not executed]; a filtered search that routes to exact search is bounded by Math.min(k, cost) from 9.11.0 (#13184); up to 9.10.0 that exact path also failed (HitQueue(k)) [exec on 9.9.2 and 9.10.0], a separate history already fixed; not fixed upstream (no change to the 9 affected files on upstream main 49483e82b357, 2026-10-26, and no open PR bounds k) [read]
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- semi-formal
- Upstream and prior reports
- none found (2026-10-26, the prior-report record: the scripted prior-report search (20 GitHub issue and PR, 3 commit, 2 downstream, 20 mailing-list queries over dev@, java-user@, issues@ and commits@, 4 JIRA-archive and 6 web queries), 23 manual read-only GitHub searches over apache/lucene, elastic/elasticsearch, opensearch-project/k-NN, opensearch-project/OpenSearch and apache/solr, the changed files of 129 of the 408 open apache/lucene PRs (kNN, vector, collector, heap, join or query in the title; 10 touch the affected files, none caps k), the upstream commit history of the 9 affected files, local git history and CHANGES.txt, the comments of #12962, #13086 and #12794, one dev@ thread, 3 JIRA entries and 8 more web queries, plus the finder's run (45 queries) and the intended-behaviour check's (43 queries); 29 hits classified, none the same bug); not fixed upstream (no change to the affected files on upstream main 49483e82b357, 2026-10-26; no open PR fixes it); closest items, not reports: #12962 / #13086 (the introducing change; no review comment discusses the dropped cap), #413 / LUCENE-9614 (added the per-segment cap 'to prevent oversizing data structures', 2021), #13184 (capped the same oversizing at the sibling exact-search site only, 9.11), #11387 / LUCENE-10351 with PR #580 (the k = 0 'maxSize must be' case on all-deleted segments, fixed in 9.0), #9588 / LUCENE-8542 (open enhancement: size regular per-slice collectors by the slice), and #12679, #11946 and a 2022-11 dev@ thread in which maintainers call a very large k costly or not recommended, all written while k was still capped per segment
- Lucene commit tested
- main
a9779ef3c193(2026-10-26): the doc's repro through gradle in a clean scratch worktree (stock, and with the fix plus the closest kNN suites and lint) and through a standalone JUnit runner on the built jars, assertions on and off; on 2026-10-28, 8 more large-k variant tests (stock and fixed, standalone runner) and the complete three-file fix through gradle (the repro and variant tests, the 13 closest kNN suites, lint). Earlier runs of the same code: mainea57360912edand the release jars 9.5.0, 9.7.0, 9.8.0, 9.9.0, 9.9.2, 9.10.0, 9.11.0, 9.12.0, 9.12.3, 10.0.0 and 10.5.2. Upstream main20ced71c1e6e(2026-10-27) has the same code (read).
- Finding record
lucene/core/src/java/org/apache/lucene/search/KnnFloatVectorQuery.java:60-61 (at ea57360912ed) ('@param k the number of documents to find', '@throws IllegalArgumentException if <code>k</code> is less than 1'; same at :73-75), :106-108 (approximateSearch builds the collector via knnCollectorManager.newCollector first), :115 ('if (Math.min(knnCollector.k(), floatVectorValues.size()) == 0)', after the allocation)lucene/core/src/java/org/apache/lucene/search/KnnByteVectorQuery.java:107, (at ea57360912ed) :114 (same shape); lucene/core/src/java/org/apache/lucene/search/KnnFloat16VectorQuery.java:108, :115 (same shape, main only)lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java:64-69 (at ea57360912ed) (constructor rejects only k < 1), :272-286 (filtered path: perLeafTopK, exact search when cost <= perLeafTopK), :319-333 (OptimisticKnnCollectorManager.newCollector: perLeafTopK from k and the leaf proportion, not the leaf size), :343-346 (perLeafTopKCalculation; equals k for a single leaf), :373 (exactSearch: 'Math.min(k, Math.toIntExact(acceptIterator.cost()))'), :444-470 (ReentrantKnnCollectorManager builds two collectors per segment on re-entry)lucene/core/src/java/org/apache/lucene/search/knn/TopKnnCollectorManager.java:43-53 (at ea57360912ed) ('new TopKnnCollector(k, visitedLimit, searchStrategy)' in newCollector and newOptimisticCollector)lucene/core/src/java/org/apache/lucene/search/TopKnnCollector.java:43-46 (at ea57360912ed) ('this.queue = new NeighborQueue(k, false)')lucene/core/src/java/org/apache/lucene/util/TernaryLongHeap.java:56-69 (at ea57360912ed) (IllegalArgumentException for initialCapacity >= ArrayUtil.MAX_ARRAY_LENGTH 'to prevent confusing OOME'; eager 'new long[initialCapacity + 1]')lucene/core/src/java/org/apache/lucene/index/LeafReader.java:273, (at ea57360912ed) :317, :361 ('k = Math.min(k, ...size())' before 'new TopKnnCollector(k, visitedLimit)' in the int-k searchNearestVectors methods)lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java:588-597 (at ea57360912ed) (searchAfter caps numHits at max(1, maxDoc))lucene/join/src/java/org/apache/lucene/search/join/DiversifyingNearestChildrenKnnCollectorManager.java:65, (at ea57360912ed) :77 (new DiversifyingNearestChildrenKnnCollector(k, ...)); DiversifyingNearestChildrenKnnCollector.java:61 ('new NodeIdCachingHeap(k)'), :140 (NodeIdCachingHeap(int maxSize))Lucene main ea57360912ed SECURITY.md lines 28, 38 (resource exhaustion from unbounded, unfiltered input is not a Lucene vulnerability), 50
- The finding in 30 seconds
a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java:67-68, 87-103, 106-110, 128-130, 252-254, 319-347, 373, 433-476a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/KnnFloatVectorQuery.java:101-118; KnnByteVectorQuery.java:106-107, 114a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/knn/TopKnnCollectorManager.java:43-53; TopKnnCollector.java:43-46a Lucene main checkout lucene/core/src/java/org/apache/lucene/util/TernaryLongHeap.java:56-69; util/hnsw/NeighborQueue.java:59-60a Lucene main checkout lucene/core/src/java/org/apache/lucene/index/LeafReader.java:288, 292, 332, 336, 376, 380; IndexSearcher.java:587-597a Lucene main checkout lucene/join/src/java/org/apache/lucene/search/join/DiversifyingNearestChildrenKnnCollectorManager.java:57-79; DiversifyingNearestChildrenKnnCollector.java:61, 140-153a Lucene clone: git diff --stat ea57360912ed a9779ef3c193 (no change to the kNN query path)a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/SeededKnnVectorQuery.java:315-316; TopScoreDocCollector.java:44; TopScoreDocCollectorManager.java:62
- Who is affected
a Lucene main checkout lucene/core/src/test/org/apache/lucene/search/TestIndexSearcher.java:78-110 (testHugeN)a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java:87-103 (match-all filter dropped)
- Why it looks unintended
a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/KnnFloatVectorQuery.java:60-61, 73-75; KnnByteVectorQuery.java:59-60, 72-74; AbstractKnnVectorQuery.java:67-68, 373a Lucene main checkout lucene/core/src/test/org/apache/lucene/search/BaseKnnVectorQueryTestCase.java:178-194; TestIndexSearcher.java:78-110a Lucene main checkout lucene/core/src/java/org/apache/lucene/index/LeafReader.java:288; search/knn/KnnCollectorManager.java (newOptimisticCollector javadoc); util/TernaryLongHeap.java:57-63; search/TopKnnCollector.java:27a Lucene clone releases/lucene/9.9.2 LeafReader.java:253, KnnFloatVectorQuery.java:82; releases/lucene/9.7.0 lucene95/Lucene95HnswVectorsReader.java:288-289
- Suggested fix
a Lucene main checkout lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsReader.java:373; util/TernaryLongHeap.java:93-103; search/knn/KnnCollectorManager.javaa Lucene clone release tags: BlockingFloatHeap(k) in TopKnnCollectorManager at 9.10.0-10.2.2 and in the join manager at 9.12.3-10.2.2; join manager absent in 9.8.0 and 9.9.2; SeededKnnVectorQuery from 10.2.0
- History
a Lucene clone: git tag --contains a6c45ebe432 (first releases/lucene/9.10.0); lucene/CHANGES.txt:2843, 3028 (#13184, #12962)
- TestKnnQueryKMaxValue.java (JUnit test):
repro/LFV-454/src/org/apache/lucene/search/TestKnnQueryKMaxValue.java(50 lines)
Made in parallel from the code at main a9779ef3c193 and executed probe runs; the PNG is the summary still.
assets/LFV-454/bug.png(530 KB)
