Siyao Meng created HDDS-16839:
---------------------------------
Summary: A new OM treats any failed bootstrap reply as final and
exits, although the leader may still be adding it or may already have logged
the change
Key: HDDS-16839
URL: https://issues.apache.org/jira/browse/HDDS-16839
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments:
CR-3-wait-for-membership-after-failed-bootstrap-request.patch,
TestBugCR3BootstrapFailsButChangeCommits.java
h3. Mechanism
{{OzoneManager.bootstrap}} sends one {{BootstrapOMRequest}} and ends the
process on any exception. The outcome of the request and the outcome of the
membership change are not the same thing in two cases.
# The leader answers {{success=false}} whenever
{{OzoneManagerRatisServer.addOMToRatisRing}} throws, which includes losing
leadership after the configuration entry that adds the new OM is already in the
Raft log. The next leader can still commit that entry.
# {{OMInterServiceProtocolClientSideImpl}} retries the request through the
failover proxy when the RPC itself fails (for example on
{{ozone.om.client.rpc.timeout}} or a lost connection). The first request keeps
running on the leader, so the retry is answered with {{RATIS_BOOTSTRAP_ERROR}}
"Reconfiguration is already in progress", which is not retried.
In both cases the new OM logs "Failed to Bootstrap OM." and exits. It never
looks at the Raft configuration it receives, although it is told about the
committed change through
{{OzoneManagerStateMachine.notifyConfigurationChanged}} like every other member.
h3. Trigger
Case 1 (run with timing help from the Ratis test hook, see Reproduction):
# Three OMs. The new OM is started with {{ozone om --bootstrap}} and catches up.
# The leader logs the configuration entry. One follower and the new OM are
slow, so the entry cannot be committed, the leader loses its majority and steps
down.
# The pending change is answered with a failure and the new OM exits.
# The slow follower recovers, a new leader is elected and commits the entry.
Without the hook the leader has to lose leadership or stop between appending
the first configuration entry of the change, which it does only after the new
OM has caught up, and committing the last one. That is two commit round trips,
a few milliseconds per bootstrap in a healthy ring (from the code). The test
hook holds this window open.
Case 2 (run without any hook): the first bootstrap request of the new OM times
out on the new OM while the leader is still adding it, with
{{ozone.om.client.rpc.timeout}} lowered to 200 ms in the test. The retry fails
with "Reconfiguration is already in progress" and the new OM gives up about a
quarter of a second after its first request reached the leader. With the
default timeout of 15 minutes this needs a leader that takes longer than that
to bring the new OM up to date, or a connection failure during that time
(argued, not run).
h3. Impact
* Case 1: the bootstrap command fails and the process of the new OM ends, but
the new OM is a voter in the committed Raft configuration of every existing OM.
A ring of three becomes four voters with three running, so it tolerates no
further failure until somebody notices and starts the OM. Starting it normally
(without {{--bootstrap}}) is the way out; I did not run that step.
* Case 2: a bootstrap that the leader was in the middle of completing is
reported as failed. What the leader does after the new OM is gone was not run.
h3. Reproduction
PASS on unmodified source. [^TestBugCR3BootstrapFailsButChangeCommits.java]
({{hadoop-ozone/integration-test}}) shows case 1 with the real OMs of a mini
cluster and its normal bootstrap. It uses the Ratis test hook
{{CodeInjectionForTesting}} to delay the handling of AppendEntries on one
follower and, from the entry that adds it, on the new OM. Only timing is
changed. The leader steps down with {{LOST_MAJORITY_HEARTBEATS}}, the bootstrap
fails with {{RATIS_BOOTSTRAP_ERROR}} "Failed to add OM omNode-bootstrap-1 in
Ratis ring.", and one to two seconds later, after a new leader is elected, all
three existing OMs have a stable Raft configuration with four voters that
include the new OM, whose process has ended. A passing test means the defect is
present.
Case 2 is the first regression test in the patch.
h3. Suggested fix
The attached patch changes only the new OM. It narrows the window and does not
close it.
* If the change is committed later than the wait, the new OM still exits. In
case 2 it now exits at the end of the wait instead of at once, so a leader that
finishes just then leaves the voter of case 1 (from the code, not run).
* In case 2 at the default {{ozone.om.client.rpc.timeout}} the first request
has already run for 15 minutes when the retry is refused, so the patch helps
only if the leader finishes within the wait that follows. Raising
{{ozone.om.client.rpc.timeout}} in the config of the new OM avoids the retry in
the first place (from the code, not run).
* The patch can also report success too early. The condition reads only the
state of the new OM, and it depends on the leader handing its current
configuration to a new peer before the log. Ratis 3.3.1 does that with the
snapshot notification the OM uses, once for each peer the leader starts to
bring up to date; it is a property of that implementation, not of the Ratis
API. A process that is started again with an empty disk while the leader is
still bringing the earlier process of the same OM up to date gets the log from
the start without that configuration. If its node ID was a member before and
the leader's log still has those entries, the wait can end for the old
configuration while the leader has not logged the new change. Run on a mini
cluster: the second process reported a completed bootstrap while the latest
entry in the leader's log was the old removal. Forced in that run: one log
entry per request ({{ozone.om.ratis.log.appender.queue.num-elements=1}}), the
second process started 0.6 s after the first had given up, and the old removal
held back with the Ratis test hook. Not run without those. The leader is still
adding the OM at that point, so the report is early, and wrong only if that
attempt fails afterwards.
The alternative to prefer changes the leader side.
{{OzoneManagerRatisServer.addOMToRatisRing}} adds the new OM to the peer list
without looking whether it is already a member. If it answered a repeated add
of a member with success (Ratis already answers a request that changes nothing
with success, once no change is in progress), a bounded retry in
{{OzoneManager.bootstrap}} would take the answer from the leader, and the new
OM would have nothing to infer. From the code, not run and not implemented
here. The attached patch is what the new OM can do alone.
h3. Patch
[^CR-3-wait-for-membership-after-failed-bootstrap-request.patch], against
7fcf31294859d5d31163e016b5a9a63f21c6edd2. It also applies to master at
1cc6423590f (not built there).
After a failed request {{OzoneManager.bootstrap}} logs a warning with the cause
and the bound, and waits for at most
{{ozone.om.ratis.server.failure.timeout.duration}} (default 120s) until the
latest Raft configuration the new OM knows has the OM itself as a voter or
listener, has been applied locally, and is newer than the configuration the OM
had before the request and not older than any configuration it has been
notified of. If that happens the OM logs a second warning and continues its
startup, otherwise it fails as before. A bootstrap that fails for good is
therefore reported up to that much later, which is noted in the description of
the key in {{ozone-default.xml}}. No request or protocol change.
The last two parts of the condition are for a node ID that was a member before.
A new OM with an empty disk can replay old configurations that have it. The
replay starts after the last transaction of the checkpoint it gets from the
leader, so this needs a ring without writes since that membership (from the
code, not run with a write). The leader's current configuration, which Ratis
3.3.1 hands to the new OM before the log, is dropped again by Ratis when the
log is replayed in more than one request. {{OzoneManagerStateMachine}}
therefore keeps the highest configuration index it has been notified of.
Run: with a condition on the Ratis peer list of the OM, such an OM reports a
completed bootstrap for a change that is not committed. With a condition on the
latest configuration being applied alone it does so when the old log reaches it
one entry per request ({{ozone.om.ratis.log.appender.queue.num-elements=1}}).
With the default of 1024 the leader sent the old entries in one request and the
start failed correctly.
Covered in the existing {{TestAddRemoveOzoneManager}}:
* {{testBootstrapWhenRequestFailsWhileOMIsAdded}} (case 2, a voter and then a
listener). Without the change it fails with "Failed to Bootstrap OM. Error
Code: RATIS_BOOTSTRAP_ERROR, Error Message: Reconfiguration is already in
progress".
* {{testBootstrapFailsWhenOnlyOldConfigurationHasOM}}: an OM is bootstrapped,
decommissioned and bootstrapped again with an empty disk, its request fails,
and the log entries from its removal on are held back with the Ratis test hook.
The bootstrap has to fail after the wait with the original exception, while the
latest configuration of the new OM is the old one that has it.
With the change the ten tests of {{TestAddRemoveOzoneManager}} pass and
checkstyle is clean. With the patch the reproduction fails at its first
assertion because the bootstrap returns normally: the new OM logs the second
warning one to two seconds after the failed reply, once the new leader has
committed the change.
Found by code review of the OM HA membership change paths (bootstrap,
decommission and leader transfer), as part of the TLA+ verification effort
under HDDS-15926, on commit 7fcf31294859d5d31163e016b5a9a63f21c6edd2. 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]