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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7be0710b3 [AMORO-4277] imporove expire_snapshots performance (#4278)
7be0710b3 is described below

commit 7be0710b3e134f889ce1f970604428663c0ca97b
Author: Wang Tao <[email protected]>
AuthorDate: Fri Jul 24 15:45:39 2026 +0800

    [AMORO-4277] imporove expire_snapshots performance (#4278)
    
    * optimize expire snapshots
    fix: logical error
    remove some comments
    
    * remove duplicate class and move test case to amoro-format-iceberg
    
    * Update 
amoro-format-iceberg/src/main/java/org/apache/amoro/utils/TableFileUtil.java
    
    Co-authored-by: Xu Bai <[email protected]>
    
    * fix review comment
    
    ---------
    
    Co-authored-by: Xu Bai <[email protected]>
---
 .../amoro/server/utils/RollingFileCleaner.java     | 138 ---------------------
 .../formats/iceberg/utils/RollingFileCleaner.java  |  41 +++---
 .../java/org/apache/amoro/utils/TableFileUtil.java |  18 ++-
 .../iceberg/utils}/TestRollingFileCleaner.java     |   3 +-
 4 files changed, 44 insertions(+), 156 deletions(-)

diff --git 
a/amoro-ams/src/main/java/org/apache/amoro/server/utils/RollingFileCleaner.java 
b/amoro-ams/src/main/java/org/apache/amoro/server/utils/RollingFileCleaner.java
deleted file mode 100644
index 821bf6661..000000000
--- 
a/amoro-ams/src/main/java/org/apache/amoro/server/utils/RollingFileCleaner.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.amoro.server.utils;
-
-import org.apache.amoro.io.AuthenticatedFileIO;
-import org.apache.amoro.shade.guava32.com.google.common.collect.Sets;
-import org.apache.amoro.utils.TableFileUtil;
-import org.apache.hadoop.fs.Path;
-import org.apache.iceberg.io.BulkDeletionFailureException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.URI;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class RollingFileCleaner {
-  private final Set<String> collectedFiles = Sets.newConcurrentHashSet();
-  private final Set<String> excludeFiles;
-  private final Set<String> parentDirectories = Sets.newConcurrentHashSet();
-
-  private static final int CLEANED_FILE_GROUP_SIZE = 1_000;
-
-  private final AtomicInteger fileCounter = new AtomicInteger(0);
-  private final AtomicInteger cleanedFileCounter = new AtomicInteger(0);
-
-  private final AuthenticatedFileIO fileIO;
-
-  private static final Logger LOG = 
LoggerFactory.getLogger(RollingFileCleaner.class);
-
-  public RollingFileCleaner(AuthenticatedFileIO fileIO, Set<String> 
excludeFiles) {
-    this.fileIO = fileIO;
-    this.excludeFiles =
-        excludeFiles != null
-            ? Sets.newConcurrentHashSet(excludeFiles)
-            : Sets.newConcurrentHashSet();
-  }
-
-  public void addFile(String filePath) {
-    if (isFileExcluded(filePath)) {
-      LOG.debug("File {} is excluded from cleaning", filePath);
-      return;
-    }
-
-    collectedFiles.add(filePath);
-    String parentDir = TableFileUtil.getParent(filePath);
-    parentDirectories.add(parentDir);
-    int currentCount = fileCounter.incrementAndGet();
-
-    if (currentCount % CLEANED_FILE_GROUP_SIZE == 0) {
-      doCleanFiles();
-    }
-  }
-
-  private boolean isFileExcluded(String filePath) {
-    if (excludeFiles.isEmpty()) {
-      return false;
-    }
-
-    if (excludeFiles.contains(filePath)) {
-      return true;
-    }
-
-    String uriPath = URI.create(filePath).getPath();
-    if (excludeFiles.contains(uriPath)) {
-      return true;
-    }
-
-    String parentPath = new Path(uriPath).getParent().toString();
-    return excludeFiles.contains(parentPath);
-  }
-
-  private void doCleanFiles() {
-    if (collectedFiles.isEmpty()) {
-      return;
-    }
-
-    if (fileIO.supportBulkOperations()) {
-      try {
-        fileIO.asBulkFileIO().deleteFiles(collectedFiles);
-        cleanedFileCounter.addAndGet(collectedFiles.size());
-      } catch (BulkDeletionFailureException e) {
-        LOG.warn("Failed to delete {} expired files in bulk", 
e.numberFailedObjects());
-      }
-    } else {
-      for (String filePath : collectedFiles) {
-        try {
-          fileIO.deleteFile(filePath);
-          cleanedFileCounter.incrementAndGet();
-        } catch (Exception e) {
-          LOG.warn("Failed to delete expired file: {}", filePath, e);
-        }
-      }
-    }
-    // Try to delete empty parent directories
-    for (String parentDir : parentDirectories) {
-      TableFileUtil.deleteEmptyDirectory(fileIO, parentDir, excludeFiles);
-    }
-    parentDirectories.clear();
-
-    LOG.debug("Cleaned expired a file group, total files: {}", 
collectedFiles.size());
-
-    collectedFiles.clear();
-  }
-
-  public int fileCount() {
-    return fileCounter.get();
-  }
-
-  public int cleanedFileCount() {
-    return cleanedFileCounter.get();
-  }
-
-  public void clear() {
-    if (!collectedFiles.isEmpty()) {
-      doCleanFiles();
-    }
-
-    collectedFiles.clear();
-    excludeFiles.clear();
-  }
-}
diff --git 
a/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/utils/RollingFileCleaner.java
 
b/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/utils/RollingFileCleaner.java
index 8d2e8ac06..3fcdcb5b2 100644
--- 
a/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/utils/RollingFileCleaner.java
+++ 
b/amoro-format-iceberg/src/main/java/org/apache/amoro/formats/iceberg/utils/RollingFileCleaner.java
@@ -27,8 +27,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.net.URI;
+import java.util.Comparator;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 /** Rolling file cleaner for Iceberg table maintenance operations. */
 public class RollingFileCleaner {
@@ -110,27 +113,34 @@ public class RollingFileCleaner {
           }
         }
       }
