Siyao Meng created HDDS-16931:
---------------------------------
Summary: SCM checks only the TransactionInfo of a downloaded
checkpoint before acknowledging it, so a checkpoint with corrupt SST blocks is
installed
Key: HDDS-16931
URL: https://issues.apache.org/jira/browse/HDDS-16931
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments:
MC-7-verify-scm-checkpoint-checksums-before-install.patch,
TestBugMC7CorruptCheckpointAcceptedAndInstalled.java
h3. Mechanism
Before a follower SCM acknowledges a downloaded checkpoint to Ratis,
{{SCMHAManagerImpl.verifyCheckpointFromLeader}} opens it read only and reads
only the TransactionInfo. A checkpoint with a damaged data block in another
table passes. {{installCheckpoint}} then calls
{{HAUtils.replaceDBWithCheckpoint}}, which moves {{scm.db}} to a backup, copies
the checkpoint in and deletes {{dbInconsistentMarker}} before any service has
read the new DB. Only then does {{reloadSCMState}} read it; its failure
triggers a rollback to the old DB. If the process stops before that rollback
(kill, OOM, power loss during the reload, which iterates every container and
pipeline), {{scm.db}} is the corrupt checkpoint, there is no marker, and the
old DB is only in {{scm.db.backup.*}}. Every start then fails on the first read
of the damaged block, and nothing restores the backup.
h3. Trigger
# The checkpoint a follower downloads has a corrupt SST block outside the
TransactionInfo, for example media corruption of a rarely read SST on the
leader (the checkpoint hard links the leader's files) or a write error on the
follower while unpacking it.
# The follower SCM stops after {{replaceDBWithCheckpoint}} returned and before
the rollback in {{installCheckpoint}} completed. A failed copy during the
rollback (for example out of disk space) reaches the same state without a stop:
its reset branch moves the corrupt checkpoint back into {{scm.db}} and deletes
the marker (by code reading, not run).
Both steps are uncommon, and a stop alone (with a good checkpoint) is harmless
today: the restart opens the installed DB and catches up.
h3. Impact
* If the damaged block is in a table the reload iterates (containers,
pipelines), the scan ends early without an error, because
{{RDBStoreAbstractIterator.hasNext}} checks only {{isValid()}} and not
{{status()}}. The reload can then report success with part of the table loaded,
and the backup of the old DB is deleted (by code reading, not run). The
iterator behaviour affects every table scan and is worth its own issue.
* If the damaged block is read by key during the reload and the SCM stops
before the rollback: the SCM does not start until an operator repairs its DB.
There is no marker, so the error is a RocksDB corruption, not the "inconsistent
state" message that points at the cause.
* No data is lost cluster wide; the other SCMs have the state and the old DB is
intact in the backup. Moving the backup back makes the DB open, but the
follower acknowledged the install and its Raft log now starts at the snapshot
index, so the old DB alone does not let it rejoin (argued, see HDDS-16928).
Replacing the DB from another SCM does.
h3. Reproduction
PASS on the unmodified source.
[^TestBugMC7CorruptCheckpointAcceptedAndInstalled.java]
({{hadoop-ozone/integration-test}}) runs a one SCM mini cluster. It copies a
clean checkpoint of the SCM DB to a separate directory as the follower's DB,
and makes a second checkpoint with a raised TransactionInfo and the first 16
bytes of its meta table SST overwritten. {{verifyCheckpointFromLeader}} on the
SCM accepts it: {{(t:1, i:1004)}}. {{HAUtils.replaceDBWithCheckpoint}} on the
follower's directory then returns with no marker and the old DB in
{{scm.db.backup.4_...}}. Opening that directory with {{SCMMetadataStoreImpl}}
and reading the apparent layout version, as SCM start does, fails with
{{Corruption: ... Failed to get #LAYOUTVERSION from ColumnFamily-meta}}. The
stop itself is not simulated with a kill: {{replaceDBWithCheckpoint}} has
returned and nothing else writes to the directory before the reload, so the
directory is in the state a stop at that point leaves. A passing test means the
defect is present.
h3. Patch
[^MC-7-verify-scm-checkpoint-checksums-before-install.patch], against
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at
52812559dad.
The patch makes {{verifyCheckpointFromLeader}} also run RocksDB
{{verifyChecksum}} on the read only checkpoint (a new
{{RocksDatabase.verifyChecksum}} and {{HAUtils.verifyCheckpointChecksums}}). A
damaged checkpoint is then handled like a failed download: the follower does
not acknowledge it, its DB is not touched, and Ratis retries the install. This
removes the precondition instead of narrowing the stop window, and it also
keeps checkpoints with damaged blocks in iterated tables out of the install.
The check is unconditional, with no switch, because it runs only during an
install, on the install thread, at a cost of the same order as the download
just before it, and installing a checkpoint with damaged blocks is never the
right outcome. The cost is one read of the downloaded checkpoint per install;
nothing changes for SCMs that do not install a checkpoint, and the shared
{{HAUtils.getTrxnInfoFromCheckpoint}} used by OM is unchanged. Keeping the
marker until the reload succeeds was not chosen: it would also refuse the start
after a stop during the reload of a good checkpoint, which recovers on its own
today. With the patch the reproduction fails at its first assertion:
{{verifyCheckpointFromLeader}} returns null and logs {{Corruption: ... Failed
to verifyChecksum}}.
Covered by {{testVerifyCheckpointFromLeaderRejectsCorruptCheckpoint}} in the
existing {{TestSCMHAManagerImpl}}: a clean checkpoint of a real SCM DB is
accepted, and the same checkpoint with damaged SST blocks (except the one with
the TransactionInfo) is rejected. Without the change it fails with "expected:
null but was: (t:1, i:1000)". With it the SCM HA unit suites
({{TestSCMHAManagerImpl}}, {{TestSCMStateMachine}},
{{TestSCMSnapshotProvider}}, 22 tests) and {{TestRDBStore}} and
{{TestRDBSnapshotProvider}} (18 tests) pass. The mini cluster suites
{{TestSCMInstallSnapshotWithHA}} and {{TestSCMInstallSnapshot}} (5 tests,
including an install through Ratis) pass and checkstyle is clean.
Found by TLA+ model checking and code review of the SCM Ratis snapshot and
checkpoint install paths under HDDS-15926, on commit
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. 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]