The optimized getTopDims of SortedSetDocValues and taxonomy facets sizes its dimension queue by the topNDims you pass, not by how many dims exist, and Lucene's priority queue refuses 2,147,483,631 slots or more.
- A facet dim (dimension) is a facet category such as
authororyear; each dim has labels with counts. SortedSetDocValues (SSDV) facets keep the labels in a doc-values field, taxonomy facets in a side index. Both implement theFacetsAPI. getAllDims(n)returns every dim that had hits, each with its top n labels.getTopDims(topNDims, n)returns only the topNDims dims with the highest counts; its javadoc says the result should begetAllDims(n)cut to the first topNDims.- Lucene's
PriorityQueueallocates its whole array, maxSize + 1 slots, when it is created, and refuses sizes of 2,147,483,631 (ArrayUtil.MAX_ARRAY_LENGTH) or more.
getAllDims(10) returns a and b, but getTopDims(Integer.MAX_VALUE, 10) asks PriorityQueue for 2,147,483,647 slots before it looks at a dim and throws; capping the queue returns a and b. Select the still to open it at full size. Generated with AI, checked against the code.- Index one document with two SortedSetDocValues facet labels,
a/xandb/y(defaultFacetsConfig), and count all hits withSortedSetDocValuesFacetCounts.getAllDims(10)returns 2 dims,aandb. - Ask for every dim with
getTopDims(Integer.MAX_VALUE, 10). You expectaandb: the javadoc says the result should begetAllDimscut to the first topNDims, and the defaultFacets#getTopDimsreturns exactly that. - You get
IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647, with assertions on and off. All five SortedSetDocValues and taxonomy count classes fail the same way. - Why: before it visits any dim, the optimized
getTopDimscreates a priority queue with room for topNDims dims (AbstractSortedSetDocValueFacetCounts.java:154,TaxonomyFacets.java:611). Lucene'sPriorityQueueallocates all topNDims + 1 slots up front and refuses 2,147,483,631 or more. The defaultgetTopDimscaps withMath.min; these overrides do not. - Below that limit the call works but still reserves the slots:
getTopDims(1 << 24, 10)on the same 2 dims allocates 67,110,600 bytes, against 1,744 bytes forgetTopDims(2, 10).
Code that passes Integer.MAX_VALUE (or any topNDims from 2,147,483,631) to getTopDims on SortedSetDocValues or taxonomy facets to mean "all dims": every such call throws once anything was counted (the concurrent SortedSetDocValues class even when nothing was), at any index size, since 9.2.0. Large legal values reserve topNDims + 1 slots per call: 64 MiB at 2^24 on the tested JVM.
Mechanism, terms and numbers647 words
Facets#getTopDims(topNDims, topNChildren) is documented to return the same as getAllDims(topNChildren) cut to its first topNDims dims, and the default implementation does exactly that, capped with Math.min(topNDims, allResults.size()) (Facets.java:61-62, :69-72). The faster overrides used by SortedSetDocValues facets (AbstractSortedSetDocValueFacetCounts.java:153-157) and taxonomy facets (TaxonomyFacets.java:610-614) instead create PriorityQueue.usingComparator(topNDims, ...) before they look at any dim. Lucene's PriorityQueue allocates topNDims + 1 slots up front and refuses 2,147,483,631 or more.
So getTopDims(Integer.MAX_VALUE, 10) throws IllegalArgumentException on an index with 2 dims in SortedSetDocValuesFacetCounts, ConcurrentSortedSetDocValuesFacetCounts, FastTaxonomyFacetCounts, TaxonomyFacetIntAssociations and TaxonomyFacetFloatAssociations, while getAllDims(10) and the default getTopDims return both dims. Below the limit results are right but each call reserves the slots: about 64 MiB at 2^24 for 2 dims. Default configuration, any index size, assertions on and off, once a hit was counted (ConcurrentSortedSetDocValuesFacetCounts throws even for a query that matches nothing); every release since getTopDims was added in 9.2.0, through 10.6.0 and main. The fix is one Math.min per site.
- Contract.
Facets#getTopDims(Facets.java:59-68): "Results should be the same as calling getAllDims and then only using the first topNDims. [...] Sub-classes may want to override this implementation with a more efficient one if they are able." The default body isallResults.subList(0, Math.min(topNDims, allResults.size()))(:69-72).validateTopNrejects onlytopN <= 0(:80-84). The elided sentence asks that dims be configured as requiring dim counts so that counts are accurate (:62-64); neither override reads that setting before it sizes the queue. - SSDV override.
AbstractSortedSetDocValueFacetCounts.getTopDims(:143) validates both arguments (:144-145), returns an empty list whenhasCounts()is false (:147-149; never forConcurrentSortedSetDocValuesFacetCounts, whosehasCounts()always returnstrue,ConcurrentSortedSetDocValuesFacetCounts.java:80-84), then createsPriorityQueue.usingComparator(topNDims, ...)(:153-157; the throw frame is:154). Only then does it iteratestate.getDims()(:162), adding a dim whilepq.size() < topNDims(:201-202).SortedSetDocValuesFacetCountsandConcurrentSortedSetDocValuesFacetCountsinherit it. - Taxonomy override.
TaxonomyFacets.getTopDims(:595) checkstopNDims <= 0 || topNChildren <= 0(:596-598), returns empty while its counters are uninitialized, i.e. no segment with hits was counted (:600-602), then createsPriorityQueue.usingComparator(topNDims, ...)(:610-614; throw frame:611) before it walks the root's children (:620).FastTaxonomyFacetCounts,TaxonomyFacetIntAssociationsandTaxonomyFacetFloatAssociationsinherit it. - The queue. The
PriorityQueueconstructor throwsIllegalArgumentExceptionformaxSize >= ArrayUtil.MAX_ARRAY_LENGTH, commented "Throw exception to prevent confusing OOME" (PriorityQueue.java:121-125), and otherwise allocatesnew Object[maxSize + 1](:129,:134).MAX_ARRAY_LENGTHisInteger.MAX_VALUE - NUM_BYTES_ARRAY_HEADER(ArrayUtil.java:33-34), 2,147,483,631 on this JVM. The class javadoc says it pre-allocates (:34); it is@lucene.internal(:39). - The cap exists next door. The taxonomy child queue of the same class is
new TopOrdAndIntQueue(Math.min(taxoReader.getSize(), topN))(TaxonomyFacets.java:250-251). Only these two classes overridegetTopDims; every otherFacetsimplementation inherits the capped default.
The numbers (main; one document with the labels a/x and b/y, as SortedSetDocValues facets, taxonomy facets and association fields; -ea and -da identical)
| class | getTopDims(Integer.MAX_VALUE, 10) | bytes at topNDims 2 | bytes at 2^24 | with the fix, 2^24 |
|---|---|---|---|---|
SortedSetDocValuesFacetCounts | IllegalArgumentException | 1,744 | 67,110,600 | 1,744 |
ConcurrentSortedSetDocValuesFacetCounts | IllegalArgumentException | 1,744 | 67,110,600 | 1,744 |
default getTopDims over SSDV counts | a, b | 1,504 | 1,504 | - |
FastTaxonomyFacetCounts | IllegalArgumentException | 1,400 | 67,110,256 | 1,408 |
TaxonomyFacetIntAssociations | IllegalArgumentException | 1,400 | 67,110,256 | 1,408 |
TaxonomyFacetFloatAssociations (association-value and values-source constructors) | IllegalArgumentException | 1,464 | 67,110,320 | 1,472 |
default getTopDims over taxonomy counts | a, b | 1,072 | 1,072 | - |
Bytes: what one call allocates on the calling thread (thread allocation counter), the minimum of 5 calls after 20 warm-up rounds, on this JVM (compressed references on). With the fix, Integer.MAX_VALUE returns a and b in all five classes and allocates the same as 2^24. getTopDims(2147483631, 10) throws the same way. An independent reproducer got an OutOfMemoryError for getTopDims(1 << 28, 5) with a 512 MB heap; an earlier probe measured 381 MiB at 10^8.
Exposure and likelihood310 words
- Who is hit. Callers of
getTopDims(topNDims, n)onSortedSetDocValuesFacetCounts,ConcurrentSortedSetDocValuesFacetCounts,FastTaxonomyFacetCounts,TaxonomyFacetIntAssociationsorTaxonomyFacetFloatAssociationswithInteger.MAX_VALUE, or any topNDims from 2,147,483,631 up. For a query that matches no document,SortedSetDocValuesFacetCountsand the taxonomy classes return an empty list before they build the queue (AbstractSortedSetDocValueFacetCounts.java:147-149,TaxonomyFacets.java:600-602);ConcurrentSortedSetDocValuesFacetCountsreports counts unconditionally (ConcurrentSortedSetDocValuesFacetCounts.java:80-84), so it throws even then, where itsgetAllDims(10)returns an empty list (executed). Code written against an implementation that keeps the capped defaultgetTopDims(range, long-value and string-value counts inherit it; the default itself was executed through a wrapper over the SortedSetDocValues and taxonomy counts, not through those classes) meets it on the first call. - Large but legal topNDims. Values such as 2^24 used as "all" return correct results but reserve topNDims + 1 slots per call however few dims exist: about 64 MiB at 2^24 on the tested JVM (executed on every release point from 9.2.0 to main), 381 MiB at 10^8; an independent reproducer got an
OutOfMemoryErrorat 2^28 with a 512 MB heap. - Default vs opt-in. Default
FacetsConfig, codec andIndexSearcher; no setting is needed.Facetsis@lucene.experimental;getTopDimsdates from 9.2.0. The trigger is the topNDims value. - Who is not.
getAllDims(n), the obvious call for all dims, works (on SSDV keep n bounded too:getAllDims(Integer.MAX_VALUE)hits the child-queue bug LFV-994). Ordinary topNDims values give correct results. - Severity. Loud: an exception for that call; no wrong results, nothing written to the index.
- How likely. Rare:
getAllDimsis the natural call for "all dims", only values from 2,147,483,631 throw, and no public report was found. ButInteger.MAX_VALUEis the usual Java "no limit" value, and Yuting Gan, who wrotegetTopDims, said on #843, a range-facetgetTopChildrenchange, that users who want all children can specify a large top-n; for dims that is an analogy.
One JUnit class, one method, one document with 2 dims, default FacetsConfig; besides Lucene's test framework it uses only public API. It fails with assertions on and off, on main and on 10.6.0, and is also the suggested regression test.
/*
* 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.facet.sortedset;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.facet.FacetResult;
import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsCollectorManager;
import org.apache.lucene.facet.FacetsConfig;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestGetTopDimsAllDims extends LuceneTestCase {
public void testGetTopDimsMaxValueReturnsAllDims() throws Exception {
FacetsConfig config = new FacetsConfig();
try (Directory dir = new ByteBuffersDirectory();
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
Document doc = new Document();
doc.add(new SortedSetDocValuesFacetField("a", "x"));
doc.add(new SortedSetDocValuesFacetField("b", "y"));
w.addDocument(config.build(doc));
try (DirectoryReader reader = DirectoryReader.open(w)) {
FacetsCollector fc =
new IndexSearcher(reader).search(new MatchAllDocsQuery(), new FacetsCollectorManager());
Facets facets =
new SortedSetDocValuesFacetCounts(
new DefaultSortedSetDocValuesReaderState(reader, config), fc);
List<FacetResult> allDims = facets.getAllDims(10);
try {
assertEquals(allDims, facets.getTopDims(Integer.MAX_VALUE, 10));
} catch (IllegalArgumentException e) {
// expected: the 2 dims of getAllDims(10); actual: IllegalArgumentException from the queue
throw new AssertionError("expected getAllDims(10) = " + allDims + ", but: " + e, e);
}
}
}
}
}
cp $REPRO/TestGetTopDimsAllDims.java $LUCENE/lucene/facet/src/test/org/apache/lucene/facet/sortedset/
cd $LUCENE && ./gradlew :lucene:facet:test --tests org.apache.lucene.facet.sortedset.TestGetTopDimsAllDims -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false (same failure)
- Expected
getTopDims(Integer.MAX_VALUE, 10)returns the 2 dims ofgetAllDims(10):a(value 1, childx (1)) andb(value 1, childy (1)).- Observed
IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647fromAbstractSortedSetDocValueFacetCounts.java:154, with assertions on and off.
More files, environment, full output291 words
The test indexes one document with SortedSetDocValuesFacetField("a", "x") and SortedSetDocValuesFacetField("b", "y"), counts MatchAllDocsQuery with SortedSetDocValuesFacetCounts, and asserts that getTopDims(Integer.MAX_VALUE, 10) equals getAllDims(10). Single thread, deterministic: the same failure for seeds DEADBEEF and 5EED0983; it fails with assertions on and off, on main and on the 10.6.0 release jars.
- The
IllegalArgumentExceptionis caught once and rethrown as anAssertionErrorthat states expected against actual (the exception stays the cause); a plainassertEqualsfails too. - It covers the SortedSetDocValues site. The taxonomy site is the same expression; the probe and the earlier checks executed it in all three taxonomy classes. If you fix both sites in one PR, a taxonomy twin of this method (
FastTaxonomyFacetCountsoverFacetField("a", "x")andFacetField("b", "y")) covers the second. - Style:
new MatchAllDocsQuery()is deprecated on main and in 10.6.0. The runs used the constructor; it does not affect the failure. - Set
-Ptests.assertsexplicitly: with a fixed seed and no value, Lucene's build picks the assertion mode from the seed.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); the 10.6.0 release fails too, as does every release run back to 9.2.0 (the releases in between by reading); 9.1.0 has no getTopDims |
|---|---|
| jdk | OpenJDK 25.0.3 |
| os | Linux aarch64 |
TestGetTopDimsAllDims > testGetTopDimsMaxValueReturnsAllDims FAILED
java.lang.AssertionError: expected getAllDims(10) = [dim=a path=[] value=1 childCount=1
x (1)
, dim=b path=[] value=1 childCount=1
y (1)
], but: java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at __randomizedtesting.SeedInfo.seed([DEADBEEF:96CD7C7B56D70AB5]:0)
at org.apache.lucene.facet.sortedset.TestGetTopDimsAllDims.testGetTopDimsMaxValueReturnsAllDims(TestGetTopDimsAllDims.java:56)
Caused by:
java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:123)
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:80)
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.util.PriorityQueue.usingComparator(PriorityQueue.java:63)
at org.apache.lucene.facet.sortedset.AbstractSortedSetDocValueFacetCounts.getTopDims(AbstractSortedSetDocValueFacetCounts.java:154)
at org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetCounts.getTopDims(SortedSetDocValuesFacetCounts.java:57)
at org.apache.lucene.facet.sortedset.TestGetTopDimsAllDims.testGetTopDimsMaxValueReturnsAllDims(TestGetTopDimsAllDims.java:53)
# main 645d4fe14a20, gradle, -Ptests.seed=DEADBEEF -Ptests.asserts=true; the same with -Ptests.asserts=false.
# On the 10.6.0 release jars (-ea and -da) the frames are AbstractSortedSetDocValueFacetCounts$1.<init>(...:153) and getTopDims(...:152).- The javadoc promises
getAllDimscut to the first topNDims, and the defaultgetTopDims, which these overrides optimize, caps withMath.min(topNDims, allResults.size()). - The only checks reject topNDims <= 0; upstream tests already call
getTopDims(10, 10)on 2 dims and expect thegetAllDims(10)result. - The taxonomy child queue in the same class caps with
Math.min(taxoReader.getSize(), topN).
The full argument248 words
- The contract was asked for in review. Greg Miller (gsmiller) asked in the #747 review for the sentence "results should be the same as calling
getAllDimsand then only using the firsttopNDims, but may be more efficient" (discussion_r829308926, 2022-03-17); it isFacets.java:61-62. - The overrides optimize a capped default. The same commit (#747) added the default
getTopDimswithMath.min(topNDims, allResults.size())and the uncapped SSDV override; Greg Miller's LUCENE-10488 issue (#11524) describes the other overrides as doing the same work "more optimally". For this input the pre-sizing is what makes the overrides diverge: with the cap they return the default's result in all five classes. - The input is in contract.
validateTopNand the taxonomy check accept every positive value (Facets.java:80-84,TaxonomyFacets.java:596-598); no javadoc names an upper bound. - Tests. "test getTopDims(10, 10) and expect same results from getAllDims(10)" runs on 2 dims in
TestSortedSetDocValuesFacets.java:128-130and on 2 dims inTestTaxonomyFacetCounts.java:151-153;Integer.MAX_VALUEis the same call at the end of the accepted range. - Precedents. Michael McCandless capped the taxonomy child queue with
Math.min(taxoReader.getSize(), topN)in LUCENE-5339 (#6403,189feaacb6f), the change that moved the existingInteger.MAX_VALUEchild-count test togetTopChildren(Integer.MAX_VALUE, "dim")(the cap is still atTaxonomyFacets.java:250-251). In LUCENE-4938 (#6002) Robert Muir reported an OutOfMemoryError, Uwe Schindler found that IndexSearcher did not apply theMath.min()logic everywhere, and Robert Muir asked for tests withInteger.MAX_VALUE. The hunt's intended-lens cross-model reviewer (OpenAI GPT-5.6) first pointed to that precedent and to thegetTopDims(10, 10)tests.
The devil's-advocate check211 words
The strongest case for "works as designed": Facets is @lucene.experimental; getAllDims is the API for all dims; PriorityQueue rejects impossible capacities on purpose (SOLR-5661 and 4c646dab3fb, Michael McCandless, crediting rsaavedraf) and documents that it pre-allocates; the javadoc says "should" and itself qualifies the feature (dims "should be configured as requiring dim counts", Facets.java:62-64); topNDims can be read as a caller-chosen resource bound; LUCENE-4938 sits under Optimizations in CHANGES. A later cross-model fact check rated this case moderate.
Why it fails: the PriorityQueue guard is an internal limit of a @lucene.internal class whose message names a maxSize the caller never passed, not the input domain of getTopDims; the "should" is the equivalence the reviewer asked for, and the default implementation honours it for the same input; the dim-count note is about count accuracy (the repro's dims are flat and single-valued, and the queue size does not depend on the setting); read as a resource bound, topNDims still does not explain why the default returns both dims and the overrides throw; and the LUCENE-4938 thread treated a missing Math.min on a top-N queue as a defect. Verdict: not intended, a defect (low severity). A maintainer may still answer "use getAllDims"; the javadoc and the capped default are the reply.
Recommended A: cap the dimension queue at the label / ordinal count (validated)
Cap the dimension queue the way the taxonomy child queue already is: Math.min(topNDims, state.getSize()) in the SortedSetDocValues getTopDims and Math.min(topNDims, taxoReader.getSize()) in TaxonomyFacets.getTopDims. With it the repro and the whole facet module (307 tests) pass. The cap is the label or ordinal count, not the dim count, so it stays loose for large fields.
// AbstractSortedSetDocValueFacetCounts.getTopDims
PriorityQueue.usingComparator(
// there cannot be more dims than labels:
Math.min(topNDims, state.getSize()),
...);
// TaxonomyFacets.getTopDims
PriorityQueue.usingComparator(
// there cannot be more dims than ordinals:
Math.min(topNDims, taxoReader.getSize()),
...);All options and trade-offs4 options · 527 words
Option A was applied in a scratch worktree of 645d4fe14a20 and run with gradle; the hunt's checks validated the same diff. Options B and C were not built.
One Math.min per site, mirroring the taxonomy child-queue cap (TaxonomyFacets.java:250-251): state.getSize() ("Number of unique labels") in AbstractSortedSetDocValueFacetCounts.getTopDims (:153-157) and taxoReader.getSize() in TaxonomyFacets.getTopDims (:610-614).
// AbstractSortedSetDocValueFacetCounts.getTopDims
PriorityQueue.usingComparator(
// there cannot be more dims than labels:
Math.min(topNDims, state.getSize()),
...);
// TaxonomyFacets.getTopDims
PriorityQueue.usingComparator(
// there cannot be more dims than ordinals:
Math.min(topNDims, taxoReader.getSize()),
...);Trade-offs
- Safe: every SSDV dim with a count has at least one label ordinal and every taxonomy dim is an ordinal, so the dims that can enter the queue never exceed the cap; results are unchanged for every topNDims that works today. The loops still test
pq.size() < topNDims(AbstractSortedSetDocValueFacetCounts.java:201,TaxonomyFacets.java:653); that stays safe for the same reason, but a cleaner patch keeps the capped size in a local and uses it in both places. - Loose: the bound is the field's label count (SSDV) or the taxonomy's ordinal count, not the dim count. With this fix, for 2 dims in a taxonomy of 524,293 ordinals, a call allocated 2,099,320 bytes at
Integer.MAX_VALUEagainst 2,160 at topNDims 2 (SSDV: 2,102,680 against 134,536). It removes the exception but does not make the allocation proportional to the dims; in theory a field or taxonomy with at leastArrayUtil.MAX_ARRAY_LENGTHlabels or ordinals would still reach the guard.
What the checks validated
- Gradle on
645d4fe14a20with the fix: the repro passes with-Ptests.asserts=trueandfalse; the whole facet module passes, 307 tests in 40 classes, 7 skipped, 0 failures (seed DEADBEEF, assertions on), includingTestSortedSetDocValuesFacets,TestTaxonomyFacetCounts,TestTaxonomyFacetAssociations,TestTaxonomyFacetValueSourceandTestDrillSideways. With the fix reverted the repro fails again. - The probe: with the fix all five classes return
aandbforInteger.MAX_VALUEand allocate 1,408-1,744 bytes at 2^24. - Earlier: the finder's 6-test JUnit 6 of 6, an independent reproducer's 7 of 7 (-ea, -da), 7 upstream facet test classes (82 tests, together with the LFV-994 fix) and the differential harness (0 saturation and 0 allocation failures with both fixes; its remaining prefix failures belong to LFV-363 and go to 0 with that fix added).
SSDV: count state.getDims() (an Iterable<String>, one pass over the dims). Taxonomy: count the root's children first (one sibling walk) or collect the dims into a growable list. Suggested by the independent root-cause-lens reviewer (OpenAI GPT-5.6).
Trade-offs
Removes the looseness of A, at the cost of one extra pass over the dims per call.
Gather the dim values into a list and pick the top topNDims from it.
Trade-offs
Removes the pre-allocation entirely; a larger change to two optimized methods.
Add an upper bound to the argument check.
Trade-offs
It would turn the working default getTopDims, and the working getTopChildren(Integer.MAX_VALUE, ...) of taxonomy facets that upstream tests use, into errors.
Services that pass a user-chosen number of dims straight to getTopDims on SortedSetDocValues or taxonomy facets should cap it, or call getAllDims: values from 2,147,483,631 up fail, and large values below that reserve heap in proportion to the value.
AI tools probing numeric limits passed Integer.MAX_VALUE as topNDims to the facet implementations: SortedSetDocValues and taxonomy getTopDims threw. A model-based differential harness (any topNDims at least the dim count must give the getAllDims result) confirmed it on 40 random indexes; one minimal test reproduces it.
The method in full156 words
Method: non-formal first signal, then semi-formal checking (an executed probe, then model-based differential testing on the stock jars).
- A planning AI tool for numeric-limit hunts read the facet code and ran a probe on main
a9779ef3c193, 10.5.2, 9.12.3 and the 10.6 release candidate: SSDVgetTopDims(Integer.MAX_VALUE, 10)threw andgetTopDims(10^8, 10)allocated 381 MiB. - The hunting AI tool wrote a reference model of top-N facet results (saturation:
getTopDims(n, k)for n at least the number of dims equalsgetTopDims(1000, k)andgetAllDims(k); allocation bounded by the dims) and a differential harness over 40 seeded random indexes: on stock main 9,924 of 59,600 saturation checks and 3,060 of 14,900 allocation checks failed (these include the sibling child-queue sites, LFV-994), and 0 of each with both fixes. An SMT solver checked the queue-size arithmetic. - It reduced the case to a public-API JUnit over all five classes with a default-
getTopDimscontrol, and later to the one-test repro.
No prior report found in GitHub issues, PRs and commits (incl. the JIRA archive), the mailing lists, downstream trackers, the web and all 414 open PRs; re-checked twice on 2026-10-30. Not fixed upstream: the fix-site files last changed in #16056 and #14761, both unrelated.
Searches4 searches
No report of this bug was found, and nothing upstream fixes it. Context only: #11361/#747 and #11524/#777/#779/#806/#889 (introduced the overrides; no discussion of the queue size); open PR #13287 (stefanvodita, edits TaxonomyFacets.getTopDims for a zero-value check, not the queue); open PRs #12862 and #16847 (touch the fix-site files, not the queue); the precedents #6002 (LUCENE-4938), #6403 (LUCENE-5339) and SOLR-5661; #7886 (LUCENE-6828, open, core hit-queue pre-allocation). LFV-363 and LFV-368 are other bugs on other lines of the same two methods; LFV-994 is the same pattern at the SSDV child queue.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues, PRs, commits (apache/lucene incl. the migrated JIRA archive; Solr, OpenSearch, Elasticsearch, Lucene.NET, nrtsearch) | 2026-10-28 to 2026-10-29 | two automated passes (code terms; use case), 16 issue/PR, 3 commit and 2 downstream queries each; 39 manual searches, e.g. getTopDims(Integer.MAX_VALUE, topNDims, DimValue, TopOrdAndNumberQueue, "maxSize must be", PriorityQueue pre-allocation; the comments of #747, #777, #779, #806, #889, #915, #12966, #14761, #13287, #751, #843 read | no report; context only |
| JIRA (LUCENE, SOLR), lists.apache.org (dev@, java-user@, issues@, commits@), the web | 2026-10-29 | 4 JIRA, 20 mailing-list and 15-16 web queries per pass | no report |
| All 414 open apache/lucene PRs; open module:facet items | 2026-10-29 | changed-file lists against the fix-site files | #12862, #16847 and #13287 touch the files, none the queue |
| GitHub search, re-checks | 2026-10-30 (twice) | getTopDims, topNDims, facet items created since 2026-10-29, "maxSize must be", PriorityQueue maxSize facet; upstream commits since 645d4fe14a20 | only the known items; no facet commit upstream |
Introduced with the API: LUCENE-10325 (#747, 7c33f04d374, 2022-03-28) added the capped default and the uncapped SortedSetDocValues override together; LUCENE-10488 (#777, #779, #806) added the uncapped concurrent and taxonomy overrides. First released in 9.2.0; every release since, 10.6.0 and main fail. Not a regression.
Versions tested6 rows
LUCENE-10325 (#11361; PR #747, 7c33f04d3746, Yuting Gan, 2022-03-28; branch_9x backport 77ccf2fb359e, #773, by Greg Miller) added Facets#getTopDims with the capped default and the SSDV override with new PriorityQueue<>(topNDims). LUCENE-10488 (#11524, 2022-05-13) added the same uncapped queue to ConcurrentSortedSetDocValuesFacetCounts (ef43242d77a2, #777), IntTaxonomyFacets (57f8cb2fd6bc, #779) and FloatTaxonomyFacets (f0ec22616723, #806); backport 87655fd015ce (#889). Later changes kept the capacity: #915 (9.3.0, SSDV overrides merged into the shared base class), #12966 (9.11.0, taxonomy overrides merged into TaxonomyFacets), #14761 (main only, PriorityQueue.usingComparator).
Release-portable main() programs ran the same 2-dim data on each version with assertions off: getTopDims(1000, 10) equals getAllDims(10) everywhere, getTopDims(Integer.MAX_VALUE, 10) throws and getTopDims(2^24, 10) allocates about 64 MiB, while a default-getTopDims control passes. Releases between the executed ones are affected by git grep on every 9.x and 10.x tag (4 override sites in 9.2.0, 3 in 9.3.0-9.10.0, 2 from 9.11.0 on).
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | minimal repro fails with gradle, assertions on and off; probe: all five classes throw, about 64 MiB at 2^24 |
10.6.0 (4ae946e5a9b9) | affected | minimal repro fails on the release jars, -ea and -da; the same commit as branch_10_6 head, where all five classes throw |
main a9779ef3c193, 10.5.2 | affected | all five classes throw; about 64 MiB at 2^24; the finder's JUnit 5 of 6 fail |
| 9.10.0, 9.11.0, 9.12.3, 10.0.0 | affected | throw (10.0.0: the three core classes run); about 64 MiB at 2^24 |
| 9.2.0, 9.3.0 | first release | 9.2.0: all five classes throw (4 override sites); 9.3.0: the three core classes throw |
| 9.1.0 | no API | no getTopDims (compile error "cannot find symbol") |
The finder, an independent reproducer, an intended-behaviour devil's advocate, an adjudication, a credibility audit and this page's gradle runs all reproduce it. Cross-model reviews (OpenAI GPT-5.6) could run nothing: some said real, the others "not real" for procedure only; separate re-runs failed as claimed.
Checks and verdicts382 words
Every executing check ran on the stock jars and voted real. The only dissent is procedural: a cross-model refute review that could not run any code voted not real on procedure only at low confidence, while every evidence item it listed supports real.
| Check | Verdict | What it found |
|---|---|---|
| Planning probe | first signal | SSDV getTopDims(Integer.MAX_VALUE, 10) throws on main, 10.5.2, 9.12.3 and the 10.6 release candidate; 381 MiB at 10^8. |
| Finder | candidate | Differential harness (failures drop to 0 with the fixes); JUnit 5 of 6 fail on main (-ea, -da), 10.5.2 and 9.12.3; the default-getTopDims control passes; 6 of 6 and 82 upstream facet tests pass with the fix. |
| Cross-model reviews, intended and refute lenses (OpenAI GPT-5.6, read-only) | real, medium; not real, low (procedural) | Could not run code; the intended lens rated the intended argument weak and raised the LUCENE-4938 precedent; the refute lens listed only supporting evidence. Both separate re-runs: 5 of 6 fail. |
| Independent reproducer | real | Own JUnit over all five classes: 6 of 7 fail on main (-ea, -da), 10.5.2, 9.12.3, 9.2.0; own Math.min patch 7 of 7; 1,496 vs 67,110,352 bytes; OutOfMemoryError at 2^28 with 512 MB. |
| Root-cause adversary (OpenAI GPT-5.6, read-only) | real, medium | Root cause agrees; the fix bound is loose (measured); suggested the tighter dim-count bound. |
| Intended-behaviour devil's advocate | real | Built the strongest works-as-designed case and rated it weak; re-runs fail on main and 10.5.2. |
| Adjudication | confirmed, high | Re-ran the independent JUnit with a new seed: 6 of 7 fail (-ea, -da), the control passes. |
| Credibility audit | keep | Re-ran the finder's JUnit with a new seed: 5 of 6 fail. |
| This page | reproduced; fix validated | Gradle on 645d4fe14a20: the repro fails (-ea, -da) and passes with the fix (-ea, -da); facet module 307 tests, 0 failures with the fix; 10.6.0 release jars fail (-ea, -da). |
| Fact checks of this page | reproduced; wording corrected | Re-ran the gradle repro (assertions on and off), the standalone runner on main, 10.6.0 and 9.2.0, the fix (facet module 307 tests, 0 failures) and an own probe; found three wording errors, fixed in this version. A read-only cross-model fact check (OpenAI GPT-5.6) found the commit count and wording points (an unpublished file; resolution in an unpublished file). |
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 515 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,514 words
- Registry title
- SortedSetDocValues and taxonomy Facets#getTopDims size their dimension priority queue by the caller's topNDims without a cap, so getTopDims(Integer.MAX_VALUE, n) throws IllegalArgumentException where getAllDims(n) and the default Facets#getTopDims return every dim, and a large topNDims allocates topNDims + 1 queue slots even for 2 dims
- Area
- facet / getTopDims dimension queue (AbstractSortedSetDocValueFacetCounts and TaxonomyFacets overrides, inherited by SortedSetDocValuesFacetCounts, ConcurrentSortedSetDocValuesFacetCounts, FastTaxonomyFacetCounts, TaxonomyFacetIntAssociations and TaxonomyFacetFloatAssociations)
- Priority
- Low, rank 644: GetTopDims(Integer.MAX_VALUE, n) on SSDV and taxonomy facets throws a clear IllegalArgumentException and large values only waste memory, the same loud MAX_VALUE idiom as LFV-994 on a less-used facet call, so it follows that row.
- Impact class
- crash
- Impact note
- Applications that call Facets#getTopDims(topNDims, topNChildren) on SortedSetDocValues facets (SortedSetDocValuesFacetCounts, ConcurrentSortedSetDocValuesFacetCounts) or taxonomy facets (FastTaxonomyFacetCounts, TaxonomyFacetIntAssociations, TaxonomyFacetFloatAssociations) with Integer.MAX_VALUE (or any value >= 2147483631) as 'all dims' get an IllegalArgumentException ('maxSize must be >= 0 and < 2147483631') on every such call, and with a large but legal topNDims pay one transient allocation of topNDims + 1 references per call however few dims exist (about 64 MiB at 2^24 for 2 dims; an OutOfMemoryError at 2^28 with a 512 MB heap); default FacetsConfig, codec and searcher, any index size, -ea and -da, every release from 9.2.0 to 10.6.0 and main; results for ordinary sizes are correct, getAllDims(n) is a working alternative, and nothing is written to the index.
- Summary
- You index two flat facet dims, call getAllDims(10) on SortedSetDocValuesFacetCounts or FastTaxonomyFacetCounts and get both dims, then call getTopDims(Integer.MAX_VALUE, 10) to ask for 'all dims' and expect the same list, because the getTopDims javadoc says results should be the same as calling getAllDims and then only using the first topNDims, and the default Facets#getTopDims caps with Math.min(topNDims, allResults.size()). Instead the optimized overrides in AbstractSortedSetDocValueFacetCounts and TaxonomyFacets build PriorityQueue.usingComparator(topNDims, ...) before they look at any dim, so the call throws IllegalArgumentException 'maxSize must be >= 0 and < 2147483631; got: 2147483647' in all five SortedSetDocValues and taxonomy implementations. Below that limit the same line pre-allocates topNDims + 1 slots: getTopDims(2^24, 10) over 2 dims allocates about 64 MiB to return 2 results.
- Introduced
- 7c33f04d3746f855479da5177df8f19b6fdbbe84 ('LUCENE-10325: Add getTopDims functionality to Facets (#747)', issue #11361; branch_9x backport 77ccf2fb359e, #773) [read: git blame and log in the history clone, confirmed by the 9.1.0 / 9.2.0 runs]: SortedSetDocValuesFacetCounts.getTopDims built new PriorityQueue<>(topNDims), while the same commit added the capped default Facets#getTopDims. The concurrent-SSDV and taxonomy overrides came with the same uncapped queue in LUCENE-10488 (issue #11524): ef43242d77a2 (#777, ConcurrentSortedSetDocValuesFacetCounts), 57f8cb2fd6bc (#779, IntTaxonomyFacets) and f0ec22616723 (#806, FloatTaxonomyFacets), branch_9x backport 87655fd015ce (#889). Written by Yuting Gan (Yuti-G), merged by Greg Miller (gsmiller), who also authored the backports. Later refactors kept the capacity: 8db1e41fc07 (#915, SSDV overrides merged into AbstractSortedSetDocValueFacetCounts), 9ba4af7b883 (#12966, taxonomy overrides merged into TaxonomyFacets) and a7704f5242d (#14761, PriorityQueue.usingComparator). Not a regression. (2022-03-28 (SSDV site, author date of 7c33f04d374, equal to its commit date); 2022-05-13 (concurrent-SSDV and taxonomy sites, LUCENE-10488))
- First affected version
- 9.2.0 [exec, -da]: all five classes throw for getTopDims(Integer.MAX_VALUE, 10) and allocate about 64 MiB for getTopDims(2^24, 10) over 2 dims; 9.1.0 has no Facets#getTopDims [exec: compile error 'cannot find symbol: method getTopDims(int,int)']
- Affected versions
- every release from 9.2.0 to 10.5.2 (all 9.2.x-9.12.x and 10.x), 10.6.0 (released 2026-10-29; run as its RC, branch_10_6 head 4ae946e5a9b9) and main a9779ef3c193; never fixed. [exec] follow-up history runs (release-portable main() over the core and facet jars, -da): 9.2.0, 9.3.0, 9.10.0, 9.11.0, 9.12.3, 10.0.0, 10.5.2, branch_10_6 and main all FAIL, with the default-getTopDims control passing on each; the finder's JUnit on main (-ea, -da), 10.5.2 and 9.12.3; the independent reproduction's JUnit on main (-ea, -da), 10.5.2 (-ea, -da), 9.12.3 and 9.2.0; the intended-behaviour check on 10.5.2. [read] the releases in between: git grep on every 9.x/10.x release tag finds the topNDims-sized dim queue at each override site (4 sites in 9.2.0, 3 in 9.3.0-9.10.0, 2 from 9.11.0 on) and no Math.min(topNDims ...) in the facet module except the default Facets#getTopDims. Upstream main 645d4fe14a (2026-10-28) has no change at the fix sites. JDK 25.0.3, aarch64; plain Java arithmetic.
- 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
- non-formal: code reading; also reached by Z3
- Upstream and prior reports
- none found (2026-10-29, read-only; the prior-report record verdict new, plus the finder's search at 2026-10-28, the independent reproduction's and the intended-behaviour check's searches, the adjudication's refresh and a recheck): two scripted prior-report searches (by code terms and by use case; 16 GitHub issue/PR, 3 commit, 2 downstream, 20 mailing-list over dev@, java-user@, issues@ and commits@, 4 JIRA-archive and 15-16 web queries each; 122 and 230 distinct hits, 0 and 1 high, the high one the sandbox PR #13568), manual apache/lucene GitHub searches ('topNDims', 'getTopDims' in bodies and comments, open getTopDims items, Integer.MAX_VALUE variants, DimValue, TopOrdAndNumberQueue, PriorityQueue sizing terms), searches in Solr, OpenSearch, Elasticsearch, Lucene.NET and nrtsearch, the changed files of all 414 open apache/lucene PRs, all open module:facet items and those closed since 2026-01-01, all 93 items updated since 2026-10-26, the commits on the three fix-site files by author date, the comments and review comments of the introducing and later PRs (#747, #777, #779, #806, #889, #915, #12966, #14761 and others), JIRA REST (LUCENE, SOLR), lists.apache.org and 16 web searches. Recheck: 'getTopDims' (issues and PRs, by update) finds only the known items; no facet issue or PR was updated since 2026-10-27; none of the PRs created since 2026-10-27 or issues created since 2026-10-28 concerns facets (titles read); the last lucene/facet commit is 5017142089 (#16783, author date 2026-10-12, unrelated); tracking issue #16937 does not mention facets or getTopDims. Related, not this bug: #11361/#747 and #11524/#777/#779/#806/#889 (introduced the overrides; no sizing discussion), open PR #13287 (stefanvodita: the zero-value check in TaxonomyFacets.getTopDims, not the queue size), open PRs #12862 and #16847 (touch the fix-site files, not the queue), the precedents #6002 (LUCENE-4938), #6403 (LUCENE-5339) and SOLR-5661, and open #7886 (LUCENE-6828, core hit-queue pre-allocation).
- Lucene commit tested
- main
645d4fe14a20(2026-10-28; upstream main2f424fd428daof 2026-10-30 has no change at the fix sites): the minimal repro with gradle, assertions on and off, stock and with the fix, and the whole facet module test suite with the fix (307 tests, 0 failures), in a scratch worktree removed after the runs; the standalone runner on the 10.6.0 release jars with -ea and -da; an executed probe of the repro index over all five classes (results and allocations, stock and with the fix, -ea and -da; 10.5.2 -da). Earlier: the finder's, an independent reproducer's, a devil's advocate's, an adjudication's and an audit's tests on maina9779ef3c193(no facet orPriorityQueuechange up to645d4fe14a20), 10.5.2, 9.12.3 and 9.2.0; release-portablemain()programs on 9.1.0 to 10.5.2 and the 10.6 branch. OpenJDK 25.0.3, Linux aarch64.
- Finding record
lucene/facet/src/java/org/apache/lucene/facet/Facets.java:25 (at a9779ef3c193) (@lucene.experimental), :58-72 (getTopDims javadoc 'Results should be the same as calling getAllDims and then only using the first topNDims'; default body subList(0, Math.min(topNDims, allResults.size()))), :80-84 (validateTopN rejects only topN <= 0)lucene/facet/src/java/org/apache/lucene/facet/sortedset/AbstractSortedSetDocValueFacetCounts.java:143-157 (at a9779ef3c193) (getTopDims: validateTopN, then PriorityQueue.usingComparator(topNDims, ...) at :153-154); lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyFacets.java:595-614 (getTopDims: 'topN must be > 0', then PriorityQueue.usingComparator(topNDims, ...) at :610-611) and :250-251 (child queue new TopOrdAndIntQueue(Math.min(taxoReader.getSize(), topN)))lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:113-125 (at a9779ef3c193) (IllegalArgumentException for maxSize >= ArrayUtil.MAX_ARRAY_LENGTH, commented 'Throw exception to prevent confusing OOME'; maxSize + 1 slots pre-allocated below it)lucene/facet/src/test/org/apache/lucene/facet/sortedset/TestSortedSetDocValuesFacets.java:127-129 (at a9779ef3c193) and lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyFacetCounts.java:151-153 ('test getTopDims(10, 10) and expect same results from getAllDims(10)', with fewer than 10 dims)
- The finding in 30 seconds
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/Facets.java:25, :59-72, :80-84Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/AbstractSortedSetDocValueFacetCounts.java:143-157, :162, :201-202; sortedset/SortedSetDocValuesFacetCounts.java:57; sortedset/ConcurrentSortedSetDocValuesFacetCounts.java:52Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyFacets.java:250-251, :595-614, :620; taxonomy/FastTaxonomyFacetCounts.java:40; taxonomy/IntTaxonomyFacets.java:28; taxonomy/FloatTaxonomyFacets.java:27; taxonomy/TaxonomyFacetIntAssociations.java:38; taxonomy/TaxonomyFacetFloatAssociations.java:51Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:34, :39, :61-64, :79-80, :113-134; lucene/core/src/java/org/apache/lucene/util/ArrayUtil.java:33-34Lucene main 645d4fe14a20: grep 'List<FacetResult> getTopDims' over lucene/ outside tests: Facets.java:69, AbstractSortedSetDocValueFacetCounts.java:143, TaxonomyFacets.java:595Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesFacetCounts.java:86-88, sortedset/ConcurrentSortedSetDocValuesFacetCounts.java:80-84 (hasCounts); taxonomy/TaxonomyFacetFloatAssociations.java:54, :67 (the two constructors the probe used); Facets.java:62-64 (dim-count note)
- Who is affected
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/AbstractSortedSetDocValueFacetCounts.java:147-149; taxonomy/TaxonomyFacets.java:600-602; Facets.java:25 (@lucene.experimental)Lucene main 645d4fe14a20: grep 'List<FacetResult> getTopDims' over lucene/ outside tests (only Facets, AbstractSortedSetDocValueFacetCounts and TaxonomyFacets define it)Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/ConcurrentSortedSetDocValuesFacetCounts.java:80-84 (hasCounts always true); sortedset/SortedSetDocValuesFacetCounts.java:86-88; taxonomy/FastTaxonomyFacetCounts.java:74-82
- Minimal repro
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/MatchAllDocsQuery.java:26, :28-34 (constructor deprecated; INSTANCE); a Lucene clone releases/lucene/10.6.0 (the same)
- Why it looks unintended
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/Facets.java:59-72, :80-84; taxonomy/TaxonomyFacets.java:250-251, :596-598; lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:39, :121-125Lucene main 645d4fe14a20: lucene/facet/src/test/org/apache/lucene/facet/sortedset/TestSortedSetDocValuesFacets.java:82-83, :119-120, :128-130; lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyFacetCounts.java:80-101 (dims Author and Publish Date), :149-153
- Suggested fix
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesReaderState.java:164-165 (getSize: number of unique labels), :184 (getDims is an Iterable); sortedset/AbstractSortedSetDocValueFacetCounts.java:201; taxonomy/TaxonomyFacets.java:250-251, :653
- History
a Lucene clone releases/lucene/10.6.0 = 4ae946e5a9b99d1e22c23865c99e561b8dcc9d71
- TestGetTopDimsAllDims.java (JUnit, package org.apache.lucene.facet.sortedset):
repro/LFV-983/src/org/apache/lucene/facet/sortedset/TestGetTopDimsAllDims.java(61 lines)
The values come from the code at that commit, the minimal repro and an executed probe of the same index.
assets/LFV-983/bug.png(515 KB)