-      // Try to delete empty parent directories. Skipped automatically by
-      // TableFileUtil.deleteEmptyDirectory when the underlying FileIO is an 
object store.
-      if (fileIO.supportFileSystemOperations()) {
-        for (String parentDir : parentDirectories) {
-          try {
-            TableFileUtil.deleteEmptyDirectory(fileIO, parentDir, 
excludeFiles);
-          } catch (Exception e) {
-            LOG.warn("Failed to delete empty parent directory: {}", parentDir, 
e);
-          }
-        }
-      }
 
       LOG.debug("Cleaned expired a file group, total files: {}", 
collectedFiles.size());
     } finally {
-      // Always drop both buffers so a failure in directory cleanup does not 
cause the
-      // already-deleted files to be re-submitted on the next batch.
-      parentDirectories.clear();
       collectedFiles.clear();
     }
   }
 
+  private void doCleanParentDirectory() {
+    // Try to delete empty parent directories. Skipped automatically by
+    // TableFileUtil.deleteEmptyDirectory when the underlying FileIO is an 
object store.
+    if (fileIO.supportFileSystemOperations()) {
+      List<String> parentDirectoriesSorted =
+          parentDirectories.stream()
+              .sorted(Comparator.comparingInt(String::length).reversed())
+              .collect(Collectors.toList());
+      for (String parentDir : parentDirectoriesSorted) {
+        try {
+          TableFileUtil.deleteEmptyDirectory(fileIO, parentDir, excludeFiles, 
parentDirectories);
+        } catch (Exception e) {
+          LOG.warn("Failed to delete empty parent directory: {}", parentDir, 
e);
+        }
+      }
+    }
+    // Always drop both buffers so a failure in directory cleanup does not 
cause the
+    // already-deleted files to be re-submitted on the next batch.
+    parentDirectories.clear();
+  }
+
   public int fileCount() {
     return fileCounter.get();
   }
