Siyao Meng created HDDS-17619:
---------------------------------
Summary: EC under replication handler runs over replication inline
while a reconstruction is pending and can delete one of its sources
Key: HDDS-17619
URL: https://issues.apache.org/jira/browse/HDDS-17619
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments:
MC-5-no-inline-ec-over-replication-while-add-pending.patch,
TestBugMC5InlineDeleteOfReconstructionSource.java
h3. Mechanism
When {{ECUnderReplicationHandler.processAndSendCommands}} gets
{{FAILED_TO_FIND_SUITABLE_NODE}} at one of its stages and the container is also
over replicated, it calls
{{ReplicationManager.processOverReplicatedContainer}}, so that a delete may
free a node for the next run. It does this even when a reconstruction or
replication of the same container is pending, whether it was sent earlier in
the same run or in an earlier run. {{ECOverReplicationHandler}} drops only
replicas with a pending delete from its candidates and picks the copy to delete
by rack crowding
({{SCMCommonPlacementPolicy.replicasToRemoveToFixOverreplication}}). It does
not look at the pending ADD ops, although each one carries the command with its
sources. So when the copy a pending reconstruction reads from sits on the more
crowded rack, SCM deletes it.
HDDS-7092 avoided this: it handed off to the over replication handler only when
no command had been created in the run, because such commands "may attempt to
use sources the over-rep handler would remove". HDDS-8333 (partial EC
reconstruction) dropped that check on purpose, so that over replication can
free a node for the next run after a partial success on a small cluster, but
did not address the source concern;
{{testUnderRepWithDecommissionAndNotEnoughNodes}} was changed to expect the
call while its comment still describes the old behaviour. Commands from earlier
runs were never considered. The patch below reverses the HDDS-8333 choice only
while an add is pending.
h3. Trigger
Example with rs-3-2: index 1 has two copies (for example after a node returned
with an old copy), indexes 2 and 3 one each, indexes 4 and 5 are lost, and only
one node can take a new replica.
# Run 1: the placement policy finds one target, so a partial reconstruction of
index 4 is sent, reading from one of the index 1 copies, index 2 and index 3.
The container is requeued ({{InsufficientDatanodesException}}).
# Run 2, about {{hdds.scm.replication.under.replicated.interval}} (30s) later:
no target for index 5, so the over replication handler runs inline and deletes
one copy of index 1. If that is the reconstruction's source and the
reconstruction has not finished yet, it fails on the datanode, since it needs
all three listed sources.
The same happens within one run when a reconstruction is sent for a missing
index and the decommission or maintenance stage then finds no target.
h3. Impact
* The reconstruction is wasted. SCM is not told about the failure (HDDS-15327),
so it keeps the pending add and does not send a new reconstruction until
{{hdds.scm.replication.event.timeout}} (12 minutes) expires.
* In the example the container was already at zero redundancy; the bug extends
that from one reconstruction to the event timeout plus a second reconstruction.
A further loss in that window loses data.
* It needs a duplicate index, a target shortfall and a pending add at the same
time, so it is not common. Small clusters and clusters short of space or with
overloaded nodes are the likely places.
The datanode failure is argued from {{ECReconstructionCoordinator}}
({{getBlockDataMap}} lists blocks on every source, and with two indexes lost on
rs-3-2 there is no spare source), not run on a real datanode.
h3. Reproduction
Deterministic unit test with the real {{ECUnderReplicationHandler}} and
{{ECOverReplicationHandler}}:
[^TestBugMC5InlineDeleteOfReconstructionSource.java]
({{hadoop-hdds/server-scm}}). {{ReplicationManager}} is mocked to record
commands and to route {{processOverReplicatedContainer}} to the real over
replication handler; the placement policy offers one free node. After run 1 the
test puts the index 1 copy the reconstruction reads from on the crowded rack,
then asserts that run 2 deletes no source. On unmodified source it fails as
below; which index 1 copy (n1 or n1b) becomes the source depends on random node
IDs, and the deleted copy is always that source:
{noformat}
Run 1: reconstruct index 4 on n4, sources [n1 (index 1), n2 (index 2), n3
(index 3)]
Run 2: delete commands to [n1 (index 1)]
[replicas deleted while a reconstruction is pending] Expecting ["n1 (index 1)"]
not to contain [...] but found ["n1 (index 1)"]
{noformat}
With the patch it passes and run 2 sends no delete.
h3. Suggested fix
Skip the inline over replication while the container has a pending add, as in
the patch. A more precise alternative is to drop the sources of pending
reconstruction and replication commands (reachable through
{{ContainerReplicaOp.getCommand}}) from the delete candidates in
{{ECOverReplicationHandler}}. It is larger, and for the duplicated index it
ends the same way, because the existing check refuses to delete the last
remaining candidate of an index.
h3. Patch
[^MC-5-no-inline-ec-over-replication-while-add-pending.patch], against
e4ae41fc4047c99af983020b4f33bd297c9d0ac5. It also applies to master at
3fc2387040f.
{{ECUnderReplicationHandler}} runs the inline over replication only if
{{ECContainerReplicaCount.hasPendingAdd()}} (new accessor) is false. The
replica count holds the pending ops of earlier runs and every command sent in
the current run, so both variants are covered. The inline delete then runs on
the first run in which no add is pending, typically after the reconstruction
completes, so the small cluster case it exists for still progresses, one
reconstruction later than before. While a reconstruction keeps failing and
being resent after each expiry, and a later stage keeps finding no target, the
inline delete keeps waiting, where before it ran on every pass; that delays the
over replication fix, not redundancy. Removal of UNHEALTHY replicas on the same
path is unchanged.
New test {{testOverRepNotHandledWhileReconstructionPending}} and the corrected
{{testUnderRepWithDecommissionAndNotEnoughNodes}} (now {{never()}}, matching
its comment) in {{TestECUnderReplicationHandler}} fail without the change with
{{NeverWantedButInvoked}} on {{processOverReplicatedContainer}}. With it, the
EC handler, replica count, {{ReplicationManager}}, scenario and under
replicated processor suites pass (215 tests) and checkstyle is clean. No mini
cluster test was run.
Found by TLA+ model checking and code review of the SCM EC container
replication and placement repair paths under HDDS-15926, on commit
e4ae41fc4047c99af983020b4f33bd297c9d0ac5. Checked against HDDS issues and
apache/ozone pull requests for duplicates before filing; the nearest is
HDDS-7948, closed as Not A Problem for an occasional source deleted under a
reconstruction, which did not look at this path. 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]