rakeshadr commented on code in PR #10588:
URL: https://github.com/apache/ozone/pull/10588#discussion_r3544415459


##########
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/util/OMMultipartUploadUtils.java:
##########
@@ -103,4 +117,91 @@ public static boolean isMultipartKeySet(OmKeyInfo 
openKeyInfo) {
     return openKeyInfo.getLatestVersionLocations() != null
         && openKeyInfo.getLatestVersionLocations().isMultipartKey();
   }
+
+  /**
+   * Cache-aware scan for multipart parts table rows belonging to a given 
upload.
+   */
+  public static SortedMap<Integer, OmMultipartPartInfo> scanParts(
+      OMMetadataManager omMetadataManager, String uploadId) throws IOException 
{
+    SortedMap<Integer, OmMultipartPartInfo> parts = new TreeMap<>();
+
+    Iterator<Map.Entry<CacheKey<OmMultipartPartKey>,
+        CacheValue<OmMultipartPartInfo>>> cacheIterator =
+        omMetadataManager.getMultipartPartsTable().cacheIterator();
+    while (cacheIterator.hasNext()) {
+      Map.Entry<CacheKey<OmMultipartPartKey>, CacheValue<OmMultipartPartInfo>>
+          cacheEntry = cacheIterator.next();
+      OmMultipartPartKey key = cacheEntry.getKey().getCacheKey();
+      if (!uploadId.equals(key.getUploadId()) || !key.hasPartNumber()) {
+        continue;
+      }
+      OmMultipartPartInfo value = cacheEntry.getValue().getCacheValue();
+      if (value == null) {
+        parts.remove(key.getPartNumber());

Review Comment:
   The cache pass does `parts.remove(partNumber)` when it sees a null value 
(tombstone), but that's a no-op on an empty TreeMap. The subsequent DB pass 
then sees the row still in RocksDB and re-inserts it via:
   
   ```
   if (key.hasPartNumber() && !parts.containsKey(key.getPartNumber())) {
       parts.put(key.getPartNumber(), kv.getValue());
   }
   ```
   
   This means any part that has a cache tombstone (committed to delete but not 
yet flushed to DB) will reappear from the DB pass. Since scanParts() is called 
by listParts, complete, abort, and expired MPU abort, this affects all four 
operations.
   
   Can you change remove by tracking tombstoned keys explicitly during the 
cache pass and skip them in the DB pass.
   
   ```
   Set<Integer> tombstoned = new HashSet<>();
   // cache pass
   if (value == null) {
       parts.remove(key.getPartNumber()); // ← always no-op
       tombstoned.add(key.getPartNumber()); // ← now DB pass can check this
   } else {
       parts.put(key.getPartNumber(), value);
   }
   // DB pass
   if (key.hasPartNumber()
       && !parts.containsKey(key.getPartNumber())
       && !tombstoned.contains(key.getPartNumber())) {
       parts.put(key.getPartNumber(), kv.getValue());
   }
   ```



##########
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/OmMultipartKeyInfo.java:
##########
@@ -41,6 +41,9 @@
  * upload part information of the key.
  */
 public final class OmMultipartKeyInfo extends WithObjectID implements 
CopyObject<OmMultipartKeyInfo> {
+  public static final byte LEGACY_SCHEMA_VERSION = 0;
+  public static final byte SPLIT_PARTS_TABLE_SCHEMA_VERSION = 1;

Review Comment:
   Zero request-path test coverage for schema version 1. 
S3InitiateMultipartUploadRequest always creates schema version 0.
   
   One idea is - tests need to directly inject a schema v1 OmMultipartKeyInfo + 
seed multipartPartsTable. Then extend the existing test classes.
   
   **Imp Note:** LLM generated tests. Please validate and then add it.
   
   
   ```
   Step 1 — Helper method in TestS3MultipartUploadCommitPartRequest
   A schema v1 MPU bypasses the initiate request and directly populates the 
tables:
   
   private OmMultipartKeyInfo createSchemaV1MpuEntry(
       String volumeName, String bucketName, String keyName,
       String uploadId, long trxnIdx) throws IOException {
     OmMultipartKeyInfo keyInfo = new OmMultipartKeyInfo.Builder()
         .setUploadID(uploadId)
         .setCreationTime(Time.now())
         
.setReplicationConfig(RatisReplicationConfig.getInstance(ReplicationFactor.ONE))
         .setObjectID(trxnIdx)
         .setUpdateID(trxnIdx)
         .setSchemaVersion(OmMultipartKeyInfo.SPLIT_PARTS_TABLE_SCHEMA_VERSION)
         .build();
     OmKeyInfo omKeyInfo = OMRequestTestUtils.createOmKeyInfo(
         volumeName, bucketName, keyName,
         RatisReplicationConfig.getInstance(ReplicationFactor.ONE));
     OMRequestTestUtils.addMultipartInfoToTable(
         false, omKeyInfo, keyInfo, trxnIdx, omMetadataManager);
     return keyInfo;
   }
   Schema v1 CommitPart validates ETag. The existing addKeyToOpenKeyTable 
doesn't set it. Add a variant:
   
   protected String addKeyToOpenKeyTableWithETag(
       String volumeName, String bucketName, String keyName,
       long clientID, List<OmKeyLocationInfo> locationList) throws Exception {
     OmKeyInfo keyInfo = OMRequestTestUtils.createOmKeyInfo(
         volumeName, bucketName, keyName,
         RatisReplicationConfig.getInstance(ReplicationFactor.ONE),
         clientID, locationList);
     keyInfo.addMetadata(OzoneConsts.ETAG, UUID.randomUUID().toString());
     String openKey = omMetadataManager.getOpenKey(
         volumeName, bucketName, keyName, clientID);
     omMetadataManager.getOpenKeyTable(getBucketLayout())
         .addCacheEntry(new CacheKey<>(openKey), CacheValue.get(clientID, 
keyInfo));
     omMetadataManager.getOpenKeyTable(getBucketLayout()).put(openKey, keyInfo);
     return openKey;
   }
   Step 2 — New test methods to add to existing test classes
   TestS3MultipartUploadCommitPartRequest — add:
   
   @Test
   public void testV1CommitWritesToPartsTable() throws Exception {
     String vol = UUID.randomUUID().toString(), bkt = 
UUID.randomUUID().toString();
     String key = getKeyName(), uploadId = UUID.randomUUID().toString();
     OMRequestTestUtils.addVolumeAndBucketToDB(vol, bkt, omMetadataManager, 
getBucketLayout());
     createParentPath(vol, bkt);
     createSchemaV1MpuEntry(vol, bkt, key, uploadId, 1L);
     long clientID = Time.now();
     List<KeyLocation> locs = getKeyLocation(2);
     addKeyToOpenKeyTableWithETag(vol, bkt, key, clientID,
         
locs.stream().map(OmKeyLocationInfo::getFromProtobuf).collect(toList()));
     OMClientResponse resp = getS3MultipartUploadCommitReq(
         doPreExecuteCommitMPU(vol, bkt, key, clientID, uploadId, 1, locs))
         .validateAndUpdateCache(ozoneManager, 2L);
     assertEquals(Status.OK, resp.getOMResponse().getStatus());
     // Part must be in multipartPartsTable
     assertNotNull(omMetadataManager.getMultipartPartsTable()
         .get(OmMultipartPartKey.of(uploadId, 1)));
     // multipartInfoTable entry must have empty part list (schema v1 never 
writes there)
     assertEquals(0, omMetadataManager.getMultipartInfoTable()
         .get(omMetadataManager.getMultipartKey(vol, bkt, key, uploadId))
         .getPartKeyInfoMap().size());
   }
   @Test
   public void testV1CommitOverwriteQueuesOldBlocksForDeletion() throws 
Exception {
     String vol = UUID.randomUUID().toString(), bkt = 
UUID.randomUUID().toString();
     String key = getKeyName(), uploadId = UUID.randomUUID().toString();
     OMRequestTestUtils.addVolumeAndBucketToDB(vol, bkt, omMetadataManager, 
getBucketLayout());
     createParentPath(vol, bkt);
     createSchemaV1MpuEntry(vol, bkt, key, uploadId, 1L);
     // First commit of part 1
     long cid1 = Time.now();
     List<KeyLocation> locs1 = getKeyLocation(2);
     addKeyToOpenKeyTableWithETag(vol, bkt, key, cid1,
         
locs1.stream().map(OmKeyLocationInfo::getFromProtobuf).collect(toList()));
     getS3MultipartUploadCommitReq(
         doPreExecuteCommitMPU(vol, bkt, key, cid1, uploadId, 1, locs1))
         .validateAndUpdateCache(ozoneManager, 2L);
     // Overwrite part 1
     long cid2 = Time.now();
     List<KeyLocation> locs2 = getKeyLocation(3);
     addKeyToOpenKeyTableWithETag(vol, bkt, key, cid2,
         
locs2.stream().map(OmKeyLocationInfo::getFromProtobuf).collect(toList()));
     OMClientResponse resp = getS3MultipartUploadCommitReq(
         doPreExecuteCommitMPU(vol, bkt, key, cid2, uploadId, 1, locs2))
         .validateAndUpdateCache(ozoneManager, 3L);
     assertEquals(Status.OK, resp.getOMResponse().getStatus());
     // Old blocks must be in the deleted table
     Map<String, RepeatedOmKeyInfo> toDelete =
         ((S3MultipartUploadCommitPartResponse) resp).getKeyToDelete();
     assertNotNull(toDelete);
     assertFalse(toDelete.isEmpty());
   }
   TestS3MultipartUploadAbortRequest — add:
   
   @Test
   public void testV1AbortMovesPartsToDeletedTable() throws Exception {
     String vol = UUID.randomUUID().toString(), bkt = 
UUID.randomUUID().toString();
     String key = getKeyName(), uploadId = UUID.randomUUID().toString();
     OMRequestTestUtils.addVolumeAndBucketToDB(vol, bkt, omMetadataManager, 
getBucketLayout());
     createSchemaV1MpuEntry(vol, bkt, key, uploadId, 1L);
     // Seed two parts directly into multipartPartsTable
     seedPartsInTable(uploadId, 2);
     OMClientResponse resp = getS3MultipartUploadAbortReq(
         doPreExecuteAbortMPU(vol, bkt, key, uploadId))
         .validateAndUpdateCache(ozoneManager, 3L);
     assertEquals(Status.OK, resp.getOMResponse().getStatus());
     // multipartPartsTable must be cleared for this uploadId
     assertNull(omMetadataManager.getMultipartPartsTable()
         .get(OmMultipartPartKey.of(uploadId, 1)));
     assertNull(omMetadataManager.getMultipartPartsTable()
         .get(OmMultipartPartKey.of(uploadId, 2)));
     // multipartInfoTable entry must be gone
     assertNull(omMetadataManager.getMultipartInfoTable()
         .get(omMetadataManager.getMultipartKey(vol, bkt, key, uploadId)));
   }
   TestS3MultipartUploadCompleteRequest — add (same pattern — seed parts, 
complete, verify multipartPartsTable cleared and key written).
   
   Step 3 — seedPartsInTable helper
   
   private void seedPartsInTable(String uploadId, int count) throws IOException 
{
     for (int i = 1; i <= count; i++) {
       List<OmKeyLocationInfo> locs = getKeyLocation(1).stream()
           .map(OmKeyLocationInfo::getFromProtobuf).collect(toList());
       OmMultipartPartInfo partInfo = new OmMultipartPartInfo.Builder()
           .setPartName(uploadId + "/" + i)
           .setPartNumber(i)
           .setDataSize(200L)
           .setModificationTime(Time.now())
           .setObjectID((long) i)
           .setUpdateID((long) i)
           .setETag(UUID.randomUUID().toString())
           .setKeyLocationInfos(Collections.singletonList(
               new OmKeyLocationInfoGroup(0, locs, false)))
           .build();
       OmMultipartPartKey partKey = OmMultipartPartKey.of(uploadId, i);
       omMetadataManager.getMultipartPartsTable().put(partKey, partInfo);
     }
   }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to