This is an automated email from the ASF dual-hosted git repository.

smengcl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 3cf259cae14 HDDS-16161. Prevent failed OM requests from changing 
bucket usage (#11216)
3cf259cae14 is described below

commit 3cf259cae14dc9ec6b10fa83f28dffb91624368b
Author: Chi-Hsuan Huang <[email protected]>
AuthorDate: Fri Sep 18 16:09:37 2026 +0800

    HDDS-16161. Prevent failed OM requests from changing bucket usage (#11216)
    
    Generated-by: Claude Code (Opus 5)
---
 .../om/request/file/OMDirectoryCreateRequest.java  |   7 +-
 .../file/OMDirectoryCreateRequestWithFSO.java      |   6 +-
 .../ozone/om/request/file/OMFileCreateRequest.java |   5 +-
 .../request/file/OMFileCreateRequestWithFSO.java   |   5 +-
 .../key/OMDirectoriesPurgeRequestWithFSO.java      |  27 +-
 .../ozone/om/request/key/OMKeyCommitRequest.java   |   5 +-
 .../om/request/key/OMKeyCommitRequestWithFSO.java  |   5 +-
 .../ozone/om/request/key/OMKeyCreateRequest.java   |   7 +-
 .../om/request/key/OMKeyCreateRequestWithFSO.java  |   5 +-
 .../ozone/om/request/key/OMKeyDeleteRequest.java   |   5 +-
 .../om/request/key/OMKeyDeleteRequestWithFSO.java  |   5 +-
 .../ozone/om/request/key/OMKeyPurgeRequest.java    |   8 +-
 .../hadoop/ozone/om/request/key/OMKeyRequest.java  |  26 +-
 .../ozone/om/request/key/OMKeysDeleteRequest.java  |   8 +-
 .../S3ExpiredMultipartUploadsAbortRequest.java     | 333 +++++++++++----------
 .../S3InitiateMultipartUploadRequestWithFSO.java   |   5 +-
 .../multipart/S3MultipartUploadAbortRequest.java   |   5 +-
 .../S3MultipartUploadCommitPartRequest.java        |   6 +-
 .../S3MultipartUploadCompleteRequest.java          |  11 +-
 .../file/TestOMDirectoryCreateRequestWithFSO.java  |  28 ++
 .../om/request/key/TestOMKeyCommitRequest.java     |  34 +++
 .../om/request/key/TestOMKeyCreateRequest.java     |  66 ++++
 .../om/request/key/TestOMKeyDeleteRequest.java     |   8 +
 .../key/TestOMKeyPurgeRequestAndResponse.java      |  65 ++++
 .../om/request/key/TestOMKeysDeleteRequest.java    |  48 +++
 .../key/TestOMKeysDeleteRequestWithFSO.java        |  16 +
 .../TestS3ExpiredMultipartUploadsAbortRequest.java | 141 ++++++++-
 ...estS3InitiateMultipartUploadRequestWithFSO.java |   5 +
 .../TestS3MultipartUploadAbortRequest.java         |  10 +
 29 files changed, 699 insertions(+), 206 deletions(-)

diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequest.java
index 97e3d612c8e..6b7051e5d60 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequest.java
@@ -177,7 +177,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
         List<String> missingParents = omPathInfo.getMissingParents();
         long baseObjId = ozoneManager.getObjectIdFromTxId(trxnLogIndex);
         OmBucketInfo omBucketInfo =
-            getBucketInfo(omMetadataManager, volumeName, bucketName);
+            getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
 
         dirKeyInfo = createDirectoryKeyInfoWithACL(keyName, keyArgs, baseObjId,
             omBucketInfo, omPathInfo, trxnLogIndex,
@@ -193,7 +193,10 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
         OMFileRequest.addKeyTableCacheEntries(omMetadataManager, volumeName,
             bucketName, omBucketInfo.getBucketLayout(),
             dirKeyInfo, missingParentInfos, trxnLogIndex);
-        
+
+        omMetadataManager.getBucketTable().addCacheEntry(
+            omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
         result = Result.SUCCESS;
         omClientResponse = new OMDirectoryCreateResponse(omResponse.build(),
             dirKeyInfo, missingParentInfos, result, getBucketLayout(),
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequestWithFSO.java
index dde1eebd02c..77974fbf354 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequestWithFSO.java
@@ -133,7 +133,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           omDirectoryResult == NONE) {
 
         OmBucketInfo omBucketInfo =
-            getBucketInfo(omMetadataManager, volumeName, bucketName);
+            getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
         // prepare all missing parents
         missingParentInfos = getAllMissingParentDirInfo(
                 ozoneManager, keyArgs, omBucketInfo, omPathInfo, trxnLogIndex);
@@ -157,6 +157,10 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
             volumeId, bucketId, trxnLogIndex,
             missingParentInfos, dirInfo);
 
+        // Publish only here: createDirectoryInfoWithACL above can still fail 
with UNAUTHORIZED.
+        omMetadataManager.getBucketTable().addCacheEntry(
+            omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
         result = OMDirectoryCreateRequest.Result.SUCCESS;
         omClientResponse =
             new OMDirectoryCreateResponseWithFSO(omResponse.build(),
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
index 1fb1c22ebeb..5b91f66cf3f 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequest.java
@@ -227,7 +227,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
 
       // do open key
       omBucketInfo =
-          getBucketInfo(omMetadataManager, volumeName, bucketName);
+          getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
       final ReplicationConfig repConfig = OzoneConfigUtil
           .resolveReplicationConfigPreference(keyArgs.getType(),
               keyArgs.getFactor(), keyArgs.getEcReplicationConfig(),
@@ -277,6 +277,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           bucketName, omBucketInfo.getBucketLayout(),
           null, missingParentInfos, trxnLogIndex);
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       // Prepare response
       omResponse.setCreateFileResponse(CreateFileResponse.newBuilder()
           .setKeyInfo(omKeyInfo.getNetworkProtobuf(getOmRequest().getVersion(),
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequestWithFSO.java
index 6036fe90dbb..18f506cf728 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMFileCreateRequestWithFSO.java
@@ -184,7 +184,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           .collect(Collectors.toList());
       omFileInfo.appendNewBlocks(newLocationList, false);
 
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+      omBucketInfo = getBucketInfoForUpdate(omMetadataManager, volumeName, 
bucketName);
       // check bucket and volume quota
       long preAllocatedSpace =
           newLocationList.size() * ozoneManager.getScmBlockSize() * repConfig
@@ -205,6 +205,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       OMFileRequest.addDirectoryTableCacheEntries(omMetadataManager, volumeId,
               bucketId, trxnLogIndex, missingParentInfos, null);
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       // Prepare response. Sets user given full key name in the 'keyName'
       // attribute in response object.
       int clientVersion = getOmRequest().getVersion();
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMDirectoriesPurgeRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMDirectoriesPurgeRequestWithFSO.java
index 128e13dd7ec..e2ca3a76136 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMDirectoriesPurgeRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMDirectoriesPurgeRequestWithFSO.java
@@ -42,6 +42,7 @@
 import org.apache.hadoop.ozone.audit.AuditLoggerType;
 import org.apache.hadoop.ozone.audit.OMSystemAction;
 import org.apache.hadoop.ozone.om.DeletingServiceMetrics;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OMMetadataManager.VolumeBucketId;
 import org.apache.hadoop.ozone.om.OMMetrics;
 import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
@@ -147,7 +148,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
 
           omMetrics.decNumKeys();
           omMetrics.incNumKeyDeletesInternal();
-          OmBucketInfo omBucketInfo = getBucketInfo(omMetadataManager,
+          OmBucketInfo omBucketInfo = accumulatedBucketInfo(volBucketInfoMap, 
omMetadataManager,
               processed.volumeName, processed.bucketName);
           // bucketInfo can be null in case of delete volume or bucket
           // or key does not belong to bucket as bucket is recreated
@@ -184,7 +185,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           omMetrics.decNumKeys();
           omMetrics.incNumKeyDeletesInternal();
           numSubFilesMoved++;
-          OmBucketInfo omBucketInfo = getBucketInfo(omMetadataManager,
+          OmBucketInfo omBucketInfo = accumulatedBucketInfo(volBucketInfoMap, 
omMetadataManager,
               processed.volumeName, processed.bucketName);
           // bucketInfo can be null in case of delete volume or bucket
           // or key does not belong to bucket as bucket is recreated
@@ -205,7 +206,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           deletedDirNames.add(path.getDeletedDir());
           BucketNameInfo bucketNameInfo = volumeBucketIdMap.get(new 
VolumeBucketId(path.getVolumeId(),
               path.getBucketId()));
-          OmBucketInfo omBucketInfo = getBucketInfo(omMetadataManager,
+          OmBucketInfo omBucketInfo = accumulatedBucketInfo(volBucketInfoMap, 
omMetadataManager,
               bucketNameInfo.getVolumeName(), bucketNameInfo.getBucketName());
           if (omBucketInfo != null && omBucketInfo.getObjectID() == 
path.getBucketId()) {
             omBucketInfo.purgeSnapshotUsedNamespace(1);
@@ -222,6 +223,14 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
       deletingServiceMetrics.incrNumSubFilesMoved(numSubFilesMoved);
       deletingServiceMetrics.incrNumDirPurged(numDirsDeleted);
 
+      // All bucket locks are still held here, and every purge path has been 
processed, so the
+      // accumulated copies can be published as a whole.
+      for (Map.Entry<Pair<String, String>, OmBucketInfo> entry : 
volBucketInfoMap.entrySet()) {
+        omMetadataManager.getBucketTable().addCacheEntry(
+            omMetadataManager.getBucketKey(entry.getKey().getLeft(), 
entry.getKey().getRight()),
+            entry.getValue(), context.getIndex());
+      }
+
       TransactionInfo transactionInfo = 
TransactionInfo.valueOf(context.getTermIndex());
       if (fromSnapshotInfo != null) {
         
fromSnapshotInfo.setLastTransactionInfo(transactionInfo.toByteString());
@@ -269,6 +278,18 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
         getBucketLayout(), volBucketInfoMap, fromSnapshotInfo, openKeyInfoMap);
   }
 
+  /**
+   * Return the copy this request accumulates deltas on, fetching it on first 
use. All three purge
+   * paths can touch the same bucket, so they must share one copy or earlier 
deltas are lost.
+   */
+  private OmBucketInfo accumulatedBucketInfo(Map<Pair<String, String>, 
OmBucketInfo> volBucketInfoMap,
+      OMMetadataManager omMetadataManager, String volumeName, String 
bucketName) {
+    OmBucketInfo omBucketInfo = volBucketInfoMap.get(Pair.of(volumeName, 
bucketName));
+
+    return omBucketInfo != null ? omBucketInfo
+        : getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
+  }
+
   /**
    * Helper class to hold processed key information.
    */
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequest.java
index 6e4386aae5f..24c2b6b3e25 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequest.java
@@ -195,7 +195,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       bucketLockAcquired = getOmLockDetails().isLockAcquired();
 
       validateBucketAndVolume(omMetadataManager, volumeName, bucketName);
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+      omBucketInfo = getBucketInfoForUpdate(omMetadataManager, volumeName, 
bucketName);
 
       // Check for directory exists with same name, if it exists throw error.
       if (LOG.isDebugEnabled()) {
@@ -407,6 +407,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
 
       omBucketInfo.incrUsedBytes(correctedSpace);
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       omResponse.setCommitKeyResponse(CommitKeyResponse.newBuilder()
           .setModificationTime(commitKeyArgs.getModificationTime())
           .build());
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequestWithFSO.java
index 8b3d32bdc1f..8e21cc8a415 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequestWithFSO.java
@@ -130,7 +130,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       bucketLockAcquired = getOmLockDetails().isLockAcquired();
 
       validateBucketAndVolume(omMetadataManager, volumeName, bucketName);
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+      omBucketInfo = getBucketInfoForUpdate(omMetadataManager, volumeName, 
bucketName);
 
       String errMsg = "Cannot create file : " + keyName
               + " as parent directory doesn't exist";
@@ -351,6 +351,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
 
       omBucketInfo.incrUsedBytes(correctedSpace);
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       omResponse.setCommitKeyResponse(CommitKeyResponse.newBuilder()
           .setModificationTime(commitKeyArgs.getModificationTime())
           .build());
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
index 71d49df7bfc..7c1056b51f5 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java
@@ -247,7 +247,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       keyArgs = validateAndRewriteIfMatchAsExpectedGeneration(keyArgs, 
dbKeyInfo);
 
       OmBucketInfo bucketInfo =
-          getBucketInfo(omMetadataManager, volumeName, bucketName);
+          getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
 
       // If FILE_EXISTS we just override like how we used to do for Key Create.
       if (LOG.isDebugEnabled()) {
@@ -333,6 +333,11 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
         OMFileRequest.addKeyTableCacheEntries(omMetadataManager, volumeName,
             bucketName, bucketInfo.getBucketLayout(),
             null, missingParentInfos, trxnLogIndex);
+
+        // Parent directory creation holds the bucket write lock; key path 
locking leaves
+        // numMissingParents at 0.
+        omMetadataManager.getBucketTable().addCacheEntry(
+            omMetadataManager.getBucketKey(volumeName, bucketName), 
bucketInfo, trxnLogIndex);
       }
 
       // Add to cache entry can be done outside of lock for this openKey.
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequestWithFSO.java
index 3642f96c7fc..c94480fb72b 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequestWithFSO.java
@@ -175,7 +175,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
               .collect(Collectors.toList());
       omFileInfo.appendNewBlocks(newLocationList, false);
 
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+      omBucketInfo = getBucketInfoForUpdate(omMetadataManager, volumeName, 
bucketName);
       // check bucket and volume quota
       long preAllocatedSpace =
           newLocationList.size() * ozoneManager.getScmBlockSize() * repConfig
@@ -201,6 +201,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
               volumeId, bucketId, trxnLogIndex,
               missingParentInfos, null);
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       // Prepare response. Sets user given full key name in the 'keyName'
       // attribute in response object.
       int clientVersion = getOmRequest().getVersion();
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequest.java
index 1aa65f36254..6cab024cd96 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequest.java
@@ -160,7 +160,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           CacheValue.get(trxnLogIndex));
 
       OmBucketInfo omBucketInfo =
-          getBucketInfo(omMetadataManager, volumeName, bucketName);
+          getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
 
       long quotaReleased = sumBlockLengths(omKeyInfo);
       // Empty entries won't be added to deleted table so this key shouldn't 
get added to snapshotUsed space.
@@ -186,6 +186,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
         }
       }
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       omClientResponse = new OMKeyDeleteResponse(
           omResponse.setDeleteKeyResponse(DeleteKeyResponse.newBuilder())
               .build(), omKeyInfo,
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java
index 769b2e43a5b..c0c83f5c4f4 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyDeleteRequestWithFSO.java
@@ -157,7 +157,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
                 CacheValue.get(trxnLogIndex));
       }
 
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+      omBucketInfo = getBucketInfoForUpdate(omMetadataManager, volumeName, 
bucketName);
 
       long quotaReleased = sumBlockLengths(omKeyInfo);
       // Empty entries won't be added to deleted table so this key shouldn't 
get added to snapshotUsed space.
@@ -188,6 +188,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
         auditMap.put(OzoneConsts.REPLICATION_CONFIG, 
omKeyInfo.getReplicationConfig().toString());
       }
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       omClientResponse = new OMKeyDeleteResponseWithFSO(omResponse
           .setDeleteKeyResponse(DeleteKeyResponse.newBuilder()).build(),
           keyName, omKeyInfo,
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyPurgeRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyPurgeRequest.java
index d4da86ef090..01d009f3cc7 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyPurgeRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyPurgeRequest.java
@@ -142,7 +142,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
         deletingServiceMetrics.setLastAOSTransactionInfo(transactionInfo);
       }
       List<OmBucketInfo> bucketInfoList = 
updateBucketSize(purgeKeysRequest.getBucketPurgeKeysSizeList(),
-          omMetadataManager);
+          omMetadataManager, context.getIndex());
 
       if (LOG.isDebugEnabled()) {
         Map<String, String> auditParams = new LinkedHashMap<>();
@@ -168,7 +168,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
   }
 
   private List<OmBucketInfo> updateBucketSize(List<BucketPurgeKeysSize> 
bucketPurgeKeysSizeList,
-      OMMetadataManager omMetadataManager) throws OMException {
+      OMMetadataManager omMetadataManager, long trxnLogIndex) throws 
OMException {
     Map<String, Map<String, List<BucketPurgeKeysSize>>> bucketPurgeKeysSizes = 
new HashMap<>();
     List<String[]> bucketKeyList = new ArrayList<>();
     for (BucketPurgeKeysSize bucketPurgeKey : bucketPurgeKeysSizeList) {
@@ -192,7 +192,7 @@ private List<OmBucketInfo> 
updateBucketSize(List<BucketPurgeKeysSize> bucketPurg
         String volumeName = volEntry.getKey();
         for (Map.Entry<String, List<BucketPurgeKeysSize>> bucketEntry : 
volEntry.getValue().entrySet()) {
           String bucketName = bucketEntry.getKey();
-          OmBucketInfo omBucketInfo = getBucketInfo(omMetadataManager, 
volumeName, bucketName);
+          OmBucketInfo omBucketInfo = 
getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
           // Check null if bucket has been deleted.
           if (omBucketInfo != null) {
             boolean bucketUpdated = false;
@@ -205,6 +205,8 @@ private List<OmBucketInfo> 
updateBucketSize(List<BucketPurgeKeysSize> bucketPurg
               }
             }
             if (bucketUpdated) {
+              omMetadataManager.getBucketTable().addCacheEntry(
+                  omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
               bucketInfoList.add(omBucketInfo.copyObject());
             }
           }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyRequest.java
index 3d478e46139..d674976c157 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyRequest.java
@@ -945,13 +945,11 @@ public static long sumBlockLengths(OmKeyInfo omKeyInfo) {
   }
 
   /**
-   * Return bucket info for the specified bucket.
+   * Return bucket info for the specified bucket, for read-only use.
    * <p>
    * The returned {@link OmBucketInfo} is the cached instance, returned by
-   * reference. A caller that mutates it (for example quota accounting) before 
a
-   * point where the request may still fail must first take a
-   * {@link OmBucketInfo#copyObject()} and publish that copy only on success,
-   * otherwise a failed request leaks the mutation into the cache.
+   * reference. Callers that mutate it must use
+   * {@link #getBucketInfoForUpdate(OMMetadataManager, String, String)} 
instead.
    */
   @Nullable
   public static OmBucketInfo getBucketInfo(OMMetadataManager omMetadataManager,
@@ -964,6 +962,24 @@ public static OmBucketInfo getBucketInfo(OMMetadataManager 
omMetadataManager,
     return value != null ? value.getCacheValue() : null;
   }
 
+  /**
+   * Return a copy of the cached bucket info for callers that mutate it.
+   * <p>
+   * Mutations stay invisible until the caller publishes the copy with
+   * {@code getBucketTable().addCacheEntry(...)}, after all fallible work and
+   * only on the path that persists the response. A caller that publishes must
+   * hold the bucket write lock for the whole read-modify-publish, not just the
+   * publish, or a concurrent writer can be lost. A caller under key path
+   * locking holds only the bucket read lock, so it must not publish.
+   */
+  @Nullable
+  public static OmBucketInfo getBucketInfoForUpdate(OMMetadataManager 
omMetadataManager,
+      String volume, String bucket) {
+    OmBucketInfo omBucketInfo = getBucketInfo(omMetadataManager, volume, 
bucket);
+
+    return omBucketInfo != null ? omBucketInfo.copyObject() : null;
+  }
+
   /**
    * Prepare OmKeyInfo which will be persisted to openKeyTable.
    * @return OmKeyInfo
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeysDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeysDeleteRequest.java
index 9a60ebbd391..0d7ff10251d 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeysDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeysDeleteRequest.java
@@ -237,7 +237,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       }
 
       OmBucketInfo omBucketInfo =
-          getBucketInfo(omMetadataManager, volumeName, bucketName);
+          getBucketInfoForUpdate(omMetadataManager, volumeName, bucketName);
 
       Map<String, OmKeyInfo> openKeyInfoMap = new HashMap<>();
       // Mark all keys which can be deleted, in cache as deleted.
@@ -260,6 +260,12 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
       }
 
       final long volumeId = omMetadataManager.getVolumeId(volumeName);
+
+      // A partial delete still persists the accepted keys and the bucket row, 
so publish here
+      // rather than only when every key was deleted.
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       omClientResponse =
           getOmClientResponse(ozoneManager, omKeyInfoList, dirList, omResponse,
               unDeletedKeys, keyToError, deleteStatus, omBucketInfo, volumeId, 
openKeyInfoMap, state);
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3ExpiredMultipartUploadsAbortRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3ExpiredMultipartUploadsAbortRequest.java
index cc28954ef38..1d7b22aa353 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3ExpiredMultipartUploadsAbortRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3ExpiredMultipartUploadsAbortRequest.java
@@ -25,6 +25,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
 import org.apache.hadoop.ozone.OzoneConsts;
@@ -59,8 +61,8 @@
 
 /**
  * Handles requests to move both MPU open keys from the open key/file table and
- * MPU part keys to delete table. Modifies the open key/file table cache only,
- * and no underlying databases.
+ * MPU part keys to delete table. Modifies the open key/file, multipart info 
and
+ * bucket table caches, and no underlying databases.
  * The delete table cache does not need to be modified since it is not used
  * for client response validation.
  */
@@ -106,18 +108,44 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
     Result result = null;
     Map<OmBucketInfo, List<OmMultipartAbortInfo>>
         abortedMultipartUploads = new HashMap<>();
+    // One accumulated copy per bucket, so a bucket listed more than once 
keeps a single entry.
+    Map<Pair<String, String>, OmBucketInfo> bucketInfoMap = new HashMap<>();
+
+    OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+    List<String[]> bucketLockKeys = submittedExpiredMPUsPerBucket.stream()
+        .map(mpuByBucket -> Pair.of(mpuByBucket.getVolumeName(), 
mpuByBucket.getBucketName()))
+        .distinct()
+        .map(volBucketPair -> new String[]{volBucketPair.getLeft(), 
volBucketPair.getRight()})
+        .collect(Collectors.toList());
+    boolean acquiredLocks = false;
 
     try {
+      // Hold every bucket lock for the whole request, so the accumulated 
copies can be published
+      // once all buckets have been processed. A later bucket failing turns 
the whole request into
+      // an error response, which persists nothing.
+      
mergeOmLockDetails(omMetadataManager.getLock().acquireWriteLocks(BUCKET_LOCK, 
bucketLockKeys));
+      acquiredLocks = getOmLockDetails().isLockAcquired();
+
       for (ExpiredMultipartUploadsBucket mpuByBucket:
           submittedExpiredMPUsPerBucket) {
-        // For each bucket where the MPU will be aborted from,
-        // get its bucket lock and update the cache accordingly.
         updateTableCache(ozoneManager, trxnLogIndex, mpuByBucket,
-            abortedMultipartUploads);
+            abortedMultipartUploads, bucketInfoMap);
       }
 
+      for (Map.Entry<Pair<String, String>, OmBucketInfo> entry : 
bucketInfoMap.entrySet()) {
+        omMetadataManager.getBucketTable().addCacheEntry(
+            omMetadataManager.getBucketKey(entry.getKey().getLeft(), 
entry.getKey().getRight()),
+            entry.getValue(), trxnLogIndex);
+      }
+
+      // Hand the response its own copies, so the instances now in the cache 
are not shared with
+      // the DB batch.
+      Map<OmBucketInfo, List<OmMultipartAbortInfo>> abortedMPUsToPersist =
+          abortedMultipartUploads.entrySet().stream()
+              .collect(Collectors.toMap(entry -> entry.getKey().copyObject(), 
Map.Entry::getValue));
+
       omClientResponse = new S3ExpiredMultipartUploadsAbortResponse(
-          omResponse.build(), abortedMultipartUploads
+          omResponse.build(), abortedMPUsToPersist
       );
 
       result = Result.SUCCESS;
@@ -128,6 +156,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           new S3ExpiredMultipartUploadsAbortResponse(createErrorOMResponse(
               omResponse, exception));
     } finally {
+      if (acquiredLocks) {
+        
mergeOmLockDetails(omMetadataManager.getLock().releaseWriteLocks(BUCKET_LOCK, 
bucketLockKeys));
+      }
       if (omClientResponse != null) {
         omClientResponse.setOmLockDetails(getOmLockDetails());
       }
@@ -194,171 +225,161 @@ private void processResults(OMMetrics omMetrics,
   @SuppressWarnings("methodlength")
   private void updateTableCache(OzoneManager ozoneManager,
         long trxnLogIndex, ExpiredMultipartUploadsBucket mpusPerBucket,
-        Map<OmBucketInfo, List<OmMultipartAbortInfo>> abortedMultipartUploads)
+        Map<OmBucketInfo, List<OmMultipartAbortInfo>> abortedMultipartUploads,
+        Map<Pair<String, String>, OmBucketInfo> bucketInfoMap)
       throws IOException {
 
-    boolean acquiredLock = false;
     String volumeName = mpusPerBucket.getVolumeName();
     String bucketName = mpusPerBucket.getBucketName();
     OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
-    OmBucketInfo omBucketInfo = null;
-    BucketLayout bucketLayout = null;
-    try {
-      mergeOmLockDetails(omMetadataManager.getLock()
-          .acquireWriteLock(BUCKET_LOCK, volumeName, bucketName));
-      acquiredLock = getOmLockDetails().isLockAcquired();
 
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+    OmBucketInfo omBucketInfo = bucketInfoMap.computeIfAbsent(
+        Pair.of(volumeName, bucketName),
+        volBucketPair -> getBucketInfoForUpdate(omMetadataManager,
+            volBucketPair.getLeft(), volBucketPair.getRight()));
 
-      if (omBucketInfo == null) {
-        LOG.warn("Volume: {}, Bucket: {} does not exist, skipping deletion.",
-            volumeName, bucketName);
-        return;
-      }
+    if (omBucketInfo == null) {
+      LOG.warn("Volume: {}, Bucket: {} does not exist, skipping deletion.",
+          volumeName, bucketName);
+      return;
+    }
 
-      // Do not use getBucketLayout since the expired MPUs request might
-      // contains MPUs from all kind of buckets
-      bucketLayout = omBucketInfo.getBucketLayout();
-
-      for (ExpiredMultipartUploadInfo expiredMPU:
-          mpusPerBucket.getMultipartUploadsList()) {
-        String expiredMPUKeyName = expiredMPU.getName();
-
-        // If the MPU key is no longer present in the table, MPU
-        // might have been completed / aborted, and should not be
-        // aborted.
-        OmMultipartKeyInfo omMultipartKeyInfo =
-            omMetadataManager.getMultipartInfoTable().get(expiredMPUKeyName);
-
-        if (omMultipartKeyInfo != null) {
-          if (trxnLogIndex < omMultipartKeyInfo.getUpdateID()) {
-            LOG.warn("Transaction log index {} is smaller than " +
-                    "the current updateID {} of MPU key {}, skipping 
deletion.",
-                trxnLogIndex, omMultipartKeyInfo.getUpdateID(),
-                expiredMPUKeyName);
-            continue;
-          }
+    // Do not use getBucketLayout since the expired MPUs request might
+    // contains MPUs from all kind of buckets
+    BucketLayout bucketLayout = omBucketInfo.getBucketLayout();
+
+    for (ExpiredMultipartUploadInfo expiredMPU:
+        mpusPerBucket.getMultipartUploadsList()) {
+      String expiredMPUKeyName = expiredMPU.getName();
+
+      // If the MPU key is no longer present in the table, MPU
+      // might have been completed / aborted, and should not be
+      // aborted.
+      OmMultipartKeyInfo omMultipartKeyInfo =
+          omMetadataManager.getMultipartInfoTable().get(expiredMPUKeyName);
+
+      if (omMultipartKeyInfo != null) {
+        if (trxnLogIndex < omMultipartKeyInfo.getUpdateID()) {
+          LOG.warn("Transaction log index {} is smaller than " +
+                  "the current updateID {} of MPU key {}, skipping deletion.",
+              trxnLogIndex, omMultipartKeyInfo.getUpdateID(),
+              expiredMPUKeyName);
+          continue;
+        }
 
-          // Set the UpdateID to current transactionLogIndex
-          omMultipartKeyInfo = omMultipartKeyInfo.toBuilder()
-              .setUpdateID(trxnLogIndex)
-              .build();
-
-          // Parse the multipart upload components (e.g. volume, bucket, key)
-          // from the multipartInfoTable db key
-
-          OmMultipartUpload multipartUpload;
-          try {
-            multipartUpload =
-                OmMultipartUpload.from(expiredMPUKeyName);
-          } catch (IllegalArgumentException e) {
-            LOG.warn("Aborting expired MPU failed: MPU key: " +
-                expiredMPUKeyName + " has invalid structure, " +
-                "skipping this MPU.");
-            continue;
-          }
+        // Set the UpdateID to current transactionLogIndex
+        omMultipartKeyInfo = omMultipartKeyInfo.toBuilder()
+            .setUpdateID(trxnLogIndex)
+            .build();
 
-          String multipartOpenKey;
-          try {
-            multipartOpenKey =
-                OMMultipartUploadUtils
-                    .getMultipartOpenKey(multipartUpload.getVolumeName(),
-                        multipartUpload.getBucketName(),
-                        multipartUpload.getKeyName(),
-                        multipartUpload.getUploadId(), omMetadataManager,
-                        bucketLayout);
-          } catch (OMException ome) {
-            LOG.warn("Aborting expired MPU Failed: volume: " +
-                multipartUpload.getVolumeName() + ", bucket: " +
-                multipartUpload.getBucketName() + ", key: " +
-                multipartUpload.getKeyName() + ". Cannot parse the open key" +
-                "for this MPU, skipping this MPU.");
-            continue;
-          }
+        // Parse the multipart upload components (e.g. volume, bucket, key)
+        // from the multipartInfoTable db key
+
+        OmMultipartUpload multipartUpload;
+        try {
+          multipartUpload =
+              OmMultipartUpload.from(expiredMPUKeyName);
+        } catch (IllegalArgumentException e) {
+          LOG.warn("Aborting expired MPU failed: MPU key: " +
+              expiredMPUKeyName + " has invalid structure, " +
+              "skipping this MPU.");
+          continue;
+        }
 
-          // When abort uploaded key, we need to subtract the PartKey length
-          // from the volume usedBytes.
-          long quotaReleased = 0;
-          long numParts;
-          List<OmKeyInfo> partsKeyInfoToDelete = new ArrayList<>();
-          List<OmMultipartPartKey> partsTableKeysToDelete = new ArrayList<>();
-          if (omMultipartKeyInfo.getSchemaVersion()
-              == OmMultipartKeyInfo.LEGACY_SCHEMA_VERSION) {
-            for (PartKeyInfo iterPartKeyInfo : omMultipartKeyInfo.
-                getPartKeyInfoMap()) {
-              quotaReleased += QuotaUtil.getReplicatedSize(
-                  iterPartKeyInfo.getPartKeyInfo().getDataSize(),
-                  omMultipartKeyInfo.getReplicationConfig());
-            }
-            numParts = omMultipartKeyInfo.getPartKeyInfoMap().size();
-          } else {
-            SortedMap<Integer, OmMultipartPartInfo> tableParts =
-                OMMultipartUploadUtils.scanParts(omMetadataManager,
-                    multipartUpload.getUploadId());
-            quotaReleased += OMMultipartUploadUtils.getReplicatedSize(
-                tableParts, omMultipartKeyInfo.getReplicationConfig());
-            partsKeyInfoToDelete.addAll(OMMultipartUploadUtils.toOmKeyInfoList(
-                tableParts, multipartUpload.getVolumeName(),
-                multipartUpload.getBucketName(), multipartUpload.getKeyName(),
-                omMultipartKeyInfo.getReplicationConfig()));
-            partsTableKeysToDelete.addAll(OMMultipartUploadUtils.getPartKeys(
-                multipartUpload.getUploadId(), tableParts));
-            
OMMultipartUploadUtils.addPartCleanupCacheEntries(omMetadataManager,
-                partsTableKeysToDelete, trxnLogIndex);
-            numParts = tableParts.size();
-          }
-          omBucketInfo.incrUsedBytes(-quotaReleased);
-
-          OmMultipartAbortInfo omMultipartAbortInfo =
-              new OmMultipartAbortInfo.Builder()
-                  .setMultipartKey(expiredMPUKeyName)
-                  .setMultipartOpenKey(multipartOpenKey)
-                  .setMultipartKeyInfo(omMultipartKeyInfo)
-                  .setBucketLayout(omBucketInfo.getBucketLayout())
-                  .setPartsKeyInfoToDelete(partsKeyInfoToDelete)
-                  .setPartsTableKeysToDelete(partsTableKeysToDelete)
-                  .build();
-
-          abortedMultipartUploads.computeIfAbsent(omBucketInfo,
-              k -> new ArrayList<>()).add(omMultipartAbortInfo);
-
-          // Update cache of openKeyTable and multipartInfo table.
-          // No need to add the cache entries to delete table, as the entries
-          // in delete table are not used by any read/write operations.
-
-          // Unlike normal MPU abort request where the MPU open keys needs
-          // to exist. For OpenKeyCleanupService run prior to
-          // HDDS-9017, these MPU open keys might already be deleted,
-          // causing "orphan" MPU keys (MPU entry exist in
-          // multipartInfoTable, but not in openKeyTable).
-          // We can skip this existence check and just delete the
-          // multipartInfoTable. The existence check can be re-added
-          // once there are no "orphan" keys
-          if (omMetadataManager.getOpenKeyTable(bucketLayout)
-              .isExist(multipartOpenKey)) {
-            omMetadataManager.getOpenKeyTable(bucketLayout)
-                .addCacheEntry(new CacheKey<>(multipartOpenKey),
-                    CacheValue.get(trxnLogIndex));
-          }
-          omMetadataManager.getMultipartInfoTable()
-              .addCacheEntry(new CacheKey<>(expiredMPUKeyName),
-                  CacheValue.get(trxnLogIndex));
+        String multipartOpenKey;
+        try {
+          multipartOpenKey =
+              OMMultipartUploadUtils
+                  .getMultipartOpenKey(multipartUpload.getVolumeName(),
+                      multipartUpload.getBucketName(),
+                      multipartUpload.getKeyName(),
+                      multipartUpload.getUploadId(), omMetadataManager,
+                      bucketLayout);
+        } catch (OMException ome) {
+          LOG.warn("Aborting expired MPU Failed: volume: " +
+              multipartUpload.getVolumeName() + ", bucket: " +
+              multipartUpload.getBucketName() + ", key: " +
+              multipartUpload.getKeyName() + ". Cannot parse the open key" +
+              "for this MPU, skipping this MPU.");
+          continue;
+        }
 
-          ozoneManager.getMetrics().incNumExpiredMPUAborted();
-          ozoneManager.getMetrics().incNumExpiredMPUPartsAborted(numParts);
-          LOG.debug("Expired MPU {} aborted containing {} parts.",
-              expiredMPUKeyName, numParts);
+        // When abort uploaded key, we need to subtract the PartKey length
+        // from the volume usedBytes.
+        long quotaReleased = 0;
+        long numParts;
+        List<OmKeyInfo> partsKeyInfoToDelete = new ArrayList<>();
+        List<OmMultipartPartKey> partsTableKeysToDelete = new ArrayList<>();
+        if (omMultipartKeyInfo.getSchemaVersion()
+            == OmMultipartKeyInfo.LEGACY_SCHEMA_VERSION) {
+          for (PartKeyInfo iterPartKeyInfo : omMultipartKeyInfo.
+              getPartKeyInfoMap()) {
+            quotaReleased += QuotaUtil.getReplicatedSize(
+                iterPartKeyInfo.getPartKeyInfo().getDataSize(),
+                omMultipartKeyInfo.getReplicationConfig());
+          }
+          numParts = omMultipartKeyInfo.getPartKeyInfoMap().size();
         } else {
-          LOG.debug("MPU key {} was not aborted, as it was not " +
-              "found in the multipart info table", expiredMPUKeyName);
+          SortedMap<Integer, OmMultipartPartInfo> tableParts =
+              OMMultipartUploadUtils.scanParts(omMetadataManager,
+                  multipartUpload.getUploadId());
+          quotaReleased += OMMultipartUploadUtils.getReplicatedSize(
+              tableParts, omMultipartKeyInfo.getReplicationConfig());
+          partsKeyInfoToDelete.addAll(OMMultipartUploadUtils.toOmKeyInfoList(
+              tableParts, multipartUpload.getVolumeName(),
+              multipartUpload.getBucketName(), multipartUpload.getKeyName(),
+              omMultipartKeyInfo.getReplicationConfig()));
+          partsTableKeysToDelete.addAll(OMMultipartUploadUtils.getPartKeys(
+              multipartUpload.getUploadId(), tableParts));
+          OMMultipartUploadUtils.addPartCleanupCacheEntries(omMetadataManager,
+              partsTableKeysToDelete, trxnLogIndex);
+          numParts = tableParts.size();
         }
-      }
-    } finally {
-      if (acquiredLock) {
-        mergeOmLockDetails(omMetadataManager.getLock()
-            .releaseWriteLock(BUCKET_LOCK, volumeName, bucketName));
+        omBucketInfo.incrUsedBytes(-quotaReleased);
+
+        OmMultipartAbortInfo omMultipartAbortInfo =
+            new OmMultipartAbortInfo.Builder()
+                .setMultipartKey(expiredMPUKeyName)
+                .setMultipartOpenKey(multipartOpenKey)
+                .setMultipartKeyInfo(omMultipartKeyInfo)
+                .setBucketLayout(omBucketInfo.getBucketLayout())
+                .setPartsKeyInfoToDelete(partsKeyInfoToDelete)
+                .setPartsTableKeysToDelete(partsTableKeysToDelete)
+                .build();
+
+        abortedMultipartUploads.computeIfAbsent(omBucketInfo,
+            k -> new ArrayList<>()).add(omMultipartAbortInfo);
+
+        // Update cache of openKeyTable and multipartInfo table.
+        // No need to add the cache entries to delete table, as the entries
+        // in delete table are not used by any read/write operations.
+
+        // Unlike normal MPU abort request where the MPU open keys needs
+        // to exist. For OpenKeyCleanupService run prior to
+        // HDDS-9017, these MPU open keys might already be deleted,
+        // causing "orphan" MPU keys (MPU entry exist in
+        // multipartInfoTable, but not in openKeyTable).
+        // We can skip this existence check and just delete the
+        // multipartInfoTable. The existence check can be re-added
+        // once there are no "orphan" keys
+        if (omMetadataManager.getOpenKeyTable(bucketLayout)
+            .isExist(multipartOpenKey)) {
+          omMetadataManager.getOpenKeyTable(bucketLayout)
+              .addCacheEntry(new CacheKey<>(multipartOpenKey),
+                  CacheValue.get(trxnLogIndex));
+        }
+        omMetadataManager.getMultipartInfoTable()
+            .addCacheEntry(new CacheKey<>(expiredMPUKeyName),
+                CacheValue.get(trxnLogIndex));
+
+        ozoneManager.getMetrics().incNumExpiredMPUAborted();
+        ozoneManager.getMetrics().incNumExpiredMPUPartsAborted(numParts);
+        LOG.debug("Expired MPU {} aborted containing {} parts.",
+            expiredMPUKeyName, numParts);
+      } else {
+        LOG.debug("MPU key {} was not aborted, as it was not " +
+            "found in the multipart info table", expiredMPUKeyName);
       }
     }
-
   }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3InitiateMultipartUploadRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3InitiateMultipartUploadRequestWithFSO.java
index 5596be4ff39..e3c1003314c 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3InitiateMultipartUploadRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3InitiateMultipartUploadRequestWithFSO.java
@@ -114,7 +114,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       // check if the directory already existed in OM
       checkDirectoryResult(keyName, pathInfoFSO.getDirectoryResult());
 
-      final OmBucketInfo bucketInfo = getBucketInfo(omMetadataManager,
+      final OmBucketInfo bucketInfo = getBucketInfoForUpdate(omMetadataManager,
           volumeName, bucketName);
 
       // add all missing parents to dir table
@@ -214,6 +214,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       omMetadataManager.getMultipartInfoTable().addCacheEntry(
           multipartKey, multipartKeyInfo, transactionLogIndex);
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), bucketInfo, 
transactionLogIndex);
+
       omClientResponse =
           new S3InitiateMultipartUploadResponseWithFSO(
               omResponse.setInitiateMultiPartUploadResponse(
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadAbortRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadAbortRequest.java
index bd7c738812b..99111ff5e09 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadAbortRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadAbortRequest.java
@@ -156,7 +156,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
 
       OmKeyInfo omKeyInfo = 
omMetadataManager.getOpenKeyTable(getBucketLayout())
           .get(multipartOpenKey);
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+      omBucketInfo = getBucketInfoForUpdate(omMetadataManager, volumeName, 
bucketName);
 
       if (omKeyInfo == null) {
         // In old env, OpenKeycleanupservice may have deleted key from 
openKeyTable leaving behind
@@ -213,6 +213,9 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           .addCacheEntry(new CacheKey<>(multipartKey),
               CacheValue.get(trxnLogIndex));
 
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       omClientResponse = getOmClientResponse(ozoneManager, multipartKeyInfo,
           multipartKey, multipartOpenKey, omResponse, omBucketInfo,
           partsKeyInfoToDelete, partsTableKeysToDelete);
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCommitPartRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCommitPartRequest.java
index 89d82cc1605..e292b0284d4 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCommitPartRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCommitPartRequest.java
@@ -260,7 +260,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
           new CacheKey<>(openKey),
           CacheValue.get(trxnLogIndex));
 
-      omBucketInfo = getBucketInfo(omMetadataManager, volumeName, bucketName);
+      omBucketInfo = getBucketInfoForUpdate(omMetadataManager, volumeName, 
bucketName);
 
       // This map should contain maximum of two entries
       // 1. Overwritten part
@@ -314,6 +314,10 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
       }
       commitResponseBuilder.setModificationTime(keyArgs.getModificationTime());
       omResponse.setCommitMultiPartUploadResponse(commitResponseBuilder);
+
+      omMetadataManager.getBucketTable().addCacheEntry(
+          omMetadataManager.getBucketKey(volumeName, bucketName), 
omBucketInfo, trxnLogIndex);
+
       omClientResponse =
           getOmClientResponse(ozoneManager, keyVersionsToDeleteMap, openKey,
               omKeyInfo, multipartKey, multipartKeyInfo, multipartPartKey,
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCompleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCompleteRequest.java
index 88e4f26b7c9..5a4e6436c9f 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCompleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCompleteRequest.java
@@ -177,15 +177,10 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
       acquiredLock = getOmLockDetails().isLockAcquired();
 
       validateBucketAndVolume(omMetadataManager, volumeName, bucketName);
-      // Work on a copy of the cached bucket so the namespace charge for
-      // recreating missing FSO parent directories (applied before parts are
-      // validated) is published only on success; a complete that fails with
-      // INVALID_PART must not leak it into the cache. See getBucketInfo.
-      OmBucketInfo omBucketInfo = getBucketInfo(omMetadataManager,
+      // The namespace charge for recreating missing FSO parent directories is 
applied before the
+      // parts are validated, so a complete that fails with INVALID_PART must 
not leak it.
+      OmBucketInfo omBucketInfo = getBucketInfoForUpdate(omMetadataManager,
           volumeName, bucketName);
-      if (omBucketInfo != null) {
-        omBucketInfo = omBucketInfo.copyObject();
-      }
 
       List<OmDirectoryInfo> missingParentInfos;
       OMFileRequest.OMPathInfoWithFSO pathInfoFSO = OMFileRequest
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMDirectoryCreateRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMDirectoryCreateRequestWithFSO.java
index 8f5b6c807ca..52031b79f2d 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMDirectoryCreateRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/file/TestOMDirectoryCreateRequestWithFSO.java
@@ -219,6 +219,34 @@ public void 
testValidateAndUpdateCacheWithNamespaceQuotaExceeded()
         OzoneManagerProtocolProtos.Status.QUOTA_EXCEEDED);
   }
 
+  @Test
+  public void testFailedRequestDoesNotLeakNamespaceQuota() throws Exception {
+    String volumeName = "vol1";
+    String bucketName = "bucket1";
+    // A single-level directory has no missing parents, so the first ACL 
lookup, and with it the
+    // first chance to fail, happens after the namespace charge is applied.
+    String keyName = "dir1";
+
+    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager, getBucketLayout());
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    long usedNamespaceBefore = 
omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace();
+
+    // Neither preExecute nor setUGI runs, so the request carries no user info 
and the ACL lookup
+    // for the leaf directory fails with UNAUTHORIZED.
+    OMDirectoryCreateRequestWithFSO omDirCreateRequestFSO =
+        new OMDirectoryCreateRequestWithFSO(
+            createDirectoryRequest(volumeName, bucketName, keyName),
+            BucketLayout.FILE_SYSTEM_OPTIMIZED);
+
+    OMClientResponse omClientResponse =
+        omDirCreateRequestFSO.validateAndUpdateCache(ozoneManager, 100L);
+
+    assertSame(OzoneManagerProtocolProtos.Status.UNAUTHORIZED,
+        omClientResponse.getOMResponse().getStatus());
+    assertEquals(usedNamespaceBefore, 
omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace());
+  }
+
   @Test
   public void testValidateAndUpdateCacheWithVolumeNotFound() throws Exception {
     String volumeName = "vol1";
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCommitRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCommitRequest.java
index 2a87c8e00a8..b5152082c7d 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCommitRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCommitRequest.java
@@ -348,6 +348,40 @@ public void 
testAtomicCreateIfNotExistsCommitKeyAlreadyExists() throws Exception
     assertEquals(ATOMIC_WRITE_CONFLICT, 
omClientResponse.getOMResponse().getStatus());
   }
 
+  @Test
+  public void testFailedCommitDoesNotLeakBucketUsage() throws Exception {
+    // Uncommitted blocks make the request build a pseudo key for deletion, 
which derives an
+    // object id from the transaction index. An index above MAX_TRXN_ID makes 
that step throw
+    // after incrUsedNamespace but before incrUsedBytes, so only the namespace 
charge is at
+    // risk of leaking here.
+    List<KeyLocation> allocatedKeyLocationList = getKeyLocation(5);
+    List<OmKeyLocationInfo> allocatedBlockList = allocatedKeyLocationList
+        .stream().map(OmKeyLocationInfo::getFromProtobuf)
+        .collect(Collectors.toList());
+
+    OMRequest modifiedOmRequest = doPreExecute(createCommitKeyRequest(
+        allocatedKeyLocationList.subList(0, 3), false));
+    OMKeyCommitRequest omKeyCommitRequest =
+        getOmKeyCommitRequest(modifiedOmRequest);
+
+    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager, omKeyCommitRequest.getBucketLayout());
+    addKeyToOpenKeyTable(allocatedBlockList);
+
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    long usedNamespaceBefore = 
omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace();
+
+    // The mock returns 0 by default, so let it run the real check the OM 
performs.
+    when(ozoneManager.getObjectIdFromTxId(anyLong())).thenAnswer(invocation -> 
OmUtils
+        .getObjectIdFromTxId(omMetadataManager.getOmEpoch(), 
invocation.getArgument(0)));
+
+    assertThrows(IllegalArgumentException.class, () -> omKeyCommitRequest
+        .validateAndUpdateCache(ozoneManager, OmUtils.MAX_TRXN_ID + 1));
+
+    assertEquals(usedNamespaceBefore,
+        omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace());
+  }
+
   @Test
   public void testValidateAndUpdateCacheWithUncommittedBlocks()
       throws Exception {
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
index f00276040df..353b04656e7 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyCreateRequest.java
@@ -40,6 +40,7 @@
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -70,6 +71,8 @@
 import org.apache.hadoop.hdds.scm.container.common.helpers.ExcludeList;
 import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.ozone.OzoneAcl;
 import org.apache.hadoop.ozone.OzoneConsts;
 import org.apache.hadoop.ozone.om.OmConfig;
@@ -1060,6 +1063,69 @@ private OMRequest 
createKeyRequestWithExpectedETag(String expectedETag) {
         .setCreateKeyRequest(createKeyRequest).build();
   }
 
+  @Test
+  public void testNamespaceChargeReachesCacheAndDb() throws Exception {
+    OzoneConfiguration configuration = getOzoneConfiguration();
+    configuration.setBoolean(OZONE_OM_ENABLE_FILESYSTEM_PATHS, true);
+    when(ozoneManager.getConfiguration()).thenReturn(configuration);
+    
when(ozoneManager.getConfig()).thenReturn(configuration.getObject(OmConfig.class));
+    when(ozoneManager.getEnableFileSystemPaths()).thenReturn(true);
+    when(ozoneManager.getOzoneLockProvider()).thenReturn(new 
OzoneLockProvider(false, true));
+
+    addVolumeAndBucketToDB(volumeName, bucketName, omMetadataManager, 
getBucketLayout());
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+
+    // dir1 and dir1/dir2 are the missing parents; the key itself is charged 
at commit.
+    OMRequest omRequest = createKeyRequest(false, 0, "dir1/dir2/file1");
+    OMKeyCreateRequest omKeyCreateRequest = getOMKeyCreateRequest(omRequest);
+    omKeyCreateRequest = 
getOMKeyCreateRequest(omKeyCreateRequest.preExecute(ozoneManager));
+
+    OMClientResponse omClientResponse =
+        omKeyCreateRequest.validateAndUpdateCache(ozoneManager, 100L);
+    assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omClientResponse.getOMResponse().getStatus());
+
+    assertEquals(2, 
omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace());
+
+    try (BatchOperation batchOperation = 
omMetadataManager.getStore().initBatchOperation()) {
+      omClientResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
+      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    }
+    assertEquals(2, 
omMetadataManager.getBucketTable().getSkipCache(bucketKey).getUsedNamespace());
+  }
+
+  @Test
+  public void testKeyPathLockLeavesBucketCacheEntryAlone() throws Exception {
+    // Key path locking is only chosen for OBJECT_STORE and for LEGACY without 
filesystem paths.
+    assumeTrue(getBucketLayout() == BucketLayout.LEGACY);
+
+    OzoneConfiguration configuration = getOzoneConfiguration();
+    configuration.setBoolean(OZONE_OM_ENABLE_FILESYSTEM_PATHS, false);
+    when(ozoneManager.getConfiguration()).thenReturn(configuration);
+    
when(ozoneManager.getConfig()).thenReturn(configuration.getObject(OmConfig.class));
+    when(ozoneManager.getEnableFileSystemPaths()).thenReturn(false);
+    when(ozoneManager.getOzoneLockProvider()).thenReturn(new 
OzoneLockProvider(true, false));
+
+    addVolumeAndBucketToDB(volumeName, bucketName, omMetadataManager, 
getBucketLayout());
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    OmBucketInfo cachedBefore = omMetadataManager.getBucketTable()
+        .getCacheValue(new CacheKey<>(bucketKey)).getCacheValue();
+
+    OMRequest omRequest = createKeyRequest(false, 0, "dir1/dir2/file1");
+    OMKeyCreateRequest omKeyCreateRequest = getOMKeyCreateRequest(omRequest);
+    omKeyCreateRequest = 
getOMKeyCreateRequest(omKeyCreateRequest.preExecute(ozoneManager));
+
+    OMClientResponse omClientResponse =
+        omKeyCreateRequest.validateAndUpdateCache(ozoneManager, 100L);
+    assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omClientResponse.getOMResponse().getStatus());
+
+    // No parent directories are charged here, and only a bucket read lock is 
held, so the request
+    // must not replace the cached bucket.
+    assertSame(cachedBefore, omMetadataManager.getBucketTable()
+        .getCacheValue(new CacheKey<>(bucketKey)).getCacheValue());
+  }
+
   @ParameterizedTest
   @ValueSource(booleans =  {true, false})
   public void testKeyCreateWithFileSystemPathsEnabled(
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequest.java
index 3878e3aca8b..828d24bf201 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyDeleteRequest.java
@@ -24,6 +24,7 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.util.UUID;
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.ozone.OzoneConsts;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
@@ -89,6 +90,12 @@ public void testValidateAndUpdateCache() throws Exception {
     OMKeyDeleteRequest omKeyDeleteRequest =
             getOmKeyDeleteRequest(modifiedOmRequest);
 
+    // The key was added straight to the table, so charge the bucket for it 
here; the delete
+    // releases it on a copy that only reaches the cache if the request 
publishes it.
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    omMetadataManager.getBucketTable().getCacheValue(new CacheKey<>(bucketKey))
+        .getCacheValue().incrUsedNamespace(1L);
+
     OMClientResponse omClientResponse =
         omKeyDeleteRequest.validateAndUpdateCache(ozoneManager, 100L);
 
@@ -99,6 +106,7 @@ public void testValidateAndUpdateCache() throws Exception {
     omKeyInfo = omMetadataManager.getKeyTable(getBucketLayout()).get(ozoneKey);
 
     assertNull(omKeyInfo);
+    assertEquals(0, 
omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace());
   }
 
   @Test
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java
index 1f05105d39f..3d6a58dc1bd 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeyPurgeRequestAndResponse.java
@@ -40,12 +40,16 @@
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
 import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.ozone.om.OmSnapshot;
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
 import org.apache.hadoop.ozone.om.helpers.SnapshotInfo;
 import org.apache.hadoop.ozone.om.lock.IOzoneManagerLock;
 import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
 import org.apache.hadoop.ozone.om.response.key.OMKeyPurgeResponse;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.BucketNameInfo;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.BucketPurgeKeysSize;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DeletedKeys;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
@@ -137,6 +141,67 @@ private OMRequest preExecute(OMRequest originalOmRequest) 
throws IOException {
     return modifiedOmRequest;
   }
 
+  @Test
+  public void testPurgedSizesReachCacheAndDb() throws Exception {
+    Pair<List<String>, List<String>> deleteKeysAndRenamedEntry =
+        createAndDeleteKeysAndRenamedEntry(1, null);
+
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    OmBucketInfo bucketInfo = omMetadataManager.getBucketTable()
+        .getCacheValue(new CacheKey<>(bucketKey)).getCacheValue();
+    bucketInfo.incrSnapshotUsedBytes(500L);
+    bucketInfo.incrSnapshotUsedNamespace(5L);
+
+    BucketNameInfo bucketNameInfo = BucketNameInfo.newBuilder()
+        .setVolumeName(volumeName)
+        .setBucketName(bucketName)
+        .setBucketId(bucketInfo.getObjectID())
+        .build();
+    // A bucket id that does not match must leave the counters alone.
+    BucketNameInfo staleBucketNameInfo = BucketNameInfo.newBuilder()
+        .setVolumeName(volumeName)
+        .setBucketName(bucketName)
+        .setBucketId(bucketInfo.getObjectID() + 1)
+        .build();
+
+    // Two entries for the same bucket, so the deltas have to accumulate on 
one copy.
+    PurgeKeysRequest purgeKeysRequest = PurgeKeysRequest.newBuilder()
+        .addDeletedKeys(DeletedKeys.newBuilder()
+            .setVolumeName(volumeName)
+            .setBucketName(bucketName)
+            .addAllKeys(deleteKeysAndRenamedEntry.getKey()))
+        .addAllRenamedKeys(deleteKeysAndRenamedEntry.getValue())
+        .addBucketPurgeKeysSize(BucketPurgeKeysSize.newBuilder()
+            
.setBucketNameInfo(bucketNameInfo).setPurgedBytes(100L).setPurgedNamespace(1L))
+        .addBucketPurgeKeysSize(BucketPurgeKeysSize.newBuilder()
+            
.setBucketNameInfo(bucketNameInfo).setPurgedBytes(200L).setPurgedNamespace(2L))
+        .addBucketPurgeKeysSize(BucketPurgeKeysSize.newBuilder()
+            
.setBucketNameInfo(staleBucketNameInfo).setPurgedBytes(999L).setPurgedNamespace(9L))
+        .build();
+
+    OMRequest omRequest = OMRequest.newBuilder()
+        .setPurgeKeysRequest(purgeKeysRequest)
+        .setCmdType(Type.PurgeKeys)
+        .setClientId(UUID.randomUUID().toString())
+        .build();
+
+    OMClientResponse omClientResponse =
+        new 
OMKeyPurgeRequest(preExecute(omRequest)).validateAndUpdateCache(ozoneManager, 
100L);
+    assertEquals(Status.OK, omClientResponse.getOMResponse().getStatus());
+
+    OmBucketInfo cached = omMetadataManager.getBucketTable().get(bucketKey);
+    assertEquals(500L - 300L, cached.getSnapshotUsedBytes());
+    assertEquals(5L - 3L, cached.getSnapshotUsedNamespace());
+
+    try (BatchOperation batchOperation = 
omMetadataManager.getStore().initBatchOperation()) {
+      omClientResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
+      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    }
+    OmBucketInfo durable = 
omMetadataManager.getBucketTable().getSkipCache(bucketKey);
+    assertEquals(cached.getSnapshotUsedBytes(), 
durable.getSnapshotUsedBytes());
+    assertEquals(cached.getSnapshotUsedNamespace(), 
durable.getSnapshotUsedNamespace());
+  }
+
   @Test
   public void testValidateAndUpdateCache() throws Exception {
     // Create and Delete keys. The keys should be moved to DeletedKeys table
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequest.java
index 52626c68802..ac14439bae4 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequest.java
@@ -38,6 +38,8 @@
 import java.util.List;
 import java.util.UUID;
 import org.apache.hadoop.hdds.client.RatisReplicationConfig;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
@@ -147,6 +149,48 @@ public void testKeysDeleteRequestFail(RequestSource 
sourceType) throws Exception
     checkDeleteKeysResponseForFailure(omKeysDeleteRequest, 
Status.PARTIAL_DELETE, sourceType);
   }
 
+  @Test
+  public void testPartialDeletePublishesBucketUsage() throws Exception {
+    createPreRequisites();
+
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    // createPreRequisites adds the keys straight to the table cache, so 
charge the bucket for
+    // them here; without it the released namespace would drive the counter 
negative.
+    omMetadataManager.getBucketTable().getCacheValue(new CacheKey<>(bucketKey))
+        .getCacheValue().incrUsedNamespace(KEY_COUNT);
+    long usedNamespaceBefore = 
omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace();
+
+    // One key does not exist, so the request succeeds partially.
+    omRequest = omRequest.toBuilder()
+        .setDeleteKeysRequest(DeleteKeysRequest.newBuilder()
+            .setDeleteKeys(DeleteKeyArgs.newBuilder()
+                .setBucketName(bucketName).setVolumeName(volumeName)
+                .addAllKeys(deleteKeyList).addKeys("dummy"))).build();
+
+    OMClientResponse omClientResponse = getOmKeysDeleteRequest(omRequest)
+        .validateAndUpdateCache(ozoneManager, 100L);
+
+    assertEquals(Status.PARTIAL_DELETE, 
omClientResponse.getOMResponse().getStatus());
+
+    // PARTIAL_DELETE still persists the accepted keys and the bucket row, so 
the released
+    // namespace has to reach the cache too. Only the keys that existed are 
released; "dummy" is
+    // counted as undeleted.
+    long usedNamespaceCached = 
omMetadataManager.getBucketTable().get(bucketKey).getUsedNamespace();
+    assertEquals(usedNamespaceBefore - 
expectedNamespaceReleasedOnPartialDelete(),
+        usedNamespaceCached);
+
+    try (BatchOperation batchOperation = 
omMetadataManager.getStore().initBatchOperation()) {
+      omClientResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
+      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    }
+    assertEquals(usedNamespaceCached,
+        
omMetadataManager.getBucketTable().getSkipCache(bucketKey).getUsedNamespace());
+  }
+
+  protected long expectedNamespaceReleasedOnPartialDelete() {
+    return KEY_COUNT;
+  }
+
   @ParameterizedTest
   @MethodSource("requestSourceType")
   public void testUpdateIDCountNoMatchKeyCount() throws Exception {
@@ -282,6 +326,10 @@ protected void createPreRequisites() throws Exception {
     createPreRequisites(RequestSource.USER);
   }
 
+  protected OMKeysDeleteRequest getOmKeysDeleteRequest(OMRequest request) {
+    return new OMKeysDeleteRequest(request, getBucketLayout());
+  }
+
   protected void createPreRequisites(RequestSource sourceType) throws 
Exception {
 
     deleteKeyList = new ArrayList<>();
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequestWithFSO.java
index 071ef81513a..8f4ad075081 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/key/TestOMKeysDeleteRequestWithFSO.java
@@ -20,6 +20,7 @@
 import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.ONE;
 import static 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status;
 import static 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type.DeleteKeys;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -105,8 +106,23 @@ protected void createPreRequisites() throws Exception {
     createPreRequisites(RequestSource.USER);
   }
 
+  @Override
+  protected OMKeysDeleteRequest getOmKeysDeleteRequest(OMRequest request) {
+    return new OmKeysDeleteRequestWithFSO(request, getBucketLayout());
+  }
+
+  @Override
+  protected long expectedNamespaceReleasedOnPartialDelete() {
+    // Only the leaf files sit under the shared parent directory created by 
createPreRequisites.
+    return 3L;
+  }
+
   @Override
   protected void createPreRequisites(RequestSource sourceType) throws 
Exception {
+    // Directories are read back through getOMKeyInfoIfExists, which stamps 
them with the OM
+    // default; without it they cannot be written to the deleted directory 
table.
+    when(ozoneManager.getDefaultReplicationConfig())
+        .thenReturn(RatisReplicationConfig.getInstance(ONE));
     setDeleteKeyList(new ArrayList<>());
     // Add volume, bucket and key entries to OM DB.
     OMRequestTestUtils
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3ExpiredMultipartUploadsAbortRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3ExpiredMultipartUploadsAbortRequest.java
index 031988ec0b0..871582e1c6b 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3ExpiredMultipartUploadsAbortRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3ExpiredMultipartUploadsAbortRequest.java
@@ -25,6 +25,11 @@
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -38,10 +43,15 @@
 import org.apache.hadoop.hdds.client.ReplicationFactor;
 import org.apache.hadoop.hdds.client.ReplicationType;
 import org.apache.hadoop.hdds.utils.UniqueId;
+import org.apache.hadoop.hdds.utils.db.BatchOperation;
+import org.apache.hadoop.hdds.utils.db.RocksDatabaseException;
+import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OMMetrics;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
 import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
@@ -57,6 +67,7 @@
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status;
 import org.apache.hadoop.security.UserGroupInformation;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
@@ -360,33 +371,121 @@ private void abortExpiredMPUsFromCache(String 
volumeName, String bucketName,
     assertEquals(Status.OK, omClientResponse.getOMResponse().getStatus());
   }
 
-  private OMRequest createAbortExpiredMPURequest(String volumeName,
-      String bucketName, List<String> mpuKeysToAbort) {
+  @Test
+  public void testLaterBucketFailureLeavesEarlierBucketUsageAlone() throws 
Exception {
+    this.bucketLayout = BucketLayout.DEFAULT;
+    final String volume = UUID.randomUUID().toString();
+    final String bucket1 = UUID.randomUUID().toString();
+    final String bucket2 = UUID.randomUUID().toString();
+    OMRequestTestUtils.addVolumeAndBucketToDB(volume, bucket1, 
omMetadataManager, getBucketLayout());
+    OMRequestTestUtils.addVolumeAndBucketToDB(volume, bucket2, 
omMetadataManager, getBucketLayout());
+
+    List<String> bucket1MPUs = createMPUs(volume, bucket1, null, 1, 1, 100L);
+    List<String> bucket2MPUs = createMPUs(volume, bucket2, null, 1, 1, 100L);
+
+    String bucket1Key = omMetadataManager.getBucketKey(volume, bucket1);
+    OmBucketInfo bucket1CachedBefore = omMetadataManager.getBucketTable()
+        .getCacheValue(new CacheKey<>(bucket1Key)).getCacheValue();
+    long bucket1UsedBytesBefore = bucket1CachedBefore.getUsedBytes();
+    assertNotEquals(0L, bucket1UsedBytesBefore);
+
+    // The second bucket's scan fails on a real table read, after the first 
bucket was processed.
+    Table<String, OmMultipartKeyInfo> multipartInfoTable =
+        spy(omMetadataManager.getMultipartInfoTable());
+    doThrow(new RocksDatabaseException("injected")).when(multipartInfoTable)
+        .get(argThat(key -> key != null && key.contains(bucket2)));
+    OMMetadataManager spiedMetadataManager = spy(omMetadataManager);
+    
doReturn(multipartInfoTable).when(spiedMetadataManager).getMultipartInfoTable();
+    when(ozoneManager.getMetadataManager()).thenReturn(spiedMetadataManager);
 
-    List<ExpiredMultipartUploadInfo> expiredMultipartUploads = mpuKeysToAbort
-        .stream().map(name ->
-            ExpiredMultipartUploadInfo.newBuilder().setName(name).build())
-        .collect(Collectors.toList());
-    ExpiredMultipartUploadsBucket expiredMultipartUploadsBucket =
-        ExpiredMultipartUploadsBucket.newBuilder()
-            .setVolumeName(volumeName)
-            .setBucketName(bucketName)
-            .addAllMultipartUploads(expiredMultipartUploads)
-            .build();
+    OMRequest omRequest = doPreExecute(createAbortExpiredMPURequest(
+        expiredMPUsForBucket(volume, bucket1, bucket1MPUs),
+        expiredMPUsForBucket(volume, bucket2, bucket2MPUs)));
+    OMClientResponse omClientResponse =
+        new S3ExpiredMultipartUploadsAbortRequest(omRequest)
+            .validateAndUpdateCache(ozoneManager, 100L);
+
+    assertNotEquals(Status.OK, omClientResponse.getOMResponse().getStatus());
+    // Nothing is persisted for a failed request, so the first bucket's 
release must not reach the
+    // cache. The value catches an in-place update of the cached instance, the 
identity catches a
+    // published copy whose counters happen to match.
+    OmBucketInfo bucket1CachedAfter = omMetadataManager.getBucketTable()
+        .getCacheValue(new CacheKey<>(bucket1Key)).getCacheValue();
+    assertEquals(bucket1UsedBytesBefore, bucket1CachedAfter.getUsedBytes());
+    assertSame(bucket1CachedBefore, bucket1CachedAfter);
+  }
 
+  @Test
+  public void testRepeatedBucketAccumulatesIntoOneEntry() throws Exception {
+    this.bucketLayout = BucketLayout.DEFAULT;
+    final String volume = UUID.randomUUID().toString();
+    final String bucket = UUID.randomUUID().toString();
+    OMRequestTestUtils.addVolumeAndBucketToDB(volume, bucket, 
omMetadataManager, getBucketLayout());
+
+    List<String> firstMPUs = createMPUs(volume, bucket, null, 1, 1, 100L);
+    List<String> secondMPUs = createMPUs(volume, bucket, null, 1, 1, 100L);
+
+    String bucketKey = omMetadataManager.getBucketKey(volume, bucket);
+    long usedBytesBefore = 
omMetadataManager.getBucketTable().get(bucketKey).getUsedBytes();
+
+    // The same bucket listed twice in one request must end up as a single 
accumulated entry.
+    OMRequest omRequest = doPreExecute(createAbortExpiredMPURequest(
+        expiredMPUsForBucket(volume, bucket, firstMPUs),
+        expiredMPUsForBucket(volume, bucket, secondMPUs)));
+
+    OMClientResponse omClientResponse =
+        new S3ExpiredMultipartUploadsAbortRequest(omRequest)
+            .validateAndUpdateCache(ozoneManager, 100L);
+    assertEquals(Status.OK, omClientResponse.getOMResponse().getStatus());
+
+    assertNotInMultipartInfoTable(firstMPUs);
+    assertNotInMultipartInfoTable(secondMPUs);
+
+    // Both listings share one accumulated copy, so the bucket row is written 
once and the cache
+    // cannot disagree with what is persisted.
+    long usedBytesCached = 
omMetadataManager.getBucketTable().get(bucketKey).getUsedBytes();
+    // One 100-byte part per batch, charged at three-way replication.
+    assertEquals(600L, usedBytesBefore);
+    assertEquals(0L, usedBytesCached, "Both batches must release their part");
+
+    try (BatchOperation batchOperation = 
omMetadataManager.getStore().initBatchOperation()) {
+      omClientResponse.checkAndUpdateDB(omMetadataManager, batchOperation);
+      omMetadataManager.getStore().commitBatchOperation(batchOperation);
+    }
+    assertEquals(usedBytesCached,
+        
omMetadataManager.getBucketTable().getSkipCache(bucketKey).getUsedBytes());
+  }
+
+  private ExpiredMultipartUploadsBucket expiredMPUsForBucket(String volumeName,
+      String bucketName, List<String> mpuKeysToAbort) {
+    return ExpiredMultipartUploadsBucket.newBuilder()
+        .setVolumeName(volumeName)
+        .setBucketName(bucketName)
+        .addAllMultipartUploads(mpuKeysToAbort.stream()
+            .map(name -> 
ExpiredMultipartUploadInfo.newBuilder().setName(name).build())
+            .collect(Collectors.toList()))
+        .build();
+  }
+
+  private OMRequest 
createAbortExpiredMPURequest(ExpiredMultipartUploadsBucket... buckets) {
     MultipartUploadsExpiredAbortRequest mpuExpiredAbortRequest =
         MultipartUploadsExpiredAbortRequest.newBuilder()
-            .addExpiredMultipartUploadsPerBucket(expiredMultipartUploadsBucket)
+            .addAllExpiredMultipartUploadsPerBucket(Arrays.asList(buckets))
             .build();
 
     return OMRequest.newBuilder()
         .setMultipartUploadsExpiredAbortRequest(mpuExpiredAbortRequest)
-        .setCmdType(OzoneManagerProtocolProtos
-            .Type.AbortExpiredMultiPartUploads)
+        
.setCmdType(OzoneManagerProtocolProtos.Type.AbortExpiredMultiPartUploads)
         .setClientId(UUID.randomUUID().toString())
         .build();
   }
 
+  private OMRequest createAbortExpiredMPURequest(String volumeName,
+      String bucketName, List<String> mpuKeysToAbort) {
+    return createAbortExpiredMPURequest(
+        expiredMPUsForBucket(volumeName, bucketName, mpuKeysToAbort));
+  }
+
   /**
    * Create MPus with randomized key name.
    */
@@ -515,6 +614,11 @@ private List<String> createMPUsWithFSO(String volume, 
String bucket,
    */
   private List<String> createMPUs(String volume, String bucket,
       String key, int count, int numParts) throws Exception {
+    return createMPUs(volume, bucket, key, count, numParts, 0L);
+  }
+
+  private List<String> createMPUs(String volume, String bucket,
+      String key, int count, int numParts, long partSize) throws Exception {
     List<String> mpuKeys = new ArrayList<>();
 
     long trxnLogIndex = 1L;
@@ -555,6 +659,10 @@ private List<String> createMPUs(String volume, String 
bucket,
         long clientID = UniqueId.next();
         OMRequest commitMultipartRequest = doPreExecuteCommitMPU(
             volume, bucket, keyName, clientID, multipartUploadID, j);
+        commitMultipartRequest = 
commitMultipartRequest.toBuilder().setCommitMultiPartUploadRequest(
+            
commitMultipartRequest.getCommitMultiPartUploadRequest().toBuilder().setKeyArgs(
+                
commitMultipartRequest.getCommitMultiPartUploadRequest().getKeyArgs().toBuilder()
+                    .setDataSize(partSize))).build();
 
         S3MultipartUploadCommitPartRequest s3MultipartUploadCommitPartRequest =
             getS3MultipartUploadCommitReq(commitMultipartRequest);
@@ -562,7 +670,8 @@ private List<String> createMPUs(String volume, String 
bucket,
         // Add key to open key table to be used in MPU commit processing
         OMRequestTestUtils.addKeyToTable(
             true, true,
-            volume, bucket, keyName, clientID, 
RatisReplicationConfig.getInstance(ONE), omMetadataManager);
+            volume, bucket, keyName, clientID,
+            
omMetadataManager.getMultipartInfoTable().get(mpuKey).getReplicationConfig(), 
omMetadataManager);
 
         OMClientResponse commitResponse =
             s3MultipartUploadCommitPartRequest.validateAndUpdateCache(
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3InitiateMultipartUploadRequestWithFSO.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3InitiateMultipartUploadRequestWithFSO.java
index d517be2bbba..3c3d4c006f1 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3InitiateMultipartUploadRequestWithFSO.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3InitiateMultipartUploadRequestWithFSO.java
@@ -89,6 +89,11 @@ public void testValidateAndUpdateCache() throws Exception {
 
     long parentID = verifyDirectoriesInDB(dirs, volumeId, bucketId);
 
+    // The missing parents are charged on a copy, so they only show up here if 
the request
+    // published it.
+    assertEquals(dirs.size(), omMetadataManager.getBucketTable()
+        .get(omMetadataManager.getBucketKey(volumeName, 
bucketName)).getUsedNamespace());
+
     String multipartFileKey = omMetadataManager
         .getMultipartKey(volumeName, bucketName, keyName,
             modifiedRequest.getInitiateMultiPartUploadRequest().getKeyArgs()
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadAbortRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadAbortRequest.java
index 3d529ad0aca..9c38af8c543 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadAbortRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadAbortRequest.java
@@ -19,12 +19,14 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.io.IOException;
 import java.util.UUID;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
 import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
 import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
@@ -77,6 +79,10 @@ public void testValidateAndUpdateCache() throws Exception {
     S3MultipartUploadAbortRequest s3MultipartUploadAbortRequest =
         getS3MultipartUploadAbortReq(abortMPURequest);
 
+    String bucketKey = omMetadataManager.getBucketKey(volumeName, bucketName);
+    OmBucketInfo cachedBeforeAbort = omMetadataManager.getBucketTable()
+        .getCacheValue(new CacheKey<>(bucketKey)).getCacheValue();
+
     omClientResponse =
         s3MultipartUploadAbortRequest.validateAndUpdateCache(ozoneManager, 2L);
 
@@ -94,6 +100,10 @@ public void testValidateAndUpdateCache() throws Exception {
         .getOpenKeyTable(s3MultipartUploadAbortRequest.getBucketLayout())
         .get(multipartOpenKey));
 
+    // No part was committed, so the released quota is zero and only the 
instance identity shows
+    // that the abort published the copy it released on.
+    assertNotSame(cachedBeforeAbort, omMetadataManager.getBucketTable()
+        .getCacheValue(new CacheKey<>(bucketKey)).getCacheValue());
   }
 
   @Test


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

Reply via email to