Siyao Meng created HDDS-17266:
---------------------------------
Summary: Reconcile container command is not resent when it is lost
before the datanode runs it (lost heartbeat reply, SCM leader change, datanode
restart)
Key: HDDS-17266
URL: https://issues.apache.org/jira/browse/HDDS-17266
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments: TestBugMC3LostHeartbeatReplyDropsCommand.java
h3. Mechanism
{{CommandQueue.getCommand}} removes the commands queued for a datanode when
{{SCMNodeManager.processHeartbeat}} builds the heartbeat response, and
{{SCMDatanodeProtocolServer.sendHeartbeat}} returns them in the reply. Nothing
acknowledges the reply and SCM keeps no copy. If the reply is lost after SCM
processed the heartbeat, the datanode gets an {{IOException}}, does not resend
that heartbeat, and the commands exist nowhere. Hadoop RPC does not resend the
call either.
Delivery is therefore at most once, and the other command types are built for
that: their issuers repair a loss on their own. From reading the code, not
tested:
* DeleteBlocks: the SENT timeout in {{SCMDeletedBlockTransactionStatusManager}}.
* Replicate, delete container, EC reconstruct: ReplicationManager pending op
expiry and re-evaluation.
* Close container: sent again while the container is CLOSING.
* Pipelines: {{BackgroundPipelineScrubber}} and {{PipelineReportHandler}}.
* Operational state, finalize and reregister: regenerated from every heartbeat.
Refresh volume usage: sent on every balancer iteration.
{{ReconcileContainerCommand}} is the exception.
{{SCMClientProtocolServer.reconcileContainer}} fires the event,
{{ReconcileContainerEventHandler}} queues one command per replica, and nothing
tracks or resends them. This is unchanged on master at e0de543cbe9.
h3. Trigger
An admin runs {{ozone admin container reconcile}}, and the command for one
replica is lost before that datanode runs it:
* the heartbeat reply carrying it is lost after SCM built it: the connection is
reset, or SCM answers after the heartbeat RPC timeout
({{ozone.scm.heartbeat.rpc-timeout}}, default 5s).
* the SCM leader changes first. The per datanode {{CommandQueue}} is in the old
leader's memory only, so the new leader has no copy; if the old leader hands
the command out on a later heartbeat, it carries the old term and the datanode
drops it once it knows the new term. A reconcile that already reached the
datanode runs through {{ReplicationSupervisor}} ({{ReconcileContainerTask}}
takes the command's term), which skips queued tasks whose term is lower than
the current leader term ("Ignoring ... since SCM leader has new term"). With a
replication backlog on the datanode, a failover long after the admin command
still discards it.
* the datanode restarts before it runs the command, or its command queue is
full.
For every other command type the new leader's ReplicationManager or report
handlers send the command again after a failover.
h3. Impact
Reconciliation runs per replica: each replica repairs itself from its peers. If
the replica that lost its command is one that needed repair, it stays
unrepaired. The admin command reports that reconciliation was triggered and
points to {{ozone admin container reconcile --status}}, which then keeps
showing that replica's checksum differing from its peers; re-running the
command repairs it. If the replica that lost its command was a healthy one, the
others still reconcile against it and the result converges. No data is lost or
changed. Not exercised: the full admin command on a cluster, the failover
routes (code reading only), and the datanode not reconciling afterwards (it
learns commands only from heartbeat replies).
h3. Reproduction
[^TestBugMC3LostHeartbeatReplyDropsCommand.java] ({{hadoop-hdds/server-scm}},
target path and Maven command in the class comment) passes on unmodified source
when the bug is present. It starts a real SCM with {{HddsTestUtils.getScm}} and
a datanode client built by the real {{SCMConnectionManager}}, connected through
a TCP relay in the test. A reconcile command is queued with the
{{DATANODE_COMMAND}} event that {{ReconcileContainerEventHandler}} fires, and
the relay loses the reply to the next heartbeat, by resetting the connection or
by swallowing it until the client times out. In both cases SCM holds no copy
afterwards and five later heartbeats carry no command. The datanode RPC server
is started explicitly because the HA stub of the test has no Ratis log whose
catch up starts it. The failover routes are not reproduced.
h3. Suggested fix
The fix belongs with the issuer, as for the other command types. The container
reconciliation design doc already plans this for Phase II (HDDS-12987):
"Automate container reconciliation requests as part of SCM's replication
manager". If ReplicationManager triggers reconciliation on a checksum mismatch,
a lost command gets the same repair as every other command type. Any issuer
side tracking has to survive an SCM failover, so it must be re-derivable on the
new leader from replica state (as ReplicationManager state is) or persisted;
tracking kept only in the leader's memory would not cover the failover routes.
There is no completion signal to stop on today (checksums that still differ can
also mean that reconciliation ran and could not repair), so this needs a
decision on what counts as done and how often to retry. Acknowledging heartbeat
replies and redelivering in SCM would be a protocol change for every command
type and would make non-idempotent commands at least once. Documenting that a
reconcile may need to be re-run is the minimal alternative.
Found by TLA+ model checking and code review of the datanode registration and
heartbeat paths under HDDS-15926, on commit
b10a7d427bdf83807a7e48a1be507d3b02d5a503. Checked against HDDS issues and
apache/ozone pull requests for duplicates before filing. Related but not
duplicates: HDDS-10372 (added the reconcile command), HDDS-13086 (blocks
duplicate reconcile requests on the datanode), HDDS-10714 (restrict
reconciliation requests by datanode status), HDDS-8882 (status tracking for
DeleteBlocks) and HDDS-17064 (a full container report lost on a failed
heartbeat, the other direction). Only the reproduction is attached, there is no
fix patch. 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]