HDDS-15766. Make Recon OM DB large tarball transfer reliable#10696
HDDS-15766. Make Recon OM DB large tarball transfer reliable#10696devmadhuu wants to merge 9 commits into
Conversation
- TestReconRDBSnapshotProvider: guard File.list() results with requireNonNull. - OzoneManagerServiceProviderImpl: make omSnapshotDBParentDir a local variable.
jojochuang
left a comment
There was a problem hiding this comment.
It would be nice to respect configration property ozone.om.db.checkpoint.use.inode.based.transfer and switch to the old endpoint if it's false.
priyeshkaratha
left a comment
There was a problem hiding this comment.
Thanks @devmadhuu for working on this. Changes overall LGTM. Please check some minor inline comments provided.
| <groupId>org.apache.hadoop</groupId> | ||
| <artifactId>hadoop-hdfs-client</artifactId> | ||
| </dependency> | ||
| <dependency> |
There was a problem hiding this comment.
Can avoid adding this dependency since it is used only to use URIBuilder in buildCheckpointUrl?
| targetFile, unTarredDb.toAbsolutePath()); | ||
| if (ratisSnapshotComplete(unTarredDb)) { | ||
| LOG.info("Ratis snapshot transfer is complete."); | ||
| LOG.info("DB snapshot transfer is complete."); |
There was a problem hiding this comment.
These log changes will also impact the OM follower logs. Do we really need this change? The existing check for ratisSnapshotComplete already aligns with the info logs.
There was a problem hiding this comment.
Downloading active OM DB by OM followers should not be tied up with this terminology "RATIS". It is just full active OM DB download from leader. Since it was originally written based in install snapshot flow, but this API can be used y any http client. So updating log makes sense.
sadanand48
left a comment
There was a problem hiding this comment.
Thanks @devmadhuu for the patch, left few minor comments inline. Like @jojochuang said lets make the behaviour switchable i.e retain both v1 and v2 code
| // Before fetching full snapshot again and create a new OM DB snapshot directory, check and delete | ||
| // any existing OM DB snapshot directories under recon om db dir location and delete all such | ||
| // om db snapshot dirs including the last known om db snapshot dir returned by reconUtils.getLastKnownDB | ||
| File lastKnownDB = reconUtils.getLastKnownDB(omSnapshotDBParentDir, RECON_OM_SNAPSHOT_DB); |
There was a problem hiding this comment.
We can still keep these 2 code blocks i.e deleting lastKnownDB and leftover staging dirs as it can happen b/w switching v1 and v2 , there leftover artifacts from v1. I think keeping this should be safe
| ReconContext.ErrorCode.GET_OM_DB_SNAPSHOT_FAILED); | ||
| // Reset the candidate dir so the next attempt starts from a clean state. | ||
| try { | ||
| reconSnapshotProvider.init(); |
There was a problem hiding this comment.
We don't support chunking/batched transfers from active om.db today so this should not be a problem but in case we did this would be a problem as failure in a single batch would delete the whole candidate dir. Just want to call it out.
There was a problem hiding this comment.
Good catch, this was left behind to remove when moved from chunk transfer impl to single tar ball transfer same as OM follower. I have fixed it.
Thanks @jojochuang for catching that. Fixed it in latest push. |
priyeshkaratha
left a comment
There was a problem hiding this comment.
Thanks @devmadhuu for improving the patch. Changes LGTM
sadanand48
left a comment
There was a problem hiding this comment.
Thanks @devmadhuu for updating the patch . Overall changes LGTM
| ServiceInfo leader = leaderInfoSupplier.get(); | ||
| URL checkpointUrl = buildCheckpointUrl(leader); | ||
| LOG.info("Downloading OM DB checkpoint from leader {}. Checkpoint: {}, " | ||
| + "URL: {}", leaderNodeID, targetFile.getName(), checkpointUrl); |
There was a problem hiding this comment.
Question regarding leader transition corner case @devmadhuu --
this method is called multiple times during a checkpoint, each time it returns partial checkpoint.
The leader may change at different time, and it may attempt to download partial checkpoint from another OM, will that be a problem?
|
Sample repro test for the Bugbot finding about leader change mid chunked transfer ( Pushed to my fork: jojochuang@fa988a0 The test simulates a two-chunk transfer where the supplier returns Run locally: mvn -pl :ozone-recon test \
-Dtest=TestReconRDBSnapshotProvider#testLeaderChangeMidTransferUsesNewLeaderWithoutResettingCandidate \
-DskipShade -DskipRecon -DskipDocsCore test code ( @Test
public void testLeaderChangeMidTransferUsesNewLeaderWithoutResettingCandidate(
@TempDir File snapshotDir) throws IOException {
ServiceInfo leaderA = mock(ServiceInfo.class);
when(leaderA.getHostname()).thenReturn("om-leader-a");
when(leaderA.getPort(any())).thenReturn(9874);
ServiceInfo leaderB = mock(ServiceInfo.class);
when(leaderB.getHostname()).thenReturn("om-leader-b");
when(leaderB.getPort(any())).thenReturn(9874);
AtomicInteger supplierCalls = new AtomicInteger();
Supplier<ServiceInfo> leaderSupplier = () ->
supplierCalls.getAndIncrement() == 0 ? leaderA : leaderB;
List<String> leadersUsedPerChunk = new ArrayList<>();
ReconRDBSnapshotProvider provider =
new ReconRDBSnapshotProvider(snapshotDir, null, false,
HttpConfig.Policy.HTTP_ONLY, false, true, leaderSupplier) {
@Override
public void downloadSnapshot(String leaderNodeID, File targetFile)
throws IOException {
ServiceInfo leader = leaderSupplier.get();
leadersUsedPerChunk.add(leader.getHostname());
if (leadersUsedPerChunk.size() == 1) {
Map<String, String> chunkOne = new HashMap<>();
chunkOne.put("fromLeaderA.sst", "partial-a");
chunkOne.put("CURRENT", "MANIFEST");
writeTar(targetFile, chunkOne);
} else {
Map<String, String> chunkTwo = new HashMap<>();
chunkTwo.put(HddsServerUtil.OZONE_RATIS_SNAPSHOT_COMPLETE_FLAG_NAME, "");
writeTar(targetFile, chunkTwo);
}
}
};
long initCountBefore = provider.getInitCount();
provider.downloadDBSnapshotFromLeader("leader-a-node-id");
boolean pinnedLeader = leadersUsedPerChunk.stream()
.allMatch("om-leader-a"::equals);
boolean resetCandidate = provider.getInitCount() > initCountBefore;
assertTrue(pinnedLeader || resetCandidate,
"When the resolved OM leader changes mid-transfer, Recon must either "
+ "pin the original leader for all chunks or reset the candidate "
+ "dir before continuing; leadersUsedPerChunk=" + leadersUsedPerChunk
+ ", initCountBefore=" + initCountBefore
+ ", initCountAfter=" + provider.getInitCount());
}This is a repro-only sketch for discussion — not intended to land as-is on the PR branch until the fix is in place. |
Thanks @jojochuang for catching that. Fixed it in latest push.
Thanks @jojochuang for the repro and putting details here.. Agree the concern is valid at the framework level, though it isn't reachable for Recon today: Recon requests That said, relying on single-chunk is fragile. |
What changes were proposed in this pull request?
Problem:
Recon keeps its own copy of the OM DB and syncs it two ways: incremental delta updates (
getDBUpdates) for the normal case, and a full OM DB snapshot download as the fallback when delta sync cannot beused (Recon fell too far behind, OM no longer has the requested updates, etc.).
The full-snapshot fallback was Recon's own implementation:
/dbCheckpointcall with Recon-specific download-and-extract code (TarExtractor), separate from the checkpoint-transfer path OM itself uses internally.Maintaining a separate downloader in Recon meant it could deviate from OM's own, well tested checkpoint-transfer code and had to be kept in sync manually over time.
Approach:
Instead of maintaining Recon's own download logic, Recon now reuses the same OM checkpoint-download path an OM follower uses to bootstrap from the leader — POST
/v2/dbCheckpoint— through the sharedRDBSnapshotProviderbase class thatOmRatisSnapshotProvideralso extends:ReconRDBSnapshotProvideroverrides only the download step (POST/v2/dbCheckpoint)and the step that assembles and promotes the finished DB; the surrounding logic (driver loop, untar) is the shared OM code.includeSnapshotData=false, since it does not need OM's snapshots), which OM sends as a single tarball.The assembled DB is always the complete OM DB, so a full snapshot still triggers a full task reprocess in Recon, exactly as before. This change is about reusing OM's proven transfer code and handling
failure cleanly — not about changing how the bytes move (the active DB is still transferred as one tarball).
What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/HDDS-15766
How was this patch tested?
Testing:
TestReconRDBSnapshotProvider— covers the post-download assembly path: promoting the untarred checkpoint into a stable om.snapshot.db_ directory while clearing the candidate dir(
testGetCheckpointPromotesDbAndClearsCandidate), installing the leader's hardLinkFile inventory (testGetCheckpointInstallsHardLinks), and the candidate-dir layout (testCandidateDirLocation).TestOzoneManagerServiceProviderImpl— drives the new provider path for both the success and the download-failure cases (via a stubReconRDBSnapshotProvider).TestTriggerDBSyncEndpoint— stubsreconUtils.getReconDbDir(...)so the now-eagerly-constructed provider receives a real snapshot dir (the provider is created in theOzoneManagerServiceProviderImplconstructor).TestReconWithOzoneManager.testOmDBSyncWithSeqNumberMismatch— end-to-end coverage of the full-snapshot fallback against a mini-cluster: it forcesgetDBUpdatesto fail and asserts Reconrecovers via a full snapshot and normalizes the OM/Recon sequence numbers.
TestReconWithOzoneManagerHA— dropped the stale v1-URL assertion, keeping its end-to-end leader-sync verification.