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

Siyao Meng updated HDDS-16891:
------------------------------
    Attachment: TestBugMC8PendingAllocationFailover.java

> After an SCM restart or leader change, SCM has no pending container 
> allocations and grants again the slots reserved for containers not yet written
> --------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16891
>                 URL: https://issues.apache.org/jira/browse/HDDS-16891
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: TestBugMC8PendingAllocationFailover.java
>
>
> h3. Mechanism
> {{PendingContainerTracker}} keeps the reservations in memory, in the 
> {{TwoWindowBucket}} of each {{DatanodeInfo}}. Only the leader fills it: 
> {{ContainerManagerImpl.allocateContainer}} calls 
> {{PipelineManagerImpl.checkSpaceAndRecordAllocation}} before the replicated 
> {{containerStateManager.addContainer}}. Followers apply {{addContainer}}, so 
> they know the container, but they do not record its slot. On restart, 
> {{ContainerStateManagerImpl.initialize}} loads the containers from the SCM DB 
> and nothing records their slots either. Nothing records the slots when an SCM 
> becomes leader. Datanodes create a container on its first write, so until 
> then no storage report counts it as committed. After a leader change or a 
> restart, SCM therefore sees the slots of granted containers that are not 
> written yet as free.
> Every SCM receives the replicated {{addContainer}} and the heartbeats and 
> container reports that datanodes send to all SCMs (followers run the report 
> handlers, which also remove pending entries). The gap is that only the leader 
> records the reservation, before submitting.
> h3. Trigger
> # The leader allocates container C1 on a pipeline whose datanodes have one 
> slot left. C1 is not written yet.
> # Leadership moves to another SCM (transfer, crash, long pause), or the SCM 
> restarts. This includes a single SCM.
> # Once out of safe mode, the leader allocates C2 on the same datanodes.
> h3. Impact
> A leader change or restart drops every unconfirmed reservation on every 
> datanode at once, as if all of them had aged out. Only reservations younger 
> than two roll intervals ({{ozone.scm.pending.container.roll.interval}}, 
> default 5m) and not yet confirmed by a container report are affected, and 
> only datanodes that are down to their last slots can be over granted. The 
> expected effect is that the datanode refuses one of the containers for lack 
> of space when it is created. On a Ratis pipeline the failed create marks the 
> pipeline's {{ContainerStateMachine}} unhealthy, so the pipeline is closed and 
> the in flight writes on it are retried elsewhere. This is argued from the 
> code and was not run. Replica placement after the leader change reads the 
> same empty buckets; that variant was not run either.
> h3. Reproduction
> [^TestBugMC8PendingAllocationFailover.java] 
> ({{hadoop-ozone/integration-test}}) starts a mini cluster with 3 SCMs, 1 OM 
> and 3 datanodes. Each datanode volume uses the existing 
> {{MockSpaceUsageCheckFactory$HalfTera}} (512 GiB) and the container size is 
> 300 GiB, so each datanode has one slot. Only the admin calls 
> {{allocateContainer}} and {{transferLeadership}} are used. The leader grants 
> C1 and holds one pending entry per datanode, and it refuses a second 
> container. After the transfer, once the new leader is ready and out of safe 
> mode, it has C1 OPEN with no replicas, committed space 0 and no pending entry 
> on all three datanodes, and it grants C2 on the same three datanodes. A 
> passing test means the defect is present (under a minute on an unmodified 
> checkout). The restart case was not run.
> h3. Suggested fix
> No patch is attached yet, so that the design can be agreed first. The 
> proposal is a rebuild of the buckets when an SCM becomes the serving leader:
> * Run once the SCM is leader ready and out of safe mode, the point where 
> {{ReplicationManager}} starts. After a restart the datanodes are registered 
> by then, and allocation is refused in safe mode anyway.
> * First drop all pending entries, then seed: for each OPEN container, record 
> a slot on each datanode of its pipeline that has no replica of it.
> * Finish the rebuild before the first allocation is served.
> This covers a leader change, a restart of one SCM or of all of them, a 
> follower that installed a Ratis snapshot, and a newly bootstrapped SCM. The 
> drop also clears stale entries of a returning leader: replication 
> reservations that are not removed when {{ReplicationManager}} clears its 
> pending ops on leader change, and the case below. It keeps the apply path 
> free of node manager calls. Its cost is that it also reserves for OPEN 
> containers that the previous leader had already aged out (for example 
> containers {{getMatchingContainer}} opened ahead of use), so for up to two 
> roll intervals after the rebuild SCM grants fewer containers than before, 
> never more. A datanode that registers after the rebuild starts with an empty 
> bucket, as today.
> The alternative, recording the slot whenever an SCM applies the replicated 
> {{addContainer}}, needs no scan, but it does not cover an SCM restart or a 
> snapshot install, because containers loaded from the SCM DB are not applied 
> again. It also puts a node manager call in the apply path (which Recon runs 
> too), has to avoid counting the leader's own entry twice, and on a lagging 
> follower the container report for a container can arrive before the follower 
> applies its {{addContainer}}, leaving the entry to age out. Waiting for a 
> container report interval after leader ready does not help either, because a 
> container that is not written yet is in no report.
> A related case: when the replicated {{addContainer}} fails after the slot was 
> reserved, the reservation is kept. That is deliberate. 
> {{SCMRatisServerImpl.submitRequest}} gives up after 
> {{ozone.scm.ha.ratis.request.timeout}} while the entry stays in the Raft log, 
> and on step down Ratis (3.3.1) answers the requests already in the leader's 
> log with {{NotLeaderException}} although the next leader may commit them. A 
> timeout never says whether the entry will commit, and a step down reply has 
> the same result code ({{SCM_NOT_LEADER}}) as a definite rejection, so 
> releasing on failure could turn a bounded under grant into an over grant. 
> Such an entry ages out within two roll intervals, or is dropped by the 
> rebuild above when the SCM becomes leader again.
> Found by TLA+ model checking and code review of the SCM pending container 
> allocation tracking under HDDS-15926, on commit 
> b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. Checked against HDDS issues and 
> apache/ozone pull requests for duplicates before filing. 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