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

Siyao Meng updated HDDS-17467:
------------------------------
    Attachment: TestBugCR11SnapshotMoveThenRenameOneBatch.java

> snapshotRenamedTable entry moved into the active DB from a deleted snapshot 
> is overwritten by a rename response flushed in the same batch
> -----------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17467
>                 URL: https://issues.apache.org/jira/browse/HDDS-17467
>             Project: Apache Ozone
>          Issue Type: Bug
>    Affects Versions: 1.4.0, 2.0.0, 1.4.1, 2.1.0, 2.2.0, 2.1.1, 2.1.2, 2.2.1
>            Reporter: Siyao Meng
>            Priority: Major
>         Attachments: CR-11-flush-snapshot-move-table-keys-in-own-batch.patch, 
> TestBugCR11SnapshotMoveThenRenameOneBatch.java
>
>
> h3. Mechanism
> When the latest snapshot of a bucket is deleted, {{SnapshotDeletingService}} 
> moves the entries of its {{snapshotRenamedTable}} out of the snapshot with 
> SnapshotMoveTableKeys. Each entry maps {{/volume/bucket/objectID}} to the 
> name the object has in the previous snapshot. With no next snapshot, 
> {{OMSnapshotMoveTableKeysResponse.addToDBBatch}} puts these entries into the 
> active DB {{BatchOperation}} on the double buffer flush thread. The request 
> adds no cache entries for them. The legacy 
> {{OMSnapshotMoveDeletedKeysResponse}} (SnapshotMoveDeletedKeys) puts moved 
> entries the same way.
> {{OMKeyRenameResponse.addToDBBatch}}, 
> {{OMKeyRenameResponseWithFSO.addToDBBatch}} and 
> {{OMKeysRenameResponse.addToDBBatch}} decide on the same thread whether to 
> record a rename: they read the entry with {{snapshotRenamedTable.get}} and 
> put their source name only if the read returns null. That read only returns 
> data from batches that are already committed. When a rename response of an 
> object comes after the move response in the same flush batch, the read misses 
> the moved entry, returns null, and the rename puts its source name over it. 
> The active DB entry then holds the object's name in the deleted snapshot 
> instead of its name in the previous snapshot. Flushed one transaction at a 
> time, the rename would read the moved entry and write nothing.
> 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 move response without a next snapshot carries an entry for an object, and 
> the active DB has no entry of its own for that object when the move is 
> flushed (the object was renamed between the previous snapshot and the deleted 
> one, and not since).
> # A rename response for that object follows the move response in the same 
> double buffer batch.
> A rename response before the move response in the same batch is harmless: the 
> move's put comes later and overwrites it, as intended.
> h3. Impact
> * The deleted snapshot is purged once its tables are empty, and the previous 
> snapshot is then the latest of the bucket. 
> {{KeyManagerImpl.getPreviousSnapshotOzonePathInfo}} looks the object up in it 
> under the stored name, for {{ReclaimableKeyFilter}} and 
> {{ReclaimableDirFilter}}. The previous snapshot holds the object under its 
> older name, so the lookup finds nothing. {{ReclaimableRenameEntryFilter}} 
> also finds nothing under the stored name and may purge the entry. When the 
> object is later deleted from the active bucket, 
> {{ReclaimableKeyFilter.isReclaimable}} returns true and 
> {{KeyDeletingService}} sends its blocks to SCM for deletion, although the 
> previous snapshot still references them. The object ID check in 
> {{ReclaimableKeyFilter.getPreviousSnapshotKeyInfo}} only runs when the lookup 
> finds a key. Before the purge nothing is reclaimed, because the filter 
> requires the previous snapshots in the chain to be active.
> * At this commit no service submits SnapshotMoveDeletedKeys, so it is reached 
> only by replaying log entries written by a 1.4.x OM. In 1.4.0 and 1.4.1 
> {{SnapshotDeletingService}} submitted it with the rename entries of the 
> versions in the deleted snapshot's {{deletedTable}}. An overwrite keeps the 
> object ID, so such a version can belong to an object that is still live and 
> can be renamed, and the double buffer split only on CreateSnapshot. 1.4.0 and 
> 1.4.1 are therefore affected in this narrower form (the object overwritten 
> after its rename and before the deleted snapshot). For 1.4.x this is from 
> reading the code, not run.
> h3. Reproduction
> [^TestBugCR11SnapshotMoveThenRenameOneBatch.java] 
> ({{hadoop-ozone/ozone-manager}}) uses the snapshot response test base and a 
> real {{OzoneManagerDoubleBuffer}}. The object is {{key}} in snapshot 1 and 
> {{key_b}} in snapshot 2. It builds the real move response for snapshot 2 (no 
> next snapshot) and a rename response {{key_b}} to {{key_c}}, and holds the 
> flush with {{OzoneManagerDoubleBuffer.pause}} while both are added. On 
> unmodified source the active DB entry is {{key_b}}, and 
> {{KeyManagerImpl.getPreviousSnapshotOzoneKeyInfo}} for {{key_c}} finds 
> nothing in snapshot 1, which holds the object as {{key}}. A control that 
> flushes the two responses in separate batches gets {{key}}. A passing test 
> means the defect is present.
> h3. Patch
> [^CR-11-flush-snapshot-move-table-keys-in-own-batch.patch], against 
> a5814528cdc8d3c1b27f259e0db2256b585cb825. It also applies to master at 
> 88119243f99.
> {{OzoneManagerDoubleBuffer.isStandaloneBatchCmdTypes}} now includes 
> SnapshotMoveTableKeys and SnapshotMoveDeletedKeys, as it already does for 
> CreateSnapshot and SnapshotPurge. The move is then committed in a batch of 
> its own, so a rename response after it reads the moved entry from the 
> committed DB and leaves it alone, and a rename response before it is still 
> overwritten by the move. The stored value no longer depends on where the 
> batch boundaries fall around a move. The split is per command type, so it 
> covers the FSO and RenameKeys rename responses too. The cost is up to two 
> extra RocksDB batch commits per move transaction. These transactions are 
> submitted only while deleted snapshots are being cleaned up, and each one 
> already commits and flushes the snapshot DBs it touches.
> The alternative of adding cache entries for the moved entries at apply time 
> was not taken. It would make the entries visible to readers before they are 
> committed and adds cache handling to the move request, for the same result.
> {{TestOMSnapshotMoveTableKeysResponse.testRenameAfterMoveToActiveDbKeepsMovedRenameEntry}}
>  flushes a real move response (either type) and a rename response of the same 
> object through one {{OzoneManagerDoubleBuffer}} flush, in both orders, and 
> asserts that the active DB entry names the object in the remaining snapshot. 
> Without the change both move first cases fail (the entry holds the name from 
> the deleted snapshot). With it, the snapshot move, create, delete and purge 
> suites, the rename response suites, the double buffer suites and 
> {{TestOzoneManagerStateMachine}} and {{TestSnapshotDeletingService}} pass, 
> and checkstyle is clean. Integration tests were not run.
> Found by code review of the OM table cache, double buffer flush and startup 
> paths, as part of the TLA+ verification effort under HDDS-15926, on commit 
> a5814528cdc8d3c1b27f259e0db2256b585cb825. 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
(v10.3.26#10030026)

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

Reply via email to