Siyao Meng created HDDS-16848:
---------------------------------

             Summary: Hard lease auto commit built before a later hsync shrinks 
the key below the length OM acknowledged and deletes the acknowledged block
                 Key: HDDS-16848
                 URL: https://issues.apache.org/jira/browse/HDDS-16848
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-1-reject-commit-shorter-than-hsynced-length.patch, 
TestBugMC1StaleAutoCommit.java

h3. Mechanism
{{OmMetadataManagerImpl.getExpiredOpenKeys}} builds the hard lease auto commit 
of an hsync'ed key from the committed key entry ({{keyTable}} or {{fileTable}}) 
as it is at scan time: its {{dataSize}} and its block list. 
{{OpenKeyCleanupService}} submits the {{CommitKey}} requests after the scan, 
one after the other. {{OMKeyCommitRequest.validateAndUpdateCache}} and 
{{OMKeyCommitRequestWithFSO}} then apply the request with no check that the key 
is still in the scanned state. The only checks related to the hsync state are 
that the open key exists, that it has no {{DELETED_HSYNC_KEY}} or 
{{OVERWRITTEN_HSYNC_KEY}} marker and that it has no {{LEASE_RECOVERY}} flag.

If the writer allocates a block and hsyncs between the scan and the apply, the 
stale commit sets the key length back to the scan time value, treats the new 
block as allocated but uncommitted ({{OmKeyInfo.updateLocationInfoList}}), 
moves it to {{deletedTable}} through {{wrapUncommittedBlocksAsPseudoKey}}, and 
removes the open key.

h3. Trigger
{{ozone.fs.hsync.enabled}} and {{ozone.hbase.enhancements.allowed}} are true 
(both default false). Any bucket layout.
# A writer writes into a block and hsyncs.
# Its open key reaches {{ozone.om.lease.hard.limit}} (default 7d). The 
modification time of the open key moves only on block allocation and on the 
first hsync, so this happens to a writer that was silent at OM for 7 days, and 
also to a live writer that stayed inside one block for 7 days (HDDS-16825).
# A cleanup run scans the open key table and builds the auto commit.
# Before that commit is applied, the writer moves to a new block, writes and 
hsyncs. OM acknowledges the longer length.
# The auto commit is applied.

The window in step 4 is the rest of the scan, the {{DeleteOpenKeys}} request of 
the same run, the auto commits submitted before this one and its own Ratis 
round trip. It is narrow, from one Ratis round trip to seconds on a large open 
key table (argued from code, not measured on a cluster).

h3. Impact
* The key is committed with the scan time length. Data OM had acknowledged to 
the writer is no longer readable.
* The acknowledged block is queued in {{deletedTable}}, so 
{{KeyDeletingService}} sends it to SCM for deletion. Nothing restores it.
* The open key is gone, but the stock client only calls OM on the first hsync 
of each block, so further hsyncs into the deleted block keep succeeding against 
the datanodes until the next block allocation or close fails with 
{{KEY_NOT_FOUND}} (argued from client code, not run).

h3. Reproduction
PASS, deterministic, unmodified source, for OBJECT_STORE and 
FILE_SYSTEM_OPTIMIZED buckets. [^TestBugMC1StaleAutoCommit.java] 
({{hadoop-ozone/ozone-manager}}) runs one real OM through 
{{OzoneManagerProtocol}} with a 200 ms hard lease limit. It performs the two 
halves of {{OpenKeyCleanupTask.call}} itself, the real 
{{KeyManager.getExpiredOpenKeys}} scan and the submission of its {{CommitKey}} 
to the OM Ratis server built as the service builds it, and issues the writer's 
allocate block and hsync between them, so no thread has to be paused. Output on 
the OBS bucket: "Acknowledged length 3000 with blocks [13046302476, 
13046302821]; after the auto commit: length 1000 with blocks [13046302476], 
blocks queued for deletion [13046302821]". The FSO bucket gives the same 
result. A passing test means the defect is present. Physical deletion on 
datanodes was not run.

h3. Patch
[^MC-1-reject-commit-shorter-than-hsynced-length.patch], against 
a6b7bdb937109ba2893688b41a89470bc95c88cf. It also applies to master at 
b0aa6475b78.

A commit that is neither an hsync nor a recovery commit, sent by the client 
that holds the hsync'ed key, is rejected with {{INVALID_REQUEST}} when its 
length is smaller than the length of the hsync'ed key in the key table. A stale 
auto commit is exactly this case. A correct client never sends it, because 
{{KeyOutputStream}} only sends growing lengths and the final close is never 
shorter than an earlier hsync. Hsync and recovery commits are not changed, and 
the next cleanup run commits from fresh state if the lease is still expired. 
Both {{OMKeyCommitRequest}} and {{OMKeyCommitRequestWithFSO}} get the check.

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. The version number is a placeholder: it would be the first OM 
version after the 2.x baseline, so a cluster at version 100 reports that 
finalization is needed after upgrading, and other open fixes may claim the same 
number. Which version (or a shared one) to use is for review to decide.

A stricter alternative is to carry the scanned {{updateID}} in 
{{CommitKeyRequest}} and compare it on apply. It would also reject a stale 
commit of equal length (which cannot lose acknowledged data), but it needs a 
new request field and a change to the cleanup service, and it changes what the 
apply writes just as the length check does. The length check uses only state 
the apply already reads and rejects only the case that loses acknowledged data. 
Reading the key again in the service before each submit would only narrow the 
window.

Covered by {{testCommitShorterThanHsyncedKey}} in the existing 
{{TestOMKeyCommitRequest}}, inherited by {{TestOMKeyCommitRequestWithFSO}}, 
once with the version finalized (the stale commit is rejected, a full close is 
accepted) and once before finalization (the stale commit is applied as before). 
Without the change the finalized case fails for both layouts with "expected: 
<INVALID_REQUEST> but was: <OK>". With it the commit request, commit response, 
recover lease, key rename, key delete, metadata manager, open key cleanup, OM 
storage and OM version suites pass (222 tests), {{TestHSync}} passes on a mini 
cluster (42 tests, with both patches), and checkstyle is clean. With the patch 
the reproduction fails at its first assertion because the auto commit is 
rejected and the key keeps 3000 bytes.

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; the closest are 
HDDS-16764 (the same staleness on the {{DeleteOpenKeys}} path) and HDDS-16825 
(how a live writer reaches the hard limit). 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