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

[Remote Store] Add support for refresh level durability #5253

Merged

Conversation

sachinpkale
Copy link
Member

@sachinpkale sachinpkale commented Nov 15, 2022

Description

  • As part of remote store feature that was introduced in OpenSearch 2.3, segments are uploaded to remote store post each refresh.
  • This means remote segment store contains segments that were part of last refresh.
  • But the _remotestore/_restore API only supports commit level durability.
  • This feature will add refresh level durability support to the restore API.

Proposed Solution

  • We upload SegmentInfosSnapshot during each refresh and keep the information of the uploaded file in the metadata file.
  • During restore, if SegmentInfosSnapshot is present, we use it as a SegmentInfos file and trigger a commit.
  • As the snapshot file does not contain latest processed checkpoint (as it is a snapshot of previous SegmentInfos file), we pass processed checkpoint as part of filename.

Issues Resolved

Check List

  • New functionality includes testing.
    • All tests pass
  • New functionality has been documented.
    • New functionality has javadoc added
  • Commits are signed per the DCO using --signoff
  • Commit changes are listed out in CHANGELOG.md file (See: Changelog)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

Comment on lines 140 to 158
try {
if (segment_info_snapshot_filename != null) {
storeDirectory.deleteFile(segment_info_snapshot_filename);
}
} catch (IOException e) {
logger.warn("Exception while deleting: " + segment_info_snapshot_filename, e);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

\why are we deleting segment_info_snapshot_filename ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Currently, RemoteDirectory does not support writing to file in remote store using buffer (we have a plan to support it). So, for any file to upload to remote store, today, it is required to be in the local directory (Referred as storeDirectory in this code).
There are 2 reasons to delete:

  • local directory does not support overriding a file with the same name. This is mostly done to keep the immutable nature of segment files.
  • As this file is of no use locally, we are deleting it once if it is uploaded. Even if the upload fails, next refresh can create a new snapshot file.

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

  • RESULT: null ❌
  • FAILURES:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

  • RESULT: null ❌
  • FAILURES:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@codecov-commenter
Copy link

codecov-commenter commented Nov 16, 2022

Codecov Report

Merging #5253 (9562461) into main (cb26035) will increase coverage by 0.04%.
The diff coverage is 91.07%.

@@             Coverage Diff              @@
##               main    #5253      +/-   ##
============================================
+ Coverage     70.95%   71.00%   +0.04%     
- Complexity    58304    58326      +22     
============================================
  Files          4733     4733              
  Lines        278256   278294      +38     
  Branches      40249    40254       +5     
============================================
+ Hits         197441   197594     +153     
+ Misses        64628    64553      -75     
+ Partials      16187    16147      -40     
Impacted Files Coverage Δ
...va/org/opensearch/index/engine/InternalEngine.java 74.68% <ø> (-1.03%) ⬇️
...search/index/shard/RemoteStoreRefreshListener.java 79.24% <88.23%> (+0.93%) ⬆️
...java/org/opensearch/index/shard/StoreRecovery.java 68.04% <90.90%> (+0.15%) ⬆️
...earch/index/store/RemoteSegmentStoreDirectory.java 99.30% <100.00%> (+0.01%) ⬆️
...a/org/opensearch/client/cluster/ProxyModeInfo.java 0.00% <0.00%> (-55.00%) ⬇️
.../java/org/opensearch/node/NodeClosedException.java 50.00% <0.00%> (-50.00%) ⬇️
.../index/shard/IndexShardNotRecoveringException.java 0.00% <0.00%> (-50.00%) ⬇️
...ain/java/org/opensearch/search/sort/MinAndMax.java 63.15% <0.00%> (-36.85%) ⬇️
...readonly/TransportVerifyShardIndexBlockAction.java 51.21% <0.00%> (-31.71%) ⬇️
...opensearch/snapshots/SnapshotMissingException.java 28.57% <0.00%> (-28.58%) ⬇️
... and 480 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@sachinpkale sachinpkale marked this pull request as ready for review November 17, 2022 03:56
@sachinpkale sachinpkale requested review from a team and reta as code owners November 17, 2022 03:56
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

  • RESULT: null ❌
  • FAILURES:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

  • RESULT: null ❌
  • FAILURES:

@dblock
Copy link
Member

dblock commented Nov 17, 2022

Rebase pls, the above should be resolved.

@sachinpkale sachinpkale force-pushed the feature/refresh-level-durability branch from 37be057 to 70a03a7 Compare November 18, 2022 03:41
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@@ -44,6 +47,7 @@ public final class RemoteStoreRefreshListener implements ReferenceManager.Refres
static final Set<String> EXCLUDE_FILES = Set.of("write.lock");
// Visible for testing
static final int LAST_N_METADATA_FILES_TO_KEEP = 10;
public static final String SEGMENT_INFO_SNAPSHOT_FILENAME = "segment_infos_snapshot_filename";
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit : should we rename to SEGMENT_INFO_SNAPSHOT_FILENAME_PREFIX ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

and please make remove public

for (String file : remoteDirectory.listAll()) {
storeDirectory.copyFrom(remoteDirectory, file, file, IOContext.DEFAULT);
if (file.startsWith(SEGMENT_INFO_SNAPSHOT_FILENAME)) {
segmentInfosSnapshotFilename = file;
Copy link
Collaborator

Choose a reason for hiding this comment

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

would we need the latest file here. The list wouldn't guarantee sorted in the order we need, right ?

Copy link
Member Author

Choose a reason for hiding this comment

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

When we create instance of RemoteSegmentStoreDirectory, we initialize it by reading latest metadata file from the remote store. Each metadata file holds reference to only one SegmentInfosSnapshot file.

@@ -317,6 +317,33 @@ public void testCopyFromException() throws IOException {
storeDirectory.close();
}

public void testCpoyFromOverride() throws IOException {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit : spelling of copy.

Comment on lines 169 to 171
IndexOutput indexOutput = storeDirectory.createOutput(segment_info_snapshot_filename, IOContext.DEFAULT);
segmentInfosSnapshot.write(indexOutput);
indexOutput.close();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we do try with resource here and ensure we close all streams

Comment on lines 489 to 492
store.commitSegmentInfos(infos_snapshot, Long.parseLong(filenameTokens[2]), Long.parseLong(filenameTokens[2]));
} catch (IOException e) {
logger.info("Exception while reading {}, falling back to commit level restore", segmentInfosSnapshotFilename);
}
}
Copy link
Collaborator

@Bukhtawar Bukhtawar Nov 22, 2022

Choose a reason for hiding this comment

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

Seems that the handling isn't correct, f.e if we support refresh level durability but are unable to guarantee it should be not be a silent(info logging) failure. We should instead throw this exception and let the shard decide on the recovery completion

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also lets add assertions on checkpoints and max seq no

Copy link
Member Author

Choose a reason for hiding this comment

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

@Bukhtawar I will add the required assertions but wanted to check on the first comment.

In the first comment, are you suggesting to fail the restore if refresh level durability is not possible? In that case, we need to provide a way to restore from the last commit if user wants.
Would having a query parameter to the restore API work?

deleteStaleCommits();
}
String segment_info_snapshot_filename = null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please follow the naming conventions

@@ -106,13 +113,19 @@ public void afterRefresh(boolean didRefresh) {
.max(Comparator.comparingLong(IndexFileNames::parseGeneration));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we use:

final String latestSegmentInfos = segmentInfos.getSegmentsFileName();

Instead of filtering the collection?

Copy link
Member Author

Choose a reason for hiding this comment

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

segmentInfosFiles can contain multiple segments_N files. We are making sure to get list of all such files and pick only the latest one by generation.

refreshedLocalFiles.addAll(SegmentInfos.readCommit(storeDirectory, latestSegmentInfos.get()).files(true));
segmentInfosFiles.stream()
.filter(file -> !file.equals(latestSegmentInfos.get()))
.forEach(refreshedLocalFiles::remove);

boolean uploadStatus = uploadNewSegments(refreshedLocalFiles);
if (uploadStatus) {
if (segmentFilesFromSnapshot.equals(new HashSet<>(refreshedLocalFiles))) {
segment_info_snapshot_filename = uploadSegmentInfosSnapshot(latestSegmentInfos.get(), segmentInfos);
refreshedLocalFiles.add(segment_info_snapshot_filename);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The uploadSegmentInfosSnapshot seems to be uploading the file to remoteDirectory, why do we need to add it to refreshedLocalFiles (and apparently re-upload again with remoteDirectory.uploadMetadata)?

Copy link
Member Author

Choose a reason for hiding this comment

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

remoteDirectory.uploadMetadata() uploads only the metadata file which keeps the mapping of files uploaded to remote store.

Filename is added to refreshedLocalFiles so that metadata file knows about snapshot file uploaded to remote store.

String uploadSegmentInfosSnapshot(String latestSegmentsNFilename, SegmentInfos segmentInfosSnapshot) throws IOException {
long localCheckpoint = indexShard.getEngine().getProcessedLocalCheckpoint();
String commitGeneration = latestSegmentsNFilename.substring((IndexFileNames.SEGMENTS + "_").length());
String segment_info_snapshot_filename = SEGMENT_INFO_SNAPSHOT_FILENAME + "__" + commitGeneration + "__" + localCheckpoint;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please follow the naming conventions

@@ -141,6 +162,18 @@ public void afterRefresh(boolean didRefresh) {
}
}

String uploadSegmentInfosSnapshot(String latestSegmentsNFilename, SegmentInfos segmentInfosSnapshot) throws IOException {
long localCheckpoint = indexShard.getEngine().getProcessedLocalCheckpoint();
String commitGeneration = latestSegmentsNFilename.substring((IndexFileNames.SEGMENTS + "_").length());
Copy link
Collaborator

Choose a reason for hiding this comment

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

IndexFileNames.parseGeneration(latestSegmentsNFilename)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Used SegmentInfos.generationFromSegmentsFileName instead. IndexFileNames.parseGeneration only works for segment files where name starts with _

@sachinpkale sachinpkale force-pushed the feature/refresh-level-durability branch from 70a03a7 to 58a125f Compare November 29, 2022 05:58
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

  • RESULT: UNSTABLE ❕
  • TEST FAILURES:
      1 org.opensearch.action.admin.indices.create.ShrinkIndexIT.testShrinkIndexPrimaryTerm
      1 org.opensearch.action.admin.indices.create.ShrinkIndexIT.testCreateShrinkIndexToN
      1 org.opensearch.action.admin.indices.create.ShrinkIndexIT.testCreateShrinkIndexFails
      1 org.opensearch.action.admin.indices.create.ShrinkIndexIT.testCreateShrinkIndex

@sachinpkale sachinpkale requested review from reta and gbbafna and removed request for gbbafna November 30, 2022 05:26
Sachin Kale added 2 commits December 15, 2022 12:58
Signed-off-by: Sachin Kale <kalsac@amazon.com>
Signed-off-by: Sachin Kale <kalsac@amazon.com>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@sachinpkale
Copy link
Member Author

Can we write Integ tests for this ?

Yes @gbbafna . Currently, we don't have any integ tests written for remote store feature. Will add.

Copy link
Collaborator

@Bukhtawar Bukhtawar left a comment

Choose a reason for hiding this comment

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

Thanks LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants