Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recovery infrastructure hook to work with older Lucene indices #81056

Merged
merged 30 commits into from
Dec 2, 2021

Conversation

ywelsch
Copy link
Contributor

@ywelsch ywelsch commented Nov 25, 2021

This is a simple infrastructure PR for more interesting follow-ups.

We add a simple hook to the snapshot recovery infrastructure that allows making changes to the incoming Lucene files before they are processed the core system. This allows a plugin to make changes to the incoming Lucene files before they are exposed to the rest of the system, e.g. upgrade old Lucene version files to a newer version.

We also add a setting for snapshot repositories to allow restoring older indices (only available in snapshot builds).

@ywelsch ywelsch added >non-issue :Distributed Coordination/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs v8.1.0 labels Nov 25, 2021
@elasticmachine elasticmachine added the Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. label Nov 25, 2021
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-distributed (Team:Distributed)

@@ -259,4 +259,7 @@ tasks.named('splitPackagesAudit').configure {
// These are tricky because Lucene itself splits the index package,
// but this should be fixed in Lucene 9
ignoreClasses 'org.apache.lucene.index.LazySoftDeletesDirectoryReaderWrapper'

// only used temporarily, will be removed in a follow-up
ignoreClasses 'org.apache.lucene.index.SegmentInfosHelper'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SegmentInfosHelper class is used by this PR to load ES 6 (= Lucene 7) indices, which allows us to test that the recovery infrastructure as added by the PR works as intended. The class here will be removed in a follow-up step that will take a different, but more involved approach to load older indices (it's out of focus for this PR).

@@ -1826,6 +1827,24 @@ public static IndexMetadata legacyFromXContent(XContentParser parser) throws IOE
} else if ("mappings".equals(currentFieldName)) {
// don't try to parse these for now
parser.skipChildren();
} else if ("in_sync_allocations".equals(currentFieldName)) {
Copy link
Contributor Author

@ywelsch ywelsch Nov 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just copy paste from the regular IndexMetadata parsing code. in_sync_allocations are required to exist in IndexMetadata for a restore to go through.

@@ -75,7 +74,11 @@ public void restore(SnapshotFiles snapshotFiles, Store store, ActionListener<Voi
// this will throw an IOException if the store has no segments infos file. The
// store can still have existing files but they will be deleted just before being
// restored.
recoveryTargetMetadata = store.getMetadata(null, true);
if (isSearchableSnapshotStore(store.indexSettings().getSettings())) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We skip some of these steps here for searchable snapshots (as they are effectively useless, and require opening up the index, which we can defer to a later stage by special-casing this here).

private void afterRestore(SnapshotFiles snapshotFiles, Store store, StoreFileMetadata restoredSegmentsFile) {
try {
if (isSearchableSnapshotStore(store.indexSettings().getSettings()) == false) {
Lucene.pruneUnreferencedFiles(restoredSegmentsFile.name(), store.directory());
Copy link
Contributor Author

@ywelsch ywelsch Nov 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code does not look to do anything, and is actually in the way as it opens the Lucene index before it's converted. No tests are breaking with this code, and it's unclear whether it still serves a purpose (even after some historic digging)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it looks like we clean up according to the snapshot contents anyway - this second cleanup was added later on in #8969 but there's no indication why it was needed. I think you're right anyway, no need to open the index here.

* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
* 2.0; you may not use this file except in compliance with the Elastic License
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code got moved from qa to x-pack:qa

@ywelsch ywelsch requested review from tlrx and DaveCTurner November 29, 2021 12:42
Copy link
Contributor

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly looked at the changes to snapshots-of-searchable-snapshots code, and I like this change. It's so much easier now that we can write searchable-snapshot-specific code in :server.

I left a couple of other comments too.

*/
@SuppressWarnings("CheckStyle")
@SuppressForbidden(reason = "Lucene class")
public class OldSegmentInfos implements Cloneable, Iterable<SegmentCommitInfo> {
Copy link
Contributor Author

@ywelsch ywelsch Nov 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is just temporarily here until we have proper shaded versions of Lucene. It just helps embedding ES 6 indices in order to let the tests pass.

No need for a review on this one.


@Override
public void onIndexModule(IndexModule indexModule) {
indexModule.addIndexEventListener(new IndexEventListener() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that we can be more restrictive here and only register the IndexEventListener once for snapshot builds with ALLOW_BWC_INDICES_SETTING setting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only ran the listener for snapshot builds. I've now moved the isSnapshotBuild check in 3357ec3 to the registration of the listener. At this point, we don't know anything about the repository, so making it conditional on the ALLOW_BWC_INDICES_SETTING won't work. As this conditional logic will go away (and be replaced by license checks etc. in follow-ups) I think we're good here.

@@ -1206,7 +1231,9 @@ public ClusterState execute(ClusterState currentState) {

final ImmutableOpenMap.Builder<ShardId, ShardRestoreStatus> shardsBuilder = ImmutableOpenMap.builder();

final Version minIndexCompatibilityVersion = currentState.getNodes().getMaxNodeVersion().minimumIndexCompatibilityVersion();
final Version minIndexCompatibilityVersion = skipVersionChecks(repositoryMetadata)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need some special handling in allocation deciders to only restore old Lucene indices on nodes >= 8.1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is not addressing allocation-related bits, focusing on recovery bits only. There are quite a few more things to do for these indices to be properly handled at the allocation level. For example, restarting a node with a BWC index will currently fail (will detect old index and refuse to start up). This will be addressed in follow-ups.

// so far only added basic infrastructure for reading 6.0.0+ indices
if (Build.CURRENT.isSnapshot() && oldVersion.onOrAfter(Version.fromString("6.0.0"))) {
// restore index
RestoreSnapshotResponse restoreSnapshotResponse = client.snapshot()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also test the restore and mount of old Lucene indices over closed indices too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've extended the test to cover this.

@ywelsch ywelsch requested a review from tlrx November 30, 2021 11:41
@ywelsch ywelsch mentioned this pull request Dec 1, 2021
32 tasks
Copy link
Member

@tlrx tlrx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - I only made minor comments that you can feel free to address or not.

I did not review the pure Lucene parts, I'd expect someone more knowledgeable to review it.

import java.util.Iterator;
import java.util.List;

public class BWCLucene70Codec extends Codec {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some documentation about what this class does?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Lucene bits are temporary and will be completely reworked in a follow-up. Will address it there.

final String segmentFileName = SegmentInfos.getLastCommitSegmentsFileName(directory);
if (segmentFileName != null) {
long generation = SegmentInfos.generationFromSegmentsFileName(segmentFileName);
try (ChecksumIndexInput input = directory.openChecksumInput(segmentFileName, IOContext.READ)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why a ChecksumIndexInput is used here? It seems that a regular IndexInput can be used, or maybe there's something I miss.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is going away in a follow-up.

CodecUtil.checkIndexHeaderSuffix(input, Long.toString(generation, Character.MAX_RADIX));

Version luceneVersion = Version.fromBits(input.readVInt(), input.readVInt(), input.readVInt());
int indexCreatedVersion = input.readVInt();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

going away in a follow-up.

int indexCreatedVersion = input.readVInt();
return luceneVersion;
} catch (Exception e) {
// ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we fail the shard if we cannot read the Lucene version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in follow-up

@@ -30,6 +31,7 @@
public class InMemoryNoOpCommitDirectory extends FilterDirectory {

private final Directory realDirectory;
private final Set<String> deletedFiles = new HashSet<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a concurrent set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++ good point

@@ -130,10 +162,98 @@ public void testOldRepoAccess() throws IOException {
assertEquals(0, snapshotStatus.getShardsStats().getFailedShards());
assertThat(snapshotStatus.getStats().getTotalSize(), greaterThan(0L));
assertThat(snapshotStatus.getStats().getTotalFileCount(), greaterThan(0));

// so far only added basic infrastructure for reading 6.0.0+ indices
if (Build.CURRENT.isSnapshot() && oldVersion.onOrAfter(Version.fromString("6.0.0"))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point we could reintroduce the Version constants for old Elasticsearch versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic will completely change in the follow-up.

// mount as searchable snapshot
RestoreSnapshotResponse mountSnapshotResponse = client.searchableSnapshots()
.mountSnapshot(
new MountSnapshotRequest("testrepo", "snap1", "test").storage(MountSnapshotRequest.Storage.FULL_COPY)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we randomize this between full/partial?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed another small tweak. I have this already in the follow-up.

@ywelsch ywelsch merged commit 1d3f684 into elastic:master Dec 2, 2021
@ywelsch
Copy link
Contributor Author

ywelsch commented Dec 2, 2021

Thanks @tlrx and @DaveCTurner !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Distributed Coordination/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs >non-issue Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. v8.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants