[
https://issues.apache.org/jira/browse/HDDS-17435?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-17435:
------------------------------
Attachment:
CR-11-do-not-recreate-missing-container-on-stream-init-replay.patch
> Replaying a stream init log entry after a datanode restart recreates a
> deleted container replica empty, which SCM can count as healthy
> --------------------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-17435
> URL: https://issues.apache.org/jira/browse/HDDS-17435
> Project: Apache Ozone
> Issue Type: Bug
> Affects Versions: 2.3.0
> Reporter: Siyao Meng
> Priority: Major
> Attachments:
> CR-11-do-not-recreate-missing-container-on-stream-init-replay.patch,
> TestBugCR11StreamInitReplayRecreatesDeletedContainer.java
>
>
> h3. Mechanism
> {{HddsDispatcher.dispatchRequest}} treats StreamInit and
> StreamInitWithPutBlock as a write stage in every context, so they auto-create
> a missing container also when their log entry is applied by
> {{ContainerStateMachine.applyTransaction}}. For WriteChunk the write stage is
> only WRITE_DATA, and the applied stage (COMMIT_DATA) returns
> CONTAINER_NOT_FOUND for a missing container, as HDDS-935 intended ("ensure
> container creation does not happen as part of applyTransaction of writeChunk
> request in Ratis"). HDDS-935's missing container set only covers containers
> recorded in the last Ratis snapshot, and a replica deleted by SCM is also
> removed from the datanode's container table, so a container created after the
> last snapshot and deleted since is recreated, empty, when the stream entry is
> replayed. For StreamInit, a replayed CloseContainer entry then closes it.
> With PutBlock through Raft the replay then reaches the block's PutBlock
> entry, which fails on the missing chunk file and marks the recreated replica
> UNHEALTHY, so SCM discards it. With
> {{ozone.client.datastream.putblock.without.raft.enabled}} (HDDS-15758,
> apache/ozone#10823, master only) the entry is StreamInitWithPutBlock and
> there is no PutBlock entry. Its replay recreates the replica and then fails
> {{Preconditions.checkArgument}} in {{HddsDispatcher.dispatchRequest}} with an
> IllegalArgumentException, because {{applyTransaction}} does not pass
> {{container2BCSIDMap}} for that type, so the pipeline is closed and the
> replica stays OPEN and empty. After SCM removes the closed pipeline, its
> close handling for the mismatched replica quasi closes it and, since every
> BCSID is 0, force closes it, so the empty replica ends CLOSED. SCM cannot
> tell it apart from the real replicas: {{RatisContainerReplicaCount}} counts
> any CLOSED replica as healthy, and {{RatisOverReplicationHandler}} picks
> excess CLOSED replicas by {{ContainerReplica.hashCode}}.
> h3. Trigger
> # Ratis data stream is enabled ({{hdds.container.ratis.datastream.enabled}})
> and keys are written with
> {{ozone.client.datastream.putblock.without.raft.enabled}}, both off by
> default.
> # A container is created and closed on a pipeline after that pipeline's last
> Ratis snapshot.
> # SCM deletes the closed replica on one pipeline member while the pipeline
> stays open, for example the source of a balancer move, or an excess replica.
> # That datanode stops without taking a snapshot (crash or kill) and restarts,
> so it replays the stream entries for that container.
> h3. Impact
> The datanode ends up with an empty CLOSED replica next to the real ones. SCM
> counts it as a healthy copy, so redundancy is silently lower than reported,
> and when the container is over replicated (for example right after a balancer
> move) SCM may delete a real replica instead of the empty one. A mini cluster
> run of this sequence on the unfixed commit showed exactly that: after the
> restart the replica was OPEN with 0 blocks, after the pipeline was removed it
> was quasi closed and then CLOSED with 0 keys and BCSID 0, and
> ReplicationManager deleted a real replica and kept the empty one (the key
> stayed readable from the other two). That run is not attached; it emulated
> the balancer move with a replicate command and a force delete, emulated the
> crash by restarting the datanode with the stop snapshot disabled, shortened
> the pipeline destroy timeout, the scrub, replication and report intervals to
> seconds, and chose the victim so that a real replica sorts first by
> {{hashCode}}. In released versions (StreamInit with PutBlock through Raft)
> the replay also recreates the container, but the replica ends UNHEALTHY, so
> they are not affected in the same way.
> h3. Reproduction
> [^TestBugCR11StreamInitReplayRecreatesDeletedContainer.java]
> ({{hadoop-hdds/container-service}}) uses a real {{HddsDispatcher}} and
> {{ContainerStateMachine}} with a mocked Ratis server. {{stream}} creates the
> container, {{KeyValueHandler.deleteContainer}} removes it, a new state
> machine with an empty {{container2BCSIDMap}} stands in for a restart from an
> older snapshot, and the stream entry and a CloseContainer entry are replayed
> through {{applyTransaction}}. On the unfixed commit both cases fail: for
> StreamInit the container comes back CLOSED with 0 blocks and the state
> machine stays healthy; for StreamInitWithPutBlock it comes back OPEN with 0
> blocks and the apply fails with the IllegalArgumentException described above.
> With the patch both pass: no replica is recreated and the apply fails with
> "ContainerID 1 does not exist". A failing test means the defect is present.
> It does not run an SCM delete, a real restart or ReplicationManager.
> h3. Patch
> [^CR-11-do-not-recreate-missing-container-on-stream-init-replay.patch],
> against 7212465c0bb19615708c144238317db51c2a696d. It also applies to master
> at ae71e2dd50c. If the patch of HDDS-17430 lands first, this one needs a
> trivial rebase in {{TestHddsDispatcher}} (adjacent imports and test method).
> The patch counts StreamInit and StreamInitWithPutBlock as a write stage only
> when the dispatcher context is not APPLY_TRANSACTION. The stream setup still
> creates the container as before, applying the entry for an existing container
> is unchanged, and applying it for a missing container now returns
> CONTAINER_NOT_FOUND like a replayed WriteChunk or CloseContainer: the state
> machine becomes unhealthy and the pipeline is closed, with no replica
> recreated. This also removes the IllegalArgumentException on a
> StreamInitWithPutBlock replay for a missing container: the precondition is
> only reached after an auto create, which no longer happens when the entry is
> applied.
> A stream init entry is written to a peer's Raft log only after that peer's
> own {{stream()}} has completed, and {{stream()}} creates the container: Ratis
> links the entry to the local stream before writing it
> ({{ContainerStateMachine.link}} fails when the stream is missing) and Ozone
> syncs state machine data before the log write. So the entry can only be
> applied for a missing container after the replica was removed, as for a
> replayed WriteChunk. The patch reverses the effect of HDDS-6592, which let
> the StreamInit replay for a missing container pass by recreating it. SCM is
> not changed: once every replica is CLOSED with BCSID 0 it has no sound signal
> to prefer one.
> Covered by
> {{TestHddsDispatcher#testStreamInitCreatesContainerOnlyWhenStreamStarts}} for
> both types, with and without {{container2BCSIDMap}} in the apply context.
> Without the change it errors with the IllegalArgumentException; with it the
> {{HddsDispatcher}}, {{ContainerStateMachine}}, {{KeyValueHandler}} and
> {{ContainerSet}} unit suites pass, {{TestBlockDataStreamOutput}} passes, and
> checkstyle is clean. The mini cluster sequence above, rerun with the patch,
> leaves no replica on the restarted datanode and the three real replicas in
> place.
> Related: HDDS-17430 (a replayed PutBlock for a container created by a data
> stream fails {{updateBCSID}}) is on the same replay path; its fix does not
> interact with this one.
> 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]