You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by ch...@apache.org on 2026/10/14 15:46:35 UTC

(ozone) branch master updated: HDDS-16998. Skip setting lastScannedKey during lifecycle cache scan (#11533)

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

chihsuan 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 2536fe9cbc3 HDDS-16998. Skip setting lastScannedKey during lifecycle cache scan (#11533)
2536fe9cbc3 is described below

commit 2536fe9cbc342edea19fe8d3e371659ac955cd51
Author: rajeshkumarchandolu <ra...@gmail.com>
AuthorDate: Wed Oct 14 21:16:28 2026 +0530

    HDDS-16998. Skip setting lastScannedKey during lifecycle cache scan (#11533)
    
    Co-authored-by: Chi-Hsuan Huang <ch...@gmail.com>
---
 .../ozone/om/service/KeyLifecycleService.java      |  3 +-
 .../ozone/om/service/TestKeyLifecycleService.java  | 60 ++++++++++++++++++++++
 2 files changed, 62 insertions(+), 1 deletion(-)

diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java
index 565a2ec86cd..d74c220e8e4 100644
--- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java
+++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/KeyLifecycleService.java
@@ -825,6 +825,8 @@ private void evaluateKeyAndDirTable(OmBucketInfo bucket, long volumeObjId, Table
         long numKeysExpired = 0;
         HashSet<String> deletedKeySetInCache = new HashSet();
         HashSet<String> keySetInCache = new HashSet();
+        // The cache is scanned separately before the table, so a cache key cannot represent table scan progress.
+        // Only advance lastScannedKey during table iteration to avoid skipping unprocessed table keys on resume.
         Iterator<Map.Entry<CacheKey<String>, CacheValue<OmKeyInfo>>> cacheIter = keyTable.cacheIterator();
         while (cacheIter.hasNext()) {
           Map.Entry<CacheKey<String>, CacheValue<OmKeyInfo>> entry = cacheIter.next();
@@ -850,7 +852,6 @@ private void evaluateKeyAndDirTable(OmBucketInfo bucket, long volumeObjId, Table
                 break;
               }
             }
-            lastScannedKey = entry.getKey().getCacheKey();
           }
         }
 
diff --git a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java
index 456050c73cd..1da98e26775 100644
--- a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java
+++ b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestKeyLifecycleService.java
@@ -62,6 +62,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -1154,6 +1155,65 @@ void testLastScannedKeySeek(boolean keyBelongToDir) throws Exception {
       }
     }
 
+    @Test
+    void testCacheOnlyKeyNotSavedAsLastScannedKey() throws Exception {
+      final String volumeName = getTestName();
+      final String bucketName = uniqueObjectName("bucket");
+      long initialDeletedKeyCount = getDeletedKeyCount();
+      long initialKeyCount = getKeyCount(FILE_SYSTEM_OPTIMIZED);
+
+      keyLifecycleService.setKeyDeleteBatchSize(1);
+      keyLifecycleService.suspend();
+
+      createVolumeAndBucket(volumeName, bucketName, FILE_SYSTEM_OPTIMIZED,
+          UserGroupInformation.getCurrentUser().getShortUserName());
+      createAndCommitKey(volumeName, bucketName, "dir1/key1", 1, null);
+      createAndCommitKey(volumeName, bucketName, "dir1/key2", 1, null);
+      GenericTestUtils.waitFor(() -> getKeyCount(FILE_SYSTEM_OPTIMIZED) - initialKeyCount == 2,
+          WAIT_CHECK_INTERVAL, 1000);
+      awaitKeyCacheDrained(FILE_SYSTEM_OPTIMIZED, volumeName, bucketName);
+
+      long volumeId = metadataManager.getVolumeTable().get(metadataManager.getVolumeKey(volumeName)).getObjectID();
+      long bucketId =
+          metadataManager.getBucketTable().get(metadataManager.getBucketKey(volumeName, bucketName)).getObjectID();
+      long dir1Id = metadataManager.getDirectoryTable().get(
+          metadataManager.getOzonePathKey(volumeId, bucketId, bucketId, "dir1")).getObjectID();
+
+      // key9 sorts after the table keys and only exists in the cache. The max epoch keeps the double buffer
+      // from evicting it before the scan.
+      Table<String, OmKeyInfo> keyTable = metadataManager.getKeyTable(FILE_SYSTEM_OPTIMIZED);
+      OmKeyInfo key1Info = keyTable.get(metadataManager.getOzonePathKey(volumeId, bucketId, dir1Id, "key1"));
+      OmKeyInfo cacheOnlyKey = key1Info.toBuilder().setKeyName("key9").build();
+      String cacheOnlyDbKey = metadataManager.getOzonePathKey(volumeId, bucketId, dir1Id, "key9");
+      keyTable.addCacheEntry(new CacheKey<>(cacheOnlyDbKey), CacheValue.get(Long.MAX_VALUE, cacheOnlyKey));
+
+      ZonedDateTime date = ZonedDateTime.now(ZoneOffset.UTC).plusSeconds(EXPIRE_SECONDS);
+      createLifecyclePolicy(volumeName, bucketName, FILE_SYSTEM_OPTIMIZED, "", null, date.toString(), true);
+
+      Field tableField = OmMetadataManagerImpl.class.getDeclaredField("lifecycleScanStateTable");
+      tableField.setAccessible(true);
+      Table<String, OmLifecycleScanState> originalTable =
+          (Table<String, OmLifecycleScanState>) tableField.get(metadataManager);
+      Table<String, OmLifecycleScanState> spyTable = spy(originalTable);
+      tableField.set(metadataManager, spyTable);
+      try {
+        runSingleLifecycleScan(volumeName, bucketName);
+
+        GenericTestUtils.waitFor(() ->
+            (getDeletedKeyCount() - initialDeletedKeyCount) == 3, WAIT_CHECK_INTERVAL, 10000);
+        assertEquals(0, getKeyCount(FILE_SYSTEM_OPTIMIZED) - initialKeyCount);
+
+        String bucketKey = metadataManager.getBucketKey(volumeName, bucketName);
+        // With a batch size of 1, the expired list fills right after the cache pass, so scan state is saved
+        // while the cache-only key is the last key evaluated.
+        verify(spyTable, atLeast(1)).addCacheEntry(argThat(k -> k.getCacheKey().equals(bucketKey)), any());
+        verify(spyTable, never()).addCacheEntry(argThat(k -> k.getCacheKey().equals(bucketKey)),
+            argThat(v -> v.getCacheValue() != null && cacheOnlyDbKey.equals(v.getCacheValue().getLastScannedKey())));
+      } finally {
+        tableField.set(metadataManager, originalTable);
+      }
+    }
+
     @ParameterizedTest
     @ValueSource(strings = {"FILE_SYSTEM_OPTIMIZED", "OBJECT_STORE"})
     void testBucketRootScannedDirResume(BucketLayout layout) throws Exception {


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org