You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@ozone.apache.org by "Siyao Meng (Jira)" <ji...@apache.org> on 2026/10/21 12:49:00 UTC
[jira] [Updated] (HDDS-17241) Quota repair can leave bucket snapshotUsedBytes negative when a snapshot purge commits after the repair's checkpoint and before the snapshot scan
[ https://issues.apache.org/jira/browse/HDDS-17241?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Siyao Meng updated HDDS-17241:
------------------------------
Attachment: CR-11-fail-quota-repair-when-snapshot-changes-after-checkpoint.patch
> Quota repair can leave bucket snapshotUsedBytes negative when a snapshot purge commits after the repair's checkpoint and before the snapshot scan
> -------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-17241
> URL: https://issues.apache.org/jira/browse/HDDS-17241
> Project: Apache Ozone
> Issue Type: Bug
> Affects Versions: 2.3.0
> Reporter: Siyao Meng
> Priority: Major
> Attachments: CR-11-fail-quota-repair-when-snapshot-changes-after-checkpoint.patch, TestBugCR11QuotaRepairSnapshotPurgeSkew.java
>
>
> h3. Mechanism
> {{QuotaRepairTask.repairTask}} takes a checkpoint of the active OM DB; the bucket baselines and all active table counts come from it. {{recalculateSnapshotDbPendingDeleteQuota}} instead opens each snapshot DB live through {{OmSnapshotManager.getSnapshot}} and counts its current {{deletedTable}} and {{deletedDirTable}}. The repair sends the computed value minus the baseline for {{snapshotUsedBytes}} and {{snapshotUsedNamespace}}, and {{OMQuotaRepairRequest.updateBucketInfo}} adds it to the current bucket.
> A request that removes entries from a snapshot DB and lowers the bucket's {{snapshotUsed*}} in the same transaction ({{OMKeyPurgeRequest}} or {{OMDirectoriesPurgeRequestWithFSO}} with a snapshot table key, as sent by the deep clean of the deleting services) and commits after the checkpoint but before that snapshot is scanned is then subtracted twice: the scan no longer sees the entries, the baseline still has them, and the live bucket has already been lowered. A purge applied after both deleted tables of that snapshot have been read is harmless. The same undercount happens when a snapshot that is active in the checkpoint is deleted, has its entries moved to the active DB and is purged before the scan reaches it: the scan walks the live snapshot chain, so it skips that snapshot, and the moved entries are in neither the checkpoint's active tables nor any scanned snapshot. Nothing serializes the repair with the deleting services, and the counters are not clamped, so {{OMKeyRequest.checkBucketQuotaInBytes}} and {{checkBucketQuotaInNamespace}} (which use used plus snapshotUsed) see the bucket that much emptier.
> h3. Trigger
> * A bucket with a snapshot that still holds deleted keys or directories that are not yet deep cleaned.
> * A quota repair ({{ozone repair om quota start}}, admin only).
> * A deep clean purge of that snapshot, or its move and purge after a snapshot delete, committing between the checkpoint and the scan of that snapshot. The window covers opening the checkpoint DB, reading the bucket table and scanning the snapshots that come earlier in the scan.
> h3. Impact
> * {{snapshotUsedBytes}} and {{snapshotUsedNamespace}} of the bucket end below their true value by what the purge removed, negative if nothing else is pending (reproduced: -3000 and -1 where 0 and 0 are correct). The value is written to RocksDB and stays until a repair runs without the race.
> * By the code, not run: quota checks undercount the bucket by the same amount, so writes beyond the quota are accepted.
> h3. Reproduction
> [^TestBugCR11QuotaRepairSnapshotPurgeSkew.java] goes into {{hadoop-ozone/ozone-manager}} (target package and Maven command in the class comment) and passes on unmodified source while the bug is present. It deletes a key into a snapshot and runs {{QuotaRepairTask}} with the request it submits captured and applied. The purge is a real {{OMKeyPurgeRequest}} with the snapshot table key; the only control is that it commits on the repair's first call to {{getOmSnapshotManager()}}, which comes from the snapshot scan after the checkpoint. A control with the purge committed before the repair ends at 0 and 0.
> h3. Patch
> [^CR-11-fail-quota-repair-when-snapshot-changes-after-checkpoint.patch], against 20d95ed37fe35013b5320713665db03c06ee36fa. It also applies to master at 41d95dd3286.
> The repair now lists the snapshots of each repaired bucket from the checkpoint's {{snapshotInfoTable}} instead of the live snapshot chain, so a snapshot removed from the chain after the checkpoint is still visited. Every request that changes a snapshot DB or removes a snapshot (key and directory purge, snapshot moves, snapshot delete and purge) already updates the snapshot's {{lastTransactionInfo}} in the snapshot info cache, or removes the entry, when it is applied. After scanning a snapshot DB the repair compares the live value with the one from the checkpoint and fails the run ("Snapshot ... changed after the quota repair checkpoint was taken, run the quota repair again") instead of submitting deltas that mix two points in time; a snapshot that can no longer be opened fails the run as well. A change applied after that comparison cannot have been seen by the scan, and a change flushed before the checkpoint is in both, so neither fails the run. The run fails whenever a snapshot of a repaired bucket changed between the checkpoint and the end of its scan, which includes some changes that would not have skewed the result; rerunning is safe. Deep cleaning is on by default, so on a busy cluster a long repair can fail several times in a row; {{--buckets}} narrows the window to the snapshots of those buckets. Clamping at zero would hide the symptom but still be wrong when other deletes are pending. A possible follow up is to pause the deleting services for the scan instead, by holding {{BOOTSTRAP_LOCK}} from before the checkpoint until the snapshot scan ends, together with a double buffer flush, as {{OMDBCheckpointServlet}} does; that always converges but stalls deletion and follower bootstrap for the length of the scan.
> Covered in the existing {{TestQuotaRepairTask}}: {{testQuotaRepairFailsWhenSnapshotPurgedAfterCheckpoint}} fails without the change ("expected: 0L but was: -3000L"), {{testQuotaRepairFailsWhenSnapshotLeavesChainAfterCheckpoint}} fails without it ("expected: 3000L but was: 0L"), and {{testQuotaRepairSucceedsWhenSnapshotPurgedBeforeCheckpoint}} checks that a purge flushed before the repair does not fail the run. With the change, the quota repair, key and directory purge and snapshot move suites (46 tests) pass, including the existing {{testQuotaRepairSnapshotDbDeletedTableQuota}} for the case without a race, as does {{TestOzoneRepairShell}} on a mini cluster, and checkstyle is clean. With the patch the race test of the reproduction fails because the repair run fails, and its control passes.
> Found by code review of the OM volume, bucket and snapshot lifecycle paths, as part of the TLA+ verification effort under HDDS-15926, on commit 20d95ed37fe35013b5320713665db03c06ee36fa. 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: issues-unsubscribe@ozone.apache.org
For additional commands, e-mail: issues-help@ozone.apache.org