Siyao Meng created HDDS-16850:
---------------------------------

             Summary: After an OBS or LEGACY rename and recreate of an hsync'ed 
key, the hard lease auto commit empties the new key and deletes blocks still in 
use
                 Key: HDDS-16850
                 URL: https://issues.apache.org/jira/browse/HDDS-16850
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
MC-5-reject-close-of-hsync-key-replaced-without-marker.patch, 
TestBugMC5RenameRecreateAutoCommit.java

h3. Mechanism
{{OmMetadataManagerImpl.getExpiredOpenKeys}} treats an open key as hsync'ed 
when its own {{HSYNC_CLIENT_ID}} matches its client id, then builds the auto 
commit from the committed key entry with the same name, without checking that 
this entry is the one that client hsync'ed. 
{{OMKeyCommitRequest.validateAndUpdateCache}} does not check it either: it 
rejects the commit only when the open key carries {{DELETED_HSYNC_KEY}} or 
{{OVERWRITTEN_HSYNC_KEY}}.

{{OMKeyRenameRequest}} (OBJECT_STORE and LEGACY) has no open file check, unlike 
{{OMKeyRenameRequestWithFSO}} (HDDS-8545), and does not touch the writer's open 
key. A key later created at the old name finds no existing entry at commit 
time, so it sets no marker either. The auto commit then commits the abandoned 
open key with the new key's length and block list: 
{{OmKeyInfo.updateLocationInfoList}} drops the new key's block as unknown, the 
abandoned writer's own block is queued as uncommitted, and the new key is 
queued as an old version.

The defect also applies to FILE_SYSTEM_OPTIMIZED buckets. There a rename of an 
open hsync'ed file is rejected, but the open key can still be left unmarked 
while another key takes the name, and the auto commit then pairs them the same 
way.

h3. Trigger
{{ozone.fs.hsync.enabled}} and {{ozone.hbase.enhancements.allowed}} are true 
(both default false), OBJECT_STORE or LEGACY bucket.
# Writer 1 writes {{k1}} and hsyncs, then stops writing without closing.
# {{k1}} is renamed to {{k2}}.
# Writer 2 creates a new {{k1}} and closes it.
# Writer 1's open key reaches {{ozone.om.lease.hard.limit}} (default 7d) and 
the next cleanup run auto commits it.

h3. Impact
* Writer 2's committed {{k1}} is replaced by an entry built from writer 1's 
open key, with writer 2's length and no blocks, and writer 2's block is queued 
in {{deletedTable}}.
* The renamed {{k2}} still references writer 1's block, which is queued in 
{{deletedTable}} too, so {{KeyDeletingService}} sends it to SCM for deletion 
while {{k2}} points at it.
* No client gets an error. Nothing restores the blocks.

h3. Reproduction
PASS, deterministic, unmodified source, for OBJECT_STORE and LEGACY buckets. 
[^TestBugMC5RenameRecreateAutoCommit.java] ({{hadoop-ozone/ozone-manager}}) 
runs one real OM through {{OzoneManagerProtocol}} with a 200 ms hard lease 
limit, uses only public write requests ({{openKey}}, {{allocateBlock}}, 
{{hsyncKey}}, {{renameKey}}, {{commitKey}}) and then calls the real 
{{OpenKeyCleanupTask}} of a second, never started {{OpenKeyCleanupService}} 
once. Output on the OBS bucket: before the cleanup "k1 length 500 blocks 
[13046326171], k2 length 1000 blocks [13046326021], deleted blocks []", after 
it "k1 length 500 blocks [], k2 length 1000 blocks [13046326021], deleted 
blocks [13046326171, 13046326021]". A passing test means the defect is present. 
Physical deletion on datanodes was not run.

h3. Patch
[^MC-5-reject-close-of-hsync-key-replaced-without-marker.patch], against 
a6b7bdb937109ba2893688b41a89470bc95c88cf. It also applies to master at 
b0aa6475b78.

