Siyao Meng created HDDS-17229:
---------------------------------
Summary: A heartbeat that arrives while SCM marks a stale datanode
DEAD leaves it HEALTHY but missing from the network topology
Key: HDDS-17229
URL: https://issues.apache.org/jira/browse/HDDS-17229
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments: CR-10-skip-stale-restore-after-node-marked-dead.patch,
TestBugCR10StaleRestoreTopology.java
h3. Mechanism
In {{NodeStateManager.checkNodesHealth}} the STALE case calls
{{updateNodeState}} twice with the same STALE {{NodeStatus}} snapshot: first
with the dead condition and TIMEOUT, then with the healthy condition and
RESTORE. Each call reads the last heartbeat time again. If the first call marks
the node DEAD, {{updateClusterMap}} removes it from the {{NetworkTopology}}. If
a heartbeat is recorded before the second call
({{SCMNodeManager.processHeartbeat}} calls
{{NodeStateManager.updateLastHeartbeatTime}} on an RPC handler thread, which
does not coordinate with the health check), the second call moves the node to
HEALTHY. It passes the STALE snapshot as the old status, and
{{updateClusterMap}} only adds a node back when the old status is DEAD, so the
node is not added back.
Nothing adds it later. Apart from recovery from DEAD, the topology is only
added to on first registration and on re-registration with a changed address,
ports or version. Further heartbeats and health checks leave it out until the
node dies and recovers again or SCM restarts.
This came with HDDS-16623, which moved topology updates from the event handlers
into {{updateNodeState}}. Before that, {{HealthyReadOnlyNodeHandler}}, which
handled the event fired by RESTORE (then a STALE to HEALTHY_READONLY
transition), added the node to the topology unconditionally, which covered this
case. The defect exists only since HDDS-16623 (fix version 2.3.0, not yet
released), so no released version is affected.
h3. Trigger
# A datanode stops heartbeating, goes STALE, and its last heartbeat becomes
older than the dead interval.
# The health check marks it DEAD and removes it from the topology.
# A heartbeat from the datanode is recorded before the same health check
reaches its RESTORE check.
No fault injection is needed, but the window is narrow: it lies between two
consecutive calls in one iteration of the health check loop (a node state map
update, a topology removal under the topology write lock and an event
dispatch), and only for a node whose heartbeat returns right at the dead
deadline.
h3. Impact
The datanode is HEALTHY in SCM but absent from {{NetworkTopology}}: in the
reproduction {{getNumOfLeafNode}} returns 0 and {{chooseRandom}} returns null
with the node HEALTHY. Placement that picks nodes from the topology (for
example {{SCMContainerPlacementRackAware}} and
{{SCMContainerPlacementRackScatter}}) cannot choose it (argued from code,
placement itself not run). The state is silent and persists until the node goes
DEAD and recovers again, re-registers with changed details, or SCM restarts.
h3. Reproduction
PASS, deterministic, unmodified source. [^TestBugCR10StaleRestoreTopology.java]
({{hadoop-hdds/server-scm}}) uses the real {{NodeStateManager}} and
{{NetworkTopologyImpl}} with default configuration and the periodic health
check paused. It sets the last heartbeat time back past the stale and then the
dead interval, and delivers a heartbeat through
{{NodeStateManager.updateLastHeartbeatTime}} on a separate thread while the
health check thread is held in its DEAD_NODE event publish. Afterwards the node
is HEALTHY, missing from the topology, the events are DEAD_NODE then
UNHEALTHY_TO_HEALTHY_NODE, and five more heartbeats and health checks do not
change that. A passing test means the defect is present.
h3. Patch
[^CR-10-skip-stale-restore-after-node-marked-dead.patch], against
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at
20d95ed37fe.
In the STALE case the RESTORE check now runs only if the TIMEOUT check did not
change the node ({{updateNodeState}} returns whether it applied a transition).
For this interleaving the node stays DEAD until the next health check (3s by
default), so it is handled like a node whose heartbeat arrives just after the
health check: {{DeadNodeHandler}}, which skips a node that is no longer DEAD
when it runs, now processes it as DEAD (closes its containers, destroys its
pipelines, removes its replicas and notifies ReplicationManager), and the next
check moves it to HEALTHY through the DEAD case and adds it back to the
topology. This is the intended outcome, as the node was silent past the dead
interval. Before the patch the RESTORE call ran microseconds after DEAD_NODE
was fired, so the handler usually found the node HEALTHY and skipped it.
Covered by {{testHeartbeatWhileMarkedDeadDoesNotLeaveHealthyNodeOutOfTopology}}
in the existing {{TestNodeStateManager}}, which delivers the heartbeat from the
DEAD_NODE publish. Without the change it fails because the node is not in the
topology. With it, {{TestNodeStateManager}}, {{TestSCMNodeManager}},
{{TestDeadNodeHandler}}, {{TestNodeDecommissionManager}},
{{TestSCMNodeMetrics}}, {{TestNodeReportHandler}} and both
{{TestContainerPlacement}} classes pass (110 tests), and checkstyle is clean.
With the patch the reproduction fails because the node is DEAD after the racing
health check; the regression test shows that the next check adds it back.
HDDS-16633 has the same symptom from a different trigger (re-registration) and
is not changed by this patch.
Found by code review of the SCM datanode decommission and maintenance admin
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]