[
https://issues.apache.org/jira/browse/HDDS-16861?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-16861:
------------------------------
Attachment: TestBugMC3QuotaRepairSkippedOnLeaderChange.java
> Upgrade quota repair is skipped on every OM if leadership moves before the
> leader applies CompleteFinalizeUpgrade
> -----------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-16861
> URL: https://issues.apache.org/jira/browse/HDDS-16861
> Project: Apache Ozone
> Issue Type: Bug
> Reporter: Siyao Meng
> Priority: Minor
> Attachments:
> MC-3-start-upgrade-quota-repair-on-next-ready-leader.patch,
> TestBugMC3QuotaRepairSkippedOnLeaderChange.java
>
>
> h3. Mechanism
> {{QuotaRepairUpgradeAction.execute}} starts {{QuotaRepairTask}} only if the
> OM applying {{CompleteFinalizeUpgrade}} is {{LEADER_AND_READY}} at that
> moment. Otherwise it logs "Skip quota repair operation during upgrade on the
> node as this is not a leader node." and returns, and
> {{ComponentVersionManager.finalizeUpgrade}} persists QUOTA as finalized.
> Followers always take that branch. If the OM that was leader at commit time
> is no longer the ready leader when its apply thread reaches the action, every
> OM takes it.
> Nothing starts the repair later. Every VERSION file is past QUOTA, so replay
> finds nothing to finalize. Repair state is in memory only, and nothing starts
> it on leader election. The code comment ("on leader node, repair will be
> triggered where finalize is called. For other nodes, it will be ignored.")
> assumes some OM is the ready leader when it applies the step.
> h3. Trigger
> # A cluster is pre-finalized at a layout before QUOTA, with
> {{ozone.om.upgrade.quota.recalculate.enabled}} at its default ({{true}}).
> # The admin finalizes and the leader commits {{CompleteFinalizeUpgrade}}.
> # Before the leader applies it, leadership moves, for example to a follower
> that has already applied it as a follower.
> The window between the commit and the leader's own apply is normally short.
> The reproduction widens it with a latch, and the leader change could come
> from a step down, a GC pause or a partition. Argued from code, not run: a
> leader crash after the commit has every OM apply the entry while not the
> ready leader, with the same result, and a restart of all OMs after the commit
> also loses the repair, because the repair state is in memory only.
> h3. Impact
> * No OM runs the quota recalculation that
> {{ozone.om.upgrade.quota.recalculate.enabled}} promises at upgrade, and the
> quota repair status reports no run (run).
> * Bucket and volume usage counters that drifted under releases before QUOTA
> stay as they were. The skip does not make counters wrong: a write that such a
> counter rejects after finalization was rejected before it too. {{ozone repair
> om quota start}} does the same repair by hand.
> * Affects 2.0.0 onward. The action and its follower skip are the same since
> 2.0.0, and in 2.x the action also runs while each OM applies the finalize
> request, so the same window exists (argued from code, not run).
> * Suggested priority Minor: it needs a leader change in a short window during
> the one time finalization, and it only skips a best effort correction that a
> documented command also does.
> h3. Reproduction
> PASS on the unmodified source.
> [^TestBugMC3QuotaRepairSkippedOnLeaderChange.java]
> ({{hadoop-ozone/integration-test}}) runs a 3 OM HA mini cluster pre-finalized
> at {{INITIAL_VERSION}}. The only test aid is a wrapper, installed through
> {{OMVersionManager.getUpgradeActionsForTesting}}, that holds the old leader's
> apply thread in front of the real, unchanged QUOTA action. The test then:
> # moves leadership with {{OzoneManager.transferLeadership}} to a follower
> that has already applied the entry;
> # releases the old leader;
> # writes through the new leader, so its leader ready notification has
> returned.
> It asserts that all 3 OMs logged the skip, all are at the software version in
> memory and in their VERSION files, and {{QuotaRepairTask.getStatus}} still
> reports no run. A passing test means the defect is present.
> h3. Patch
> [^MC-3-start-upgrade-quota-repair-on-next-ready-leader.patch], against
> a6b7bdb937109ba2893688b41a89470bc95c88cf. It also applies to master at
> b0aa6475b78.
> The patch defers the repair instead of dropping it:
> * An OM that finalizes QUOTA while it is not the ready leader still logs the
> skip and now marks the repair as pending in memory
> ({{OzoneManager.deferUpgradeQuotaRepair}}).
> * {{OzoneManagerStateMachine.notifyLeaderReady}} queues the start of a
> pending repair on the state machine apply executor, behind the entries
> already handed to it. It starts the repair once, if this OM is the ready
> leader. Ratis signals leader ready before the new leader has applied its
> backlog, so starting it inline could run a second repair next to a
> {{QuotaRepair}} that was committed but not yet applied, and correct the
> counters twice.
> * An applied {{QuotaRepair}} request clears the pending mark on every OM, so
> the followers of a normal finalization do not repeat the repair on a later
> failover.
> The leader path at apply time is unchanged. There are no new requests, proto
> fields, DB keys or configuration, so OMs with and without the patch apply the
> same log in the same way.
> Limits:
> * The mark is lost if every OM that holds it restarts before one becomes the
> ready leader. A pending marker in the meta table would close that, at the
> cost of a metadata change.
> * Followers hold the mark until they apply the leader's quota repair, so a
> leader change while that repair is still running also starts a repair on the
> new leader. The old leader's run then fails at submit, because its Ratis
> server is no longer the leader (argued from code).
> * A leader repair that fails before submit (any error, including a cluster
> without buckets) leaves the mark set on every follower, so a full repair can
> start at a failover long after the upgrade. The same holds for a follower
> that finalized from an installed snapshot that already contains the repair.
> * A repair still running on a leader that lost leadership can still apply if
> that OM becomes leader again before it finishes, so two repairs can both
> apply if leadership moves back and forth during one repair run.
> * Any applied {{QuotaRepair}} clears the mark, including one started from the
> CLI with {{--buckets}}.
> A smaller alternative is a WARN only change: extend the skip message with "If
> no quota repair was started for this upgrade (check {{ozone repair om quota
> status}} on the leader), run {{ozone repair om quota start}}." It adds no
> state and cannot start a repair, at the cost of relying on the operator.
> Covered by two tests in the existing {{TestOMUpgradeFinalization}}:
> * {{testQuotaRepairRunsAfterLeaderChangeDuringFinalization}} reproduces the
> leader change as above. It asserts that exactly one repair runs and succeeds,
> and that moving leadership to the remaining OM afterwards does not start
> another. Without the change it times out waiting for the repair.
> * {{testNewLeaderDoesNotRepeatQuotaRepairQueuedForApply}} uses the existing
> {{OzoneManagerRequestHandler}} fault injector to hold a follower's apply of
> the leader's {{QuotaRepair}}, then moves leadership to that follower. It
> asserts that no second repair starts. It fails if the pending repair is
> started inline in {{notifyLeaderReady}} (a second repair runs and applies),
> and passes with the patch.
> With the patch, these pass:
> * {{TestQuotaRepairTask}}, {{TestOMQuotaRepairRequest}},
> {{TestOMVersionManager}}, {{TestOzoneManagerStateMachine}},
> {{TestOMUpgradeFinalizeService}} and {{TestOMCompleteFinalizeUpgradeRequest}}
> (106 tests);
> * {{TestOMUpgradeFinalization}}, {{TestOMBucketLayoutUpgrade}},
> {{TestUpgradeFinalizationWithOldClients}} and {{TestOzoneRepairShell}} on a
> mini cluster (17 tests).
> Checkstyle is clean. With the patch the reproduction fails because the new
> leader has run the repair.
> Relates to HDDS-16860, a separate defect in the same action. The patches for
> this issue and HDDS-16860 change the same few lines of
> {{QuotaRepairUpgradeAction.execute}}, so whichever lands later needs a
> trivial rebase. The open pull request for HDDS-16275 (apache/ozone#11190)
> also changes {{OzoneManagerStateMachine}} and merges cleanly with this patch;
> it gates only {{applyTransaction}} while the state machine is paused for a
> checkpoint install, so the deferred repair start, which is submitted to the
> apply executor directly, is neither rejected nor waited for by it.
> Found by TLA+ model checking and code review of the OM upgrade finalization
> paths 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]