Siyao Meng created HDDS-16930:
---------------------------------

             Summary: Follower SCM exits when it installs a Ratis snapshot 
after applying a delete transaction removal that its buffer has not flushed yet
                 Key: HDDS-16930
                 URL: https://issues.apache.org/jira/browse/HDDS-16930
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
CR-6-flush-scm-transaction-buffer-before-checkpoint-install.patch, 
TestBugCR6InstallAfterDeleteTxRemoval.java

h3. Mechanism
{{DeletedBlockLogStateManagerImpl.removeTransactionsFromDB}} adds the removed 
transaction IDs to the in memory {{deletingTxIDs}} and queues the deletes in 
the SCM transaction buffer. Only {{onFlush()}}, called when the buffer is 
flushed, clears the set. {{DeletedBlockLogStateManagerImpl.reinitialize}} runs 
{{Preconditions.checkArgument(deletingTxIDs.isEmpty())}}, and its comment says 
that a flush is called before the reload. No flush happens on the install path: 
Ratis calls {{SCMStateMachine.pause}}, which only changes the lifecycle state, 
and then {{SCMStateMachine.reinitialize}}, and 
{{SCMHAManagerImpl.installCheckpoint}} stops the store, replaces the DB and 
calls {{startServices}}, which calls {{DeletedBlockLog.reinitialize}}. If the 
set is not empty the check throws. The rollback puts the old DB back and 
reloads again, the set is still not empty, the same check throws, and SCM calls 
{{exitSystem(1)}} ("Failed to reload SCM state and instantiate services.").

On a follower the buffer is flushed by a Ratis snapshot (every 
{{ozone.scm.ha.ratis.snapshot.threshold}} entries, default 1000), at the 
pending operation limit (default 10000), when it becomes leader, when it serves 
a checkpoint download ({{InterSCMGrpcService.download}}) and when it applies a 
replicated {{StatefulServiceStateManager.saveConfiguration}}. The periodic 
flush ({{ozone.scm.ha.dbtransactionbuffer.flush.interval}}) runs on the leader 
only. So after a follower applies a removal (sent for every delete 
acknowledgement from datanodes and by the scanner cleanup), the set stays non 
empty until the next of these flushes, which for a follower that stops applying 
may never come.

h3. Trigger
# {{ozone.scm.ha.ratis.log.purge.enabled}} is true (default false) and the 
leader has purged its log past the follower's position. The leader purges only 
after {{ozone.scm.ha.ratis.log.purge.gap}} entries (default 1000000) past its 
last purge.
# The follower applied a delete transaction removal since its last buffer 
flush, then fell behind the purged log while running (for example paused or cut 
off from the leader).
# Ratis asks the follower to install a snapshot.

With default settings Ratis purges only below every peer's commit index, so a 
running follower is not sent a snapshot, and a newly bootstrapped SCM has an 
empty set. I did not find a default configuration route.

h3. Impact
* The follower SCM process exits during the install (run with the exit replaced 
by a recording exit manager; the process exit itself not run).
* A restart does not recover it. In a run with the exit replaced, stopping and 
restarting that SCM right after the failed install failed: the stop could not 
flush the transaction buffer ({{RocksDatabaseException: Rocks Database is 
closed}} from {{SCMHADBTransactionBufferImpl.flush}}), and the restarted SCM 
could not open the restored {{scm.db}} (RocksDB reported a missing {{.sst}} 
file and a possibly corrupted MANIFEST). The rollback restores the old DB while 
the store opened by the failed reload is still open, which is HDDS-16921: with 
the patch of that issue applied, the same restart opened the DB (2 of 2 runs). 
A real process exit was not run.
* Through the real Ratis path the failure comes after Ratis has already purged 
the follower's log up to the snapshot index and replied SNAPSHOT_INSTALLED to 
the leader ({{SnapshotInstallationHandler}} does both before {{reinitialize}} 
runs on the state machine updater thread). So even with a DB that opens, the 
restarted follower is left in the wedge reported in HDDS-16928 (code reading 
for this trigger; the wedge itself is run there). HA redundancy stays reduced 
until that SCM is repaired.

h3. Reproduction
PASS on the unmodified source, default configuration. 
[^TestBugCR6InstallAfterDeleteTxRemoval.java] 
({{hadoop-ozone/integration-test}}) starts three SCMs, has the leader add one 
delete transaction and remove it again ({{removeTransactions}}, the request a 
datanode acknowledgement sends), and checks that both followers applied the 
removal without a flush. Both followers then install the leader's checkpoint 
through the state machine calls Ratis makes ({{pause}}, 
{{setInstallingSnapshotData}}, {{reinitialize}}); the log purge notification 
itself is not exercised. The control follower flushes its buffer first and 
installs fine. The other one fails at 
{{DeletedBlockLogStateManagerImpl.reinitialize}} in the reload and in the 
rollback reload, and the exit is requested with the 
{{IllegalArgumentException}}. A passing test means the defect is present. With 
the patch both installs succeed and the test fails at its 
{{assertThatThrownBy}}.

h3. Patch
[^CR-6-flush-scm-transaction-buffer-before-checkpoint-install.patch], against 
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
52812559dad.

{{SCMHAManagerImpl.installCheckpoint}} flushes the transaction buffer before it 
stops the store. This clears {{deletingTxIDs}} through {{onFlush()}} as the 
comments in {{DeletedBlockLogStateManagerImpl.reinitialize}} and 
{{SCMDeletedBlockTransactionStatusManager.reinitialize}} already assume, and it 
leaves the old DB, which a rollback restores, consistent with the follower's 
applied index. Ratis calls {{reinitialize}} on its state machine updater 
thread, so no transaction is applied during the flush. Resetting the set in 
{{reinitialize}} instead would also avoid the crash, but it drops the check and 
leaves a restored old DB behind the applied index.

Covered by {{testInstallCheckpointAfterDeleteTransactionRemoval}} in the 
existing {{TestSCMInstallSnapshotWithHA}}: the follower applies a removal that 
is not flushed yet and then installs the leader's checkpoint. Without the 
change the install fails with the {{IllegalArgumentException}} above in both 
reloads. With it {{TestSCMInstallSnapshotWithHA}}, {{TestSCMInstallSnapshot}}, 
{{TestSCMSnapshot}}, {{TestScmHAFinalization}}, {{TestSecretKeySnapshot}} and 
the server-scm {{TestDeletedBlockLog}}, {{TestSCMHADBTransactionBufferImpl}}, 
{{TestSCMHAManagerImpl}} and {{TestSCMStateMachine}} pass, and checkstyle is 
clean. {{TestSCMInstallSnapshotWithHA}} is tagged flaky (HDDS-5631), so the new 
test does not run in the regular CI checks.

Found by code review of the SCM Ratis snapshot and checkpoint install paths, as 
part of the TLA+ verification effort 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]

Reply via email to