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

Siyao Meng updated HDDS-16794:
------------------------------
    Attachment: TestBugMC2StaleRunDoublePurge.java

> KeyDeletingService run that outlives a lost and regained OM leadership purges 
> keys again and reduces bucket usage twice
> -----------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16794
>                 URL: https://issues.apache.org/jira/browse/HDDS-16794
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: 
> MC-2-do-not-submit-key-purge-after-term-change-during-run.patch, 
> TestBugMC2StaleRunDoublePurge.java
>
>
> h3. Mechanism
> {{KeyDeletingService.KeyDeletingTask.call}} checks {{shouldRun}} (leader 
> ready) and the flush of the previous purge once, at the start of a run. It 
> then scans {{deletedTable}}, calls SCM {{deleteKeyBlocks}} and submits 
> {{PurgeKeys}}. Nothing ties the request to the leadership under which the 
> scan was made, and Ratis accepts it whenever this OM is the leader at the 
> time of the submit. {{OMKeyPurgeRequest.validateAndUpdateCache}} subtracts 
> the sizes in {{bucketPurgeKeysSize}} from {{snapshotUsedBytes}} and 
> {{snapshotUsedNamespace}} whether or not the keys still exist, and deleting 
> the absent {{deletedTable}} rows is a no-op.
> If the OM loses the leadership and gets it back while a run is between its 
> checks and its submit, and the leader in between has purged the same keys, 
> the usage is subtracted a second time on every OM.
> HDDS-13760 added the flush check "so that duplicate purge requests are not 
> submitted". It covers a second scan on the same OM before the flush, not a 
> run that has already passed the check.
> h3. Trigger
> # OM1 is leader and a key is deleted. A KeyDeletingService run on OM1 passes 
> its checks and scans the key.
> # The run stalls before it submits the purge, for example in the SCM call. 
> OM1 loses the leadership, OM2 becomes leader and its own run purges the key.
> # OM1 becomes leader again.
> # The stalled run continues and submits its purge of the key, which is 
> applied in the new term.
> The stall has to cover both leadership changes and the run of the other 
> leader, which comes on that leader's own schedule, so with the default 
> service interval of 60s it has to last up to about a minute. I expect this to 
> be rare (argued, not measured).
> By history the defect is present since 2.1.0: {{ozone-2.1.0}} is the first 
> release tag in which the purge reduces the bucket usage (HDDS-13764). Not run 
> on older code.
> h3. Impact
> * The pending deletion bytes and namespace of the bucket become too low, 
> negative for a bucket with nothing else pending. 
> {{OmBucketInfo.getTotalBucketSpace}} and {{getTotalBucketNamespace}} include 
> them without a lower bound, so clients see wrong (in the reproduction 
> negative) used bytes and namespace, and the quota checks in {{OMKeyRequest}} 
> admit more than the quota. In the reproduction a bucket with a namespace 
> quota of 1 accepted two keys. The space quota check reads the same total 
> (code reading, not run).
> * The stale request repeats the whole batch of the stalled run, not a single 
> key.
> * The wrong values are written to the bucket table on all OMs and nothing 
> corrects them automatically. The quota repair recomputes these counters when 
> an operator starts it (code reading, not run).
> h3. Reproduction
> PASS, unmodified source. [^TestBugMC2StaleRunDoublePurge.java] 
> ({{hadoop-ozone/integration-test}}) starts a mini cluster with three OMs and 
> one datanode. The deleting services get a long interval and are run by hand 
> with {{runPeriodicalTaskNow}}, and the leadership is moved with 
> {{OzoneManager.transferLeadership}}. The run on the first leader is made by a 
> second {{KeyDeletingService}} on that OM whose SCM client delays the reply of 
> {{deleteKeyBlocks}}, so the run is held in the SCM call. After the sequence 
> above the client reads {{usedBytes = -1024}} and {{usedNamespace = -1}} for 
> the empty bucket, the bucket row holds -1024 pending deletion bytes in 
> RocksDB on all three OMs, and with a namespace quota of 1 the bucket accepts 
> two keys and rejects the third with {{QUOTA_EXCEEDED}}. A passing test means 
> the defect is present.
> h3. Patch
> [^MC-2-do-not-submit-key-purge-after-term-change-during-run.patch], against 
> a6332fb59879b80615b9233e953d6813d03ae7a8. It also applies to master at 
> 3a22acfaa21.
> A run of {{KeyDeletingService}} records the Raft term right after the leader 
> check, before the flush check and the scan, and compares it with the current 
> term immediately before each submit of the run ({{PurgeKeys}} and the 
> snapshot property updates, for the active object store and for the snapshot 
> tasks). If the term is different the request is not submitted and the run 
> ends like a run whose purge failed. The term is read from the same Ratis 
> division info that {{shouldRun}} uses for leader ready. There is no proto 
> change and nothing changes at apply time. The keys stay in {{deletedTable}} 
> and the next run, which starts in the new term, purges them once (asserted in 
> the new test).
> This narrows the window, it does not close it. The comparison is not atomic 
> with the append to the Ratis log, so a run that stalls between the comparison 
> and the append for as long as described under Trigger still purges twice. An 
> earlier form of the reproduction, which holds the run inside the submit call 
> after the comparison, still passes with the patch. The stricter alternative 
> is a new optional field in {{PurgeKeysRequest}} with the term of the run, and 
> a check in {{OMKeyPurgeRequest}} that rejects the request when the term of 
> its log entry is different, which is exact and decided identically on every 
> OM. It is not proposed here because it adds a field without a layout feature 
> gate to an existing request, and in a ring where OMs of two versions apply 
> the same log an OM without the check applies a stale request that an OM with 
> the check rejects, so the OMs disagree.
> Subtracting only for keys that are still in {{deletedTable}} was also 
> considered and not taken. It cannot be decided consistently at apply time, 
> because {{deletedTable}} has no table cache entries and the DB lags the apply 
> by the double buffer, so OMs could decide differently.
> Covered by {{testNoPurgeAfterTermChangeDuringRun}} in the existing 
> {{TestKeyDeletingService}}: the term changes while the run is in the SCM 
> call, the run does not purge, and the next run purges the key once. It fails 
> when the comparison in {{KeyDeletingService.submitRequest}} is removed, 
> because the first run then purges the key. With the patch 173 unit tests of 
> {{hadoop-ozone/ozone-manager}} (deleting service, purge request, open key 
> cleanup, key manager, snapshot manager and quota repair suites) and 22 mini 
> cluster tests (key purging, OM HA with stopped nodes and the snapshot 
> deleting service suite) pass, checkstyle is clean. With the patch the 
> reproduction fails at its first assertion of the buggy outcome, because the 
> stale run is not submitted and the usage stays 0.
> Not covered by the patch: {{DirectoryDeletingService}} submits 
> {{PurgeDirectories}} with the same single check at the start of a run, and 
> {{OMDirectoriesPurgeRequestWithFSO}} also reduces the bucket usage without a 
> presence check (code reading, not run). It submits from its worker threads, 
> so the term would have to be handed to them, and it is left out here. A term 
> change between two requests of one run now stops the later requests, which is 
> one more way into HDDS-16790 (a key purged by one request with its size in 
> the next) until that is fixed.
> The flush check that misses a purge which is not applied yet on a new leader 
> is a different defect and is filed separately. Neither fix covers the other: 
> that one covers a run that starts after a leader change, this one a run that 
> started before it.
> Found by TLA+ model checking and code review of the OM key deletion service 
> and key purge paths under HDDS-15926, on commit 
> a6332fb59879b80615b9233e953d6813d03ae7a8. 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
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to