Siyao Meng created HDDS-17248:
---------------------------------

             Summary: Deleted keys moved out of a deleted snapshot lose their 
bucket ID, so their purge reduces the usage of a later bucket of the same name
                 Key: HDDS-17248
                 URL: https://issues.apache.org/jira/browse/HDDS-17248
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
MC-2-keep-bucket-id-of-deleted-keys-moved-from-snapshot.patch, 
TestBugMC2SnapshotMoveBucketIdAfterRecreate.java

h3. Mechanism
HDDS-13733 tags each {{deletedTable}} row with the objectID of the bucket the 
key was deleted in, and HDDS-13764 lets {{OMKeyPurgeRequest}} subtract the 
purged size from that bucket's {{snapshotUsedBytes}} and 
{{snapshotUsedNamespace}} only when the tag equals the current bucket's 
objectID. That keeps the purge of a deleted bucket's rows away from a later 
bucket of the same name, except on one path:
# Bucket delete does not wait for the bucket's {{deletedTable}} rows to be 
purged, so rows of the deleted bucket can still be in the active DB when a 
bucket of the same name is created.
# Snapshot create on the new bucket moves the active {{deletedTable}} rows by 
the {{/volume/bucket/}} name prefix into the snapshot ({{OmSnapshotManager}}), 
the earlier bucket's rows included, with their tags.
# When that snapshot is deleted, {{SnapshotDeletingService}} moves the rows to 
the next snapshot or the active DB. {{OMSnapshotMoveTableKeysRequest}} passes 
the current bucket's objectID, and 
{{OMSnapshotMoveTableKeysResponse.addKeysToNextSnapshot}} rebuilds each row 
with {{SnapshotUtils.createMergedRepeatedOmKeyInfoFromDeletedTableEntry}} as 
{{new RepeatedOmKeyInfo(keyInfoList, bucketId)}} when the destination has no 
row of that name. The tag the row carried in the snapshot is dropped.
# {{KeyDeletingService}} purges the rows, the tag now matches the new bucket, 
and {{purgeSnapshotUsedBytes}} / {{purgeSnapshotUsedNamespace}} subtract usage 
that bucket never had. There is no lower bound.

Bucket quota checks in {{OMKeyRequest}} use {{getTotalBucketSpace}} and 
{{getTotalBucketNamespace}} (used plus snapshot used), so the negative value 
lets the bucket accept that much more.

h3. Trigger
A bucket is deleted while some of its deleted keys are not yet purged (a 
{{KeyDeletingService}} backlog), a bucket of the same name is created, and a 
snapshot of the new bucket is created and later deleted before those rows are 
purged.

h3. Impact
Reproduced on unmodified source with three deleted 1 KB keys (replication ONE) 
in an OBJECT_STORE bucket and a new bucket with a namespace quota of 2:
* The rows come back from the snapshot tagged with the new bucket's objectID 
instead of the deleted bucket's.
* After the purge the new bucket has {{snapshotUsedBytes=-3072}} and 
{{snapshotUsedNamespace=-3}}.
* The new bucket then accepted 5 keys under its namespace quota of 2 and 
rejected the 6th ("namespace consumed: 3").
* Without the snapshot create and delete (same sequence otherwise) the new 
bucket stays at 0.

The wrong values are persisted and nothing in normal operation corrects them; a 
quota repair recomputes {{snapshotUsed*}} from the tags and should reset them 
(argued, not run). Shown for OBJECT_STORE keys. FSO deleted files are keyed by 
name in {{deletedTable}} and take the same path (not run); FSO deleted 
directories are keyed by object IDs and are not moved by the name prefix.

h3. Reproduction
[^TestBugMC2SnapshotMoveBucketIdAfterRecreate.java] runs one OM on a mini 
cluster in {{hadoop-ozone/integration-test}} with public client calls only 
(target path and Maven command are in the class comment). The only aid is the 
existing {{KeyDeletingService.suspend()}} / {{resume()}} test hook, which 
stands for the purge backlog. {{deletedKeysOfEarlierBucketPassThroughSnapshot}} 
passes when the bug is present; {{deletedKeysOfEarlierBucketWithoutSnapshot}} 
is the control and passes either way.

h3. Patch
[^MC-2-keep-bucket-id-of-deleted-keys-moved-from-snapshot.patch], against 
20d95ed37fe35013b5320713665db03c06ee36fa. It also applies to master at 
64d4a791502.

{{OMSnapshotMoveTableKeysResponse.addKeysToNextSnapshot}} reads the from 
snapshot's {{deletedTable}} row of each moved key and passes that row's bucket 
ID to {{createMergedRepeatedOmKeyInfoFromDeletedTableEntry}}. Only when the row 
is missing (for example on a replayed flush) does it use the current bucket's 
objectID. Rows deleted in the current bucket keep the same tag as today; rows 
of an earlier bucket keep that bucket's ID, so the existing purge check skips 
them. Rows without a tag keep 0 instead of taking the current bucket's ID: they 
were written before 2.1.0, which added both the tag and {{snapshotUsed*}}, so 
they were never added to {{snapshotUsed*}}, and the purge check and quota 
repair already skip tag 0 in the active DB. The legacy 
{{OMSnapshotMoveDeletedKeysResponse}} re-tags the same way; nothing at this 
commit submits it, and it is not changed. No request or proto change. The 
unused {{bucketId}} field of {{SnapshotMoveKeyInfos}} could carry the tag 
instead, but filling it needs {{KeyManager.getDeletedKeyEntries}} to return it, 
and the response would still have to use it. The cost is one point lookup per 
moved key in the from snapshot's {{deletedTable}}. The patch does not repair 
buckets whose usage is already negative.

The change alters what an OM writes for such a move. With the current upgrade, 
which goes through OM prepare, OMs of two versions do not apply the same 
entries; under zero downtime upgrade this apply change, like others, would need 
a version flag.

Covered by the existing 
{{TestOMSnapshotMoveTableKeysResponse.testMoveTableKeysToNextSnapshot}}, now 
run with rows tagged with an earlier bucket's ID and with untagged rows, with 
and without a next snapshot, and asserting the tag of every row at the 
destination, merged rows included. Without the change all four cases fail (the 
moved rows carry the current bucket's ID, for example "expected: <0> but was: 
<1792590778128>"). With it {{TestSnapshotUtils}}, 
{{TestOMSnapshotMoveTableKeysResponse}}, 
{{TestOMSnapshotMoveTableKeysRequest}}, 
{{TestOMSnapshotPurgeRequestAndResponse}}, 
{{TestOMKeyPurgeRequestAndResponse}}, {{TestSnapshotDeletingService}} and 
{{TestKeyDeletingService}} pass (107 tests), as does 
{{TestSnapshotDeletingServiceIntegrationTest}}, checkstyle is clean, and the 
attached reproduction no longer shows the bug ({{snapshotUsedNamespace}} stays 
0).

Found by TLA+ model checking and code review of the OM volume, bucket and 
snapshot lifecycle paths under HDDS-15926, on commit 
20d95ed37fe35013b5320713665db03c06ee36fa. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. Related but not 
duplicates: HDDS-13733 and HDDS-13764 (added the tag and the purge check this 
path bypasses), HDDS-16126 (fences quota repair deltas by bucket objectID) and 
HDDS-7565 (FSO directory purge of an old bucket updating the new one). 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