[
https://issues.apache.org/jira/browse/HDDS-16851?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Siyao Meng updated HDDS-16851:
------------------------------
Attachment: MC-2-overwrite-open-key-records-open-time.patch
> Open key cleanup deletes an overwrite in progress when the overwritten key is
> older than the expire threshold, so its commit fails
> ----------------------------------------------------------------------------------------------------------------------------------
>
> Key: HDDS-16851
> URL: https://issues.apache.org/jira/browse/HDDS-16851
> Project: Apache Ozone
> Issue Type: Bug
> Reporter: Siyao Meng
> Priority: Critical
> Attachments: MC-2-overwrite-open-key-records-open-time.patch
>
>
> h3. Mechanism
> When a key is overwritten, {{OMKeyRequest.prepareFileInfo}} builds the open
> key from the existing key with {{dbKeyInfo.toBuilder()}}. It replaces the
> modification time, size, metadata, tags and update ID, but keeps the creation
> time of the existing key. {{OmMetadataManagerImpl.getExpiredOpenKeys}}
> selects an open key that is not hsync'ed when its creation time is older than
> {{ozone.om.open.key.expire.threshold}} (default 7d), and
> {{OMOpenKeysDeleteRequest}} only checks that the open key still exists. So
> when the existing key is older than the threshold, the next
> {{OpenKeyCleanupService}} run deletes the open key of the overwrite, however
> recently it was opened. This applies to createKey and createFile in OBS,
> LEGACY and FSO buckets, which all go through {{prepareFileInfo}}.
> Keeping the creation time is intended for the committed key. An overwrite or
> rewrite keeps the original creation time of the committed key (asserted since
> HDDS-10527), and the committed key takes it from the open key. Only its use
> as the age of the open key is wrong. The description of
> {{ozone.om.open.key.expire.threshold}} also says "if a key has been open
> longer than" the threshold.
> Present since 1.3.0, where {{OpenKeyCleanupService}} (HDDS-4123) started to
> age open keys by creation time.
> h3. Trigger
> # Key k is committed.
> # More than {{ozone.om.open.key.expire.threshold}} later, a client opens k
> again (createKey or createFile) and allocates a block, without hsync.
> # A cleanup run happens before the client closes the key.
> No fault is needed. With the defaults (cleanup every 24h, threshold 7d), any
> overwrite of a key older than 7 days that is still open at a cleanup run is
> affected, so long writes are the most exposed. A writer that uses hsync is
> exposed until its first hsync.
> h3. Impact
> * The open key is deleted and the blocks of the new write are put in
> {{deletedTable}}.
> * The next allocateBlock, hsync or commit of the writer fails with
> {{KEY_NOT_FOUND}} (for the commit: "entry is not found in the OpenKey
> table"). The data of that write is lost and has to be written again. On a
> bucket without versioning the previous version of the key is not changed.
> * On a bucket with versioning enabled, the open key of the overwrite also
> carries the block versions of the live key (the {{prepareFileInfo}} branch
> described in HDDS-16732), so the cleanup queues the committed key's blocks
> for deletion while the key table still references them: committed data of
> that key is lost. Reproduced on the same commit at the OM level. Versioning
> can only be enabled through the Java client API.
> h3. Reproduction
> PASS on unmodified source. [^TestBugMC2OverwriteOpenKeyExpiredEarly.java]
> ({{hadoop-ozone/ozone-manager}}) drives one real OM through
> {{OzoneManagerProtocol}}, with the testing SCM block client and no datanodes.
> The open key expiry is shortened to 2 s and the service interval is set to
> one hour, and the test runs one cleanup pass with {{runPeriodicalTaskNow}}.
> It commits key1, waits until key1 is older than the threshold, then opens an
> overwrite of key1 and a new key2 at the same moment. The cleanup pass, a few
> milliseconds later, deletes only the overwrite and queues its block for
> deletion. The commit of the overwrite then fails with {{KEY_NOT_FOUND}}, key1
> keeps its old size, and key2 commits normally. The only timing dependency is
> the 2 s wait. A passing test means the defect is present.
> h3. Patch
> [^MC-2-overwrite-open-key-records-open-time.patch], against
> a6b7bdb937109ba2893688b41a89470bc95c88cf. It also applies to master at
> b0aa6475b78.
> The open key of an overwrite now records its open time as creation time, the
> same value a new key gets. {{OMKeyCommitRequest}} and
> {{OMKeyCommitRequestWithFSO}} set the creation time of the committed key from
> the key it replaces, so an overwrite or rewrite still keeps the original
> creation time. {{ozone admin om list-open-files}} now shows the open time for
> an overwrite, not the creation time of the overwritten key. Changing the
> expiry check to use the modification time was not chosen, because
> {{OMAllocateBlockRequest}} updates it, which would change expiry for every
> open key.
> Covered by {{testOverwriteOfOldKeyIsNotExpiredOpenKey}} in
> {{TestOMKeyCreateRequest}} and an extra assertion in
> {{TestOMKeyCommitRequest.testAtomicRewrite}}, both inherited by the FSO
> subclasses. Without the change both fail for both layouts.
> {{TestOMKeyCommitRequest.testValidateAndUpdateCache}} now also asserts that a
> new key keeps the creation time of its open key. Three existing assertions in
> {{TestOMKeyCreateRequest}} that expected the open key to carry the old
> creation time are updated. With the change, these all pass and checkstyle is
> clean:
> * the key create, commit, file create, allocate block, delete, rename, ACL,
> recover lease, purge and open key delete request suites,
> {{TestOmMetadataManager}} and {{TestOpenKeyCleanupService}} (441 tests)
> * the rewrite tests of {{TestOzoneRpcClient}} on a mini cluster (15 tests)
> With the patch the reproduction fails, because the open key of the overwrite
> now carries its own open time.
> The committed creation time now comes from the key present at commit time,
> not the key present when the write was opened, as
> {{S3MultipartUploadCompleteRequest}} already does. The two differ only if the
> key is deleted, or created by another writer, between open and commit. Open
> keys written before the upgrade keep the inherited creation time, so the
> first cleanup run after the upgrade can still delete such an overwrite in
> progress. No version gate is added: during a rolling upgrade, OMs on the old
> and new version store different creation times in the open key row of an
> overwrite (the committed key gets the same value on both except in the corner
> case above). Reviewers may prefer to gate it.
> 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 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]