reorderIndexDirectory rewrites the index in place: a reader on the current commit, a new CREATE-mode writer on the same directory, then one addIndexes per segment. The writer gets a default config, so the new index is stamped with the running major (11 on Lucene 11) and has no soft-deletes field, while every re-added segment keeps its own.
- Every index records the major version that created it, and that never changes, even after upgrades. Lucene 11 opens and writes indexes created by 9.x and 10.x.
IndexWriter.addIndexesaccepts only segments created by the same major as the target index, and segments that use soft deletes only if the writer's config names the soft-deletes field.- A writer in
OpenMode.CREATEstarts a new index whose created major comes fromIndexWriterConfig.setIndexCreatedVersionMajor; the default is the running major, 11 on Lucene 11.
addIndexes refuses the major-10 segment; keeping the source's major fixes it. Select the still to open it at full size. Generated with AI, checked against the code.- You have an index of 10 documents with a 2-d vector field
f, written by Lucene 10.5.2 with default settings. Lucene 11 opens and writes it, and the index stays "created by major 10" for good. - You run the tool on it:
java -cp lucene-core.jar:lucene-misc.jar org.apache.lucene.misc.index.BpVectorReorderer $IDX f. You expect the same 10 documents back, renumbered so that similar vectors get nearby doc ids. - The tool opens a second
IndexWriteron the same directory withnew IndexWriterConfig()andOpenMode.CREATE. That writer starts a fresh index stamped "created by major 11", then re-adds each old segment withaddIndexes, which accepts only segments of the index's own major. - You get exit code 1:
IllegalArgumentException: Cannot merge a segment that has been created with major version 10 into this index which has been created by major version 11. A 9.12.3 index fails the same way; an index that used soft deletes fails with "soft-deletes field is not configured in IWC". - With the fix (copy the source's created major and soft-deletes field into that config) the same command exits 0: 10 documents, still created by major 10.
Anyone who runs this lucene/misc tool on 11.0 on a compatible index carried over from 9.x or 10.x: such an index keeps its creation version for good. Default settings and options are enough, and every attempt fails at the first segment. Unreleased (branch_11x, main) and loud: a clear exception names the cause.
Mechanism, terms and numbers619 words
BpVectorReorderer (lucene/misc, added for 11.0 by #14097) has a command line, main(indexPath, field), that renumbers the documents of an existing index in place so that documents with similar vectors get nearby doc ids. It calls reorderIndexDirectory, which opens a reader on the index, then a new IndexWriter on the same directory with new IndexWriterConfig() and OpenMode.CREATE, and adds each segment back with addIndexes(SortingCodecReader.wrap(...)). A CREATE writer takes the index's created major from its config, by default the running major (11 on Lucene 11), and the default config has no soft-deletes field, while each wrapped segment keeps its own created major. So addIndexes refuses the first segment of every index created by 9.x or 10.x, and of every index that used soft deletes, with an IllegalArgumentException. Lucene 11 documents 9.x and 10.x indexes as fully writable and an index's creation version as permanent, so on 11.0 the tool cannot reorder a compatible index carried over from 9.x or 10.x. On main, now 12.0, it also refuses 11.x indexes. The javadoc of IndexWriterConfig.setIndexCreatedVersionMajor names exactly this addIndexes restriction and its remedy; copying the source's created major and soft-deletes field into the rewrite's config fixes it (+8 lines). Unreleased: the tool is only on the 11.0 release branch branch_11x and on main.
main(BpVectorReorderer.java:653-682) takes the index path, the field and three tuning options (--max-iters,--min-partition-size,--thread-count); it has no option for the writer configuration. It callsreorderIndexDirectory(FSDirectory.open(path), executor)(:679-680).reorderIndexDirectory(:684-697) opens aDirectoryReaderon the directory, thennew IndexWriterConfig()withsetOpenMode(OpenMode.CREATE)(:686-687) and a writer on the same directory, and for each segment callswriter.addIndexes(SortingCodecReader.wrap(segment, computeDocMap(...), null))(:689-694).- In CREATE mode the writer starts
new SegmentInfos(config.getIndexCreatedVersionMajor())(IndexWriter.java:1009); the default is the major ofVersion.LATEST, 11 at this commit (IndexWriterConfig.java:177-179). No soft-deletes field is configured. SortingCodecReader.wrapkeeps the segment'screatedVersionMajor(SortingCodecReader.java:454-457).addIndexesvalidates each segment first (IndexWriter.java:3290):validateMergeReaderthrows when the index's created major differs from the segment's (:3228-3236), 11 vs 10 or 9. For segments that carry a soft-deletes field, the field check that follows (verifyFieldInfo,IndexWriter.java:3292->FieldInfos.java:401-403) reachesFieldInfos.verifySoftDeletedFieldName, which throws because the writer has none configured (FieldInfos.java:459-465).- All segments of an index share its created major, so the very first
addIndexescall fails and nothing is reordered. A soft-deletes field that was configured but never used leaves no trace in the segments and reorders fine. - On main after #16942 (2026-10-29)
Version.LATESTis 12, so there the same code also refuses indexes created by 11.x: the test below fails on main716d6f216f5with "created with major version 11 into this index which has been created by major version 12".
| index | stock command line | with the fix |
|---|---|---|
| 10 docs written by the real 10.5.2 jar, default settings | exit 1, ... created with major version 10 into this index which has been created by major version 11 | exit 0, 10 docs, created major 10 |
10 docs written by the real 9.12.3 jar, default settings (read with lucene-backward-codecs on the class path) | ... created with major version 9 ... | 10 docs, created major 9 |
10 docs + 1 softUpdateDocument, written on main | this index has [soft_del] as soft-deletes already but soft-deletes field is not configured in IWC | 11 docs on disk kept |
| control: 10 docs written on main, no soft deletes | reordered, 10 of 10 | reordered, 10 of 10 |
After each stock failure the index held 0 documents; that data loss is the sibling bug LFV-1022 and is not counted here.
Exposure and likelihood156 words
- Who is hit. Users of the 11.0 release branch
branch_11xor of main (unreleased) who runBpVectorReorderer's command line, or callreorderIndexDirectoryfrom the same package.BPReorderingMergePolicyandcomputeDocMapwith the caller's ownIndexWriterare not affected. - Default vs opt-in. The tool is an opt-in expert utility. Once it runs, default index settings and default options are enough, and no option avoids the failure.
- Scale. No size or timing condition: the first segment fails, every time, with assertions on and off. An index's creation version "never changes" (
MIGRATE.md:165), also afterIndexUpgraderorforceMerge, so an index created by 9.x or 10.x can never be reordered by the tool. On main (12.0) the same holds for indexes created by 11.x. - Effect. A clear
IllegalArgumentExceptionnaming the cause; nothing is reordered. Today the failure also empties the index (LFV-1022, a separate bug in the same method); with LFV-1022 fixed, the index would be kept.
One JUnit test in lucene/misc, public API only: write 1 document with a 2-d vector using setIndexCreatedVersionMajor(Version.LATEST.major - 1) (the created major an index from the previous major carries), run BpVectorReorderer.main(path, "f", "--thread-count", "1"), then count the documents. It fails with assertions on and off.
/*
* 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.misc.index;
import java.nio.file.Path;
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.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.util.Version;
public class TestBpVectorReorderEarlierMajorIndex extends LuceneTestCase {
public void testReorderIndexCreatedByPreviousMajor() throws Exception {
Path path = createTempDir();
try (Directory dir = FSDirectory.open(path)) {
IndexWriterConfig iwc =
new IndexWriterConfig().setIndexCreatedVersionMajor(Version.LATEST.major - 1);
try (IndexWriter w = new IndexWriter(dir, iwc)) {
Document doc = new Document();
doc.add(new KnnFloatVectorField("f", new float[] {1, 1}));
w.addDocument(doc);
}
// expected: the 10.x-created index is reordered; actual: IllegalArgumentException
BpVectorReorderer.main(path.toString(), "f", "--thread-count", "1");
try (DirectoryReader reader = DirectoryReader.open(dir)) {
assertEquals(1, reader.numDocs());
}
}
}
}
cp $REPRO/TestBpVectorReorderEarlierMajorIndex.java $LUCENE/lucene/misc/src/test/org/apache/lucene/misc/index/
cd $LUCENE && ./gradlew :lucene:misc:test --tests TestBpVectorReorderEarlierMajorIndex -Ptests.seed=5EED1023
- Expected
- The index is reordered and still holds its 1 document.
- Observed
main()throwsIllegalArgumentException: Cannot merge a segment that has been created with major version 10 into this index which has been created by major version 11.
More files, environment, full output260 words
One test class, one method, single-threaded, unchanged by the module's tidy task. The expert setter gives the index the same created major as one written by the previous major (only that: its segment is still written by the running version), so this is the minimal regression test for the created-major check. The compatibility evidence is an index written by the real 10.5.2 jar, which fails with the same exception through the default-options command line (see the steps above). The seed only randomizes the test environment; the failure does not depend on it.
It fails the same way on the 11.0 release branch branch_11x (603af870375), and on main 716d6f216f5, already 12.0, where it writes an 11.x index and gets "created with major version 11 into this index which has been created by major version 12".
The suggested regression test is this same test.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); also fails on branch_11x 603af870375 and main 716d6f216f5 (12.0.0-SNAPSHOT) |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux aarch64 (pure Java logic; nothing platform-specific) |
cp $REPRO/TestBpVectorReorderEarlierMajorIndex.java $LUCENE/lucene/misc/src/test/org/apache/lucene/misc/index/
cd $LUCENE && ./gradlew :lucene:misc:test --tests TestBpVectorReorderEarlierMajorIndex -Ptests.seed=5EED1023
# the same failure with -Ptests.asserts=false
TestBpVectorReorderEarlierMajorIndex > testReorderIndexCreatedByPreviousMajor FAILED
java.lang.IllegalArgumentException: Cannot merge a segment that has been created with major version 10 into this index which has been created by major version 11
at __randomizedtesting.SeedInfo.seed([5EED1023:A364E3378B2403E]:0)
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.index.IndexWriter.validateMergeReader(IndexWriter.java:3235)
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.index.IndexWriter.addIndexes(IndexWriter.java:3290)
at org.apache.lucene.misc.index.BpVectorReorderer.reorderIndexDirectory(BpVectorReorderer.java:691)
at org.apache.lucene.misc.index.BpVectorReorderer.main(BpVectorReorderer.java:680)
at org.apache.lucene.misc.index.TestBpVectorReorderEarlierMajorIndex.testReorderIndexCreatedByPreviousMajor(TestBpVectorReorderEarlierMajorIndex.java:43)
1 test completed, 1 failed
# the same with -Ptests.asserts=false, on branch_11x 603af870375, and with the standalone runner (assertions on and off); passes with the fixmain()takes "a path containing an index to reorder"; Lucene 11 keeps 9.x indexes "fully supported (open and write)", and an index's creation version "never changes".setIndexCreatedVersionMajor's javadoc names this exactaddIndexesrestriction and its remedy; the tool does not apply it.IndexUpgraderandIndexSplitterkeep the source's created major; the tool's PRs, tests and CHANGES state no version limit.
The full argument242 words
- Contract.
main: "@param args two args: a path containing an index to reorder" (BpVectorReorderer.java:648-651), with no version restriction; neither the class normainis marked@lucene.experimental.MIGRATE.md:143: "Set to 9 for Lucene 11.0.0, so indexes created with Lucene 9.x remain fully supported (open and write)";:165: "The index's creation version is still 9 (this never changes)"; CHANGES 11.0, GITHUB#16741: "Indexes created with 9.x remain fully supported in Lucene 11, no reindexing required". - The documented remedy.
IndexWriterConfig.setIndexCreatedVersionMajor: "It is sometimes useful to set the previous major version for compatibility due to the fact that IndexWriter#addIndexes only accepts indices that have been written with the same major version as the current index" (IndexWriterConfig.java:174-178), added by LUCENE-8508 (#9554) so that a new index can take an older created major and so accept older segments throughaddIndexes(the issue's stated motivation is resharding withaddIndexes). - Other tools.
IndexSplitter.splitwritesnew SegmentInfos(infos.getIndexCreatedVersionMajor())(IndexSplitter.java:112);IndexUpgraderwrites into the existing index with the default open modeCREATE_OR_APPEND(IndexUpgrader.java:183), so its created major is kept. - Tests and history.
TestBpVectorReordererrunsmain()as "the index reordering tool" (:353-354,:412-413), only on indexes it just created on main. #13683, the tool's first version, describes renumbering "an existing HNSW graph ... without the need to re-index it" and lists limitations (float vectors only, no index sort), but no index-version or soft-deletes limit; #14097 discusses none either.
The devil's-advocate check226 words
Strongest case (built by the devil's-advocate check and the cross-model reviews, rated moderate): the same-major rule of addIndexes is deliberate and tested (LUCENE-7756; TestBasicBackwardsCompatibility.testAddOldIndexesReader asserts the same message); the command line began as a research tool (in #13565 its author wrote "it would be easier to start with a tool that rewrites the index" if "we want to just get an idea of what the impact of such a change might be"; on #14097 a reviewer accepted open questions "especially for the misc module"); CHANGES describes #14097 as a merge policy and never mentions the command line; and IndexMergeTool and MultiPassIndexSplitter use the same default-config addIndexes pattern without a report.
Why it fails: the guard stops mixing majors in one index, but this tool rewrites one index's own segments into the same directory, which mixes nothing, and the documented setter exists for exactly this case. The command line is public, unmarked and tested upstream, and ships in 11.0, where an index carried over from 9.x or 10.x is a documented, supported index state; failing on it is not a research trade-off. IndexMergeTool and MultiPassIndexSplitter write a new index elsewhere, where stamping the current major is arguable; this tool overwrites the user's index in place, whose creation version main says never changes. Verdict: a defect. Residual risk: a maintainer may prefer to document the limit (option C).
Recommended Keep the source's created major and soft-deletes field in the rewrite's config (validated)
In reorderIndexDirectory, give the rewrite's config the source's created major (from the latest commit) and, if the index has one, its soft-deletes field (+8 lines). The test then passes with assertions on and off, TestBpVectorReorderer passes 8 of 8, all 317 lucene/misc tests pass, and a real 10.5.2 index is reordered keeping created major 10.
// addIndexes only accepts segments of the index's own created major, and checks the
// soft-deletes field: keep both from the index being rewritten
iwc.setIndexCreatedVersionMajor(
SegmentInfos.readLatestCommit(directory).getIndexCreatedVersionMajor());
String softDeletesField = FieldInfos.getMergedFieldInfos(reader).getSoftDeletesField();
if (softDeletesField != null) {
iwc.setSoftDeletesField(softDeletesField);
}All options and trade-offs3 options ยท 567 words
Option A was built and validated with gradle in a fresh scratch worktree at 645d4fe14a20, and earlier with the standalone runner and through the stock command line on a real 10.5.2 index; options B and C were not built.
Keep the source's created major and soft-deletes field in the rewrite's config (validated) (recommended)
In reorderIndexDirectory, after setOpenMode(CREATE), set the config's created major to the one recorded in the index's latest commit, and its soft-deletes field to the one recorded in the index's field infos, if any (+8 lines and two imports in BpVectorReorderer.java; the hunt's fix sketch). It carries over only these two settings: commit user data, a parent field and an index sort are still not carried over (the parent field deliberately, see below; the other two behave as today and are not claimed).
// addIndexes only accepts segments of the index's own created major, and checks the
// soft-deletes field: keep both from the index being rewritten
iwc.setIndexCreatedVersionMajor(
SegmentInfos.readLatestCommit(directory).getIndexCreatedVersionMajor());
String softDeletesField = FieldInfos.getMergedFieldInfos(reader).getSoftDeletesField();
if (softDeletesField != null) {
iwc.setSoftDeletesField(softDeletesField);
}Trade-offs
- The success path for indexes created by the running major is unchanged.
- The rewritten index keeps its created major, as
IndexUpgraderdoes, so it stops being openable whenMIN_SUPPORTED_MAJORis later raised, exactly like the original (the note onsetIndexCreatedVersionMajor). readLatestCommitre-readssegments_N; it is the commit the reader just opened unless another writer commits in between (the tool assumes none). Reading the reader's own commit,SegmentInfos.readCommit(directory, ((DirectoryReader) reader).getIndexCommit().getSegmentsFileName()), closes even that window (not built).- Parent-field indexes are still refused ("this IndexWriter has no parent document field configured"); that is arguably right, because the tool does not keep document blocks (LFV-093), so the fix deliberately does not copy the parent field.
- It does not fix LFV-1022: a failed run still commits an empty index. Land both; each fix alone leaves the other bug's test failing (LFV-1022's fix alone: this test still fails; this fix alone: LFV-1022's test still fails, 10 documents expected, 0 found).
- Open PR #15430 moves the class to core with this method unchanged; if it merges first, the fix moves with it.
What the checks validated
Gradle, fresh scratch worktree at 645d4fe14a20, JDK 25, seed 5EED1023: without the fix the test fails (assertions on and off); with it TestBpVectorReorderEarlierMajorIndex + TestBpVectorReorderer pass 9 of 9 with assertions on and again with -Ptests.asserts=false, and :lucene:misc:test passes 24 suites, 317 tests, 0 failures (9 skipped by the test framework). Standalone runner: the test passes with -ea and -da; indexes written by the real 10.5.2 and 9.12.3 jars are reordered keeping created major 10 / 9 and all 10 documents; the soft-deletes index keeps its 11 documents; the default-options command line on a 10.5.2 index exits 0 with 10 documents, created major 10.
Take the created major from reader.leaves().get(0).reader().getMetaData().createdVersionMajor() instead of re-reading segments_N.
Trade-offs
Same behaviour, but needs a special case for an empty index (no segments).
Check the created major and the soft-deletes field before opening the writer, and refuse with "reindex with the current major first".
Trade-offs
Documents the limit instead of removing it: still no reorder for any index from an earlier major. It never opens the CREATE writer for these inputs, so it also avoids LFV-1022's wipe for them.
A product that offers this tool for existing indexes would see it fail on every index created before the product moved to Lucene 11.
While testing how the same tool handles a failed run, the hunt read the rewrite's writer config against IndexWriter's addIndexes checks, then ran ordinary indexes through the stock command line: one from an earlier major, one with soft deletes and a control, then indexes written by the real 9.12.3 and 10.5.2 jars. Code reading confirmed by execution.
The method in full117 words
Method: code reading confirmed by execution (non-formal tier: the first signal was code reading, not a model).
- The hunt unit was testing the failure atomicity of the tool's in-place rewrite (the sibling bug LFV-1022). One leg of its harness ran config-only natural inputs through the stock command line: an index with an earlier created major, a soft-deletes index, a parent-field index and a control. The earlier-major and soft-deletes indexes were refused; the control was reordered.
- It then wrote indexes with the real 9.12.3 and 10.5.2 jars and ran them through main's command line: both refused. It wrote the JUnit, later reduced to the 1-document test above. The parent-field refusal was not claimed (refusing it is reasonable, LFV-093).
No prior report found (searched 2026-10-29, re-checked 2026-10-30): GitHub issues, PRs and commits, the JIRA archive, the dev@, java-user@, issues@ and commits@ lists, downstream projects and the web. The nearest items introduced the tool, the guards or the remedy; open PR #15430 moves the class with this config unchanged.
Searches4 searches
Verdict new. reorderIndexDirectory has 0 hits anywhere; the two error messages lead only to the items that introduced the guards and to SOLR-12646, the same failure class in Solr's own shard-split code (2018), which is not a report of this bug. Tracking issue #16937 does not mention the tool.
| Where | When | How | Found |
|---|---|---|---|
| apache/lucene issues, PRs and commits incl. the migrated JIRA archive; downstream repositories; dev@, java-user@, issues@, commits@; the web | 2026-10-29 | three runs of a scripted prior-report search, 51 queries each (the tool's identifiers and both error messages; the bug class in addIndexes-based tools; the soft-deletes half) | context and term-overlap hits only; none about this tool |
| GitHub (apache/lucene and downstream search engines) | 2026-10-29 | 57 manual GraphQL searches plus REST searches; the changed files of all 415 open PRs (26 touch the files involved), all PRs created since 2026-10-26 and open PRs updated since 2026-10-27 | #14097 and #13683 (the tool), #13565 (its origin), #15430 (moves the class, same config), #16879 (other topic); no PR changes the config or the guards |
| Mailing lists, JIRA, web | 2026-10-29 | 24 list queries, 5 JQL queries, 7 web queries | LUCENE-7756, LUCENE-8335, LUCENE-8508 (the guards and the remedy), LUCENE-7671 and a 2017 java-user thread (cross-major upgrades hitting the guard), SOLR-12646 (same class, Solr code) |
| GitHub apache/lucene (issues, PRs, commits on the file) | 2026-10-30 | issue/PR search for BpVectorReorderer created after 2026-10-28 and for reorderIndexDirectory; commits on BpVectorReorderer.java on main and branch_11x | 0 new items; last commit on the file still 54283150a81 (2026-06-14); re-run later on 2026-10-30: still 0 new items, same last commit |
Introduced with the tool by #14097 (2025-01-07); 9.x indexes joined on 2026-10-15 (#16741). The test fails at that commit, at the next commit to the file, on main and on branch_11x; on main, now 12.0, 11.x indexes are refused too. No release has the tool.
Versions tested8 rows
Introduced by 02a09f6ab12 "HNSW BP reordering (#14097)" (Michael Sokolov, 2025-01-07), which added BpVectorReorderer with main and reorderIndexDirectory, already with this config; git blame gives it every line of the method. The guards (LUCENE-7756, 2017; LUCENE-8335, 2018) and the remedy setIndexCreatedVersionMajor (LUCENE-8508, 2018) all predate the tool, so nothing elsewhere regressed. 8476457b875 (#16741, 2026-10-15) lowered MIN_SUPPORTED_MAJOR to 9, so 9.x-created indexes became writable on main and fail the same way. e0fa3535ea5e (#16942, 2026-10-29) made main 12.0 after branch_11x was cut (2026-10-28), so main now also refuses 11.x-created indexes.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | this doc's test fails (gradle and the standalone runner, assertions on and off); the default-options command line refuses real 10.5.2 and 9.12.3 indexes; passes with the fix |
branch_11x 603af870375 (2026-10-29) | 11.0 branch | the test fails with gradle: "major version 10 ... major version 11"; current head d0c0decc6606 has the same files (read; raw files hashed 2026-10-30) |
main 716d6f216f5 (2026-10-29, 12.0.0-SNAPSHOT) | affected | the test fails with gradle: "major version 11 ... major version 12"; current head 2f424fd428da has the same files (read; raw files hashed 2026-10-30) |
b7c7fe064ce (#14117) | affected | the 10-document version fails with the same exception |
02a09f6ab12 (#14097) | introduced | the 10-document version fails with -ea and -da |
72d93de50c2 (parent) | no API | the repro does not compile: the class does not exist |
| 9.12.3, 10.1.0, 10.2.0, 10.5.2 | no API | the repro does not compile |
branch_10x, branch_10_6 (10.6.0), branch_10_5 | read | the file does not exist |
Confirmed by an independent reproducer, executing reviews from another model family (OpenAI GPT-5.6), a devil's advocate, an adjudication and an audit, each with their own run. This doc's test was re-run with gradle on main, branch_11x and the 12.0 main, and the fix with gradle on main.
Checks and verdicts346 words
Every executing check wrote or ran its own test on the stock jars, with a passing current-major control; the fix passes each of their tests.
| Check | Verdict | What it found |
|---|---|---|
| independent reproducer | real | Own JUnit fails with -ea and -da; its own soft-deletes test fails; its current-major control passes; an index written by the real 10.5.2 jar is refused. With the fix its 3 tests pass. |
| cross-model review, intended lens | real, high | An independent reviewer from another model family (OpenAI GPT-5.6), executing: reproduced both halves with its own suite and on an index it wrote with the real 10.5.2 jars; controls pass; the fix passes TestBpVectorReorderer 8 of 8. Works-as-designed case rated moderate and rejected. |
| cross-model review, refute lens | real, high | Same reviewer family, executing: its own suite fails exactly the earlier-major and soft-deletes cases over 3 iterations and with -da; all controls pass; the fix passes. The cross-model reviews ran on the hunt's other main checkout ea57360912ed, where the code is identical. |
| devil's advocate | real, high | Re-ran the repro (-ea, -da), ran its own natural trigger on a real 10.5.2 index (refused), built the strongest works-as-designed case and rejected it. |
| adjudication | confirmed, high | Re-ran the JUnit (-ea: 1 failed). The created-major half on a real 10.x index is the main case and soft deletes are secondary; the data loss that comes with the failure is a separate finding, LFV-1022. |
| no-noise gate and audit | claim (minor) | Minimal 1-document repro fails with -ea and -da and passes with the fix; real but minor: a loud failure of a little-used, unreleased misc tool. The audit re-ran it with a fresh seed: fails. |
| this doc | reproduces; fix validated | The test fails with gradle on 645d4fe14a20 (assertions on and off), on branch_11x 603af870375 and on main 716d6f216f5 (12.0), and passes with the fix together with TestBpVectorReorderer and all lucene/misc tests; the default-options command line refuses a real 10.5.2 index and, with the fix, reorders it keeping created major 10. |
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 571 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,419 words
- Registry title
- On unreleased main and branch_11x, the BpVectorReorderer command line cannot reorder an index created by Lucene 9.x or 10.x, or one with a soft-deletes field: it rewrites the index through a default IndexWriterConfig (created major 11 on branch_11x and 12 on main, no soft-deletes field), so the first addIndexes throws IllegalArgumentException
- Area
- misc / vector BP reordering (BpVectorReorderer command line: in-place rewrite through IndexWriter.addIndexes)
- Priority
- Low, rank 666: The BpVectorReorderer tool fails loudly with a clear IllegalArgumentException on indexes created by an earlier major version or with a soft-deletes field (the data loss that can come with it is the separate bug LFV-1022), in an unreleased, little-used command-line tool, so it is Low.
- Impact class
- minor
- Impact note
- Only users of the lucene/misc BpVectorReorderer command line (BpVectorReorderer.main) on unreleased main and branch_11x are hit, but on its normal input: every run on an index created by 9.x or 10.x, which main opens and writes (MIN_SUPPORTED_MAJOR = 9), even one written with default settings, and every run on an index with a soft-deletes field fails at the first addIndexes with IllegalArgumentException, with -ea and -da and no scale or timing condition, so the tool cannot reorder such an index at all; once 11.0 ships, every existing index was created before 11. Today the exception also wipes the index, which is counted in LFV-1022, not here. The no-noise gate (2026-10-29) reclassified it from crash to minor, consistent with the same bug class at other fix sites (LFV-618 MultiPassIndexSplitter, LFV-695 IndexRearranger, both minor): the tool fails loudly with a clear IllegalArgumentException and the data loss that accompanies it today is counted in LFV-1022.
- Summary
- You run BpVectorReorderer.main(path, field) on an index written by Lucene 10.x or 9.x, or on any index that has a soft-deletes field, and expect it to reorder the vectors in place: its javadoc takes 'a path containing an index to reorder', and on main indexes created with 9.x and 10.x 'remain fully supported (open and write)'. Instead reorderIndexDirectory opens a new IndexWriter with new IndexWriterConfig() and OpenMode.CREATE, which records created major 11 and no soft-deletes field, so the first addIndexes(SortingCodecReader.wrap(leaf, ...)) throws IllegalArgumentException 'Cannot merge a segment that has been created with major version 10 into this index which has been created by major version 11' (branch_11x; on main, 12.0.0-SNAPSHOT since #16942, the numbers are 11 and 12) (or 'soft-deletes field is not configured in IWC'). The javadoc of IndexWriterConfig.setIndexCreatedVersionMajor names exactly this addIndexes restriction and its remedy (use the source's created major), which the tool does not apply.
- Introduced
- 02a09f6ab124 (apache/lucene#14097 'HNSW BP reordering', by Michael Sokolov, GitHub msokolov; main only, milestone 11.0.0): it created BpVectorReorderer with main() and reorderIndexDirectory(), which already rewrote the index through new IndexWriterConfig() + OpenMode.CREATE; git blame of reorderIndexDirectory gives 02a09f6ab12 for every line [read]; the repro fails at 02a09f6ab12 (-ea and -da) and at the next functional change b7c7fe064ce (#14117), and its parent 72d93de50c2 has no BpVectorReorderer (compile error) [exec]. 8476457b875 (#16741, 'Lower min supported major version to 9 in main', 2026-10-15) widened the reach to 9.x-created indexes, which main could not open before [read]. Not a regression elsewhere: the created-major guard (LUCENE-7756, 2017), the soft-deletes guard (LUCENE-8335, 2018) and the documented remedy setIndexCreatedVersionMajor (LUCENE-8508, 2018) all predate the tool [read] (2025-01-07 (02a09f6ab12 author date = commit date; #14097 opened 2025-01-02); 9.x-created indexes affected since 2026-10-15 (#16741))
- First affected version
- unreleased (main 12.0.0-SNAPSHOT and branch_11x 11.0.0-SNAPSHOT: BpVectorReorderer.java blob f2b987b3a51 identical on both [read 2026-11-02]): no release contains BpVectorReorderer; the repro does not compile on 9.12.3, 10.1.0, 10.2.0 or 10.5.2 (class absent) [exec], and branch_10x and branch_10_6 (from which 10.6.0 was released on 2026-10-29) do not have the file [read]. Unless fixed first, 11.0.0 ships with it
- Affected versions
- main from 02a09f6ab12 (2025-01-07) through 645d4fe14a20, the upstream main head on 2026-10-29 [exec: the one-test repro fails at 02a09f6ab12 (-ea and -da), b7c7fe064ce and 645d4fe14a20 (-ea and -da) with the same IllegalArgumentException; on 645d4fe14a20 the stock command line also rejects indexes written by the real 10.5.2 and 9.12.3 jars with default settings, and a soft-deletes index]; the only later commits to BpVectorReorderer.java (b7c7fe064ce #14117, 54283150a81 #16255) do not touch reorderIndexDirectory [read]. Not affected (API absent): 9.12.3, 10.1.0, 10.2.0, 10.5.2 [exec: compile error], every 9.x and 10.x lucene-misc jar, branch_10x 657014b3c579, branch_10_6 and branch_10_5 [read: jar listing, git ls-tree, GitHub contents API]
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- non-formal: code reading
- Upstream and prior reports
- none found (2026-10-29, read-only, the prior-report record verdict new; re-checked): three scripted prior-report searches of 51 queries each (the tool's identifiers and both error messages; the bug class in addIndexes-based offline tools; the soft-deletes half) over apache/lucene issues, PRs and commits incl. the migrated JIRA archive, downstream repositories, the dev@, java-user@, issues@ and commits@ lists, JIRA and the web; 57 manual GitHub GraphQL searches plus REST searches ('reorderIndexDirectory' 0 hits; BpVectorReorderer with major version or soft deletes 0; every item naming BpVectorReorderer read); 24 list and 5 JQL queries; 7 web queries; the changed files of all 415 open apache/lucene PRs (26 touch the files involved; none changes the tool's config or the guards), all PRs created since 2026-10-26 and open PRs updated since 2026-10-27. Related, not reports: #14097 (introduced the tool) and #13683 (its unmerged first version; listed limitations do not include index version or soft deletes), #15430 (open, moves the class to core with reorderIndexDirectory unchanged), #9554 / LUCENE-8508 (added setIndexCreatedVersionMajor for this addIndexes case), #8807 / LUCENE-7756 and #9382 / LUCENE-8335 (the guards), #8722 / LUCENE-7671 and a 2017 java-user thread (cross-major upgrade through addIndexes, the guard's intended use), SOLR-12646 (Solr shard split, the same failure class in Solr's own code, 2018). Not fixed upstream: the last commit on BpVectorReorderer.java is still 54283150a81 (2026-06-14, unrelated)
- Lucene commit tested
- main
645d4fe14a20(2026-10-28, 11.0.0-SNAPSHOT) with gradle in fresh scratch worktrees (the test fails with assertions on and off; with the fix the test andTestBpVectorReordererpass with assertions on and off, and alllucene/misctests pass) and with the standalone JUnit runner;branch_11x603af870375(11.0) and main716d6f216f5(12.0.0-SNAPSHOT) with gradle (the test fails on both); the stock command line with default options on an index written by the real lucene-core 10.5.2 jar, stock and with the fix; the hunt's runs on indexes written by the real 9.12.3 and 10.5.2 jars and on a soft-deletes index; the introducing commit02a09f6ab12, the next commit to the fileb7c7fe064ce, and (compile error, the tool does not exist) its parent72d93de50c2and the releases 9.12.3, 10.1.0, 10.2.0 and 10.5.2. Re-checked 2026-10-30 by reading, and again by hashing the raw files at both heads: the current heads main2f424fd428daandbranch_11xd0c0decc6606have the sameBpVectorReorderer.java,IndexWriter.javaandIndexWriterConfig.javaas645d4fe14a20.
- Finding record
lucene/misc/src/java/org/apache/lucene/misc/index/BpVectorReorderer.java :648-651 (main javadoc 'a path containing an index to reorder'), :679-680 (FSDirectory.open, reorderIndexDirectory), :684-697 (new IndexWriterConfig(); OpenMode.CREATE; one addIndexes(SortingCodecReader.wrap(...)) per leaf)lucene/core/src/java/org/apache/lucene/index/IndexWriter.java :3228-3236 (validateMergeReader message); IndexWriterConfig.java :174-180 (setIndexCreatedVersionMajor javadoc: addIndexes only accepts the same major; set the previous major for compatibility); FieldInfos.java :459-465 (verifySoftDeletedFieldName); util/Version.java :308 (MIN_SUPPORTED_MAJOR = 9); lucene/MIGRATE.md :142-145 (9.x indexes 'remain fully supported (open and write)')
- The finding in 30 seconds
lucene/misc/src/java/org/apache/lucene/misc/index/BpVectorReorderer.java:648-651, :653-682, :684-697lucene/core/src/java/org/apache/lucene/index/IndexWriter.java:1009 (CREATE: created major from the config), :3228-3236 (validateMergeReader), :3290 (validateMergeReader called by addIndexes), :3292 (verifyFieldInfo)lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java:174-184 (setIndexCreatedVersionMajor javadoc, default LATEST.major)lucene/core/src/java/org/apache/lucene/index/SortingCodecReader.java:454-457; FieldInfos.java:401-403, :459-465
- Who is affected
lucene/MIGRATE.md:143 (9.x indexes fully supported, open and write), :165 (creation version never changes)lucene/core/src/java/org/apache/lucene/util/Version.java:308 (MIN_SUPPORTED_MAJOR = 9)
- Why it looks unintended
lucene/misc/src/java/org/apache/lucene/misc/index/BpVectorReorderer.java:50-55 (class javadoc, no experimental marker), :648-651 (main javadoc)lucene/MIGRATE.md:143, :165; lucene/CHANGES.txt:10-11 (GITHUB#16741), :164 (GITHUB#14097)lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java:174-178lucene/misc/src/java/org/apache/lucene/misc/index/IndexSplitter.java:112; lucene/core/src/java/org/apache/lucene/index/IndexUpgrader.java:138, :183lucene/misc/src/test/org/apache/lucene/misc/index/TestBpVectorReorderer.java:353-354, :412-413lucene/backward-codecs/src/test/org/apache/lucene/backward_index/TestBasicBackwardsCompatibility.java:822-837
- Suggested fix
lucene/core/src/java/org/apache/lucene/index/IndexWriterConfig.java:181-184 (lower creation version: unopenable sooner)
- History
a Lucene clone git objects: 603af870375:lucene/core/src/java/org/apache/lucene/util/Version.java:300 (MIN_SUPPORTED_MAJOR = 9), 603af870375:lucene/MIGRATE.md:157
- TestBpVectorReorderEarlierMajorIndex.java (lucene/misc, one test):
repro/LFV-1023/TestBpVectorReorderEarlierMajorIndex.java(49 lines)
A picture of the run: the index and its created major, what reorderIndexDirectory does, and the outcome with and without the fix. See the diagram notes for what each frame shows and where each value comes from.
assets/LFV-1023/bug.png(571 KB)
