[ 
https://issues.apache.org/jira/browse/HDDS-16897?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-16897:
------------------------------
    Attachment: MC-6-skip-delete-of-container-added-after-command-received.patch

> Datanode runs a resent DeleteContainerCommand against a replica imported 
> after the command arrived, so a balancer move can leave the container under 
> replicated while reporting COMPLETED
> -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16897
>                 URL: https://issues.apache.org/jira/browse/HDDS-16897
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: 
> MC-6-skip-delete-of-container-added-after-command-received.patch, 
> TestBugMC6StaleDeleteWipesMoveTarget.java
>
>
> h3. Mechanism
> SCM completes a pending DELETE op as soon as a report says the replica is 
> gone ({{ContainerStateManagerImpl.removeContainerReplica}} calls 
> {{ContainerReplicaPendingOps.completeDeleteReplica}}), whichever command 
> removed it. Since HDDS-12127, {{ReplicationManager.opCompleted}} resends an 
> expired DELETE as the same command with a new deadline. If the first copy 
> already ran on the datanode but its DELETED report reaches SCM only after the 
> SCM deadline, SCM resends the command, and the late report then completes the 
> op while the resent copy is still queued on the datanode. SCM no longer knows 
> about that copy.
> {{MoveManager.move}} checks the replicas and pending ops SCM knows of, but 
> not commands already handed to the datanode, so it accepts that datanode as a 
> move target. When the copy has been imported and reported, 
> {{MoveManager.handleSuccessfulAdd}} sees a healthy replica set and deletes 
> the source. The queued command then runs: 
> {{DeleteContainerCommandHandler.handleInternal}} checks only the deadline and 
> the SCM term and deletes by container ID, so it force deletes the replica the 
> move just created.
> h3. Trigger
> With the default timeouts ({{hdds.scm.replication.event.timeout}} 12 minutes, 
> datanode offset 6 minutes):
> # A CLOSED RATIS/THREE container has 4 replicas. ReplicationManager sends a 
> force DELETE to datanode V.
> # V runs it, but cannot reach SCM for long enough that the DELETED report 
> arrives after the 12 minute SCM deadline (so at least about 6 minutes, and 
> less than the dead node interval).
> # The scrubber resends the DELETE to V.
> # V reconnects. The heartbeat carries the late report and the response 
> carries the resent DELETE, which waits behind other work in V's delete 
> container threads (2 by default).
> # Within the 6 minute datanode deadline of the resent command, the balancer 
> moves this container to V, the copy completes and MoveManager deletes the 
> source.
> # The queued DELETE runs on V and deletes the imported replica.
> h3. Impact
> The container is left with 2 of 3 replicas while the balancer is told the 
> move {{COMPLETED}}. ReplicationManager re-replicates from the remaining two, 
> so there is no data loss on its own, only a window with reduced redundancy. 
> The trigger needs a long report delay, a backed up delete queue and the 
> balancer choosing that container and datanode within a few minutes, so it is 
> unlikely.
> h3. Reproduction
> PASS, deterministic, unmodified source. 
> [^TestBugMC6StaleDeleteWipesMoveTarget.java] ({{hadoop-hdds/server-scm}}) 
> runs the real ReplicationManager (over replication through its own health 
> check and {{OverReplicatedProcessor}}), {{ContainerReplicaPendingOps}}, 
> {{ContainerManagerImpl}}, {{IncrementalContainerReportHandler}} and 
> {{MoveManager}}, and gives each datanode a real 
> {{DeleteContainerCommandHandler}}, {{ContainerController}} and 
> {{ContainerSet}}. Only the disk work of the container handler is mocked. The 
> test controls when the delayed report is processed and keeps V's delete 
> thread (1 thread in the test) busy until the moved replica is imported. 
> Output on the unmodified source:
> {noformat}
> late ICR completed (removed) the pending DELETE, DELETE #2 still queued on V 
> (V=d2) | scmReplicas=[d1, d3, d4] pendingOps=[] dnHas=[d1,d3,d4] 
> V.deleteQueue=1
> MoveManager accepted move d1 -> d2 (V=d2) | scmReplicas=[d1, d3, d4] 
> pendingOps=[ADD@d2] dnHas=[d1,d3,d4] V.deleteQueue=1
> ADD completed, MoveManager sent DELETE to source d1 (V=d2) | scmReplicas=[d1, 
> d2, d3, d4] pendingOps=[DELETE@d1] dnHas=[d1,d2,d3,d4] V.deleteQueue=1
> V delete thread drained, delete calls on V = 2 (V=d2) | scmReplicas=[d1, d2, 
> d3, d4] pendingOps=[DELETE@d1] dnHas=[d1,d3,d4] V.deleteQueue=0
> RESULT: move=COMPLETED dnReplicas=2 scmReplicas=[d3, d4] 
> health=UNDER_REPLICATED (RF 3)
> {noformat}
> A passing test means the defect is present. The heartbeat transport and the 
> datanode disk work are simulated.
> h3. Suggested fix
> The patch makes the datanode skip a delete for a container that was not 
> present when the command arrived but is present when it runs. Other ways a 
> queued delete can meet a newer replica (the replica disappears for another 
> reason, such as a failed volume, while the original command is still queued) 
> are not covered. An SCM side alternative would keep a resent DELETE's target 
> excluded from moves until the command's datanode deadline has passed, which 
> needs new state in SCM; it is not part of this patch.
> h3. Patch
> [^MC-6-skip-delete-of-container-added-after-command-received.patch], against 
> b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
> 6818707698c.
> {{DeleteContainerCommandHandler.handle}} records whether the container exists 
> when the command is received. If it did not, and it exists when the command 
> runs, the command is skipped with an INFO log. In every other case the 
> behaviour is unchanged: an absent container is still a no-op, and a replica 
> that was present on receipt is deleted as before, including when the 
> container object is replaced in between (for example by the disk balancer). 
> No protocol change.
> The resend comes from HDDS-12127. HDDS-16447 is the same race on the pending 
> op in the opposite order (a report removes the op, then the resend inserts it 
> again); both follow from SCM tracking a DELETE per target and replica index 
> rather than per command. This patch also keeps the orphan resends described 
> there from deleting a later replica on that datanode, because the container 
> is absent when they arrive. The linked issue covers the other defect of the 
> same resend (sent without rechecking that other copies remain), which needs 
> an SCM side fix.
> Covered by {{testContainerAddedAfterCommandReceivedIsNotDeleted}} and 
> {{testContainerPresentWhenCommandReceivedIsDeleted}} in the existing 
> {{TestDeleteContainerCommandHandler}}. Without the change the first fails 
> ({{NeverWantedButInvoked}} on {{deleteContainer}}). With it the datanode 
> state machine and command handler unit suites pass and checkstyle is clean. 
> With the patch the reproduction fails: the queued DELETE is skipped, the 
> moved replica survives and the container ends with 3 replicas, HEALTHY.
> Found by TLA+ model checking and code review of the SCM container move and 
> balancer paths 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]

Reply via email to