Siyao Meng created HDDS-16891:
---------------------------------

             Summary: 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
         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