Siyao Meng created HDDS-16880:
---------------------------------

             Summary: Recon reuses the truncated file left by a failed OM DB 
checkpoint untar, so its retry from the same OM leader builds a DB with a 
truncated SST
                 Key: HDDS-16880
                 URL: https://issues.apache.org/jira/browse/HDDS-16880
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: CR-16-recon-clear-candidate-dir-after-failed-fetch.patch, 
TestBugCR16TruncatedSstReusedOnReconRetry.java

h3. Mechanism
When Recon fetches the OM DB, 
{{RDBSnapshotProvider.downloadDBSnapshotFromLeader}} untars the downloaded part 
into {{om.snapshot.db.candidate}} with {{FileUtil.unTar}}, which runs GNU tar 
on Linux. If the archive ends in the middle of an entry, or extraction runs out 
of space, tar exits with code 2 and leaves the partly written file in the 
candidate dir. Since HDDS-15766, 
{{OzoneManagerServiceProviderImpl.getOzoneManagerDBSnapshot}} deliberately 
keeps the candidate dir after a failure, and 
{{RDBSnapshotProvider.checkLeaderConsistency}} keeps it for the same leader. On 
the next fetch {{ReconRDBSnapshotProvider.downloadSnapshot}} sends every file 
name in the candidate dir ({{HAUtils.getExistingFiles}}, names only) as already 
received. {{OMDBCheckpointServletInodeBasedXfer.collectFilesFromDir}} skips 
files whose {{<inode>-<mtime>}} id is in that list, which still matches as long 
as the SST is live on the leader, but still lists them in the hardLinkFile. 
{{InodeMetadataRocksDBCheckpoint}} then links the truncated file as 
{{om.db/<n>.sst}}, and {{ReconRDBSnapshotProvider.getCheckpointFromUntarredDb}} 
promotes it to {{om.snapshot.db_<ts>}}. Nothing on the Recon side compares file 
sizes or checksums.

The comment that keeps the dir says this "matches how the OM follower handles a 
failed download", but it does not: {{OzoneManager.installSnapshotFromLeader}} 
deletes the candidate dir after any failed download 
({{cleanupCheckpoint(null)}}). The OM follower and {{ozone repair om}} are not 
affected.

h3. Trigger
# Recon fetches a full OM DB snapshot (first start, or fallback after delta 
updates fail).
# The untar of the response fails part way. One way is the leader side: 
{{OMDBCheckpointServletInodeBasedXfer.processMetadataSnapshotRequest}} catches 
an {{IOException}} from {{OMDBArchiver.writeToArchive}} after the response is 
committed, logs it and returns normally, so Recon gets HTTP 200 and a tar cut 
in the middle of an entry (see HDDS-16864). A dropped connection does not 
trigger this, because {{ReconRDBSnapshotProvider.downloadSnapshot}} deletes the 
partial tar on a read error. The other way, more likely on Recon, is the Recon 
OM snapshot volume filling up during extraction: the tar stays on the volume 
until the untar succeeds, so extraction needs about twice the checkpoint size. 
Neither was run end to end; the reproduction uses a response cut in the middle 
of an SST entry.
# The next fetch goes to the same OM leader while that SST is still live there.

h3. Impact
* The retry builds an OM DB with a truncated SST. RocksDB refuses to open it 
({{Corruption: Sst file size mismatch between expected 24743 and file system 
12288 sstable: .../om.snapshot.db_<ts>/000010.sst}}), so Recon does not read 
the truncated data.
* {{ReconOmMetadataManagerImpl.initializeNewRdbStore}} only logs the open 
failure. {{updateReconOmDBWithNewSnapshot}} returns true, logs "Successfully 
updated Recon OM DB with new snapshot." and keeps the previous OM DB handle 
(none on first start). {{getOzoneManagerDBSnapshot}} already deleted that 
directory before the first fetch, so a later flush on it fails. In the 
scheduled sync the run is then marked failed and {{GET_OM_DB_SNAPSHOT_FAILED}} 
is set again, but only because the task reinitialization that follows cannot 
checkpoint that deleted DB (or the missing one on first start).
* The retry that should have repaired Recon is wasted, and the corrupt 
{{om.snapshot.db_<ts>}} stays on disk. Recon recovers at the next full fetch, 
which deletes that dir and starts from an empty candidate dir, or after a 
restart (the next full fetch was run; the restart was argued, not run).
* No effect on OM. Affects Version: 2.3.0. The affected code is only on master 
(HDDS-15766 is in no release; 2.2.1 and earlier extract the OM DB into a fresh 
dir without an exclusion list).

