Skip to content

HDDS-15167. Concurrent Conflict Detection for Conditional MPU Complete#10183

Open
peterxcli wants to merge 2 commits intoapache:masterfrom
peterxcli:feat/concurrent-conflict-detection-for-conditional-mpu-complete
Open

HDDS-15167. Concurrent Conflict Detection for Conditional MPU Complete#10183
peterxcli wants to merge 2 commits intoapache:masterfrom
peterxcli:feat/concurrent-conflict-detection-for-conditional-mpu-complete

Conversation

@peterxcli
Copy link
Copy Markdown
Member

@peterxcli peterxcli commented May 4, 2026

What changes were proposed in this pull request?

relate: #10164

If two or more independent clients send the exact same Complete Multipart Upload request at the same time, only one client should receive a successful response and the others should get a 409. That way, they know they must re-read the object, re-upload the parts if necessary, and then complete the MPU again using the latest ETag they observed.

ref: docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html

If-Match

Uploads the object only if the ETag (entity tag) value provided during the WRITE operation matches the ETag of the object in S3. If the ETag values do not match, the operation returns a 412 Precondition Failed error.
If a conflicting operation occurs during the upload S3 returns a 409 ConditionalRequestConflict response. On a 409 failure you should fetch the object's ETag, re-initiate the multipart upload with CreateMultipartUpload, and re-upload each part.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-15167

How was this patch tested?

UT/IT

https://github.com/peterxcli/ozone/actions/runs/25326550113

Signed-off-by: peterxcli <peterxcli@gmail.com>
@peterxcli peterxcli changed the title HDDS-15167 Concurrent Conflict Detection for Conditional MPU Complete HDDS-15167. Concurrent Conflict Detection for Conditional MPU Complete May 4, 2026
@peterxcli peterxcli added om s3 S3 Gateway AI-gen labels May 4, 2026
@peterxcli peterxcli marked this pull request as ready for review May 4, 2026 16:23
@peterxcli
Copy link
Copy Markdown
Member Author

cc @YutaLin

…ction

Signed-off-by: peterxcli <peterxcli@gmail.com>
Copy link
Copy Markdown
Contributor

@chungen0126 chungen0126 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @peterxcli for the patch. Left some comments.

Comment on lines +1354 to +1375
protected void validateAtomicRewriteAtCommit(OmKeyInfo existing,
OmKeyInfo toCommit, Map<String, String> auditMap) throws OMException {
validateAtomicRewriteAtCommit(existing,
toCommit.getExpectedDataGeneration(), auditMap);
}

/**
* Validates an already admitted condition at serialized commit time.
*
* This form is for callers that must check the admitted generation before
* building the final key info to commit.
*/
protected void validateAtomicRewriteAtCommit(OmKeyInfo existing,
Long expectedGen, Map<String, String> auditMap) throws OMException {
if (expectedGen == null) {
return;
}
validateExpectedDataGeneration(existing, expectedGen, auditMap,
AtomicRewritePhase.COMMIT);
}

private void validateExpectedDataGeneration(OmKeyInfo existing,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like splitting the validation logic into these three methods might be a bit of over-engineering. Could we simplify it?

Comment on lines +1323 to +1328
KeyArgs resolvedKeyArgs = validateAndRewriteIfMatchAsExpectedGeneration(
keyArgs, dbKeyInfo);
if (resolvedKeyArgs.hasExpectedDataGeneration()) {
addRewriteGenerationToAuditMap(
resolvedKeyArgs.getExpectedDataGeneration(), auditMap);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few quick suggestions to streamline the logic:

  1. Could we make validateAndRewriteIfMatchAsExpectedGeneration private if it's not used elsewhere?
  2. The keyArgs.hasExpectedDataGeneration() check inside validateAndRewriteIfMatchAsExpectedGeneration is redundant, as the caller already filters it out.
  3. Consider moving addRewriteGenerationToAuditMap into validateAndRewriteIfMatchAsExpectedGeneration. This will make the logic much cleaner.

Comment on lines +419 to +427
if (omMetadataManager.getMultipartInfoTable().get(multipartKey) == null) {
throw new OMException(
failureMessage(keyArgs.getVolumeName(), keyArgs.getBucketName(),
keyArgs.getKeyName()),
OMException.ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR);
}

OmKeyInfo existingKeyInfo = getExistingKeyInfo(
omMetadataManager, keyArgs);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking if calling rocksdb inside preExecute is a good idea

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling rocksDB inside preExecute is not a big problem. We also check ACLs inside preExecute, it also calls rocksDB.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, great.

@peterxcli
Copy link
Copy Markdown
Member Author

peterxcli commented May 5, 2026

And I think we should apply this concept to conditional put, too.

  • CreateKey.preExecute -> admission preparation
  • CreateKey.validateAndUpdateCache -> serialized admission
  • CommitKey.preExecute -> commit-request admission preparation / best-effort precondition check
  • CommitKey.validateAndUpdateCache -> serialized commit revalidation

cc @ivandika3

@peterxcli peterxcli self-assigned this May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants