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

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


The following commit(s) were added to refs/heads/master by this push:
     new 53e814e87c6 fix: Batch deletes in DurableStorageCleaner to avoid 
overlord OOM (#20425)
53e814e87c6 is described below

commit 53e814e87c6b28d5ee9710f36a61565b7456be89
Author: Akshat Jain <[email protected]>
AuthorDate: Fri Sep 25 16:10:47 2026 +0530

    fix: Batch deletes in DurableStorageCleaner to avoid overlord OOM (#20425)
    
    * Batch deletes in DurableStorageCleaner to avoid overlord OOM
    
    * Fix forbidden check
    
    * Minor changes
---
 .../indexing/cleaner/DurableStorageCleaner.java    | 22 +++++++-----
 .../msq/indexing/DurableStorageCleanerTest.java    | 40 ++++++++++++++++++++++
 2 files changed, 54 insertions(+), 8 deletions(-)

diff --git 
a/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/cleaner/DurableStorageCleaner.java
 
b/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/cleaner/DurableStorageCleaner.java
index 525da3c63fb..dae4848630c 100644
--- 
a/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/cleaner/DurableStorageCleaner.java
+++ 
b/multi-stage-query/src/main/java/org/apache/druid/msq/indexing/cleaner/DurableStorageCleaner.java
@@ -50,6 +50,7 @@ import java.util.stream.Collectors;
 public class DurableStorageCleaner implements OverlordDuty
 {
   private static final Logger LOG = new Logger(DurableStorageCleaner.class);
+  private static final int DELETE_BATCH_SIZE = 10000;
 
   private final DurableStorageCleanerConfig config;
   private final StorageConnector storageConnector;
@@ -101,6 +102,7 @@ public class DurableStorageCleaner implements OverlordDuty
                    .collect(Collectors.toSet());
 
     Set<String> filesToRemove = new HashSet<>();
+    int removedCount = 0;
     while (allFiles.hasNext()) {
       String currentFile = allFiles.next();
       String nextDirName = 
DurableStorageUtils.getNextDirNameWithPrefixFromPath(currentFile);
@@ -112,19 +114,23 @@ public class DurableStorageCleaner implements OverlordDuty
           // do nothing, query results should not be cleaned if the task is 
created before retain period
         } else {
           filesToRemove.add(currentFile);
+
+          if (filesToRemove.size() >= DELETE_BATCH_SIZE) {
+            storageConnector.deleteFiles(filesToRemove);
+            removedCount += filesToRemove.size();
+            filesToRemove.clear();
+            LOG.info("Removed [%d] files so far which are not associated with 
any MSQ task.", removedCount);
+          }
         }
       }
     }
 
-    if (filesToRemove.isEmpty()) {
-      LOG.info("There are no leftover directories to delete.");
-    } else {
-      LOG.info(
-          "Removing [%d] files which are not associated with any MSQ task.",
-          filesToRemove.size()
-      );
-      LOG.debug("Files to remove:\n[%s]\n", filesToRemove);
+    if (!filesToRemove.isEmpty()) {
       storageConnector.deleteFiles(filesToRemove);
+      removedCount += filesToRemove.size();
+      LOG.info("Removed [%d] files so far which are not associated with any 
MSQ task.", removedCount);
+    } else if (removedCount == 0) {
+      LOG.info("There are no leftover directories to delete.");
     }
   }
 
diff --git 
a/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/DurableStorageCleanerTest.java
 
b/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/DurableStorageCleanerTest.java
index 5f4d804ed69..e2524f06a06 100644
--- 
a/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/DurableStorageCleanerTest.java
+++ 
b/multi-stage-query/src/test/java/org/apache/druid/msq/indexing/DurableStorageCleanerTest.java
@@ -45,6 +45,8 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -135,6 +137,44 @@ public class DurableStorageCleanerTest
     Assertions.assertEquals(Sets.newHashSet(STRAY_DIR, intermediateFilesPath), 
capturedArguments.getValue());
   }
 
+  @Test
+  public void testRunDeletesInBatchesAndKeepsTheRemainder() throws Exception
+  {
+    final List<String> strayFiles = new ArrayList<>();
+    for (int i = 0; i < 10_001; i++) {
+      strayFiles.add(STRAY_DIR + i);
+    }
+
+    final List<String> listing = ImmutableList.<String>builder()
+        .addAll(strayFiles)
+        .add(DurableStorageUtils.getControllerDirectory(TASK_ID))
+        .build();
+
+    Mockito.when(TASK_STORAGE.getCompletedTasksInfo(ArgumentMatchers.any(), 
ArgumentMatchers.isNull())).thenReturn(List.of());
+    
Mockito.when(STORAGE_CONNECTOR.listDir(ArgumentMatchers.anyString())).thenReturn(listing.iterator());
+    Mockito.when(TASK_RUNNER_WORK_ITEM.getTaskId()).thenReturn(TASK_ID);
+    
Mockito.doReturn(ImmutableList.of(TASK_RUNNER_WORK_ITEM)).when(TASK_RUNNER).getRunningTasks();
+    
Mockito.when(TASK_MASTER.getTaskRunner()).thenReturn(Optional.of(TASK_RUNNER));
+
+    final Set<String> deleted = new HashSet<>();
+    final List<Integer> batchSizes = new ArrayList<>();
+    Mockito.doAnswer(invocation -> {
+      final Iterable<String> batch = invocation.getArgument(0);
+      int size = 0;
+      for (String file : batch) {
+        deleted.add(file);
+        size++;
+      }
+      batchSizes.add(size);
+      return null;
+    }).when(STORAGE_CONNECTOR).deleteFiles(ArgumentMatchers.any());
+
+    durableStorageCleaner.run();
+
+    Assertions.assertTrue(batchSizes.size() > 1, "expected more than one 
batch, got " + batchSizes);
+    Assertions.assertEquals(Sets.newHashSet(strayFiles), deleted);
+  }
+
   @Test
   public void testGetSchedule()
   {


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

Reply via email to