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 d6751c8d38e Pipe: improve incremental memory estimation (#18049)
d6751c8d38e is described below

commit d6751c8d38e1df010116e04f14c033b03aa1ba96
Author: Caideyipi <[email protected]>
AuthorDate: Fri Jul 3 15:43:53 2026 +0800

    Pipe: improve incremental memory estimation (#18049)
    
    * Pipe: improve incremental memory estimation
    
    * Pipe: add memory estimation unit tests
    
    * Pipe: localize memory check messages
---
 .../db/pipe/agent/task/PipeDataNodeTaskAgent.java  | 292 ++++++++++++++++++---
 .../task/builder/PipeDataNodeTaskBuilder.java      |  35 ++-
 .../task/subtask/sink/PipeSinkSubtaskManager.java  |  96 ++++---
 .../pipe/agent/task/PipeDataNodeTaskAgentTest.java | 159 +++++++++++
 .../task/builder/PipeDataNodeTaskBuilderTest.java  | 111 ++++++++
 .../subtask/sink/PipeSinkSubtaskManagerTest.java   |  95 +++++++
 .../apache/iotdb/commons/i18n/PipeMessages.java    |   7 +
 .../apache/iotdb/commons/i18n/PipeMessages.java    |   6 +
 .../commons/pipe/agent/task/PipeTaskAgent.java     |  15 +-
 9 files changed, 718 insertions(+), 98 deletions(-)

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 dfd3ccf4f03..6a9352deb77 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
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.db.pipe.agent.task;
 
+import org.apache.iotdb.common.rpc.thrift.TEndPoint;
 import org.apache.iotdb.common.rpc.thrift.TPipeHeartbeatResp;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.concurrent.IoTThreadFactory;
@@ -38,6 +39,7 @@ import org.apache.iotdb.commons.pipe.agent.task.PipeTaskAgent;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInAgent;
@@ -48,6 +50,7 @@ import 
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
 import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
 import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
 import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.commons.utils.NodeUrlUtils;
 import org.apache.iotdb.consensus.exception.ConsensusException;
 import org.apache.iotdb.db.conf.IoTDBConfig;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -56,6 +59,7 @@ import org.apache.iotdb.db.i18n.DataNodePipeMessages;
 import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
 import org.apache.iotdb.db.pipe.agent.task.builder.PipeDataNodeBuilder;
 import org.apache.iotdb.db.pipe.agent.task.builder.PipeDataNodeTaskBuilder;
+import org.apache.iotdb.db.pipe.agent.task.subtask.sink.PipeSinkSubtaskManager;
 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;
@@ -194,6 +198,11 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
           // For internal source
           || needConstructDataRegionTask
           || needConstructSchemaRegionTask) {
+        calculateMemoryUsage(
+            pipeStaticMeta,
+            Collections.singletonList(new Pair<>(consensusGroupId, 
pipeTaskMeta)),
+            false);
+
         final PipeDataNodeTask pipeTask =
             new PipeDataNodeTaskBuilder(pipeStaticMeta, consensusGroupId, 
pipeTaskMeta).build();
         pipeTask.create();
@@ -799,25 +808,49 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
   }
 
   @Override
-  protected void calculateMemoryUsage(
+  protected void calculateMemoryUsage(final PipeMeta pipeMetaFromCoordinator)
+      throws IllegalPathException {
+    final PipeStaticMeta staticMeta = pipeMetaFromCoordinator.getStaticMeta();
+    if (!PipeConfig.getInstance().isPipeEnableMemoryCheck()
+        || !isInnerSource(staticMeta.getSourceParameters())
+        || !PipeType.USER.equals(staticMeta.getPipeType())) {
+      return;
+    }
+
+    final PipeMeta pipeMetaInAgent = 
pipeMetaKeeper.getPipeMeta(staticMeta.getPipeName());
+    final boolean ignoreRegisteredSinkSubtasks =
+        Objects.nonNull(pipeMetaInAgent)
+            && (!pipeMetaInAgent.getStaticMeta().equals(staticMeta)
+                || 
PipeStatus.DROPPED.equals(pipeMetaInAgent.getRuntimeMeta().getStatus().get()));
+    calculateMemoryUsage(
+        staticMeta,
+        collectPipeTasksToBeCreated(pipeMetaFromCoordinator),
+        ignoreRegisteredSinkSubtasks);
+  }
+
+  private void calculateMemoryUsage(
       final PipeStaticMeta staticMeta,
-      final PipeParameters sourceParameters,
-      final PipeParameters processorParameters,
-      final PipeParameters sinkParameters) {
+      final List<Pair<Integer, PipeTaskMeta>> pipeTasksToBeCreated,
+      final boolean ignoreRegisteredSinkSubtasks)
+      throws IllegalPathException {
     if (!PipeConfig.getInstance().isPipeEnableMemoryCheck()
-        || !isInnerSource(sourceParameters)
+        || !isInnerSource(staticMeta.getSourceParameters())
         || !PipeType.USER.equals(staticMeta.getPipeType())) {
       return;
     }
 
-    calculateInsertNodeQueueMemory(sourceParameters);
+    if (pipeTasksToBeCreated.isEmpty()) {
+      calculateInsertNodeQueueMemory(staticMeta.getSourceParameters(), 1);
+      return;
+    }
 
-    long needMemory = 0;
+    final MemoryEstimation memoryEstimation =
+        calculateIncrementalMemoryUsage(
+            staticMeta, pipeTasksToBeCreated, ignoreRegisteredSinkSubtasks);
+    calculateInsertNodeQueueMemory(
+        staticMeta.getSourceParameters(), 
memoryEstimation.dataRegionTaskCount);
 
-    needMemory += calculateTsFileParserMemory(sourceParameters, 
sinkParameters);
-    needMemory += calculateSinkBatchMemory(sinkParameters);
-    needMemory += calculateSendTsFileReadBufferMemory(sourceParameters, 
sinkParameters);
-    needMemory += calculateAssignerMemory(sourceParameters);
+    final long needMemory = memoryEstimation.nonFloatingMemoryInBytes;
 
     PipeMemoryManager pipeMemoryManager = PipeDataNodeResourceManager.memory();
     final long freeMemorySizeInBytes = 
pipeMemoryManager.getFreeMemorySizeInBytes();
@@ -828,8 +861,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
     if (freeMemorySizeInBytes < needMemory + reservedMemorySizeInBytes) {
       final String message =
           String.format(
-              "%s Need memory: %d bytes, free memory: %d bytes, reserved 
memory: %d bytes, total memory: %d bytes",
-              PipeMessages.NOT_ENOUGH_MEMORY_FOR_PIPE,
+              PipeMessages.NOT_ENOUGH_MEMORY_FOR_PIPE_FORMAT,
               needMemory,
               freeMemorySizeInBytes,
               reservedMemorySizeInBytes,
@@ -839,6 +871,109 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
     }
   }
 
+  private List<Pair<Integer, PipeTaskMeta>> collectPipeTasksToBeCreated(
+      final PipeMeta pipeMetaFromCoordinator) throws IllegalPathException {
+    final PipeStaticMeta pipeStaticMeta = 
pipeMetaFromCoordinator.getStaticMeta();
+    final PipeParameters sourceParameters = 
pipeStaticMeta.getSourceParameters();
+    final Set<DataRegionId> dataRegionIds =
+        new HashSet<>(StorageEngine.getInstance().getAllDataRegionIds());
+    final Set<SchemaRegionId> schemaRegionIds =
+        new HashSet<>(SchemaEngine.getInstance().getAllSchemaRegionIds());
+    final List<Pair<Integer, PipeTaskMeta>> pipeTasksToBeCreated = new 
ArrayList<>();
+
+    for (final Map.Entry<Integer, PipeTaskMeta> consensusGroupIdToPipeTaskMeta 
:
+        
pipeMetaFromCoordinator.getRuntimeMeta().getConsensusGroupId2TaskMetaMap().entrySet())
 {
+      final int consensusGroupId = consensusGroupIdToPipeTaskMeta.getKey();
+      final PipeTaskMeta pipeTaskMeta = 
consensusGroupIdToPipeTaskMeta.getValue();
+      if (pipeTaskMeta.getLeaderNodeId() != CONFIG.getDataNodeId()) {
+        continue;
+      }
+
+      final boolean needConstructTask;
+      if (pipeStaticMeta.isSourceExternal()) {
+        needConstructTask = true;
+      } else {
+        final DataRegionId dataRegionId = new DataRegionId(consensusGroupId);
+        final boolean needConstructDataRegionTask =
+            dataRegionIds.contains(dataRegionId)
+                && DataRegionListeningFilter.shouldDataRegionBeListened(
+                    sourceParameters, dataRegionId);
+        final boolean needConstructSchemaRegionTask =
+            schemaRegionIds.contains(new SchemaRegionId(consensusGroupId))
+                && SchemaRegionListeningFilter.shouldSchemaRegionBeListened(
+                    consensusGroupId, sourceParameters);
+        needConstructTask = needConstructDataRegionTask || 
needConstructSchemaRegionTask;
+      }
+
+      if (needConstructTask) {
+        pipeTasksToBeCreated.add(new Pair<>(consensusGroupId, pipeTaskMeta));
+      }
+    }
+    return pipeTasksToBeCreated;
+  }
+
+  private MemoryEstimation calculateIncrementalMemoryUsage(
+      final PipeStaticMeta staticMeta,
+      final List<Pair<Integer, PipeTaskMeta>> pipeTasksToBeCreated,
+      final boolean ignoreRegisteredSinkSubtasks) {
+    long needMemory = 0;
+    int dataRegionTaskCount = 0;
+    final Set<String> sinkSubtasksToBeCreated = new HashSet<>();
+
+    for (final Pair<Integer, PipeTaskMeta> regionIdAndTaskMeta : 
pipeTasksToBeCreated) {
+      final int regionId = regionIdAndTaskMeta.getLeft();
+      final PipeTaskMeta pipeTaskMeta = regionIdAndTaskMeta.getRight();
+      final PipeParameters sourceParameters =
+          PipeDataNodeTaskBuilder.blendUserAndSystemParameters(
+              staticMeta.getSourceParameters(), pipeTaskMeta);
+      final PipeParameters sinkParameters =
+          PipeDataNodeTaskBuilder.blendUserAndSystemParameters(
+              staticMeta.getSinkParameters(), pipeTaskMeta);
+      PipeDataNodeTaskBuilder.preprocessParameters(sourceParameters, 
sinkParameters);
+
+      final boolean isDataRegionTask = isDataRegionTask(regionId);
+      if (isDataRegionTask) {
+        dataRegionTaskCount++;
+        needMemory += calculateTsFileParserMemory(sourceParameters, 
sinkParameters);
+      }
+
+      final String sinkSubtaskId =
+          PipeSinkSubtaskManager.generateAttributeSortedString(sinkParameters, 
regionId);
+      if (isDataRegionTask
+          && !sinkSubtasksToBeCreated.contains(sinkSubtaskId)
+          && (ignoreRegisteredSinkSubtasks
+              || !PipeSinkSubtaskManager.instance()
+                  .hasRegisteredSubtasks(sinkParameters, regionId))) {
+        sinkSubtasksToBeCreated.add(sinkSubtaskId);
+        final int sinkSubtaskNum =
+            PipeSinkSubtaskManager.calculateSinkSubtaskNum(sinkParameters, 
regionId);
+        needMemory += calculateSinkBatchMemory(sinkParameters) * 
sinkSubtaskNum;
+        needMemory +=
+            calculateSendTsFileReadBufferMemory(sourceParameters, 
sinkParameters) * sinkSubtaskNum;
+      }
+    }
+
+    if (dataRegionTaskCount > 0) {
+      needMemory += calculateAssignerMemory(staticMeta.getSourceParameters());
+    }
+    return new MemoryEstimation(needMemory, dataRegionTaskCount);
+  }
+
+  private boolean isDataRegionTask(final int regionId) {
+    return StorageEngine.getInstance().getAllDataRegionIds().contains(new 
DataRegionId(regionId))
+        || PipeRuntimeMeta.isSourceExternal(regionId);
+  }
+
+  private static class MemoryEstimation {
+    private final long nonFloatingMemoryInBytes;
+    private final int dataRegionTaskCount;
+
+    private MemoryEstimation(final long nonFloatingMemoryInBytes, final int 
dataRegionTaskCount) {
+      this.nonFloatingMemoryInBytes = nonFloatingMemoryInBytes;
+      this.dataRegionTaskCount = dataRegionTaskCount;
+    }
+  }
+
   private boolean isInnerSource(final PipeParameters sourceParameters) {
     final String pluginName =
         sourceParameters
@@ -851,7 +986,11 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
         || 
pluginName.equals(BuiltinPipePlugin.IOTDB_SOURCE.getPipePluginName());
   }
 
-  private void calculateInsertNodeQueueMemory(final PipeParameters 
sourceParameters) {
+  private void calculateInsertNodeQueueMemory(
+      final PipeParameters sourceParameters, final int dataRegionTaskCount) {
+    if (dataRegionTaskCount <= 0) {
+      return;
+    }
 
     // Realtime source is enabled by default, so we only need to check the 
source realtime
     if (!sourceParameters.getBooleanOrDefault(
@@ -870,16 +1009,17 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent 
{
       return;
     }
 
+    final long needFloatingMemory =
+        PipeConfig.getInstance().getPipeInsertNodeQueueMemory() * 
dataRegionTaskCount;
     final long allocatedMemorySizeInBytes = 
this.getAllFloatingMemoryUsageInByte();
     final long remainingMemory =
         
PipeDataNodeResourceManager.memory().getTotalFloatingMemorySizeInBytes()
             - allocatedMemorySizeInBytes;
-    if (remainingMemory < 
PipeConfig.getInstance().getPipeInsertNodeQueueMemory()) {
+    if (remainingMemory < needFloatingMemory) {
       final String message =
           String.format(
-              "%s Need Floating memory: %d  bytes, free Floating memory: %d 
bytes",
-              PipeMessages.NOT_ENOUGH_MEMORY_FOR_PIPE,
-              PipeConfig.getInstance().getPipeInsertNodeQueueMemory(),
+              PipeMessages.NOT_ENOUGH_FLOATING_MEMORY_FOR_PIPE_FORMAT,
+              needFloatingMemory,
               remainingMemory);
       LOGGER.warn(message);
       throw new PipeException(message);
@@ -958,36 +1098,112 @@ public class PipeDataNodeTaskAgent extends 
PipeTaskAgent {
     return PipeConfig.getInstance().getTsFileParserMemory();
   }
 
-  private long calculateSinkBatchMemory(final PipeParameters sinkParameters) {
+  private static long calculateSinkBatchMemory(final PipeParameters 
sinkParameters) {
+    final String format =
+        sinkParameters.getStringOrDefault(
+            Arrays.asList(PipeSinkConstant.CONNECTOR_FORMAT_KEY, 
PipeSinkConstant.SINK_FORMAT_KEY),
+            PipeSinkConstant.CONNECTOR_FORMAT_HYBRID_VALUE);
+    final boolean usingTsFileBatch = 
PipeSinkConstant.CONNECTOR_FORMAT_TS_FILE_VALUE.equals(format);
 
-    // If the sink format is tsfile , we need to use batch
-    boolean needUseBatch =
-        PipeSinkConstant.CONNECTOR_FORMAT_TS_FILE_VALUE.equals(
-            sinkParameters.getStringOrDefault(
+    // TsFile format always uses a batch. Other formats only use a batch when 
batch mode is enabled.
+    final boolean needUseBatch =
+        usingTsFileBatch
+            || sinkParameters.getBooleanOrDefault(
                 Arrays.asList(
-                    PipeSinkConstant.CONNECTOR_FORMAT_KEY, 
PipeSinkConstant.SINK_FORMAT_KEY),
-                PipeSinkConstant.CONNECTOR_FORMAT_HYBRID_VALUE));
+                    PipeSinkConstant.CONNECTOR_IOTDB_BATCH_MODE_ENABLE_KEY,
+                    PipeSinkConstant.SINK_IOTDB_BATCH_MODE_ENABLE_KEY),
+                
PipeSinkConstant.CONNECTOR_IOTDB_BATCH_MODE_ENABLE_DEFAULT_VALUE);
 
-    if (needUseBatch) {
-      return PipeConfig.getInstance().getSinkBatchMemoryTsFile();
+    if (!needUseBatch) {
+      return 0;
     }
 
-    // If the sink is batch mode, we need to use batch
-    needUseBatch =
-        sinkParameters.getBooleanOrDefault(
+    final long batchSizeInBytes =
+        sinkParameters.getLongOrDefault(
             Arrays.asList(
-                PipeSinkConstant.CONNECTOR_IOTDB_BATCH_MODE_ENABLE_KEY,
-                PipeSinkConstant.SINK_IOTDB_BATCH_MODE_ENABLE_KEY),
-            PipeSinkConstant.CONNECTOR_IOTDB_BATCH_MODE_ENABLE_DEFAULT_VALUE);
+                PipeSinkConstant.CONNECTOR_IOTDB_BATCH_SIZE_KEY,
+                PipeSinkConstant.SINK_IOTDB_BATCH_SIZE_KEY),
+            usingTsFileBatch
+                ? 
PipeSinkConstant.CONNECTOR_IOTDB_TS_FILE_BATCH_SIZE_DEFAULT_VALUE
+                : 
PipeSinkConstant.CONNECTOR_IOTDB_PLAIN_BATCH_SIZE_DEFAULT_VALUE);
 
-    if (!needUseBatch) {
-      return 0;
+    return batchSizeInBytes * calculateBatchShardCount(sinkParameters, 
usingTsFileBatch);
+  }
+
+  private static long calculateBatchShardCount(
+      final PipeParameters sinkParameters, final boolean usingTsFileBatch) {
+    if (usingTsFileBatch
+        || !sinkParameters.getBooleanOrDefault(
+            Arrays.asList(
+                PipeSinkConstant.CONNECTOR_LEADER_CACHE_ENABLE_KEY,
+                PipeSinkConstant.SINK_LEADER_CACHE_ENABLE_KEY),
+            PipeSinkConstant.CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE)) {
+      return 1;
     }
 
-    return PipeConfig.getInstance().getSinkBatchMemoryInsertNode();
+    // Plain batches always allocate the default batch and may lazily allocate 
one batch per target
+    // endpoint when leader cache splits events by endpoint.
+    return 1L + calculateTargetEndPointCount(sinkParameters);
+  }
+
+  private static int calculateTargetEndPointCount(final PipeParameters 
sinkParameters) {
+    final Set<TEndPoint> targetEndPoints = new HashSet<>();
+    try {
+      addTargetEndPoint(
+          targetEndPoints,
+          sinkParameters,
+          PipeSinkConstant.CONNECTOR_IOTDB_IP_KEY,
+          PipeSinkConstant.CONNECTOR_IOTDB_HOST_KEY,
+          PipeSinkConstant.CONNECTOR_IOTDB_PORT_KEY);
+      addTargetEndPoint(
+          targetEndPoints,
+          sinkParameters,
+          PipeSinkConstant.SINK_IOTDB_IP_KEY,
+          PipeSinkConstant.SINK_IOTDB_HOST_KEY,
+          PipeSinkConstant.SINK_IOTDB_PORT_KEY);
+      if 
(sinkParameters.hasAttribute(PipeSinkConstant.CONNECTOR_IOTDB_NODE_URLS_KEY)) {
+        targetEndPoints.addAll(
+            NodeUrlUtils.parseTEndPointUrls(
+                Arrays.asList(
+                    sinkParameters
+                        
.getStringByKeys(PipeSinkConstant.CONNECTOR_IOTDB_NODE_URLS_KEY)
+                        .replace(" ", "")
+                        .split(","))));
+      }
+      if 
(sinkParameters.hasAttribute(PipeSinkConstant.SINK_IOTDB_NODE_URLS_KEY)) {
+        targetEndPoints.addAll(
+            NodeUrlUtils.parseTEndPointUrls(
+                Arrays.asList(
+                    sinkParameters
+                        
.getStringByKeys(PipeSinkConstant.SINK_IOTDB_NODE_URLS_KEY)
+                        .replace(" ", "")
+                        .split(","))));
+      }
+    } catch (final Exception ignored) {
+      return 1;
+    }
+    return Math.max(1, targetEndPoints.size());
+  }
+
+  private static void addTargetEndPoint(
+      final Set<TEndPoint> targetEndPoints,
+      final PipeParameters sinkParameters,
+      final String ipKey,
+      final String hostKey,
+      final String portKey) {
+    if (sinkParameters.hasAttribute(ipKey) && 
sinkParameters.hasAttribute(portKey)) {
+      targetEndPoints.add(
+          new TEndPoint(
+              sinkParameters.getStringByKeys(ipKey), 
sinkParameters.getIntByKeys(portKey)));
+    }
+    if (sinkParameters.hasAttribute(hostKey) && 
sinkParameters.hasAttribute(portKey)) {
+      targetEndPoints.add(
+          new TEndPoint(
+              sinkParameters.getStringByKeys(hostKey), 
sinkParameters.getIntByKeys(portKey)));
+    }
   }
 
-  private long calculateSendTsFileReadBufferMemory(
+  private static long calculateSendTsFileReadBufferMemory(
       final PipeParameters sourceParameters, final PipeParameters 
sinkParameters) {
     // If the source is history enable, we need to transfer tsfile
     boolean needTransferTsFile =
@@ -1013,7 +1229,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
       return 0;
     }
 
-    return PipeConfig.getInstance().getSendTsFileReadBuffer();
+    return PipeConfig.getInstance().getPipeSinkReadFileBufferSize();
   }
 
   private long calculateAssignerMemory(final PipeParameters sourceParameters) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/builder/PipeDataNodeTaskBuilder.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/builder/PipeDataNodeTaskBuilder.java
index 8747c12d076..878823d4696 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/builder/PipeDataNodeTaskBuilder.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/builder/PipeDataNodeTaskBuilder.java
@@ -67,14 +67,11 @@ public class PipeDataNodeTaskBuilder {
   private static final PipeProcessorSubtaskExecutor PROCESSOR_EXECUTOR =
       PipeSubtaskExecutorManager.getInstance().getProcessorExecutor();
 
-  protected final Map<String, String> systemParameters = new HashMap<>();
-
   public PipeDataNodeTaskBuilder(
       final PipeStaticMeta pipeStaticMeta, final int regionId, final 
PipeTaskMeta pipeTaskMeta) {
     this.pipeStaticMeta = pipeStaticMeta;
     this.regionId = regionId;
     this.pipeTaskMeta = pipeTaskMeta;
-    generateSystemParameters();
   }
 
   public PipeDataNodeTask build() {
@@ -82,11 +79,10 @@ public class PipeDataNodeTaskBuilder {
 
     // Analyzes the PipeParameters to identify potential conflicts.
     final PipeParameters sourceParameters =
-        blendUserAndSystemParameters(pipeStaticMeta.getSourceParameters());
+        blendUserAndSystemParameters(pipeStaticMeta.getSourceParameters(), 
pipeTaskMeta);
     final PipeParameters sinkParameters =
-        blendUserAndSystemParameters(pipeStaticMeta.getSinkParameters());
-    checkConflict(sourceParameters, sinkParameters);
-    injectParameters(sourceParameters, sinkParameters);
+        blendUserAndSystemParameters(pipeStaticMeta.getSinkParameters(), 
pipeTaskMeta);
+    preprocessParameters(sourceParameters, sinkParameters);
 
     // We first build the source and sink, then build the processor.
     final PipeTaskSourceStage sourceStage =
@@ -125,7 +121,7 @@ public class PipeDataNodeTaskBuilder {
         new PipeTaskProcessorStage(
             pipeStaticMeta.getPipeName(),
             pipeStaticMeta.getCreationTime(),
-            
blendUserAndSystemParameters(pipeStaticMeta.getProcessorParameters()),
+            
blendUserAndSystemParameters(pipeStaticMeta.getProcessorParameters(), 
pipeTaskMeta),
             regionId,
             sourceStage.getEventSupplier(),
             sinkStage.getPipeSinkPendingQueue(),
@@ -143,22 +139,25 @@ public class PipeDataNodeTaskBuilder {
         pipeStaticMeta.getPipeName(), regionId, sourceStage, processorStage, 
sinkStage);
   }
 
-  private void generateSystemParameters() {
+  public static PipeParameters blendUserAndSystemParameters(
+      final PipeParameters userParameters, final PipeTaskMeta pipeTaskMeta) {
+    // Deep copy the user parameters to avoid modification of the original 
parameters.
+    // If the original parameters are modified, progress index report will be 
affected.
+    final Map<String, String> blendedParameters = new 
HashMap<>(userParameters.getAttribute());
     if (!(pipeTaskMeta.getProgressIndex() instanceof MinimumProgressIndex)
         || pipeTaskMeta.isNewlyAdded()) {
-      systemParameters.put(SystemConstant.RESTART_OR_NEWLY_ADDED_KEY, 
Boolean.TRUE.toString());
+      blendedParameters.put(SystemConstant.RESTART_OR_NEWLY_ADDED_KEY, 
Boolean.TRUE.toString());
     }
+    return new PipeParameters(blendedParameters);
   }
 
-  private PipeParameters blendUserAndSystemParameters(final PipeParameters 
userParameters) {
-    // Deep copy the user parameters to avoid modification of the original 
parameters.
-    // If the original parameters are modified, progress index report will be 
affected.
-    final Map<String, String> blendedParameters = new 
HashMap<>(userParameters.getAttribute());
-    blendedParameters.putAll(systemParameters);
-    return new PipeParameters(blendedParameters);
+  public static void preprocessParameters(
+      final PipeParameters sourceParameters, final PipeParameters 
sinkParameters) {
+    checkConflict(sourceParameters, sinkParameters);
+    injectParameters(sourceParameters, sinkParameters);
   }
 
-  private void checkConflict(
+  private static void checkConflict(
       final PipeParameters sourceParameters, final PipeParameters 
sinkParameters) {
     final Pair<Boolean, Boolean> insertionDeletionListeningOptionPair;
     final boolean shouldTerminatePipeOnAllHistoricalEventsConsumed;
@@ -228,7 +227,7 @@ public class PipeDataNodeTaskBuilder {
     }
   }
 
-  private void injectParameters(
+  private static void injectParameters(
       final PipeParameters sourceParameters, final PipeParameters 
sinkParameters) {
     final boolean isSourceExternal =
         !BuiltinPipePlugin.BUILTIN_SOURCES.contains(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskManager.java
index cca6de44f0b..33c89aa8967 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskManager.java
@@ -69,13 +69,7 @@ public class PipeSinkSubtaskManager {
       final Supplier<? extends PipeSinkSubtaskExecutor> executorSupplier,
       final PipeParameters pipeSinkParameters,
       final PipeTaskSinkRuntimeEnvironment environment) {
-    final String connectorName =
-        PipeSinkConstant.getConnectorOrSinkNameWithDefault(pipeSinkParameters);
-    final String connectorKey =
-        connectorName
-            // Convert the value of `CONNECTOR_KEY` or `SINK_KEY` to lowercase
-            // for matching in `CONNECTOR_CONSTRUCTORS`
-            .toLowerCase();
+    final String connectorKey = getConnectorKey(pipeSinkParameters);
     PipeEventCommitManager.getInstance()
         .register(
             environment.getPipeName(),
@@ -83,48 +77,23 @@ public class PipeSinkSubtaskManager {
             environment.getRegionId(),
             connectorKey);
 
-    final boolean isDataRegionSink =
-        StorageEngine.getInstance()
-                .getAllDataRegionIds()
-                .contains(new DataRegionId(environment.getRegionId()))
-            || PipeRuntimeMeta.isSourceExternal(environment.getRegionId());
-
-    final int sinkNum;
+    final boolean isDataRegionSink = 
isDataRegionSink(environment.getRegionId());
+    final int sinkNum = calculateSinkSubtaskNum(pipeSinkParameters, 
isDataRegionSink, connectorKey);
     boolean realTimeFirst = false;
-    boolean serializeByRegion = false;
-    String attributeSortedString = 
generateAttributeSortedString(pipeSinkParameters);
+    final String attributeSortedString =
+        generateAttributeSortedString(pipeSinkParameters, 
environment.getRegionId());
     final String attributeDisplayString = 
generateAttributeDisplayString(pipeSinkParameters);
     if (isDataRegionSink) {
-      serializeByRegion = 
PipeSinkConstant.isSerializeByRegionEnabled(pipeSinkParameters);
-      sinkNum =
-          serializeByRegion
-              ? 1
-              : pipeSinkParameters.getIntOrDefault(
-                  Arrays.asList(
-                      PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_KEY,
-                      PipeSinkConstant.SINK_IOTDB_PARALLEL_TASKS_KEY),
-                  
PipeSinkConstant.SINGLE_THREAD_DEFAULT_SINK.contains(connectorKey)
-                      ? 1
-                      : 
PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_DEFAULT_VALUE);
       realTimeFirst =
           pipeSinkParameters.getBooleanOrDefault(
               Arrays.asList(
                   PipeSinkConstant.CONNECTOR_REALTIME_FIRST_KEY,
                   PipeSinkConstant.SINK_REALTIME_FIRST_KEY),
               PipeSinkConstant.CONNECTOR_REALTIME_FIRST_DEFAULT_VALUE);
-      attributeSortedString =
-          serializeByRegion
-              ? "data_region_" + environment.getRegionId() + "_" + 
attributeSortedString
-              : "data_" + attributeSortedString;
-    } else {
-      // Do not allow parallel tasks for schema region connectors
-      // to avoid the potential disorder of the schema region data transfer
-      sinkNum = 1;
-      attributeSortedString = "schema_" + attributeSortedString;
     }
     final String attributeDisplayStringWithPrefix =
         isDataRegionSink
-            ? serializeByRegion
+            ? PipeSinkConstant.isSerializeByRegionEnabled(pipeSinkParameters)
                 ? "data_region_" + environment.getRegionId() + "_" + 
attributeDisplayString
                 : "data_" + attributeDisplayString
             : "schema_" + attributeDisplayString;
@@ -285,6 +254,59 @@ public class PipeSinkSubtaskManager {
         .getPendingQueue();
   }
 
+  public synchronized boolean hasRegisteredSubtasks(
+      final PipeParameters pipeSinkParameters, final int regionId) {
+    return attributeSortedString2SubtaskLifeCycleMap.containsKey(
+        generateAttributeSortedString(pipeSinkParameters, regionId));
+  }
+
+  public static int calculateSinkSubtaskNum(
+      final PipeParameters pipeSinkParameters, final int regionId) {
+    final String connectorKey = getConnectorKey(pipeSinkParameters);
+    return calculateSinkSubtaskNum(pipeSinkParameters, 
isDataRegionSink(regionId), connectorKey);
+  }
+
+  public static String generateAttributeSortedString(
+      final PipeParameters pipeSinkParameters, final int regionId) {
+    final String attributeSortedString = 
generateAttributeSortedString(pipeSinkParameters);
+    if (isDataRegionSink(regionId)) {
+      return PipeSinkConstant.isSerializeByRegionEnabled(pipeSinkParameters)
+          ? "data_region_" + regionId + "_" + attributeSortedString
+          : "data_" + attributeSortedString;
+    }
+    return "schema_" + attributeSortedString;
+  }
+
+  private static String getConnectorKey(final PipeParameters 
pipeSinkParameters) {
+    return 
PipeSinkConstant.getConnectorOrSinkNameWithDefault(pipeSinkParameters).toLowerCase();
+  }
+
+  private static boolean isDataRegionSink(final int regionId) {
+    return StorageEngine.getInstance().getAllDataRegionIds().contains(new 
DataRegionId(regionId))
+        || PipeRuntimeMeta.isSourceExternal(regionId);
+  }
+
+  private static int calculateSinkSubtaskNum(
+      final PipeParameters pipeSinkParameters,
+      final boolean isDataRegionSink,
+      final String connectorKey) {
+    if (!isDataRegionSink) {
+      // Do not allow parallel tasks for schema region connectors to avoid the 
potential disorder of
+      // the schema region data transfer.
+      return 1;
+    }
+    if (PipeSinkConstant.isSerializeByRegionEnabled(pipeSinkParameters)) {
+      return 1;
+    }
+    return pipeSinkParameters.getIntOrDefault(
+        Arrays.asList(
+            PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_KEY,
+            PipeSinkConstant.SINK_IOTDB_PARALLEL_TASKS_KEY),
+        PipeSinkConstant.SINGLE_THREAD_DEFAULT_SINK.contains(connectorKey)
+            ? 1
+            : PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_DEFAULT_VALUE);
+  }
+
   private static String generateAttributeSortedString(
       final PipeParameters pipeConnectorParameters) {
     final TreeMap<String, String> sortedStringSourceMap =
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentTest.java
new file mode 100644
index 00000000000..0582a94170d
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.iotdb.db.pipe.agent.task;
+
+import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
+import org.apache.iotdb.commons.pipe.config.PipeConfig;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
+import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
+import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
+import org.apache.iotdb.pipe.api.exception.PipeException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class PipeDataNodeTaskAgentTest {
+
+  @Test
+  public void testCreateMemoryCheckStillRunsWhenNoPipeTasksNeedToBeCreated() 
throws Exception {
+    final boolean originalPipeEnableMemoryCheck =
+        CommonDescriptor.getInstance().getConfig().isPipeEnableMemoryChecked();
+    final long originalPipeInsertNodeQueueMemory =
+        
CommonDescriptor.getInstance().getConfig().getPipeInsertNodeQueueMemory();
+    final double originalPipeTotalFloatingMemoryProportion =
+        
CommonDescriptor.getInstance().getConfig().getPipeTotalFloatingMemoryProportion();
+
+    try {
+      
CommonDescriptor.getInstance().getConfig().setIsPipeEnableMemoryChecked(true);
+      
CommonDescriptor.getInstance().getConfig().setPipeInsertNodeQueueMemory(1);
+      
CommonDescriptor.getInstance().getConfig().setPipeTotalFloatingMemoryProportion(0);
+
+      Assert.assertThrows(
+          PipeException.class,
+          () ->
+              PipeDataNodeAgent.task()
+                  .calculateMemoryUsage(
+                      new PipeMeta(
+                          new PipeStaticMeta(
+                              "p", 1L, new HashMap<>(), new HashMap<>(), new 
HashMap<>()),
+                          new PipeRuntimeMeta())));
+    } finally {
+      CommonDescriptor.getInstance()
+          .getConfig()
+          .setIsPipeEnableMemoryChecked(originalPipeEnableMemoryCheck);
+      CommonDescriptor.getInstance()
+          .getConfig()
+          .setPipeInsertNodeQueueMemory(originalPipeInsertNodeQueueMemory);
+      CommonDescriptor.getInstance()
+          .getConfig()
+          
.setPipeTotalFloatingMemoryProportion(originalPipeTotalFloatingMemoryProportion);
+    }
+  }
+
+  @Test
+  public void testPlainBatchMemoryIncludesLeaderCacheEndpointShards() throws 
Exception {
+    final Map<String, String> sinkAttributes = new HashMap<>();
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_FORMAT_KEY, 
PipeSinkConstant.CONNECTOR_FORMAT_TABLET_VALUE);
+    sinkAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_BATCH_SIZE_KEY, 
"1024");
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_IOTDB_NODE_URLS_KEY, "127.0.0.1:6667, 
127.0.0.2:6667");
+    sinkAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_IP_KEY, "127.0.0.3");
+    sinkAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_PORT_KEY, "6667");
+
+    Assert.assertEquals(
+        4 * 1024L, invokeCalculateSinkBatchMemory(new 
PipeParameters(sinkAttributes)));
+
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_LEADER_CACHE_ENABLE_KEY, 
Boolean.FALSE.toString());
+    Assert.assertEquals(1024L, invokeCalculateSinkBatchMemory(new 
PipeParameters(sinkAttributes)));
+  }
+
+  @Test
+  public void testTsFileBatchMemoryIgnoresLeaderCacheEndpointShards() throws 
Exception {
+    final Map<String, String> sinkAttributes = new HashMap<>();
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_FORMAT_KEY, 
PipeSinkConstant.CONNECTOR_FORMAT_TS_FILE_VALUE);
+    sinkAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_BATCH_SIZE_KEY, 
"2048");
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_IOTDB_NODE_URLS_KEY, 
"127.0.0.1:6667,127.0.0.2:6667");
+
+    Assert.assertEquals(2048L, invokeCalculateSinkBatchMemory(new 
PipeParameters(sinkAttributes)));
+  }
+
+  @Test
+  public void testPlainBatchMemoryReturnsZeroWhenBatchModeIsDisabled() throws 
Exception {
+    final Map<String, String> sinkAttributes = new HashMap<>();
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_FORMAT_KEY, 
PipeSinkConstant.CONNECTOR_FORMAT_TABLET_VALUE);
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_IOTDB_BATCH_MODE_ENABLE_KEY, 
Boolean.FALSE.toString());
+    sinkAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_BATCH_SIZE_KEY, 
"1024");
+
+    Assert.assertEquals(0L, invokeCalculateSinkBatchMemory(new 
PipeParameters(sinkAttributes)));
+  }
+
+  @Test
+  public void testSendTsFileReadBufferMemoryUsesSinkReadFileBufferSize() 
throws Exception {
+    final Map<String, String> sourceAttributes = new HashMap<>();
+    sourceAttributes.put(PipeSourceConstant.EXTRACTOR_HISTORY_ENABLE_KEY, 
Boolean.FALSE.toString());
+
+    final Map<String, String> sinkAttributes = new HashMap<>();
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_FORMAT_KEY, 
PipeSinkConstant.CONNECTOR_FORMAT_TABLET_VALUE);
+    Assert.assertEquals(
+        0L,
+        invokeCalculateSendTsFileReadBufferMemory(
+            new PipeParameters(sourceAttributes), new 
PipeParameters(sinkAttributes)));
+
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_FORMAT_KEY, 
PipeSinkConstant.CONNECTOR_FORMAT_HYBRID_VALUE);
+    Assert.assertEquals(
+        PipeConfig.getInstance().getPipeSinkReadFileBufferSize(),
+        invokeCalculateSendTsFileReadBufferMemory(
+            new PipeParameters(sourceAttributes), new 
PipeParameters(sinkAttributes)));
+  }
+
+  private long invokeCalculateSinkBatchMemory(final PipeParameters 
sinkParameters)
+      throws Exception {
+    final Method method =
+        PipeDataNodeTaskAgent.class.getDeclaredMethod(
+            "calculateSinkBatchMemory", PipeParameters.class);
+    method.setAccessible(true);
+    return (long) method.invoke(null, sinkParameters);
+  }
+
+  private long invokeCalculateSendTsFileReadBufferMemory(
+      final PipeParameters sourceParameters, final PipeParameters 
sinkParameters) throws Exception {
+    final Method method =
+        PipeDataNodeTaskAgent.class.getDeclaredMethod(
+            "calculateSendTsFileReadBufferMemory", PipeParameters.class, 
PipeParameters.class);
+    method.setAccessible(true);
+    return (long) method.invoke(null, sourceParameters, sinkParameters);
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/builder/PipeDataNodeTaskBuilderTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/builder/PipeDataNodeTaskBuilderTest.java
new file mode 100644
index 00000000000..e00f9500bbe
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/builder/PipeDataNodeTaskBuilderTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.iotdb.db.pipe.agent.task.builder;
+
+import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
+import org.apache.iotdb.commons.consensus.index.impl.SimpleProgressIndex;
+import org.apache.iotdb.commons.pipe.agent.plugin.builtin.BuiltinPipePlugin;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
+import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
+import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PipeDataNodeTaskBuilderTest {
+
+  @Test
+  public void testBlendUserAndSystemParametersDoesNotMutateOriginal() {
+    final Map<String, String> attributes = new HashMap<>();
+    attributes.put(PipeSourceConstant.EXTRACTOR_PATTERN_KEY, "root.sg.**");
+    final PipeParameters userParameters = new PipeParameters(attributes);
+
+    final PipeParameters blendedParameters =
+        PipeDataNodeTaskBuilder.blendUserAndSystemParameters(
+            userParameters, new PipeTaskMeta(MinimumProgressIndex.INSTANCE, 
1));
+
+    Assert.assertEquals(
+        "root.sg.**", 
blendedParameters.getStringByKeys(PipeSourceConstant.EXTRACTOR_PATTERN_KEY));
+    
Assert.assertFalse(blendedParameters.hasAttribute(SystemConstant.RESTART_OR_NEWLY_ADDED_KEY));
+    
Assert.assertFalse(userParameters.hasAttribute(SystemConstant.RESTART_OR_NEWLY_ADDED_KEY));
+  }
+
+  @Test
+  public void testBlendUserAndSystemParametersMarksRestartOrNewlyAddedTask() {
+    final PipeParameters restartedParameters =
+        PipeDataNodeTaskBuilder.blendUserAndSystemParameters(
+            new PipeParameters(new HashMap<>()),
+            new PipeTaskMeta(new SimpleProgressIndex(1, 2L), 1));
+
+    Assert.assertEquals(
+        Boolean.TRUE.toString(),
+        
restartedParameters.getStringByKeys(SystemConstant.RESTART_OR_NEWLY_ADDED_KEY));
+
+    final PipeTaskMeta newlyAddedTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1).markAsNewlyAdded();
+    final PipeParameters newlyAddedParameters =
+        PipeDataNodeTaskBuilder.blendUserAndSystemParameters(
+            new PipeParameters(new HashMap<>()), newlyAddedTaskMeta);
+
+    Assert.assertEquals(
+        Boolean.TRUE.toString(),
+        
newlyAddedParameters.getStringByKeys(SystemConstant.RESTART_OR_NEWLY_ADDED_KEY));
+  }
+
+  @Test
+  public void testPreprocessParametersInjectsRuntimeDefaults() {
+    final Map<String, String> sourceAttributes = new HashMap<>();
+    sourceAttributes.put(PipeSourceConstant.EXTRACTOR_INCLUSION_KEY, 
"data.delete");
+    final PipeParameters sourceParameters = new 
PipeParameters(sourceAttributes);
+    final PipeParameters sinkParameters = new PipeParameters(new HashMap<>());
+
+    PipeDataNodeTaskBuilder.preprocessParameters(sourceParameters, 
sinkParameters);
+
+    Assert.assertEquals(
+        Boolean.FALSE.toString(),
+        
sinkParameters.getStringByKeys(PipeSinkConstant.CONNECTOR_REALTIME_FIRST_KEY));
+    Assert.assertEquals(
+        Boolean.TRUE.toString(),
+        
sinkParameters.getStringByKeys(PipeSinkConstant.SINK_ENABLE_SEND_TSFILE_LIMIT));
+  }
+
+  @Test
+  public void 
testPreprocessParametersInjectsEventUserForExternalWriteBackSink() {
+    final Map<String, String> sourceAttributes = new HashMap<>();
+    sourceAttributes.put(PipeSourceConstant.EXTRACTOR_KEY, "external-source");
+
+    final Map<String, String> sinkAttributes = new HashMap<>();
+    sinkAttributes.put(
+        PipeSinkConstant.CONNECTOR_KEY, 
BuiltinPipePlugin.WRITE_BACK_CONNECTOR.getPipePluginName());
+    final PipeParameters sinkParameters = new PipeParameters(sinkAttributes);
+
+    PipeDataNodeTaskBuilder.preprocessParameters(
+        new PipeParameters(sourceAttributes), sinkParameters);
+
+    Assert.assertEquals(
+        Boolean.TRUE.toString(),
+        
sinkParameters.getStringByKeys(PipeSinkConstant.CONNECTOR_USE_EVENT_USER_NAME_KEY));
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskManagerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskManagerTest.java
new file mode 100644
index 00000000000..9545d71d965
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskManagerTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.iotdb.db.pipe.agent.task.subtask.sink;
+
+import org.apache.iotdb.commons.pipe.agent.plugin.builtin.BuiltinPipePlugin;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant;
+import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
+import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PipeSinkSubtaskManagerTest {
+
+  @Test
+  public void 
testGenerateAttributeSortedStringAddsRegionPrefixAndIgnoresRestartFlag() {
+    final Map<String, String> attributes = new HashMap<>();
+    attributes.put("z", "1");
+    attributes.put("a", "2");
+    attributes.put(SystemConstant.RESTART_OR_NEWLY_ADDED_KEY, 
Boolean.TRUE.toString());
+
+    Assert.assertEquals(
+        "data_region_-1_{a=2, z=1}",
+        PipeSinkSubtaskManager.generateAttributeSortedString(
+            new PipeParameters(new HashMap<>(attributes)), -1));
+
+    attributes.put(PipeSinkConstant.CONNECTOR_SERIALIZE_BY_REGION_KEY, 
Boolean.FALSE.toString());
+    Assert.assertEquals(
+        "data_{a=2, connector.serialize-by-region=false, z=1}",
+        PipeSinkSubtaskManager.generateAttributeSortedString(
+            new PipeParameters(new HashMap<>(attributes)), -1));
+  }
+
+  @Test
+  public void testCalculateSinkSubtaskNumForDataRegionSink() {
+    final Map<String, String> parallelAttributes = new HashMap<>();
+    parallelAttributes.put(
+        PipeSinkConstant.CONNECTOR_SERIALIZE_BY_REGION_KEY, 
Boolean.FALSE.toString());
+    
parallelAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_KEY, 
"3");
+    Assert.assertEquals(
+        3,
+        PipeSinkSubtaskManager.calculateSinkSubtaskNum(new 
PipeParameters(parallelAttributes), -1));
+
+    final Map<String, String> serializedAttributes = new HashMap<>();
+    serializedAttributes.put(
+        PipeSinkConstant.CONNECTOR_SERIALIZE_BY_REGION_KEY, 
Boolean.TRUE.toString());
+    
serializedAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_KEY, 
"3");
+    Assert.assertEquals(
+        1,
+        PipeSinkSubtaskManager.calculateSinkSubtaskNum(
+            new PipeParameters(serializedAttributes), -1));
+  }
+
+  @Test
+  public void 
testCalculateSinkSubtaskNumUsesSingleThreadDefaultSinkAndSchemaRegionLimit() {
+    final Map<String, String> singleThreadAttributes = new HashMap<>();
+    singleThreadAttributes.put(
+        PipeSinkConstant.CONNECTOR_SERIALIZE_BY_REGION_KEY, 
Boolean.FALSE.toString());
+    singleThreadAttributes.put(
+        PipeSinkConstant.CONNECTOR_KEY, 
BuiltinPipePlugin.OPC_UA_SINK.getPipePluginName());
+    Assert.assertEquals(
+        1,
+        PipeSinkSubtaskManager.calculateSinkSubtaskNum(
+            new PipeParameters(singleThreadAttributes), -1));
+
+    final Map<String, String> schemaRegionAttributes = new HashMap<>();
+    schemaRegionAttributes.put(
+        PipeSinkConstant.CONNECTOR_SERIALIZE_BY_REGION_KEY, 
Boolean.FALSE.toString());
+    
schemaRegionAttributes.put(PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_KEY, 
"3");
+    Assert.assertEquals(
+        1,
+        PipeSinkSubtaskManager.calculateSinkSubtaskNum(
+            new PipeParameters(schemaRegionAttributes), Integer.MAX_VALUE));
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java
 
b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java
index f0086f26898..171d3689f0f 100644
--- 
a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java
+++ 
b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/PipeMessages.java
@@ -256,6 +256,13 @@ public final class PipeMessages {
   // ===================== PipeTaskAgent =====================
 
   public static final String NOT_ENOUGH_MEMORY_FOR_PIPE = "Not enough memory 
for pipe.";
+  public static final String NOT_ENOUGH_MEMORY_FOR_PIPE_FORMAT =
+      NOT_ENOUGH_MEMORY_FOR_PIPE
+          + " Need memory: %d bytes, free memory: %d bytes, "
+          + "reserved memory: %d bytes, total memory: %d bytes";
+  public static final String NOT_ENOUGH_FLOATING_MEMORY_FOR_PIPE_FORMAT =
+      NOT_ENOUGH_MEMORY_FOR_PIPE
+          + " Need floating memory: %d bytes, free floating memory: %d bytes";
   public static final String UNKNOWN_PIPE_STATUS = "Unknown pipe status %s for 
pipe %s";
   public static final String UNEXPECTED_PIPE_STATUS = "Unexpected pipe status 
%s: ";
   public static final String INTERRUPTED_ACQUIRING_READ_LOCK =
diff --git 
a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java
 
b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java
index d081a8fd4cf..1ac0e021bfa 100644
--- 
a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java
+++ 
b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/PipeMessages.java
@@ -255,6 +255,12 @@ public final class PipeMessages {
   // ===================== PipeTaskAgent =====================
 
   public static final String NOT_ENOUGH_MEMORY_FOR_PIPE = "pipe 内存不足。";
+  public static final String NOT_ENOUGH_MEMORY_FOR_PIPE_FORMAT =
+      NOT_ENOUGH_MEMORY_FOR_PIPE
+          + "需要内存:%d 字节,空闲内存:%d 字节,保留内存:%d 字节,"
+          + "总内存:%d 字节";
+  public static final String NOT_ENOUGH_FLOATING_MEMORY_FOR_PIPE_FORMAT =
+      NOT_ENOUGH_MEMORY_FOR_PIPE + "需要浮动内存:%d 字节,空闲浮动内存:%d 字节";
   public static final String UNKNOWN_PIPE_STATUS = "Pipe %s 的状态 %s 未知";
   public static final String UNEXPECTED_PIPE_STATUS = "意外的 pipe 状态 %s:";
   public static final String INTERRUPTED_ACQUIRING_READ_LOCK =
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
index 94bfc6deac2..a8763131307 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
@@ -530,11 +530,7 @@ public abstract class PipeTaskAgent {
     final String pipeName = 
pipeMetaFromCoordinator.getStaticMeta().getPipeName();
     final long creationTime = 
pipeMetaFromCoordinator.getStaticMeta().getCreationTime();
 
-    calculateMemoryUsage(
-        pipeMetaFromCoordinator.getStaticMeta(),
-        pipeMetaFromCoordinator.getStaticMeta().getSourceParameters(),
-        pipeMetaFromCoordinator.getStaticMeta().getProcessorParameters(),
-        pipeMetaFromCoordinator.getStaticMeta().getSinkParameters());
+    calculateMemoryUsage(pipeMetaFromCoordinator);
 
     final PipeMeta existedPipeMeta =
         
pipeMetaKeeper.getOverlappedPipeMeta(pipeMetaFromCoordinator.getStaticMeta());
@@ -580,6 +576,15 @@ public abstract class PipeTaskAgent {
     return needToStartPipe;
   }
 
+  protected void calculateMemoryUsage(final PipeMeta pipeMetaFromCoordinator)
+      throws IllegalPathException {
+    calculateMemoryUsage(
+        pipeMetaFromCoordinator.getStaticMeta(),
+        pipeMetaFromCoordinator.getStaticMeta().getSourceParameters(),
+        pipeMetaFromCoordinator.getStaticMeta().getProcessorParameters(),
+        pipeMetaFromCoordinator.getStaticMeta().getSinkParameters());
+  }
+
   protected void calculateMemoryUsage(
       final PipeStaticMeta staticMeta,
       final PipeParameters extractorParameters,

Reply via email to