Siyao Meng created HDDS-16928:
---------------------------------

             Summary: SCM never catches up after a checkpoint install that 
fails or is interrupted once Ratis has acknowledged it, even after a restart
                 Key: HDDS-16928
                 URL: https://issues.apache.org/jira/browse/HDDS-16928
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
MC-1-test-follower-scm-catches-up-after-failed-checkpoint-install.patch, 
TestBugMC1FailedInstallWedgesFollower.java

h3. Mechanism
{{SCMStateMachine.notifyInstallSnapshotFromLeader}} completes its future with 
the checkpoint TermIndex as soon as the leader's checkpoint is downloaded and 
verified. The DB swap is deferred to {{SCMStateMachine.reinitialize}}, which 
Ratis runs later on the {{StateMachineUpdater}} thread. On the next install 
notification Ratis (3.3.1) pauses the state machine and signals the 
{{StateMachineUpdater}} thread to run {{reinitialize}}, but does not wait for 
it: in the same call it purges the follower's Raft log up to the checkpoint 
index and replies SNAPSHOT_INSTALLED, and the leader continues with the entries 
after that index. {{reinitialize}} then calls 
{{SCMHAManagerImpl.installCheckpoint}}.

If that install fails, every failure path keeps the old DB (failed backup move, 
failed copy with restore, failed reload with rollback), and SCM shuts down. The 
same holds if the process is killed after the acknowledgement and before 
{{HAUtils.replaceDBWithCheckpoint}} moves the old DB aside (argued, not run); a 
kill during the copy leaves the {{dbInconsistentMarker}} file, and the SCM then 
refuses to start. The pending checkpoint was only held in memory. After a 
restart the applied index is the old DB's (for a new SCM, -1) while the Raft 
log starts after the checkpoint index, so {{StateMachineUpdater}} never finds 
the next entry to apply. The leader does not send another install notification, 
because the follower's next index is already inside the leader's log.

h3. Trigger
# An SCM installs the leader's checkpoint through the Ratis install 
notification. At default settings 
({{ozone.scm.ha.ratis.log.purge.enabled=false}}) this is in practice an SCM 
that is bootstrapping (new or replaced node); with purge enabled, also a 
follower that fell behind.
# The leader commits at least one more entry that reaches this SCM before the 
install completes (any SCM write traffic).
# {{installCheckpoint}} fails with the old DB kept, or the process is killed 
after the acknowledgement and before the old DB is moved aside (a kill during 
the copy leaves the {{dbInconsistentMarker}}, and the SCM then refuses to 
start). Realistic causes are a full disk during the copy of the whole DB, an 
I/O error, or a failure while reloading the SCM services from the new DB. In 
the reproduction the failure is forced with a leftover {{dbInconsistentMarker}} 
file in the DB directory, which makes {{HAUtils.replaceDBWithCheckpoint}} fail 
and restore the old DB. The kill case was not run. For a bootstrapping SCM this 
needs the leader to have added it to the Raft group, which happens within 
moments of the acknowledgement, long before a copy of a real DB ends; when the 
injected immediate failure came first, the bootstrap did not complete instead 
(not followed further).
# The SCM is restarted.

h3. Impact
* The restarted SCM never applies another transaction and never starts its 
datanode server, with no error beyond the periodic "Deferred datanode-server 
start is still waiting for follower catch-up" warning. Nothing recovers it 
automatically.
* A graceful stop of that SCM never completes ({{StateMachineUpdater}} waits to 
reach the commit index), and its updater thread spins on one core.
* Its Raft log is complete, so it can become leader. After a leadership 
transfer to it (admin {{transferLeadership}}), it was the Ratis leader and no 
SCM reported itself as a ready leader for the 20 s observed, so the SCM service 
was unavailable. I did not run an election that picks it without the transfer.
* Recovery needs an operator: kill it, remove its SCM DB and Ratis storage, and 
bootstrap it again (argued, not run).

h3. Reproduction
PASS on unmodified source, default configuration. 
[^TestBugMC1FailedInstallWedgesFollower.java] 
({{hadoop-ozone/integration-test}}) starts three SCMs with one inactive, places 
the marker file in the inactive SCM's DB directory, starts it while the leader 
keeps allocating containers, waits until it shuts down after the failed 
install, removes the file and restarts it. Result: SNAPSHOT_INSTALLED and the 
leader moving to the next index logged before the install ran; after the 
restart applied index -1 before and after, log start 90, commit 192 against 
leader applied 191, 0 install notifications, datanode server not started, 0 of 
80 containers, about 9.7 s of updater CPU in 10 s; after a leadership transfer 
to it, it was the Ratis leader and no SCM was a ready leader; a graceful stop 
still running after 20 s. In this JVM the restarted SCM gets new datanode and 
gRPC ports, because the stopped instance keeps them bound; a process exit 
releases them. If the injected failure comes before the leader has added the 
SCM to the Raft group, the test aborts and has to be rerun. A passing test 
means the defect is present.

h3. Suggested fix
SCM cannot undo the purge and the acknowledgement from {{reinitialize}}. 
Recommended: install the checkpoint inside the notification future, as 
{{OzoneManagerStateMachine.notifyInstallSnapshotFromLeader}} does, and fail the 
future on any error, so Ratis neither purges nor acknowledges a failed install. 
This reverses the deferral chosen in HDDS-5173 and needs the updater to be 
quiesced during the swap. It should not copy OM's handling of a failed install, 
which completes the future with null (HDDS-11470). As a safety net, SCM should 
check at startup whether its applied index is below the start of its Raft log 
and fail with a clear message; that also catches the same state reached through 
other paths, such as a transaction buffer init failure in {{reinitialize}} or a 
reload failure that rolls back to the old DB.

Alternatives: change Ratis to purge and reply SNAPSHOT_INSTALLED only after 
{{reinitialize}} has succeeded, or persist the pending checkpoint before 
acknowledging and install it at startup.

h3. Patch
[^MC-1-test-follower-scm-catches-up-after-failed-checkpoint-install.patch], 
against b0aa6475b7856d2c7f467dd3db12b9a1dd116f63, test only. It also applies to 
master at 52812559dad.

It adds {{testSCMCatchesUpAfterFailedCheckpointInstall}} to 
{{TestStorageContainerManagerHA}}. A follower SCM that is a voting member falls 
behind the start of the leader's log (log purge enabled, one snapshot requested 
on the leader), and when it restarts its install fails once in 
{{installCheckpoint}} with the old DB in place (its metadata store, set through 
{{SCMConfigurator.setMetadataStore}}, refuses to stop once). After another 
restart the follower must catch up with the leader. On the current code it 
fails with "Timed out waiting for condition": the follower stays at applied 
index 42 while the leader is at 1475 and the follower's log starts at 1261, and 
the teardown then takes several minutes (about 5) because the wedged SCM's stop 
does not complete. It is meant to land with the fix. A member follower is used 
instead of the bootstrap case of the reproduction because an injected failure 
is immediate, and for a bootstrapping SCM it can come before the leader has 
added the SCM to the Raft group.

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 and RATIS issues 
and apache/ozone pull requests for duplicates before filing. The attached patch 
is a test 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