h3. Reproduction
Deterministic, unmodified source. 
[^TestBugCR16TruncatedSstReusedOnReconRetry.java] ({{hadoop-ozone/recon}}) runs 
Recon's own {{OzoneManagerServiceProviderImpl.updateReconOmDBWithNewSnapshot}} 
and {{ReconRDBSnapshotProvider}}, with the real request form data and shell 
untar, against a real OM RocksDB. Only the HTTP transport, the OM RPC client 
and Recon's task wiring are mocked; the snapshot provider, the form data, the 
shell untar, the hard link install and the RocksDB open are the real code. The 
leader side is a stand-in for {{OMDBCheckpointServletInodeBasedXfer}} with 
{{includeSnapshotData=false}}: a fresh checkpoint per request, entries named by 
inode and mtime, files in the request's exclusion list skipped, then the 
hardLinkFile and the completion flag. The first response ends in the middle of 
the largest SST. The test asserts the correct behaviour, so it fails while the 
defect is present. On the unfixed code the second request excludes the 
truncated file, and the promoted DB has {{000010.sst}} at 12288 bytes instead 
of 24743. The same run logs the RocksDB size mismatch, then "Successfully 
updated Recon OM DB with new snapshot.". With the patch it passes.

h3. Patch
[^CR-16-recon-clear-candidate-dir-after-failed-fetch.patch], against 
a6b7bdb937109ba2893688b41a89470bc95c88cf. It also applies to master at 
6818707698c.

After a failed fetch, {{getOzoneManagerDBSnapshot}} now calls 
{{reconSnapshotProvider.init()}}, which empties the candidate dir, as the OM 
follower does. A {{RuntimeException}} from {{init()}} (for example 
{{HddsUtils.createDir}} failing) is logged, so the method still returns null.

This puts back the reset that an earlier revision of HDDS-15766 
(apache/ozone#10696) had in the same place, with the same guard. It was dropped 
on review so that a future batched transfer could resume the parts already 
received. Keeping the dir is only safe if every file in it is complete, and a 
failed untar breaks that. Resuming a batched transfer would need each part 
extracted atomically (to a staging dir, moved in only on success) or verified 
(HDDS-14651). The OM follower, which does use batches, also wipes the dir after 
a failure. Today Recon requests the checkpoint with 
{{includeSnapshotData=false}}, which the leader sends in a single part, so 
after a failure the dir only holds the output of a failed part and nothing that 
is safe to resume is lost. This holds for the v1 endpoint too 
({{ozone.om.db.checkpoint.use.inode.based.transfer=false}}), where Recon sends 
SST paths and the leader also excludes by name (argued, not run). A leader side 
fix would need sizes or checksums on the wire (see HDDS-14651). That 
{{initializeNewRdbStore}} reports a failed open as success is not changed here; 
it is filed as a separate issue.

Covered by {{testPartiallyUntarredFileNotReportedOnRetry}} in the existing 
{{TestOzoneManagerServiceProviderImpl}}: after an untar failure on a truncated 
SST, the next request must list no files and the candidate dir must be empty. 
Without the change it fails with {{[[], ["000010.sst"]]}}. 
{{testGetOzoneManagerDBSnapshotReturnsNullWhenCandidateDirResetFails}} covers 
the guard. With the change, {{TestOzoneManagerServiceProviderImpl}}, 
{{TestReconRDBSnapshotProvider}} and {{TestTriggerDBSyncEndpoint}} (23 tests) 
and the mini-cluster {{TestReconWithOzoneManagerHA}} pass, and checkstyle is 
clean.

Related: HDDS-15766 (introduced the retention), HDDS-16864 (the same failed 
untar also leaves the downloaded tarball behind), HDDS-14651 (manifest and 
checksum validation would detect the short file).

Found by code review of the OM Ratis snapshot (checkpoint) install paths, as 
part of the TLA+ verification effort under HDDS-15926, on commit 
a6b7bdb937109ba2893688b41a89470bc95c88cf. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. The attached patch is 
a proposal for review. Generated with Specula (Claude Opus 5.5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to