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

Siyao Meng updated HDDS-17430:
------------------------------
    Affects Version/s: 2.2.1
                       2.0.0
                       1.4.0
                       2.1.2
                       2.1.0
                       1.4.1
                       2.1.1
                       2.2.0

> After a datanode crash, every PutBlock to a container created by a Ratis data 
> stream fails updateBCSID, closing the pipeline on log replay
> ------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17430
>                 URL: https://issues.apache.org/jira/browse/HDDS-17430
>             Project: Apache Ozone
>          Issue Type: Bug
>    Affects Versions: 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: 
> CR-7-re-add-stream-created-container-to-bcsid-map-on-putblock.patch, 
> TestBugCR7StreamContainerMissingFromBcsidMap.java
>
>
> h3. Mechanism
> {{ContainerStateMachine.stream}} dispatches the StreamInit (or 
> StreamInitWithPutBlock) request on every datanode of the pipeline. If the 
> container does not exist, {{HddsDispatcher.dispatchRequest}} creates it and 
> records it in the in memory {{container2BCSIDMap}}. On restart that map is 
> rebuilt only from the last Ratis snapshot 
> ({{ContainerStateMachine.loadSnapshot}}). {{stream}} is not called again when 
> the log is replayed, applying a stream entry for a container that exists adds 
> nothing to the map, and the only re-add on replay is the one HDDS-1843 added 
> for WriteChunk COMMIT_DATA, which a streamed container never has. So after a 
> restart from a snapshot taken before the container was created, every 
> PutBlock for that container on that datanode is applied by the handler and 
> then fails 
> {{Preconditions.checkArgument(container2BCSIDMap.containsKey(containerId))}} 
> in {{HddsDispatcher.updateBCSID}} with an IllegalArgumentException.
> h3. Trigger
> # Ratis data stream is enabled ({{hdds.container.ratis.datastream.enabled}}, 
> off by default) and a streaming write creates a new container.
> # A datanode of that pipeline stops without taking a Ratis snapshot (kill -9, 
> OOM kill, power loss). A graceful stop takes one 
> ({{raft.server.snapshot.trigger-when-stop.enabled}} defaults to true in Ratis 
> 3.3.1 and Ozone does not change it) and hides the problem. Automatic 
> snapshots are taken every {{hdds.ratis.snapshot.threshold}} entries (default 
> 100000), so a recently created container is usually not in the last one.
> # The datanode restarts.
> h3. Impact
> * PutBlock through Raft (the default for streams): the PutBlocks of the 
> stream are Raft log entries of their own, and they can come before the 
> stream's entry in the log. Replaying the first one fails, the datanode's 
> {{ContainerStateMachine}} is marked unhealthy and it asks SCM to close the 
> pipeline. Shown in the reproduction ({{failed to applyTransaction at logIndex 
> 1 for PutBlock}}, then {{pipeline Action CLOSE}}).
> * PutBlock without Raft 
> ({{ozone.client.datastream.putblock.without.raft.enabled}}, master only): the 
> replay succeeds, but every later stream PutBlock to that container on the 
> restarted datanode (the one sent in the middle of the stream and the one at 
> close) fails after it has been persisted. The client reports {{Failed to 
> commit PutBlock ... through the data stream}} and retries the block; the 
> aborted block stays behind, since its PutBlock was already persisted. The 
> container stays out of the map until it is closed. Shown in the reproduction.
> * No key data was lost in these runs. By code reading only: while the 
> container is missing from the map, later snapshots miss it too, so its BCSID 
> is not validated on restart, losing it would not mark it missing, and 
> {{notifyGroupRemove}} does not quasi close it.
> h3. Reproduction
> [^TestBugCR7StreamContainerMissingFromBcsidMap.java] 
> ({{hadoop-ozone/integration-test}}) starts a 3 datanode mini cluster whose 
> only Ratis change is 
> {{hdds.ratis.raft.server.snapshot.trigger-when-stop.enabled=false}}, which 
> leaves the snapshot state a kill -9 leaves. It writes a 600 byte stream key 
> into a new container (small buffers, so one PutBlock is sent in the middle of 
> the stream), restarts a follower without waiting for it to go stale, waits 
> for its log replay and writes a second key to the same container. It runs 
> once with PutBlock through Raft and once without. On the unfixed commit both 
> fail: the first at {{state machine of the restarted datanode after log 
> replay}}, the second at {{container2BCSIDMap of the restarted datanode after 
> the second key}} (empty, read by reflection). With the patch both pass. A 
> failing test means the defect is present. A real process kill was not run.
> h3. Patch
> [^CR-7-re-add-stream-created-container-to-bcsid-map-on-putblock.patch], 
> against 7212465c0bb19615708c144238317db51c2a696d. It also applies to master 
> at ae71e2dd50c.
> {{HddsDispatcher.dispatchRequest}} already puts an existing container that is 
> missing from the map back with its current BCSID, but only for WriteChunk 
> COMMIT_DATA (HDDS-1843). The patch runs that same block for PutBlock and 
> PutSmallFile whenever the request carries {{container2BCSIDMap}} (Raft apply, 
> stream command, stream link), which are the commands {{updateBCSID}} checks. 
> The re-add now uses {{putIfAbsent}}, because stream PutBlocks do not run on 
> the apply thread and a plain {{put}} could overwrite a newer BCSID. This puts 
> the container back into the map on the first PutBlock to it after the 
> restart: during log replay with PutBlock through Raft, at the next stream 
> PutBlock without Raft. It changes nothing when the container is already in 
> the map or does not exist. Re-adding only when the stream's own entry is 
> applied would not be enough, because the Raft PutBlocks can precede it in the 
> log. With PutBlock without Raft the log has no PutBlock, so after replay the 
> container stays out of the map until the next stream PutBlock to it; 
> re-adding it when a stream entry is applied is left as a follow up.
> Covered by 
> {{TestHddsDispatcher#testPutBlockReAddsStreamCreatedContainerAfterRestart}} 
> for PutBlock in the apply, stream command and stream link contexts and for 
> PutSmallFile in the apply context. Without the change all four fail with the 
> IllegalArgumentException from {{updateBCSID}}. With it 
> {{TestHddsDispatcher}}, {{TestContainerStateMachineLeader}}, 
> {{TestContainerStateMachineFollower}}, {{TestKeyValueHandler}}, 
> {{TestKeyValueHandlerWithUnhealthyContainer}} and {{TestContainerSet}} (177 
> tests) and, on a mini cluster, {{TestContainerStateMachineFailures}} and 
> {{TestBlockDataStreamOutput}} (29 tests) pass, and checkstyle is clean.
> Found by code review of the datanode streaming PutBlock paths, as part of the 
> TLA+ verification effort under HDDS-15926, on commit 
> 7212465c0bb19615708c144238317db51c2a696d. 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
(v10.3.26#10030026)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to