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 <[email protected]>
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 <[email protected]>
---
.../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: [email protected]
For additional commands, e-mail: [email protected]