Siyao Meng created HDDS-17468:
---------------------------------

             Summary: Rename responses miss an earlier snapshotRenamedTable put 
in the same flush batch, so blocks a snapshot still holds can be reclaimed
                 Key: HDDS-17468
                 URL: https://issues.apache.org/jira/browse/HDDS-17468
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-2-record-first-rename-once-per-flush-batch.patch

h3. Mechanism
In a bucket that has a snapshot, {{snapshotRenamedTable}} maps 
{{/volume/bucket/objectID}} to the name the object has in the latest snapshot, 
and only the first rename after the snapshot may write it. 
{{OMKeyRenameResponse.addToDBBatch}}, {{OMKeysRenameResponse.addToDBBatch}} and 
{{OMKeyRenameResponseWithFSO.addToDBBatch}} enforce that on the double buffer 
flush thread: each reads the entry with {{snapshotRenamedTable.get}} and puts 
its source name only if the read returns null.

Nothing adds cache entries to {{snapshotRenamedTable}}, so that read only 
returns data from batches that are already committed. A put that an earlier 
response added to the current {{BatchOperation}} is not visible to it. When two 
rename responses for the same object are in one flush batch, the second one 
also reads null and its put replaces the first one's. The entry then holds the 
intermediate name, which the object never had in the snapshot, instead of its 
name in the snapshot.

Which value is stored depends on where the batch boundaries fall, so two OMs 
that apply the same log can store different values for the same entry.

h3. Trigger
# A bucket has a snapshot, and an object in it has no {{snapshotRenamedTable}} 
entry yet.
# Two rename responses for that object are flushed in the same double buffer 
batch, the second one renaming from the name the first one produced.
# The object's key is deleted and {{KeyDeletingService}} processes its 
{{deletedTable}} entry.

h3. Impact
* {{KeyManagerImpl.getPreviousSnapshotOzonePathInfo}} looks the deleted key up 
in the snapshot under the stored intermediate name and finds nothing. 
{{ReclaimableKeyFilter.isReclaimable}} returns true, and {{KeyDeletingService}} 
purges the entry and sends its blocks to SCM for deletion, although the 
snapshot still holds the key under its original name with the same object ID 
and blocks. The object ID check in 
{{ReclaimableKeyFilter.getPreviousSnapshotKeyInfo}} runs only when the lookup 
finds a key, so it does not catch this. Once the datanodes delete those blocks, 
the key can no longer be read from the snapshot. That last step was not run: 
the test setup has no datanodes.
* Nothing corrects the wrong entry later. The next snapshot of the bucket 
copies it through its checkpoint, so the snapshot deep clean path reads it as 
well. This was found by reading the code, not run.

h3. Reproduction
The regression tests in the attached patch cover it, and the ones marked below 
fail on unmodified source. All are in {{hadoop-ozone/ozone-manager}}:
* {{TestOMKeyRenameResponse.testSecondRenameKeepsSnapshotName}}, also run 
through {{TestOMKeyRenameResponseWithFSO}}: two rename responses for one key in 
a snapshotted bucket. With both in one batch it fails without the change (the 
entry holds the intermediate name). With a commit between them it passes, as a 
control.
* {{TestOMKeysRenameResponse.testSecondRenameInSameBatchKeepsSnapshotName}}: 
two {{OMKeysRenameResponse}} instances for one key in one batch. Fails without 
the change.
* 
{{TestOMKeyRenameResponseWithFSO.testSecondDirectoryRenameInSameBatchKeepsSnapshotName}}:
 two directory rename responses in one batch. Fails without the change.
* 
{{TestOMKeyRenameResponse.testRenameOfObjectNotCommittedAtSourceRecordsNothing}},
 also run through {{TestOMKeyRenameResponseWithFSO}}: the committed source name 
holds a different object, so no entry may be written. Without the change an 
entry is written.

There is no standalone reproduction.

h3. Patch
[^MC-2-record-first-rename-once-per-flush-batch.patch], against 
a5814528cdc8d3c1b27f259e0db2256b585cb825. It also applies to master at 
88119243f99.

The decision stays on the flush thread, and all three responses get one more 
condition: the committed DB must still hold the same object (same object ID) at 
the rename's source key. The check reads with {{getSkipCache}} from the key 
table, the file table, or for an FSO directory rename the directory table 
(helper {{OMKeyRenameResponse.isCommittedAt}}). If an earlier rename of the 
object is in the same batch, the object got its current name inside that batch. 
The committed table then does not hold it at the source, and the later rename 
leaves the entry that the first rename wrote. So for renames of the same 
object, the stored value no longer depends on where the batch boundaries fall.

The decision is deliberately not moved to apply time. Snapshot create clears 
the bucket's {{snapshotRenamedTable}} range only when its own batch is flushed, 
and it adds no cache entries. A rename applied after a snapshot create but 
before that flush would read the stale entry, write nothing, and leave the 
object without an entry once the range is cleared. That loses blocks the same 
way. On the flush thread, snapshot create is already a batch of its own, so a 
rename after it always reads the cleared range.

A separate, older defect is not addressed here: an 
{{OMSnapshotMoveTableKeysResponse}} that moves entries into the active DB, 
followed in the same batch by a rename of one of those objects, still 
overwrites the moved entry. That is HDDS-17467, whose patch is independent of 
this one.

The patch adds no request or response field and no proto change. Its cost is 
one extra point read per rename, only in a bucket that has a snapshot and only 
while the object has no entry. An object that is created and renamed within one 
batch now gets no entry. It is in no snapshot, so that entry was never used. 
Entries already stored with a wrong name are not repaired.

With the patch, these pass: the rename, key purge, directory purge, snapshot 
create and snapshot move table keys response and request suites, 
{{TestKeyManagerImpl}}, {{TestKeyDeletingService}}, 
{{TestSnapshotDeletingService}}, {{TestReclaimableKeyFilter}} and 
{{TestReclaimableRenameEntryFilter}}. Checkstyle is clean. Integration tests 
were not run.

Found by TLA+ model checking and code review of the OM table cache, double 
buffer flush and startup paths under HDDS-15926, on commit 
a5814528cdc8d3c1b27f259e0db2256b585cb825. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. Related but not 
duplicates: HDDS-8189 and HDDS-8435 (introduced the check), HDDS-17173 (table 
iterators do not return unflushed entries), HDDS-15125 (proposed redesign of 
snapshot reclaimability). 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