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

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


The following commit(s) were added to refs/heads/master by this push:
     new 350c6c7bcd2 Optimize pipe TsFile cleanup on drop (#18163)
350c6c7bcd2 is described below

commit 350c6c7bcd25b883eca53897b294b57637850119
Author: Zhenyu Luo <[email protected]>
AuthorDate: Fri Jul 10 14:15:25 2026 +0800

    Optimize pipe TsFile cleanup on drop (#18163)
    
    * Optimize pipe TsFile cleanup on drop
    
    * Use ThreadName for pipe TsFile cleanup executor
    
    * Make pipe TsFile cleanup thread lazy
    
    * Use IoTDB thread pool factory for pipe cleanup
    
    * Clarify pipe TsFile cleanup deletion guard
    
    * Reuse periodical cleaner for Pipe TsFile cleanup
    
    * Clarify stale Pipe dir collection
    
    * Revert "Clarify stale Pipe dir collection"
    
    This reverts commit 25625fb1cf2a76fb54360800b990d27449b12feb.
    
    * Avoid duplicate stale Pipe dir collection
---
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |  2 +
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |  2 +
 .../db/pipe/agent/task/PipeDataNodeTaskAgent.java  | 22 ++++++
 ...aNodeHardlinkOrCopiedFileDirStartupCleaner.java | 85 +++++++++++++++-------
 .../pipe/resource/tsfile/PipeTsFileResource.java   |  8 +-
 .../resource/tsfile/PipeTsFileResourceManager.java | 39 +++++++++-
 6 files changed, 131 insertions(+), 27 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index e7c6c85ca5c..67932c73967 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -1410,6 +1410,8 @@ public final class DataNodePipeMessages {
       "Finished deleting all stale pipe hardlink dirs by periodical job.";
   public static final String PIPE_HARDLINK_DIR_MOVE_FAILED_DELETING_SYNC =
       "Failed to move pipe hardlink dir {} for periodical deletion, deleting 
it synchronously.";
+  public static final String 
PIPE_HARDLINK_DIR_MOVE_FAILED_SKIPPING_PERIODICAL_DELETE =
+      "Failed to move pipe hardlink dir {} for periodical deletion, skip 
registering the original dir to avoid deleting files of a recreated pipe.";
   public static final String PIPE_SNAPSHOT_DIR_FOUND_DELETING_IT =
       "Pipe snapshot dir found, deleting it: {},";
   public static final String 
SHRINK_CALLBACK_IS_NOT_SUPPORTED_IN_PIPEFIXEDMEMORYBLOCK =
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index 54b68d18e0d..1e2954d80ff 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -1326,6 +1326,8 @@ public final class DataNodePipeMessages {
       "已通过周期性任务删除全部过期的 Pipe hardlink 目录。";
   public static final String PIPE_HARDLINK_DIR_MOVE_FAILED_DELETING_SYNC =
       "为周期性删除移动 Pipe hardlink 目录 {} 失败,改为同步删除。";
+  public static final String 
PIPE_HARDLINK_DIR_MOVE_FAILED_SKIPPING_PERIODICAL_DELETE =
+      "为周期性删除移动 Pipe hardlink 目录 {} 失败,跳过注册原目录以避免删除重建 Pipe 的文件。";
   public static final String PIPE_SNAPSHOT_DIR_FOUND_DELETING_IT =
       "发现 Pipe snapshot 目录,正在删除:{},";
   public static final String 
SHRINK_CALLBACK_IS_NOT_SUPPORTED_IN_PIPEFIXEDMEMORYBLOCK =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
index df08311151a..dce7ee5f6d9 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
@@ -59,6 +59,7 @@ import 
org.apache.iotdb.db.pipe.metric.overview.PipeDataNodeSinglePipeMetrics;
 import org.apache.iotdb.db.pipe.metric.overview.PipeTsFileToTabletsMetrics;
 import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
 import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager;
+import org.apache.iotdb.db.pipe.resource.tsfile.PipeTsFileResourceManager;
 import org.apache.iotdb.db.pipe.source.dataregion.DataRegionListeningFilter;
 import 
org.apache.iotdb.db.pipe.source.dataregion.realtime.listener.PipeInsertionDataNodeListener;
 import 
org.apache.iotdb.db.pipe.source.schemaregion.SchemaRegionListeningFilter;
@@ -304,13 +305,20 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
 
   @Override
   protected boolean dropPipe(final String pipeName, final long creationTime) {
+    final String pipeTsFileResourcePipeName =
+        PipeTsFileResourceManager.getPipeTsFileResourcePipeName(pipeName, 
creationTime);
+    
PipeDataNodeResourceManager.tsfile().markPipeTsFileDirUnderDeletion(pipeTsFileResourcePipeName);
+
     if (!super.dropPipe(pipeName, creationTime)) {
+      PipeDataNodeResourceManager.tsfile()
+          .unmarkPipeTsFileDirUnderDeletion(pipeTsFileResourcePipeName);
       return false;
     }
 
     final String taskId = pipeName + "_" + creationTime;
     PipeTsFileToTabletsMetrics.getInstance().deregister(taskId);
     PipeDataNodeSinglePipeMetrics.getInstance().deregister(taskId);
+    
PipeDataNodeResourceManager.tsfile().cleanPipeTsFileDir(pipeTsFileResourcePipeName);
 
     return true;
   }
@@ -319,6 +327,15 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
   protected boolean dropPipe(final String pipeName) {
     // Get the pipe meta first because it is removed after 
super#dropPipe(pipeName)
     final PipeMeta pipeMeta = pipeMetaKeeper.getPipeMeta(pipeName);
+    final String pipeTsFileResourcePipeName =
+        Objects.isNull(pipeMeta)
+            ? null
+            : PipeTsFileResourceManager.getPipeTsFileResourcePipeName(
+                pipeName, pipeMeta.getStaticMeta().getCreationTime());
+    if (Objects.nonNull(pipeTsFileResourcePipeName)) {
+      PipeDataNodeResourceManager.tsfile()
+          .markPipeTsFileDirUnderDeletion(pipeTsFileResourcePipeName);
+    }
 
     // Record whether there are pipe tasks before dropping the pipe
     final boolean hasPipeTasks;
@@ -331,6 +348,10 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
     }
 
     if (!super.dropPipe(pipeName)) {
+      if (Objects.nonNull(pipeTsFileResourcePipeName)) {
+        PipeDataNodeResourceManager.tsfile()
+            .unmarkPipeTsFileDirUnderDeletion(pipeTsFileResourcePipeName);
+      }
       return false;
     }
 
@@ -339,6 +360,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
       final String taskId = pipeName + "_" + creationTime;
       PipeTsFileToTabletsMetrics.getInstance().deregister(taskId);
       PipeDataNodeSinglePipeMetrics.getInstance().deregister(taskId);
+      
PipeDataNodeResourceManager.tsfile().cleanPipeTsFileDir(pipeTsFileResourcePipeName);
       // When the pipe contains no pipe tasks, there is no corresponding 
prefetching queue for the
       // subscribed pipe, so the subscription needs to be manually marked as 
completed.
       if (!hasPipeTasks && PipeStaticMeta.isSubscriptionPipe(pipeName)) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner.java
index bb1f760f449..db60fc4645b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner.java
@@ -37,6 +37,7 @@ import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -49,6 +50,9 @@ public class 
PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner {
       "PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner#cleanTsFileDir()";
   private static final long DELETE_MAX_PATH_COUNT_PER_ROUND = 100_000L;
   private static final long DELETE_MAX_TIME_PER_ROUND_MS = 1_000L;
+  private static final PeriodicalStalePipeDirCleaner 
PERIODICAL_STALE_PIPE_DIR_CLEANER =
+      new PeriodicalStalePipeDirCleaner();
+  private static final AtomicBoolean PERIODICAL_CLEANUP_JOB_REGISTERED = new 
AtomicBoolean(false);
 
   /**
    * Delete the data directory and all of its subdirectories that contain the
@@ -71,7 +75,8 @@ public class 
PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner {
         moveAsideAndCollect(pipeHardLinkDir, pipeHardlinkBaseDirName, 
stalePipeDirs);
       }
     }
-    registerPeriodicalCleanupJob(periodicalJobRegistrar, stalePipeDirs);
+    PERIODICAL_STALE_PIPE_DIR_CLEANER.addStalePipeDirs(stalePipeDirs);
+    registerPeriodicalCleanupJob(periodicalJobRegistrar);
   }
 
   private static void collectInterruptedStalePipeDirs(
@@ -82,7 +87,7 @@ public class 
PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner {
         localDataDir.listFiles(
             file ->
                 file.isDirectory()
-                    && file.getName().startsWith(pipeHardlinkBaseDirName + 
STALE_PIPE_DIR_SUFFIX));
+                    && file.getName().contains(pipeHardlinkBaseDirName + 
STALE_PIPE_DIR_SUFFIX));
     if (stalePipeDirFiles == null) {
       return;
     }
@@ -137,17 +142,47 @@ public class 
PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner {
   }
 
   private static void registerPeriodicalCleanupJob(
-      final PeriodicalJobRegistrar periodicalJobRegistrar, final List<File> 
stalePipeDirs) {
-    if (stalePipeDirs.isEmpty()) {
+      final PeriodicalJobRegistrar periodicalJobRegistrar) {
+    if (!PERIODICAL_CLEANUP_JOB_REGISTERED.compareAndSet(false, true)) {
       return;
     }
 
     periodicalJobRegistrar.register(
         PERIODICAL_CLEANUP_JOB_ID,
-        new PeriodicalStalePipeDirCleaner(stalePipeDirs)::cleanOneRound,
+        PERIODICAL_STALE_PIPE_DIR_CLEANER::cleanOneRound,
         
PipeConfig.getInstance().getPipeSubtaskExecutorCronHeartbeatEventIntervalSeconds());
   }
 
+  public static void submitStalePipeDirForPeriodicalCleanup(final File 
stalePipeDir) {
+    if (stalePipeDir == null) {
+      return;
+    }
+
+    final File stalePipeDirToDelete;
+    if (stalePipeDir.isDirectory()) {
+      try {
+        stalePipeDirToDelete = moveAside(stalePipeDir, stalePipeDir.getName());
+        LOGGER.info(
+            
DataNodePipeMessages.PIPE_HARDLINK_DIR_FOUND_MOVED_TO_PERIODICAL_DELETE,
+            stalePipeDir,
+            stalePipeDirToDelete);
+      } catch (final IOException e) {
+        LOGGER.warn(
+            
DataNodePipeMessages.PIPE_HARDLINK_DIR_MOVE_FAILED_SKIPPING_PERIODICAL_DELETE,
+            stalePipeDir,
+            e);
+        return;
+      }
+    } else {
+      stalePipeDirToDelete = stalePipeDir;
+    }
+
+    LOGGER.info(
+        
DataNodePipeMessages.PIPE_STALE_HARDLINK_DIR_FOUND_REGISTERING_PERIODICAL_DELETE,
+        stalePipeDirToDelete);
+    PERIODICAL_STALE_PIPE_DIR_CLEANER.addStalePipeDir(stalePipeDirToDelete);
+  }
+
   private static CleanupRoundResult deleteQuietlyWithThrottle(final File 
stalePipeDir) {
     if (!stalePipeDir.exists()) {
       return CleanupRoundResult.finished();
@@ -230,33 +265,36 @@ public class 
PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner {
 
   private static class PeriodicalStalePipeDirCleaner {
 
-    private final List<File> stalePipeDirs;
-    private int currentDirIndex;
-    private boolean finished;
+    private final ConcurrentLinkedQueue<File> stalePipeDirs = new 
ConcurrentLinkedQueue<>();
+    private File currentStalePipeDir;
 
-    private PeriodicalStalePipeDirCleaner(final List<File> stalePipeDirs) {
-      this.stalePipeDirs = stalePipeDirs;
-      currentDirIndex = 0;
-      finished = false;
+    private void addStalePipeDir(final File stalePipeDir) {
+      stalePipeDirs.offer(stalePipeDir);
     }
 
-    private void cleanOneRound() {
-      if (finished) {
-        return;
-      }
+    private void addStalePipeDirs(final List<File> stalePipeDirs) {
+      stalePipeDirs.forEach(this::addStalePipeDir);
+    }
 
+    private void cleanOneRound() {
       long deletedPathCount = 0;
-      while (currentDirIndex < stalePipeDirs.size()) {
-        final File stalePipeDir = stalePipeDirs.get(currentDirIndex);
-        final CleanupRoundResult result = 
deleteQuietlyWithThrottle(stalePipeDir);
+      while (true) {
+        if (currentStalePipeDir == null) {
+          currentStalePipeDir = stalePipeDirs.poll();
+        }
+        if (currentStalePipeDir == null) {
+          return;
+        }
+
+        final CleanupRoundResult result = 
deleteQuietlyWithThrottle(currentStalePipeDir);
         deletedPathCount += result.deletedPathCount;
 
         if (result.finished) {
           LOGGER.info(
               
DataNodePipeMessages.PIPE_HARDLINK_DIR_PERIODICAL_DELETE_FINISHED,
-              stalePipeDir,
+              currentStalePipeDir,
               result.success);
-          ++currentDirIndex;
+          currentStalePipeDir = null;
           continue;
         }
 
@@ -264,14 +302,11 @@ public class 
PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner {
           LOGGER.info(
               
DataNodePipeMessages.PIPE_HARDLINK_DIR_PERIODICAL_DELETE_PROGRESS,
               deletedPathCount,
-              stalePipeDir,
+              currentStalePipeDir,
               result.success);
         }
         return;
       }
-
-      finished = true;
-      
LOGGER.info(DataNodePipeMessages.PIPE_HARDLINK_DIR_PERIODICAL_DELETE_ALL_FINISHED);
     }
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResource.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResource.java
index 51421cdcec8..a8a7aa7e195 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResource.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResource.java
@@ -70,9 +70,15 @@ public class PipeTsFileResource implements AutoCloseable {
   }
 
   public boolean decreaseReferenceCount() {
+    return decreaseReferenceCount(true);
+  }
+
+  public boolean decreaseReferenceCount(final boolean 
deleteFileWhenNoReference) {
     final int finalReferenceCount = referenceCount.addAndGet(-1);
     if (finalReferenceCount == 0) {
-      close();
+      if (deleteFileWhenNoReference) {
+        close();
+      }
       return true;
     }
     if (finalReferenceCount < 0) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
index 4fc1c3063df..5bcdaec14a1 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.commons.utils.FileUtils;
 import org.apache.iotdb.commons.utils.TestOnly;
 import org.apache.iotdb.db.i18n.DataNodePipeMessages;
+import 
org.apache.iotdb.db.pipe.resource.PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
 
 import org.apache.tsfile.enums.TSDataType;
@@ -39,6 +40,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class PipeTsFileResourceManager {
@@ -54,6 +56,9 @@ public class PipeTsFileResourceManager {
   // PipeName -> TsFilePath -> PipeTsFileResource
   private final Map<String, Map<String, PipeTsFileResource>>
       hardlinkOrCopiedFileToPipeTsFileResourceMap = new ConcurrentHashMap<>();
+  private final Map<String, String> pipeNameToPipeTsFileDirPathMap = new 
ConcurrentHashMap<>();
+  private final Set<String> pipeTsFileResourcePipeNameSetUnderDeletion =
+      ConcurrentHashMap.newKeySet();
   private final PipeTsFileResourceSegmentLock segmentLock = new 
PipeTsFileResourceSegmentLock();
 
   public static String getPipeTsFileResourcePipeName(
@@ -126,6 +131,8 @@ public class PipeTsFileResourceManager {
       // file in pipe dir, create a hardlink or copy it to pipe dir, maintain 
a reference count for
       // the hardlink or copied file, and return the hardlink or copied file.
       if (Objects.nonNull(pipeName)) {
+        pipeNameToPipeTsFileDirPathMap.putIfAbsent(
+            pipeName, hardlinkOrCopiedFile.getParentFile().getPath());
         hardlinkOrCopiedFileToPipeTsFileResourceMap
             .computeIfAbsent(pipeName, k -> new ConcurrentHashMap<>())
             .put(resultFile.getPath(), new PipeTsFileResource(resultFile));
@@ -225,7 +232,8 @@ public class PipeTsFileResourceManager {
     try {
       final String filePath = hardlinkOrCopiedFile.getPath();
       final PipeTsFileResource resource = 
getResourceMap(pipeName).get(filePath);
-      if (resource != null && resource.decreaseReferenceCount()) {
+      if (resource != null
+          && 
resource.decreaseReferenceCount(shouldDeleteFileWhenNoReference(pipeName))) {
         getResourceMap(pipeName).remove(filePath);
       }
     } finally {
@@ -246,6 +254,35 @@ public class PipeTsFileResourceManager {
     decreaseFileReference(new File(getCommonFilePath(file)), null);
   }
 
+  private boolean shouldDeleteFileWhenNoReference(
+      final @Nullable String pipeTsFileResourcePipeName) {
+    return Objects.isNull(pipeTsFileResourcePipeName)
+        || 
!pipeTsFileResourcePipeNameSetUnderDeletion.contains(pipeTsFileResourcePipeName);
+  }
+
+  public void markPipeTsFileDirUnderDeletion(final @Nonnull String 
pipeTsFileResourcePipeName) {
+    pipeTsFileResourcePipeNameSetUnderDeletion.add(pipeTsFileResourcePipeName);
+  }
+
+  public void unmarkPipeTsFileDirUnderDeletion(final @Nonnull String 
pipeTsFileResourcePipeName) {
+    
pipeTsFileResourcePipeNameSetUnderDeletion.remove(pipeTsFileResourcePipeName);
+  }
+
+  public void cleanPipeTsFileDir(final @Nonnull String 
pipeTsFileResourcePipeName) {
+    final String pipeTsFileDirPath =
+        pipeNameToPipeTsFileDirPathMap.remove(pipeTsFileResourcePipeName);
+    
hardlinkOrCopiedFileToPipeTsFileResourceMap.remove(pipeTsFileResourcePipeName);
+
+    if (Objects.isNull(pipeTsFileDirPath)) {
+      
pipeTsFileResourcePipeNameSetUnderDeletion.remove(pipeTsFileResourcePipeName);
+      return;
+    }
+
+    
PipeDataNodeHardlinkOrCopiedFileDirStartupCleaner.submitStalePipeDirForPeriodicalCleanup(
+        new File(pipeTsFileDirPath));
+    
pipeTsFileResourcePipeNameSetUnderDeletion.remove(pipeTsFileResourcePipeName);
+  }
+
   // Warning: Shall not be called by the assigner
   private String getCommonFilePath(final @Nonnull File file) {
     // If the parent or grandparent is null then this is testing scenario

Reply via email to