A commit that is not a recovery commit is rejected with {{KEY_NOT_FOUND}} when 
the open key carries the committing client's own {{HSYNC_CLIENT_ID}} and the 
committed entry at its name is not the key that client hsync'ed (or there is 
none). This holds for hsync commits as well as for the close, as for the 
existing {{DELETED_HSYNC_KEY}} and {{OVERWRITTEN_HSYNC_KEY}} markers, and uses 
the same result code. The first hsync of an open key is not affected, because 
the open key gets {{HSYNC_CLIENT_ID}} only in that commit. The check compares 
the client's own id, as {{getExpiredOpenKeys}} does, so an open key that 
carries another client's {{HSYNC_CLIENT_ID}} copied with the metadata of an 
open hsync'ed key (S3 {{CopyObject}}, {{ozone sh key cp}}) still commits. The 
auto commit can then no longer commit another key's data, and nothing is queued 
for deletion. A writer whose hsync'ed key was renamed away now gets 
{{KEY_NOT_FOUND}} on its next hsync or close, as it already does after a delete 
or an overwrite, so it can no longer put its blocks back at the old name while 
the renamed key still references them. An open key that was never hsync'ed can 
still overwrite the name. {{OMKeyCommitRequestWithFSO}} gets the same check. No 
request field is added.

The check changes what the apply writes, so it is enabled by a new 
{{OzoneManagerVersion.HSYNC_COMMIT_VALIDATION}} (101) through 
{{OMVersionManager.isAllowed}}. Until the cluster is finalized to that version 
every OM applies the commit as before, so OMs on different software versions 
never apply the same log entry differently, and the defect remains until 
finalization. HDDS-16848 adds the same {{HSYNC_COMMIT_VALIDATION}} constant for 
a related commit check, so whichever lands second drops its copy; the number 
itself is a placeholder to be agreed at merge.

Side effects: the abandoned open key is never cleaned. Every cleanup run 
selects it again, uses one slot of {{ozone.om.open.key.cleanup.limit.per.task}} 
for it and logs the rejected commit as an ERROR with a stack trace. No admin 
command clears it: {{recoverLease}} on the old name returns 
{{KEY_ALREADY_CLOSED}} or {{KEY_NOT_FOUND}}. Deleting it instead would send the 
renamed key's blocks for deletion, so cleaning it is left to a follow up, 
either a scan side skip or a removal that does not queue its blocks. Rejecting 
the rename of an hsync'ed key in OBJECT_STORE and LEGACY buckets removes this 
trigger for new renames and is proposed separately in HDDS-16849, which also 
covers a rename without a recreate (the scan then fails with a 
{{NullPointerException}} before this check is reached); this check also covers 
keys renamed before such a change. The scan hunk in the patch attached to 
HDDS-16768 would send such a leftover open key row (no key table entry) to 
{{DeleteOpenKeys}}, which on OBJECT_STORE and LEGACY buckets queues the renamed 
key's blocks for deletion; limiting that hunk to FSO buckets, or skipping the 
row instead of deleting it, avoids that.

Covered by {{testCommitWhenHsyncedKeyIsReplacedWithoutMarker}} in the existing 
{{TestOMKeyCommitRequest}}, inherited by {{TestOMKeyCommitRequestWithFSO}}. 
With the version finalized it checks that the close and a later hsync of the 
hsync'ed client are rejected and that a client whose open key carries a copied 
{{HSYNC_CLIENT_ID}} can still overwrite the name; before finalization it checks 
that the close is applied as before. Without the change the finalized case 
fails for both layouts with "expected: <KEY_NOT_FOUND> but was: <OK>". With it 
the commit request, commit response, recover lease, key rename, key delete, 
metadata manager, open key cleanup, OM storage, OM version and related request 
and response suites pass (248 tests), {{TestHSync}} passes on a mini cluster 
(42 tests, with both patches), and checkstyle is clean. With the patch the 
reproduction fails because {{k1}} keeps its block and nothing is queued for 
deletion.

Found by TLA+ model checking and code review of the OM open key cleanup and 
hsync lease recovery paths under HDDS-15926, on commit 
a6b7bdb937109ba2893688b41a89470bc95c88cf. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing; related but different 
are HDDS-10631 (block any open key rename) and HDDS-10770 (overwrite of an 
hsync'ed key, which added {{OVERWRITTEN_HSYNC_KEY}}; HDDS-10736 was closed as 
its duplicate). 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