[
https://issues.apache.org/jira/browse/HDDS-17519?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-17519:
------------------------------
Affects Version/s: 2.2.1
2.0.0
1.4.0
2.1.2
1.1.0
0.5.0
1.0.0
1.2.1
1.2.0
2.1.0
1.4.1
2.1.1
1.3.0
2.2.0
> After a long SCM pause the node health checker marks live datanodes STALE or
> DEAD, as it skips only one run and misses some pauses
> ----------------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-17519
> URL: https://issues.apache.org/jira/browse/HDDS-17519
> Project: Apache Ozone
> Issue Type: Bug
> Affects Versions: 0.5.0, 1.0.0, 1.1.0, 1.2.0, 1.3.0, 1.2.1, 1.4.0, 2.0.0,
> 1.4.1, 2.1.0, 2.2.0, 2.1.1, 2.1.2, 2.2.1
> Reporter: Siyao Meng
> Priority: Major
> Attachments: MC-4-hold-off-node-health-checks-after-scm-pause.patch,
> TestBugMC3PauseInsideHealthCheck.java, TestBugMC4PauseHoldOff.java
>
>
> h3. Mechanism
> HDDS-1454 added {{NodeStateManager.shouldSkipCheck}} so that an SCM pause
> (GC, VM suspend, live migration, SIGSTOP) does not mark every datanode stale
> and close all pipelines. {{NodeStateManager.run}} skips the health check when
> the time since the previous run is at least {{min(stale, dead)}} interval.
> Three gaps remain:
> # *One skipped run is too short.* After a detected pause the next run comes
> one checker interval later ({{ozone.scm.heartbeat.thread.interval}}, 3s),
> while datanodes heartbeat every {{hdds.heartbeat.interval}} (30s). A datanode
> whose last heartbeat before the pause is older than the stale interval and
> whose next heartbeat is processed more than 3s after resume is marked STALE
> by that run, and DEAD by the next one if the pause was at least the dead
> interval. A heartbeat sent during the pause can be lost: the datanode's call
> fails after {{ozone.scm.heartbeat.rpc-timeout}} (5s), the proxy's retry
> policy only covers connection setup, and the datanode retries at its next
> heartbeat.
> # *Pauses just under the stale interval are not detected.* A datanode can be
> up to one heartbeat interval into its cycle when SCM pauses, so a pause
> between the stale interval minus one heartbeat interval and the stale
> interval (4.5 to 5 min by default) makes it look stale, but is not skipped.
> The checker task was due during the pause, so it runs at the moment SCM
> resumes.
> # *The pause check and the deadlines read the clock separately.*
> {{shouldSkipCheck}} reads the clock, then {{checkNodesHealth}} reads it again
> for the stale and dead deadlines. A pause between the two readings is not
> detected and the deadlines are taken from after the pause.
> {{scheduleNextHealthCheck}} has a similar window: a pause after the next run
> is scheduled but before {{lastHealthCheck}} is read starts the next run at
> once with a fresh {{lastHealthCheck}}. These windows are a few statements
> wide, so this case is very unlikely in practice.
> h3. Trigger
> An SCM process that stops for at least the stale interval minus one heartbeat
> interval (4.5 min by default), with datanodes running. Gap 1 also needs a
> datanode whose first heartbeat after resume is processed more than one
> checker interval after resume. By reading the code (not run on a real
> transport):
> * With Kerberos, the SASL handshake cannot complete while SCM is stopped, so
> nothing is queued at SCM. The datanode retries the handshake
> ({{ipc.client.connect.max.retries.on.sasl}}, 5) with a random backoff of up
> to 5s, so its first heartbeat after resume can come more than 3s later.
> * With default SIMPLE authentication a heartbeat written to an accepted
> connection stays in SCM's socket buffer and is processed after resume, with
> SCM's own clock, so it counts as fresh. Every heartbeat opens a new
> connection, because {{ipc.client.connection.maxidletime}} (10s) is shorter
> than the heartbeat interval and a timed out call also closes its connection,
> so each datanode adds about one entry per heartbeat interval to SCM's accept
> queue while SCM is stopped. That queue holds the smaller of
> {{ipc.server.listen.queue.size}} (256 by default) and the kernel's
> {{net.core.somaxconn}}. Gap 1 is therefore masked for clusters with fewer
> datanodes than that, and otherwise only the datanodes whose first connection
> during the pause did not fit are exposed; they reconnect on the TCP and IPC
> connect retry schedule.
> * Gap 2 does not depend on the transport: the run at resume races the
> processing of the queued heartbeats. It is the main route with SIMPLE
> authentication.
> h3. Impact
> A STALE transition makes {{StaleNodeHandler}} close every pipeline of the
> datanode. A DEAD transition makes {{DeadNodeHandler}} close its containers,
> destroy its pipelines, remove its replicas from SCM's container map, notify
> ReplicationManager and clear its command queue. The datanode returns to
> HEALTHY with its next heartbeat, but none of this is undone. In a cluster
> wide pause this hits many datanodes at once, which is the outcome HDDS-1454
> set out to prevent. The DEAD outcome needs a pause of at least the dead
> interval and more than two checker intervals (6s) of silence after resume,
> which was shown only with heartbeats withheld, not on a real transport. With
> SIMPLE authentication the reachable outcome is mainly STALE.
> h3. Reproduction
> Two self contained classes for {{hadoop-hdds/server-scm}} (target path and
> Maven command in each class comment). They run a real SCM from
> {{HddsTestUtils.getScm}} with its event queue and the real
> {{StaleNodeHandler}} and {{DeadNodeHandler}}. Both pass on unmodified source,
> which means the defect is present.
> * [^TestBugMC4PauseHoldOff.java] uses the existing
> {{SCMNodeManager.pauseHealthCheck}} / {{unpauseHealthCheck}} hook with timing
> scaled to the default ratios (checker 100ms, heartbeat 1s, stale 3s, dead
> 6s). A heartbeat lost during the pause is modelled by not calling
> {{processHeartbeat}}. After a 6.5s pause the check is skipped once, and the
> datanode is DEAD when it heartbeats 611ms after resume; its only replica of a
> CLOSED container is removed (gap 1). After a 2.5s pause that starts 500ms
> into the datanode's heartbeat interval nothing is skipped, and the datanode
> is STALE when it heartbeats 606ms after resume (gap 2). A control with the
> heartbeat right after the skipped run keeps the datanode HEALTHY and the
> replica.
> * [^TestBugMC3PauseInsideHealthCheck.java] models a pause between the two
> clock readings by mocking {{Time.monotonicNow}} on the test thread, so that
> the second reading in one {{run}} is later than the first by more than the
> stale interval, and calls {{run}} on three datanodes that have just
> heartbeated. No check is skipped, all three become STALE and all of their
> pipelines (five) are CLOSED (gap 3). The same jump before the run is skipped
> and changes nothing.
> h3. Patch
> [^MC-4-hold-off-node-health-checks-after-scm-pause.patch], against
> 6752db7978761a749a7d42a72a85311818e3d870. It also applies to master at
> 9118d5a85a3 and on top of the open apache/ozone#11688. It conflicts textually
> with the draft apache/ozone#11349 (HDDS-16627), which replaces the
> {{Time.monotonicNow}} calls in the same methods with an injected clock, so
> whichever lands second needs a small rebase.
> {{NodeStateManager.run}} reads the clock once and uses that reading for the
> pause check, for the deadlines and as {{lastHealthCheck}}, so a pause
> anywhere in a run is part of the gap the next run sees (gap 3). A gap of at
> least the stale interval minus one heartbeat interval counts as a pause (gap
> 2). After a pause, health checks are skipped for one heartbeat interval plus
> the heartbeat RPC timeout (35s by default) instead of for one run, so every
> live datanode has had a heartbeat cycle to report to SCM (gap 1). There is no
> new configuration and no protocol change.
> Recon runs the same checker, but datanodes heartbeat Recon every
> {{hdds.heartbeat.recon.interval}} (60s by default), while the patch takes
> SCM's heartbeat interval. For Recon the change therefore narrows gaps 1 and 2
> without closing them; the harm there is limited to Recon's own view. Passing
> the expected heartbeat interval from the node manager to {{NodeStateManager}}
> is the follow up.
> What changes for a cluster that never pauses: nothing, as its gaps stay near
> the checker interval. After a pause, a datanode that really died is marked
> STALE about 35s after resume instead of 3s, and, if the pause was at least
> the dead interval, DEAD one checker interval later; otherwise DEAD comes at
> its normal time. During the hold off no transition happens at all, so a
> recovered datanode also returns to HEALTHY up to 35s later. A single run that
> itself takes 4.5 min would now also count as a pause. The INFO line is logged
> once per detected pause with the gap and the hold off, instead of "Skipping
> heartbeat checks for one iteration." per skipped run.
> Covered by {{TestSCMNodeManager.testScmHandleJvmPause}}, now parameterized
> with a pause just under and at the stale interval and a heartbeat interval
> longer than the checker interval: half a heartbeat interval after resume both
> datanodes must still be HEALTHY, and the datanode that does not heartbeat
> must still become STALE. And by
> {{TestNodeStateManager.testPauseDuringHealthCheckRunDoesNotMarkNodesStale}}
> for gap 3. Without the change they fail with "We did not skip any heartbeat
> checks", "expected: <2> but was: <0>" and "expected: <HEALTHY> but was:
> <STALE>". With it the SCM node package tests pass (177 tests, including
> {{node.states}}) and checkstyle is clean. With the patch the gap 1 and gap 2
> cases of [^TestBugMC4PauseHoldOff.java] and the inside run case of
> [^TestBugMC3PauseInsideHealthCheck.java] fail, because the datanodes stay
> HEALTHY; the control cases still pass.
> Found by TLA+ model checking and code review of the SCM datanode health
> checker and node state paths under HDDS-15926, on commit
> 6752db7978761a749a7d42a72a85311818e3d870. Checked against HDDS issues and
> apache/ozone pull requests for duplicates before filing; the only related
> issue is HDDS-1454, which introduced the skip. 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]