Siyao Meng created HDDS-17622:
---------------------------------

             Summary: RackScatter placement fails although a rack has a 
writable datanode, because a new rack gets only one round of random picks
                 Key: HDDS-17622
                 URL: https://issues.apache.org/jira/browse/HDDS-17622
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
CR-4-retry-skipped-rack-in-rackscatter-unique-rack-phase.patch, 
TestBugCR4RackScatterSingleRoundOfPicks.java

h3. Mechanism
{{SCMContainerPlacementRackScatter.chooseDatanodesInternal}} first picks one 
node on each rack that holds no replica yet ("unique racks"), then fills the 
rest with a same-rack fallback. For each rack, {{chooseNode}} draws up to 5 
random nodes ({{INNER_LOOP_MAX_RETRY}}) and excludes each one that 
{{isValidNode}} rejects. {{chooseNodesFromRacks}} can retry a rack whose picks 
all missed in a later round ({{skippedRacks}}, ending after 4 rounds without 
progress, {{OUTER_LOOP_MAX_RETRY}} + 1), but the unique-rack phase calls it 
with {{maxOuterLoopIterations = maxReplicasPerRack}}. For EC on at least as 
many racks as replicas that is 1, so a rack gets a single round of 5 picks. If 
those miss, the method throws {{FAILED_TO_FIND_HEALTHY_NODES}} ("Chosen nodes 
size from Unique Racks: 0, but required nodes to choose from Unique Racks: 1 do 
not match"), and the same-rack fallback is not reached.

Full, STALE, decommissioning and maintenance datanodes stay in the network 
topology and are not in the excluded list, so on a rack where most datanodes 
cannot take a replica, 5 picks often miss the ones that can.

h3. Trigger
An EC container needs a target on a new rack, most datanodes of that rack are 
full or not writable, and it is the only rack without a replica, for example a 
cluster with exactly as many racks as the EC width (rs-3-2 on 5 racks). With 
more candidate racks, the first round moves on to the next rack, and the 
placement fails only if every candidate rack misses. For example rs-3-2 with 
index 1 to 4 on four racks and index 5 missing, and a fifth rack with 10 
datanodes of which one is writable: 5 picks without replacement miss it with 
probability 0.5. With 20 unusable datanodes and one writable it is 16/21 (about 
0.76).

h3. Impact
The placement fails although a valid target exists. 
{{ReplicationManagerUtil.getTargetDatanodes}} turns it into 
{{FAILED_TO_FIND_SUITABLE_NODE}}, {{ECUnderReplicationHandler}} rethrows, and 
the container is requeued for the next pass, which draws again. Repair still 
completes, but with failure probability p it takes p/(1-p) extra passes on 
average (the under-replicated queue is processed every 30 s by default): one 
more pass in the first layout above and about three in the second, while the 
container runs with reduced redundancy. Each failed pass also logs an ERROR 
with a stack trace, raises the container's requeue count (which sorts it behind 
others with the same remaining redundancy), and runs the handler's remedies for 
a missing target, such as deleting an UNHEALTHY replica if the container has 
one. The same applies to every caller of the EC policy, for example copies for 
decommission and mis-replication, and EC pipeline creation on the write path 
({{ECPipelineProvider}} calls the same {{chooseDatanodes}} with no used nodes), 
where {{WritableECContainerProvider}} hides most failures by falling back to 
existing open pipelines and a second allocation attempt. Not measured end to 
end; only the per placement failure rate was run.

HDDS-16634 reports the same kind of probe limit in 
{{SCMContainerPlacementRackAware}}, measured as a slower decommission.

h3. Reproduction
[^TestBugCR4RackScatterSingleRoundOfPicks.java] ({{hadoop-hdds/server-scm}}, 
target path and Maven command in the class comment) builds the example above 
with a real {{NetworkTopologyImpl}} and the real policy (only {{NodeManager}} 
is a mock that returns the datanodes): rack4 has 5 full, 4 STALE and 1 writable 
datanode, and rack0 to rack3 each have the used node and 2 free writable nodes. 
It runs 200 placements for one target through 
{{ReplicationManagerUtil.getTargetDatanodes}}. On unmodified source about half 
failed (102 of 200 in one run) with {{FAILED_TO_FIND_SUITABLE_NODE}}, the other 
98 chose the writable datanode of rack4, and none used the free nodes of rack0 
to rack3. The test passes when the defect is present. With the patch all 200 
chose the writable datanode and the test fails at the assertion on the failure 
count.

h3. Patch
[^CR-4-retry-skipped-rack-in-rackscatter-unique-rack-phase.patch], against 
e4ae41fc4047c99af983020b4f33bd297c9d0ac5. It also applies to master at 
3fc2387040f.

The unique-rack phase no longer limits {{chooseNodesFromRacks}} to 
{{maxReplicasPerRack}} rounds (the same-rack fallback already passed 
{{Integer.MAX_VALUE}}), so the existing skipped-rack retry runs: a rack whose 
picks all missed is tried again in the next round, with the rejected nodes 
still excluded, until 4 rounds bring no progress. The round limit parameter, 
now unused, is removed. The per rack limit is unchanged, so no rack gets more 
than {{maxReplicasPerRack}} nodes, and the final {{validateContainerPlacement}} 
check still applies. A placement that succeeded at the old limit is unchanged; 
only placements that used to throw get more picks. A rack with no usable node 
(as in HDDS-17362) still ends in the same exception, after 20 picks instead of 
5, so such a failing placement now logs up to 4 times as many INFO lines from 
{{isValidNode}} and {{chooseNode}} on each pass. This is bounded and rack 
scoped, and the same-rack fallback already behaves this way.

This narrows the failure, it does not remove it: a rack is now searched up to 
20 picks instead of 5. Picks are made without replacement, so a rack of up to 
20 datanodes is always searched completely, and the remaining failure needs a 
larger rack that is almost all unusable. With 20 unusable datanodes and one 
writable, 91 of 2000 placements still failed (1/21 expected), against about 76% 
before. Choosing only among datanodes that can take a replica would remove it; 
that is the direction of HDDS-16634 and out of scope here. Falling through to 
the same-rack fallback instead of throwing, as HDDS-16629 did for 
{{SCMContainerPlacementRackAware}} when no other rack has a writable node, was 
not taken: here another rack does have one, and the fallback would put the 
replica on a rack that already holds one, leaving the container mis-replicated.

Covered by {{chooseOnlyWritableNodeOfMostlyUnusableRack}} in the existing 
{{TestSCMContainerPlacementRackScatter}}: one writable datanode among 10 on the 
only new rack must be chosen in 20 of 20 placements, and when it is made STALE 
too the placement still fails with {{FAILED_TO_FIND_HEALTHY_NODES}}. Without 
the change it fails with the "Unique Racks: 0" exception above. With the 
change, the placement policy, pipeline placement and Replication Manager 
handler suites in {{hadoop-hdds/server-scm}} pass (651 tests) and checkstyle is 
clean.

Found by code review of the SCM EC container replication and placement repair 
paths, as part of the TLA+ verification effort under HDDS-15926, on commit 
e4ae41fc4047c99af983020b4f33bd297c9d0ac5. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. Related but not 
duplicates: HDDS-16634 (the probe limit in {{SCMContainerPlacementRackAware}}), 
HDDS-17362 (RackScatter counts a rack of out-of-service datanodes as available; 
the patch does not change that case beyond the extra picks described above). 
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