Hello,
At Community Over Code in Minneapolis, Rahul Goswami gave a presentation
on upgrading a Solr/Lucene index in-place with a clever blocking index
merge with a background process that would, in tandem, cause all index
segments on-disk to be migrated from the previous Lucene version (e.g.
7.x) to the current version (e.g. 8.x).
There seemed to be consensus that this alone would be a great addition
to Solr even if the next step was not (yet?) possible. This is
represented by https://issues.apache.org/jira/browse/SOLR-17725
The final step required in order to push an on-disk index from Lucene
X-1 to Lucene X is to re-write the indexCreatedVersionMajor field in the
segment info structure.
A short conversation during the session indicated that a command-line
tool might be helpful for intrepid users who were willing to force a
change to the index created version major.
I've written (with some help from ChatGPT) a short CLI utility to check
the segments and segment info and optionally force a change to the major
version. It should only work if the requested version is *higher* than
the current version AND if all the segments are already compatible. You
also have to use a command-line argument to enable WRITE mode.
So, you can run it on an index and get info, and verify beforehand that
it SHOULD work before asking it to actually write to the segment info
and "forge" the new indexCreatedVersionMajor field.
The code is below. Please let me know if you all think this would be
something worth exploring, expanding, and possibly maintaining.
-chris
import org.apache.lucene.index.*;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
public class LuceneIndexUpgrader {
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java LuceneIndexUpgrader
<path-to-index> [newMajorVersion] [--write]");
System.exit(1);
}
Path indexPath = null;
Integer newMajorVersion = null;
boolean shouldWrite = false;
// Parse args
for (String arg : args) {
if (arg.equals("--write")) {
shouldWrite = true;
} else if (arg.matches("\\d+")) {
newMajorVersion = Integer.parseInt(arg);
} else if (indexPath == null) {
indexPath = Paths.get(arg);
} else {
System.err.println("Unrecognized argument: " + arg);
System.exit(1);
}
}
if (indexPath == null) {
System.err.println("Error: Index path is required.");
System.exit(1);
}
if (newMajorVersion != null && (newMajorVersion < 1 ||
newMajorVersion > 99)) {
System.err.println("Invalid Lucene major version: " +
newMajorVersion);
System.exit(1);
}
try (FSDirectory directory = FSDirectory.open(indexPath)) {
SegmentInfos segmentInfos =
SegmentInfos.readLatestCommit(directory);
int currentMajorVersion =
segmentInfos.getIndexCreatedVersionMajor();
// Print index-level version info
System.out.println("Lucene Index Metadata:");
System.out.println("--------------------------------------------------");
System.out.println("Index Created Version Major: " +
currentMajorVersion);
System.out.println("Min Segment Lucene Version: " +
segmentInfos.getMinSegmentLuceneVersion());
System.out.println("\nSegment Details:");
boolean compatible = true;
for (SegmentCommitInfo sci : segmentInfos) {
SegmentInfo si = sci.info;
Version version = si.getVersion();
Version minVersion = si.getMinVersion();
System.out.println("--------------------------------------------------");
System.out.println("Segment name: " + si.name);
System.out.println(" Lucene version: " + version);
System.out.println(" Min Lucene ver: " + minVersion);
if (newMajorVersion != null) {
int actualVersion = minVersion != null ?
minVersion.major :
(version != null ? version.major : -1);
if (actualVersion == -1 || actualVersion <
newMajorVersion) {
System.err.printf(" Incompatible: segment
minVersion %d < requested indexCreatedVersionMajor %d%n",
actualVersion, newMajorVersion);
compatible = false;
}
}
}
if (newMajorVersion != null) {
System.out.printf("\nRequested new
indexCreatedVersionMajor: %d%n", newMajorVersion);
// Check that new version is not less than current
if (newMajorVersion < currentMajorVersion) {
System.err.printf("Aborting: new major version %d
is less than current index version %d%n",
newMajorVersion, currentMajorVersion);
System.exit(1);
}
if (!compatible) {
System.err.println("Aborting: Not all segments are
compatible with the requested version.");
System.exit(1);
} else if (shouldWrite) {
// Proceed with update
Field field =
SegmentInfos.class.getDeclaredField("indexCreatedVersionMajor");
field.setAccessible(true);
Field modifiersField =
Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() &
~java.lang.reflect.Modifier.FINAL);
field.setInt(segmentInfos, newMajorVersion);
segmentInfos.commit(directory);
System.out.printf("Updated indexCreatedVersionMajor
to %d and saved to disk.%n", newMajorVersion);
} else {
System.out.println("Dry run:
indexCreatedVersionMajor would be updated, but --write was not set.");
}
} else {
System.out.println("\n(No version change requested.)");
}
} catch (IOException | NoSuchFieldException |
IllegalAccessException e) {
System.err.println("Error while processing Lucene index: "
+ e.getMessage());
e.printStackTrace();
}
}
}