Siyao Meng created HDDS-17525:
---------------------------------
Summary: Container balancer that stops on its own keeps persisted
shouldRun=true if the save fails, so a later leader change starts a new run
Key: HDDS-17525
URL: https://issues.apache.org/jira/browse/HDDS-17525
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments: MC-2-retry-failed-save-on-balancer-self-stop.patch,
TestBugMC2TerminalSaveFailureResume.java
h3. Mechanism
When the balancing task stops on its own (INITIALIZATION_FAILED,
CAN_NOT_BALANCE_ANY_MORE, COMPLETED_ALL_ITERATIONS),
{{ContainerBalancerTask.tryStopWithSaveConfiguration}} saves
{{shouldRun=false}} through the Ratis replicated
{{StatefulServiceStateManager}}. If that save throws, the catch only records
the stop reason and logs a WARN. It does not retry, and nothing writes
{{false}} later without an admin command. The task still ends STOPPED, so
{{ContainerBalancer.getBalancerStatusInfo}} reports {{shouldRun=false}} while
the persisted value is {{true}}.
The next {{ContainerBalancer.notifyStatusChanged}} on a leader out of safe mode
(failover back to this SCM, failover to another SCM, or a restart) reads the
persisted {{true}} and calls {{start()}}, which starts a new balancing run for
a run that had already ended.
For comparison, a failed save in the admin {{stopBalancer}} path is returned to
the caller and the balancer keeps running, so the admin knows the stop did not
take effect.
h3. Trigger
# An admin starts the balancer ({{shouldRun=true}} is saved).
# The task stops on its own, for example because the cluster is already
balanced.
# The {{shouldRun=false}} write is rejected by Ratis before it is appended
while the SCM stays leader, for example {{ResourceUnavailableException}}
(pending write limit, Ratis default 4096 requests or 64MB, not overridden by
SCM) or {{LeaderSteppingDownException}} during a leadership transfer that then
fails. {{ScmInvoker}} turns the reply into an {{SCMException}}.
# Later, after the cluster has drifted, any leader change or restart starts a
new run.
Step 3 is narrow under default settings: SCM Ratis writes are synchronous, so
the pending limit is only reached after commits stall for minutes while the
leader keeps its majority, and the terminal save has to land then. A save that
times out while the SCM stays leader commits later and ends durable as
{{false}}, which is harmless. An exception while applying the entry terminates
the SCM. These points are from reading the code, not run.
h3. Impact
* A balancing run nobody asked for starts after the next leader change or
restart, with the last admin config, and moves containers.
* Until then the status command says the balancer will not run, while the
persisted state says it will. Only the WARN {{Save configuration failed}} shows
it. Running {{stopBalancer}} fixes the state.
* For COMPLETED_ALL_ITERATIONS the new run starts at the persisted next
iteration index. It stops after the start delay without moving data only if the
index save of the last iteration succeeded, which runs just before the terminal
save.
h3. Reproduction
PASS, deterministic, unmodified source, injected save failure.
[^TestBugMC2TerminalSaveFailureResume.java] ({{hadoop-hdds/server-scm}}) uses a
mocked SCM as {{TestContainerBalancer}} does, with {{ContainerBalancer}} and
{{ContainerBalancerTask}} unmodified on a real {{SCMContext}}. The cluster is
balanced, so the first iteration fails to initialize, and the first
{{shouldRun=false}} save throws the {{SCMException}} that wraps a
{{ResourceUnavailableException}}. After the task stops, status reports
{{shouldRun=false}} and the persisted value is {{true}} on a leader. The
cluster is then made unbalanced and a step-down and step-up are driven through
{{SCMContext.updateLeaderAndTerm}} and {{notifyStatusChanged}}: a new task
starts and selects one over-utilized and one under-utilized datanode. A passing
test means the defect is present. A real Ratis rejection was not exercised.
h3. Patch
[^MC-2-retry-failed-save-on-balancer-self-stop.patch], against
9118d5a85a37db8c64404d56703b7bacb040e063. It also applies to master at
f59f8d3d123.
{{tryStopWithSaveConfiguration}} retries a failed {{shouldRun=false}} save
while the task is still running and the SCM is still leader ready and out of
safe mode, waiting 1 second before the first retry and doubling the wait up to
60 seconds. The first failure is logged with its stack trace, later ones as one
line. It gives up, leaving {{shouldRun=true}} so the next leader resumes as on
any step down today, when the SCM steps down or enters safe mode or the task is
stopped by someone else. The task lock is not held while waiting, so a step
down or an admin stop waits at most for one attempt as today and then ends the
wait. While it retries, the task stays RUNNING and status shows
{{shouldRun=true}}, which matches the persisted value, the same way a failed
admin stop leaves the balancer running. During that time an admin
{{startBalancer}} is refused as already running, and an admin {{stopBalancer}}
ends the retry (if its own save succeeds).
Covered by {{testFailedSaveOnSelfStopIsRetried}} and
{{testStepDownWhileRetryingFailedSaveOnSelfStop}} in the existing
{{TestContainerBalancer}}. Without the change the first fails because the
persisted value is still {{true}} and the second times out because nothing
retries. With it the balancer unit suites in {{hadoop-hdds/server-scm}} pass
(437 tests) and checkstyle is clean. With the patch the reproduction fails
because the persisted value is {{false}} after the stop.
Not covered: when the save fails because the SCM is stepping down, the next
leader still resumes the ended run, as in the step-down case today. That run
starts within minutes on almost the same cluster state, so it usually stops
again with the same reason.
Found by TLA+ model checking and code review of the SCM container balancer
start, stop and failover resume paths under HDDS-15926, on commit
9118d5a85a37db8c64404d56703b7bacb040e063. 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
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]