You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by si...@apache.org on 2026/10/21 07:15:04 UTC
(ozone) branch master updated: HDDS-16438. FSO lifecycle scan can skip an unscanned directory's expired keys after a mid scan crash (#11558)
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 2cb1b6dd4e9 HDDS-16438. FSO lifecycle scan can skip an unscanned directory's expired keys after a mid scan crash (#11558)
2cb1b6dd4e9 is described below
commit 2cb1b6dd4e9ace32c9c0c52ce80d1c3d3a1ffd8a
Author: Sammi Chen <sa...@apache.org>
AuthorDate: Wed Oct 21 15:14:58 2026 +0800
HDDS-16438. FSO lifecycle scan can skip an unscanned directory's expired keys after a mid scan crash (#11558)
---
.../ozone/om/service/KeyLifecycleService.java | 82 +++++++++++++++++-----
.../ozone/om/service/TestKeyLifecycleService.java | 43 ++++++++++++
2 files changed, 107 insertions(+), 18 deletions(-)
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 d74c220e8e4..416e2d4115b 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
@@ -58,6 +58,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.IntPredicate;
import java.util.stream.Collectors;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
@@ -760,30 +761,25 @@ private void evaluateKeyAndDirTable(OmBucketInfo bucket, long volumeObjId, Table
if (item.isFirstEvaluate()) {
// filter sub directory list
- if (!subDirSummary.getSubDirList().isEmpty()) {
- Iterator<OmDirectoryInfo> iterator = subDirSummary.getSubDirList().iterator();
- while (iterator.hasNext()) {
- OmDirectoryInfo subDir = iterator.next();
- String subDirPath = currentDirPath.isEmpty() ? subDir.getName() :
- currentDirPath + OM_KEY_PREFIX + subDir.getName();
+ if (!subDirSummary.isEmpty()) {
+ subDirSummary.removeIf(i -> {
+ OmDirectoryInfo subDir = subDirSummary.getSubDirList().get(i);
+ String subDirPath = currentDirPath.isEmpty() ? subDir.getName()
+ : currentDirPath + OM_KEY_PREFIX + subDir.getName();
if (subDirPath.startsWith(TRASH_PREFIX)) {
- iterator.remove();
- continue;
+ return true;
}
- boolean matched = false;
for (OmLCRule rule : ruleList) {
- if (rule.getEffectivePrefix() != null && subDirPath.startsWith(rule.getEffectivePrefix())) {
- matched = true;
- break;
+ if (rule.getEffectivePrefix() != null
+ && subDirPath.startsWith(rule.getEffectivePrefix())) {
+ return false;
}
}
- if (!matched) {
- iterator.remove();
- }
- }
+ return true;
+ });
}
- if (!subDirSummary.getSubDirList().isEmpty()) {
+ if (!subDirSummary.isEmpty()) {
item.setDirectoryList(subDirSummary);
item.setFirstEvaluate(false);
try {
@@ -954,6 +950,7 @@ private DirectoryList getSubDirectory(long dirObjID, String prefix, OMMetadataMa
Iterator<Map.Entry<CacheKey<String>, CacheValue<OmDirectoryInfo>>>
cacheIter = dirTable.cacheIterator();
HashSet<String> deletedDirSet = new HashSet();
+ HashSet<String> dirSet = new HashSet();
while (cacheIter.hasNext()) {
Map.Entry<CacheKey<String>, CacheValue<OmDirectoryInfo>> entry =
cacheIter.next();
@@ -964,6 +961,7 @@ private DirectoryList getSubDirectory(long dirObjID, String prefix, OMMetadataMa
continue;
}
if (cacheOmDirInfo.getParentObjectID() == dirObjID) {
+ dirSet.add(entry.getKey().getCacheKey());
subDirList.addSubDir(entry.getKey().getCacheKey(), cacheOmDirInfo, cacheOmDirInfo.getName());
}
}
@@ -975,7 +973,7 @@ private DirectoryList getSubDirectory(long dirObjID, String prefix, OMMetadataMa
numDirIterated++;
Table.KeyValue<String, OmDirectoryInfo> entry = iterator.next();
OmDirectoryInfo dir = entry.getValue();
- if (deletedDirSet.contains(entry.getKey())) {
+ if (deletedDirSet.contains(entry.getKey()) || dirSet.contains(entry.getKey())) {
continue;
}
if (dir.getParentObjectID() == dirObjID) {
@@ -983,6 +981,12 @@ private DirectoryList getSubDirectory(long dirObjID, String prefix, OMMetadataMa
}
}
}
+ // Cache children (hash order) and table children (ascending) are appended in that order; sort
+ // by name so the DFS stack pops siblings in strictly descending name order, which canSkipDir
+ // relies on to decide a resumed directory was already scanned.
+ if (!dirSet.isEmpty()) {
+ subDirList.sortByName();
+ }
return subDirList;
}
@@ -2001,6 +2005,48 @@ public void addSubDir(String key, OmDirectoryInfo dir, String dirPath) {
subDirCount++;
}
+ /** Removes the entry at {@code index} from all parallel lists. */
+ void removeAt(int index) {
+ subDirList.remove(index);
+ subDirKeyList.remove(index);
+ subDirKeyPathList.remove(index);
+ subDirCount--;
+ }
+
+ /** Removes the entry if shouldRemoveIndex returns true. */
+ void removeIf(IntPredicate shouldRemoveIndex) {
+ for (int i = subDirCount - 1; i >= 0; i--) {
+ if (shouldRemoveIndex.test(i)) {
+ removeAt(i);
+ }
+ }
+ }
+
+ public void sortByName() {
+ if (subDirCount <= 1) {
+ return;
+ }
+ List<Integer> order = new ArrayList<>(subDirCount);
+ for (int i = 0; i < subDirCount; i++) {
+ order.add(i);
+ }
+ order.sort(Comparator.comparing((Integer idx) -> subDirList.get(idx).getName()));
+ List<OmDirectoryInfo> sortedDirs = new ArrayList<>(subDirCount);
+ List<String> sortedKeys = new ArrayList<>(subDirCount);
+ List<String> sortedPaths = new ArrayList<>(subDirCount);
+ for (int idx : order) {
+ sortedDirs.add(subDirList.get(idx));
+ sortedKeys.add(subDirKeyList.get(idx));
+ sortedPaths.add(subDirKeyPathList.get(idx));
+ }
+ subDirList.clear();
+ subDirList.addAll(sortedDirs);
+ subDirKeyList.clear();
+ subDirKeyList.addAll(sortedKeys);
+ subDirKeyPathList.clear();
+ subDirKeyPathList.addAll(sortedPaths);
+ }
+
public OmDirectoryInfo getLastSubDir() {
return subDirCount > 0 ? subDirList.get(subDirCount - 1) : null;
}
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 1da98e26775..ed51e2e4649 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
@@ -45,6 +45,7 @@
import static org.apache.hadoop.ozone.om.helpers.BucketLayout.FILE_SYSTEM_OPTIMIZED;
import static org.apache.hadoop.ozone.om.helpers.BucketLayout.OBJECT_STORE;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.ALL;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -262,6 +263,48 @@ private void createSubject() throws Exception {
metadataManager = omTestManagers.getMetadataManager();
}
+ private static OmDirectoryInfo dirInfoWithName(String name) {
+ return OmDirectoryInfo.newBuilder().setName(name)
+ .setObjectID(Math.abs((long) name.hashCode()) + 1).setParentObjectID(1L).build();
+ }
+
+ @Test
+ void testDirectoryListSortByName() {
+ KeyLifecycleService.DirectoryList list = new KeyLifecycleService.DirectoryList();
+ // Add children out of order, as a cache-resident dir (lower name) followed by table siblings would.
+ list.addSubDir("keyC", dirInfoWithName("dirC"), "root/dirC");
+ list.addSubDir("keyA", dirInfoWithName("dirA"), "root/dirA");
+ list.addSubDir("keyB", dirInfoWithName("dirB"), "root/dirB");
+
+ list.sortByName();
+
+ // Ascending name order means the LIFO stack pops siblings in strictly descending name order,
+ // which is the property canSkipDir depends on when deciding a resumed directory was already scanned.
+ assertThat(list.getSubDirList())
+ .extracting(OmDirectoryInfo::getName)
+ .containsExactly("dirA", "dirB", "dirC");
+ // Parallel key list must remain index-aligned with the sorted directories.
+ assertThat(list.getSubDirKeyList()).containsExactly("keyA", "keyB", "keyC");
+ }
+
+ @Test
+ void testDirectoryListRemoveIf() {
+ KeyLifecycleService.DirectoryList list = new KeyLifecycleService.DirectoryList();
+ list.addSubDir("keyA", dirInfoWithName("dirA"), "root/dirA");
+ list.addSubDir("keyB", dirInfoWithName("dirB"), "root/dirB");
+ list.addSubDir("keyC", dirInfoWithName("dirC"), "root/dirC");
+
+ // Remove first and last (multiple backward removals), as prefix filtering might.
+ list.removeIf(i -> i == 0 || i == 2);
+
+ assertThat(list.getSubDirCount()).isEqualTo(1);
+ assertThat(list.getSubDirList()).hasSize(1);
+ assertThat(list.getSubDirKeyList()).hasSize(1);
+ assertThat(list.getSubDirList().get(0).getName()).isEqualTo("dirB");
+ assertThat(list.getSubDirKeyList().get(0)).isEqualTo("keyB");
+ assertThat(list.getLastSubDirPath()).isEqualTo("root/dirB");
+ }
+
/**
* Tests happy path.
*/
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org