@@ -143,6 +153,9 @@ public class RollingFileCleaner {
     if (!collectedFiles.isEmpty()) {
       doCleanFiles();
     }
+    if (!parentDirectories.isEmpty()) {
+      doCleanParentDirectory();
+    }
 
     collectedFiles.clear();
     excludeFiles.clear();
diff --git 
a/amoro-format-iceberg/src/main/java/org/apache/amoro/utils/TableFileUtil.java 
b/amoro-format-iceberg/src/main/java/org/apache/amoro/utils/TableFileUtil.java
index 1bbe17d01..5ec207467 100644
--- 
a/amoro-format-iceberg/src/main/java/org/apache/amoro/utils/TableFileUtil.java
+++ 
b/amoro-format-iceberg/src/main/java/org/apache/amoro/utils/TableFileUtil.java
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.net.URI;
+import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -75,15 +76,25 @@ public class TableFileUtil {
     return filePath.substring(0, lastSlash);
   }
 
+  public static void deleteEmptyDirectory(
+      AuthenticatedFileIO io, String directoryPath, Set<String> exclude) {
+    deleteEmptyDirectory(io, directoryPath, exclude, new HashSet<>());
+  }
+
   /**
    * Try to recursiveDelete the empty directory
    *
    * @param io mixed-format file io
    * @param directoryPath directory location
    * @param exclude the directory will not be deleted
+   * @param directoriesToBeDeleted: all the directories that need to be 
deleted directories that
+   *     need to be deleted
    */
   public static void deleteEmptyDirectory(
-      AuthenticatedFileIO io, String directoryPath, Set<String> exclude) {
+      AuthenticatedFileIO io,
+      String directoryPath,
+      Set<String> exclude,
+      Set<String> directoriesToBeDeleted) {
     if (directoryPath == null || directoryPath.isEmpty()) {
       return;
     }
@@ -108,7 +119,10 @@ public class TableFileUtil {
     if (io.asFileSystemIO().isEmptyDirectory(directoryPath)) {
       io.asFileSystemIO().deletePrefix(directoryPath);
       LOG.debug("success delete empty directory {}", directoryPath);
-      deleteEmptyDirectory(io, parent, exclude);
+      // for parent must be deleted after the sub-directory
+      if (directoriesToBeDeleted == null || 
!directoriesToBeDeleted.contains(parent)) {
+        deleteEmptyDirectory(io, parent, exclude);
+      }
     }
   }
 
diff --git 
a/amoro-ams/src/test/java/org/apache/amoro/server/util/TestRollingFileCleaner.java
 
b/amoro-format-iceberg/src/test/java/org/apache/amoro/formats/iceberg/utils/TestRollingFileCleaner.java
similarity index 95%
rename from 
amoro-ams/src/test/java/org/apache/amoro/server/util/TestRollingFileCleaner.java
rename to 
amoro-format-iceberg/src/test/java/org/apache/amoro/formats/iceberg/utils/TestRollingFileCleaner.java
index 5931f72f3..e3461b588 100644
--- 
a/amoro-ams/src/test/java/org/apache/amoro/server/util/TestRollingFileCleaner.java
+++ 
b/amoro-format-iceberg/src/test/java/org/apache/amoro/formats/iceberg/utils/TestRollingFileCleaner.java
@@ -16,11 +16,10 @@
  * limitations under the License.
  */
 
-package org.apache.amoro.server.util;
+package org.apache.amoro.formats.iceberg.utils;
 
 import org.apache.amoro.io.AuthenticatedFileIO;
 import org.apache.amoro.io.AuthenticatedFileIOAdapter;
-import org.apache.amoro.server.utils.RollingFileCleaner;
 import org.apache.amoro.shade.guava32.com.google.common.collect.Sets;
 import org.apache.iceberg.inmemory.InMemoryFileIO;
 import org.junit.jupiter.api.Assertions;

Reply via email to