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

Caideyipi 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 47f71715238 [Pipe] Fairly limit concurrent TsFile parsers (#18260)
47f71715238 is described below

commit 47f717152387a46406caadf1026c69c0294737be
Author: Caideyipi <[email protected]>
AuthorDate: Fri Jul 24 10:28:20 2026 +0800

    [Pipe] Fairly limit concurrent TsFile parsers (#18260)
---
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |   2 +
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |   2 +
 .../org/apache/iotdb/db/conf/IoTDBDescriptor.java  |   2 +
 .../common/tsfile/PipeTsFileInsertionEvent.java    | 123 ++++----
 .../db/pipe/resource/memory/PipeMemoryManager.java | 315 ++++++++++++++++++-
 .../org/apache/iotdb/db/conf/PropertiesTest.java   |  29 ++
 .../PipeTsFileInsertionEventAdmissionTest.java     |  65 ++--
 .../resource/memory/PipeMemoryManagerTest.java     | 347 +++++++++++++++++++++
 .../conf/iotdb-system.properties.template          |  13 +
 .../apache/iotdb/commons/i18n/PipeMessages.java    |   4 +
 .../apache/iotdb/commons/i18n/PipeMessages.java    |   4 +
 .../apache/iotdb/commons/conf/CommonConfig.java    |  40 +++
 .../iotdb/commons/pipe/config/PipeConfig.java      |  14 +
 .../iotdb/commons/pipe/config/PipeDescriptor.java  |  10 +
 14 files changed, 852 insertions(+), 118 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 f13bca9a4a8..c56efafbcd9 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
@@ -2585,4 +2585,6 @@ public final class DataNodePipeMessages {
       "Topic metadata for %s is unavailable during consensus subscription 
setup";
   public static final String 
EXCEPTION_TOPIC_CONFIG_FOR_ARG_IS_UNAVAILABLE_DURING_CONSENSUS_SUBSCRIPTION_SETUP_B94404EE
 =
       "Topic config for %s is unavailable during consensus subscription setup";
+  public static final String 
LOG_FAILED_TO_RELEASE_TSFILE_PARSER_MEMORY_FOR_PIPE_ARG_CREATION_TIME_ARG_IN_DATAREGION_ARG_BECAUSE_NO_RESERVATION_EXISTS_BB8321C0
 =
+      "Failed to release TsFile parser memory for Pipe {} (creation time {}) 
in DataRegion {} because no reservation exists.";
 }
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 17dd4c6d836..3c4cc129596 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
@@ -2413,4 +2413,6 @@ public final class DataNodePipeMessages {
       "共识订阅设置期间 topic %s 的元数据不可用";
   public static final String 
EXCEPTION_TOPIC_CONFIG_FOR_ARG_IS_UNAVAILABLE_DURING_CONSENSUS_SUBSCRIPTION_SETUP_B94404EE
 =
       "共识订阅设置期间 topic %s 的配置不可用";
+  public static final String 
LOG_FAILED_TO_RELEASE_TSFILE_PARSER_MEMORY_FOR_PIPE_ARG_CREATION_TIME_ARG_IN_DATAREGION_ARG_BECAUSE_NO_RESERVATION_EXISTS_BB8321C0
 =
+      "无法释放 Pipe {}(创建时间 {})在 DataRegion {} 中的 TsFile 解析器内存,因为不存在对应的预留。";
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
index ecd3d73a3e3..09584dfe619 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
@@ -42,6 +42,7 @@ import org.apache.iotdb.consensus.config.IoTConsensusV2Config;
 import org.apache.iotdb.db.conf.rest.IoTDBRestServiceDescriptor;
 import org.apache.iotdb.db.consensus.DataRegionConsensusImpl;
 import org.apache.iotdb.db.i18n.DataNodeMiscMessages;
+import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.LastCacheLoadStrategy;
 import org.apache.iotdb.db.service.metrics.IoTDBInternalLocalReporter;
 import org.apache.iotdb.db.storageengine.StorageEngine;
@@ -2766,6 +2767,7 @@ public class IoTDBDescriptor {
 
   private void loadPipeHotModifiedProp(TrimProperties properties) throws 
IOException {
     PipeDescriptor.loadPipeProps(commonDescriptor.getConfig(), properties, 
true);
+    
PipeDataNodeResourceManager.memory().notifyNextTsFileParserMemoryReservation();
     LoggerPeriodicalLogReducer.update();
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
index 988f57ce5cb..ba2a9d275c3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
@@ -47,6 +47,7 @@ import 
org.apache.iotdb.db.pipe.event.common.tsfile.parser.TsFileInsertionEventP
 import org.apache.iotdb.db.pipe.metric.overview.PipeDataNodeSinglePipeMetrics;
 import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
 import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager;
+import 
org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager.TsFileParserMemoryReservation;
 import org.apache.iotdb.db.pipe.resource.tsfile.PipeTsFileResourceManager;
 import 
org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
 import org.apache.iotdb.db.storageengine.dataregion.memtable.TsFileProcessor;
@@ -69,7 +70,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
@@ -81,6 +81,7 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
   private static final Logger LOGGER = 
LoggerFactory.getLogger(PipeTsFileInsertionEvent.class);
 
   private final TsFileResource resource;
+  private final String dataRegionId;
   private File tsFile;
   private long extractTime = 0;
 
@@ -99,6 +100,8 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
   private final AtomicBoolean isClosed;
   private final AtomicReference<TsFileInsertionEventParser> eventParser;
   private final AtomicBoolean isTsFileParserMemoryReserved = new 
AtomicBoolean(false);
+  private final TsFileParserMemoryReservation tsFileParserMemoryReservationKey 
=
+      new TsFileParserMemoryReservation();
   private final AtomicReference<Iterator<TabletInsertionEvent>> 
tabletInsertionEventIterator =
       new AtomicReference<>();
   private final AtomicReference<PipeRawTabletInsertionEvent> 
pendingTabletInsertionEvent =
@@ -229,6 +232,7 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
         databaseNameFromDataRegion);
 
     this.resource = resource;
+    this.dataRegionId = getDataRegionId(resource);
 
     // For events created at assigner or historical extractor, the tsFile is 
get from the resource
     // For events created for source, the tsFile is inherited from the 
assigner, because the
@@ -294,6 +298,17 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
         });
   }
 
+  private static String getDataRegionId(final TsFileResource resource) {
+    // TsFileResource#getDataRegionId assumes the storage-engine directory 
structure, while a
+    // synthetic resource may wrap a standalone file.
+    final File resourceTsFile = resource.getTsFile();
+    final File timePartitionDir =
+        Objects.isNull(resourceTsFile) ? null : resourceTsFile.getParentFile();
+    final File dataRegionDir =
+        Objects.isNull(timePartitionDir) ? null : 
timePartitionDir.getParentFile();
+    return Objects.isNull(dataRegionDir) ? "" : dataRegionDir.getName();
+  }
+
   /**
    * @return {@code false} if this file can't be sent by pipe because it is 
empty. {@code true}
    *     otherwise.
@@ -944,32 +959,12 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
     final long startTime = System.currentTimeMillis();
     long lastRecordTime = startTime;
 
-    final long initialMemoryCheckIntervalMs =
-        Math.max(1, 
PipeConfig.getInstance().getPipeCheckMemoryEnoughIntervalMs());
-    final long maxMemoryCheckIntervalMs =
-        getMaxMemoryCheckIntervalMs(
-            initialMemoryCheckIntervalMs,
-            PipeConfig.getInstance().getPipeMemoryAllocateMaxRetries());
-    long memoryCheckIntervalMs = initialMemoryCheckIntervalMs;
-    while (true) {
-      final long elapsedTimeMs = Math.max(0, System.currentTimeMillis() - 
startTime);
-      if (elapsedTimeMs >= timeoutMs) {
-        // should contain 'TimeoutException' in exception message
-        throw new PipeRuntimeOutOfMemoryCriticalException(
-            String.format(
-                DataNodePipeMessages
-                    
.PIPE_EXCEPTION_TIMEOUTEXCEPTION_WAITED_S_SECONDS_FOR_MEMORY_TO_PARSE_TSFILE_0E4EF8FD,
-                elapsedTimeMs / 1000.0));
-      }
-
-      memoryManager.waitForTsFileParserMemory(
-          Math.min(
-              getMemoryCheckIntervalWithJitter(memoryCheckIntervalMs), 
timeoutMs - elapsedTimeMs));
-
+    while (!tryReserveTsFileParserMemory(memoryManager)) {
       final long currentTime = System.currentTimeMillis();
-      final double elapsedRecordTimeSeconds = (currentTime - lastRecordTime) / 
1000.0;
-      final double waitTimeSeconds = (currentTime - startTime) / 1000.0;
-      if (elapsedRecordTimeSeconds > 10.0) {
+      final long elapsedRecordTimeInMs = currentTime - lastRecordTime;
+      final long waitTimeInMs = currentTime - startTime;
+      final double waitTimeSeconds = waitTimeInMs / 1000.0;
+      if (elapsedRecordTimeInMs > 10_000) {
         LOGGER.info(
             DataNodePipeMessages.WAIT_FOR_MEMORY_ENOUGH_FOR_PARSING_FOR,
             resource != null ? resource.getTsFilePath() : "tsfile",
@@ -982,35 +977,28 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
             waitTimeSeconds);
       }
 
-      if (tryReserveTsFileParserMemory(memoryManager)) {
-        LOGGER.info(
-            DataNodePipeMessages.WAIT_FOR_MEMORY_ENOUGH_FOR_PARSING_FOR,
-            resource != null ? resource.getTsFilePath() : "tsfile",
-            waitTimeSeconds);
-        return;
+      if (waitTimeInMs > timeoutMs) {
+        // should contain 'TimeoutException' in exception message
+        throw new PipeRuntimeOutOfMemoryCriticalException(
+            String.format(
+                DataNodePipeMessages
+                    
.PIPE_EXCEPTION_TIMEOUTEXCEPTION_WAITED_S_SECONDS_FOR_MEMORY_TO_PARSE_TSFILE_0E4EF8FD,
+                waitTimeSeconds));
       }
 
-      memoryCheckIntervalMs =
-          getNextMemoryCheckIntervalMs(memoryCheckIntervalMs, 
maxMemoryCheckIntervalMs);
+      tsFileParserMemoryReservationKey.await(
+          Math.max(
+              1,
+              Math.min(
+                  timeoutMs - waitTimeInMs, 10_000 - Math.min(10_000, 
elapsedRecordTimeInMs))));
     }
-  }
 
-  static long getMaxMemoryCheckIntervalMs(final long initialIntervalMs, final 
int maxRetries) {
-    final long multiplier = Math.max(1, maxRetries);
-    return initialIntervalMs > Long.MAX_VALUE / multiplier
-        ? Long.MAX_VALUE
-        : initialIntervalMs * multiplier;
-  }
-
-  static long getNextMemoryCheckIntervalMs(final long currentIntervalMs, final 
long maxIntervalMs) {
-    return currentIntervalMs >= maxIntervalMs - currentIntervalMs
-        ? maxIntervalMs
-        : currentIntervalMs << 1;
-  }
-
-  static long getMemoryCheckIntervalWithJitter(final long intervalMs) {
-    return Math.max(
-        1, (long) (intervalMs * (0.5 + 
ThreadLocalRandom.current().nextDouble() * 0.5)));
+    final long currentTime = System.currentTimeMillis();
+    final double waitTimeSeconds = (currentTime - startTime) / 1000.0;
+    LOGGER.info(
+        DataNodePipeMessages.WAIT_FOR_MEMORY_ENOUGH_FOR_PARSING_FOR,
+        resource != null ? resource.getTsFilePath() : "tsfile",
+        waitTimeSeconds);
   }
 
   private boolean tryReserveTsFileParserMemory(final PipeMemoryManager 
memoryManager) {
@@ -1019,7 +1007,8 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
         return true;
       }
 
-      if (!memoryManager.tryReserveTsFileParserMemory()) {
+      if (!memoryManager.tryReserveTsFileParserMemory(
+          pipeName, creationTime, dataRegionId, 
tsFileParserMemoryReservationKey)) {
         return false;
       }
 
@@ -1031,11 +1020,20 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
   private void releaseTsFileParserMemoryIfReserved() {
     synchronized (isTsFileParserMemoryReserved) {
       if (isTsFileParserMemoryReserved.compareAndSet(true, false)) {
-        PipeDataNodeResourceManager.memory().releaseTsFileParserMemory();
+        PipeDataNodeResourceManager.memory()
+            .releaseTsFileParserMemory(pipeName, creationTime, dataRegionId);
       }
     }
   }
 
+  private void cancelTsFileParserMemoryReservationIfPending() {
+    if (!isTsFileParserMemoryReserved.get()) {
+      PipeDataNodeResourceManager.memory()
+          .cancelTsFileParserMemoryReservation(
+              pipeName, creationTime, dataRegionId, 
tsFileParserMemoryReservationKey);
+    }
+  }
+
   /** The method is used to prevent circular replication in IoTConsensusV2 */
   public boolean isGeneratedByIoTConsensusV2() {
     return isGeneratedByIoTConsensusV2;
@@ -1104,6 +1102,7 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
   /** Release the resource of {@link TsFileInsertionEventParser}. */
   @Override
   public void close() {
+    cancelTsFileParserMemoryReservationIfPending();
     tabletInsertionEventIterator.set(null);
     releaseParsedTabletEvent(pendingTabletInsertionEvent.getAndSet(null));
     parsedTabletInsertionEventCount.set(0);
@@ -1153,12 +1152,14 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
         this.referenceCount,
         this.pipeName,
         this.creationTime,
+        this.dataRegionId,
         this.tsFile,
         this.isWithMod,
         this.modFile,
         this.sharedModFile,
         this.eventParser,
-        this.isTsFileParserMemoryReserved);
+        this.isTsFileParserMemoryReserved,
+        this.tsFileParserMemoryReservationKey);
   }
 
   private static class PipeTsFileInsertionEventResource extends 
PipeEventResource {
@@ -1170,33 +1171,42 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
     private final AtomicReference<TsFileInsertionEventParser> eventParser;
     private final String pipeName;
     private final long creationTime;
+    private final String dataRegionId;
     private final AtomicBoolean isTsFileParserMemoryReserved;
+    private final TsFileParserMemoryReservation 
tsFileParserMemoryReservationKey;
 
     private PipeTsFileInsertionEventResource(
         final AtomicBoolean isReleased,
         final AtomicInteger referenceCount,
         final String pipeName,
         final long creationTime,
+        final String dataRegionId,
         final File tsFile,
         final boolean isWithMod,
         final File modFile,
         final File sharedModFile,
         final AtomicReference<TsFileInsertionEventParser> eventParser,
-        final AtomicBoolean isTsFileParserMemoryReserved) {
+        final AtomicBoolean isTsFileParserMemoryReserved,
+        final TsFileParserMemoryReservation tsFileParserMemoryReservationKey) {
       super(isReleased, referenceCount);
       this.pipeName = pipeName;
       this.creationTime = creationTime;
+      this.dataRegionId = dataRegionId;
       this.tsFile = tsFile;
       this.isWithMod = isWithMod;
       this.modFile = modFile;
       this.sharedModFile = sharedModFile;
       this.eventParser = eventParser;
       this.isTsFileParserMemoryReserved = isTsFileParserMemoryReserved;
+      this.tsFileParserMemoryReservationKey = tsFileParserMemoryReservationKey;
     }
 
     @Override
     protected void finalizeResource() {
       try {
+        PipeDataNodeResourceManager.memory()
+            .cancelTsFileParserMemoryReservation(
+                pipeName, creationTime, dataRegionId, 
tsFileParserMemoryReservationKey);
         final String pipeTsFileResourcePipeName =
             PipeTsFileResourceManager.getPipeTsFileResourcePipeName(pipeName, 
creationTime);
         // decrease reference count
@@ -1217,7 +1227,8 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
             });
         synchronized (isTsFileParserMemoryReserved) {
           if (isTsFileParserMemoryReserved.compareAndSet(true, false)) {
-            PipeDataNodeResourceManager.memory().releaseTsFileParserMemory();
+            PipeDataNodeResourceManager.memory()
+                .releaseTsFileParserMemory(pipeName, creationTime, 
dataRegionId);
           }
         }
       } catch (final Exception e) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
index acd85b98378..46d957424f5 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
@@ -31,10 +31,15 @@ import 
org.apache.iotdb.db.pipe.resource.memory.strategy.ThresholdAllocationStra
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.LongUnaryOperator;
 
@@ -58,6 +63,15 @@ public class PipeMemoryManager {
 
   private volatile long reservedTsFileParserCount;
 
+  private final Map<PipeIdentity, Integer> reservedTsFileParserCountByPipe = 
new HashMap<>();
+  private final Map<PipeRegionIdentity, Integer> 
reservedTsFileParserCountByPipeRegion =
+      new HashMap<>();
+  private final Map<PipeRegionIdentity, 
LinkedHashSet<TsFileParserMemoryReservation>>
+      waitingTsFileParserRequestsByPipeRegion = new HashMap<>();
+  private final Map<PipeIdentity, ArrayDeque<PipeRegionIdentity>>
+      waitingTsFileParserRegionOrderByPipe = new HashMap<>();
+  private final ArrayDeque<PipeIdentity> waitingTsFileParserPipeOrder = new 
ArrayDeque<>();
+
   // Only non-zero memory blocks will be added to this set.
   private final Set<PipeMemoryBlock> allocatedBlocks = new HashSet<>();
   private final Set<PipeMemoryBlock> shrinkableBlocks = new HashSet<>();
@@ -128,9 +142,12 @@ public class PipeMemoryManager {
             < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTablets();
   }
 
-  private boolean isHardEnough4TabletParsingWithReservedParserMemory() {
+  private boolean isHardEnough4TabletParsingWithReservedParserMemory(
+      final long extraMemoryInBytes) {
     final double tabletMemoryWithParserMemory =
-        (double) usedMemorySizeInBytesOfTablets + 
getReservedTsFileParserMemorySizeInBytes();
+        (double) usedMemorySizeInBytesOfTablets
+            + getReservedTsFileParserMemorySizeInBytes()
+            + extraMemoryInBytes;
     return tabletMemoryWithParserMemory + (double) 
usedMemorySizeInBytesOfTsFiles
             < allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
         && tabletMemoryWithParserMemory < 
allowedMaxMemorySizeInBytesOfTablets();
@@ -149,32 +166,236 @@ public class PipeMemoryManager {
         && (double) usedMemorySizeInBytesOfTablets < 
allowedMaxMemorySizeInBytesOfTablets();
   }
 
-  public synchronized boolean tryReserveTsFileParserMemory() {
-    if (!PIPE_MEMORY_MANAGEMENT_ENABLED) {
-      return true;
+  public synchronized boolean tryReserveTsFileParserMemory(
+      final String pipeName,
+      final long creationTime,
+      final String dataRegionId,
+      final TsFileParserMemoryReservation reservationKey) {
+    if (reservationKey == null) {
+      return false;
+    }
+
+    final PipeIdentity pipeIdentity = new PipeIdentity(pipeName, creationTime);
+    final PipeRegionIdentity pipeRegionIdentity =
+        new PipeRegionIdentity(pipeIdentity, dataRegionId);
+    enqueueTsFileParserReservationRequest(pipeRegionIdentity, reservationKey);
+
+    final int globalLimit = Math.max(1, 
PIPE_CONFIG.getPipeTsFileParserInFlightMaxNum());
+    final int perPipeRegionLimit =
+        Math.max(
+            1, Math.min(globalLimit, 
PIPE_CONFIG.getPipeTsFileParserInFlightMaxNumPerPipeRegion()));
+    final int reservedCountOfPipeRegion =
+        reservedTsFileParserCountByPipeRegion.getOrDefault(pipeRegionIdentity, 
0);
+    if (reservedTsFileParserCount >= globalLimit
+        || reservedCountOfPipeRegion >= perPipeRegionLimit) {
+      notifyNextTsFileParserMemoryReservationInternal();
+      return false;
     }
 
     final long parserMemorySizeInBytes = getTsFileParserMemorySizeInBytes();
-    if 
(isEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes)) {
-      reservedTsFileParserCount++;
-      return true;
+    final boolean isSoftMemoryEnough =
+        !PIPE_MEMORY_MANAGEMENT_ENABLED
+            || 
isEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes);
+    if (!isSoftMemoryEnough
+        && 
!isHardEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes)) {
+      return false;
     }
 
-    return false;
+    final PipeRegionIdentity nextPipeRegion =
+        getNextEligibleTsFileParserPipeRegion(perPipeRegionLimit, 
!isSoftMemoryEnough);
+    final LinkedHashSet<TsFileParserMemoryReservation> requestsOfPipeRegion =
+        waitingTsFileParserRequestsByPipeRegion.get(pipeRegionIdentity);
+    if (!pipeRegionIdentity.equals(nextPipeRegion)
+        || requestsOfPipeRegion == null
+        || !reservationKey.equals(requestsOfPipeRegion.iterator().next())) {
+      notifyNextTsFileParserMemoryReservationInternal();
+      return false;
+    }
+
+    removeTsFileParserReservationRequest(pipeRegionIdentity, reservationKey, 
true);
+    reservedTsFileParserCount++;
+    reservedTsFileParserCountByPipe.merge(pipeIdentity, 1, Integer::sum);
+    reservedTsFileParserCountByPipeRegion.put(pipeRegionIdentity, 
reservedCountOfPipeRegion + 1);
+    notifyNextTsFileParserMemoryReservationInternal();
+    return true;
   }
 
-  public synchronized void releaseTsFileParserMemory() {
-    if (!PIPE_MEMORY_MANAGEMENT_ENABLED) {
+  public synchronized void cancelTsFileParserMemoryReservation(
+      final String pipeName,
+      final long creationTime,
+      final String dataRegionId,
+      final TsFileParserMemoryReservation reservationKey) {
+    if (reservationKey == null) {
       return;
     }
+    removeTsFileParserReservationRequest(
+        new PipeRegionIdentity(new PipeIdentity(pipeName, creationTime), 
dataRegionId),
+        reservationKey,
+        false);
+    notifyNextTsFileParserMemoryReservationInternal();
+  }
 
-    reservedTsFileParserCount = Math.max(0, reservedTsFileParserCount - 1);
-    this.notifyAll();
+  public synchronized void releaseTsFileParserMemory(
+      final String pipeName, final long creationTime, final String 
dataRegionId) {
+    final PipeIdentity pipeIdentity = new PipeIdentity(pipeName, creationTime);
+    final PipeRegionIdentity pipeRegionIdentity =
+        new PipeRegionIdentity(pipeIdentity, dataRegionId);
+    final int reservedCountOfPipeRegion =
+        reservedTsFileParserCountByPipeRegion.getOrDefault(pipeRegionIdentity, 
0);
+    if (reservedCountOfPipeRegion <= 0) {
+      LOGGER.warn(
+          DataNodePipeMessages
+              
.LOG_FAILED_TO_RELEASE_TSFILE_PARSER_MEMORY_FOR_PIPE_ARG_CREATION_TIME_ARG_IN_DATAREGION_ARG_BECAUSE_NO_RESERVATION_EXISTS_BB8321C0,
+          pipeName,
+          creationTime,
+          dataRegionId);
+      return;
+    }
+
+    if (reservedCountOfPipeRegion == 1) {
+      reservedTsFileParserCountByPipeRegion.remove(pipeRegionIdentity);
+    } else {
+      reservedTsFileParserCountByPipeRegion.put(pipeRegionIdentity, 
reservedCountOfPipeRegion - 1);
+    }
+    final int reservedCountOfPipe = 
reservedTsFileParserCountByPipe.getOrDefault(pipeIdentity, 0);
+    if (reservedCountOfPipe == 1) {
+      reservedTsFileParserCountByPipe.remove(pipeIdentity);
+    } else {
+      reservedTsFileParserCountByPipe.put(pipeIdentity, reservedCountOfPipe - 
1);
+    }
+    reservedTsFileParserCount--;
+    notifyNextTsFileParserMemoryReservationInternal();
+  }
+
+  private void enqueueTsFileParserReservationRequest(
+      final PipeRegionIdentity pipeRegionIdentity,
+      final TsFileParserMemoryReservation reservationKey) {
+    final LinkedHashSet<TsFileParserMemoryReservation> requestsOfPipeRegion =
+        waitingTsFileParserRequestsByPipeRegion.computeIfAbsent(
+            pipeRegionIdentity,
+            key -> {
+              final ArrayDeque<PipeRegionIdentity> regionOrder =
+                  waitingTsFileParserRegionOrderByPipe.computeIfAbsent(
+                      key.pipeIdentity,
+                      pipe -> {
+                        waitingTsFileParserPipeOrder.addLast(pipe);
+                        return new ArrayDeque<>();
+                      });
+              regionOrder.addLast(key);
+              return new LinkedHashSet<>();
+            });
+    requestsOfPipeRegion.add(reservationKey);
+  }
+
+  public synchronized void notifyNextTsFileParserMemoryReservation() {
+    notifyNextTsFileParserMemoryReservationInternal();
   }
 
-  public synchronized void waitForTsFileParserMemory(final long timeoutInMs)
-      throws InterruptedException {
-    this.wait(Math.max(1, timeoutInMs));
+  private void notifyNextTsFileParserMemoryReservationInternal() {
+    final int globalLimit = Math.max(1, 
PIPE_CONFIG.getPipeTsFileParserInFlightMaxNum());
+    if (reservedTsFileParserCount >= globalLimit) {
+      return;
+    }
+
+    final long parserMemorySizeInBytes = getTsFileParserMemorySizeInBytes();
+    final boolean isSoftMemoryEnough =
+        !PIPE_MEMORY_MANAGEMENT_ENABLED
+            || 
isEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes);
+    if (!isSoftMemoryEnough
+        && 
!isHardEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes)) {
+      return;
+    }
+
+    final int perPipeRegionLimit =
+        Math.max(
+            1, Math.min(globalLimit, 
PIPE_CONFIG.getPipeTsFileParserInFlightMaxNumPerPipeRegion()));
+    final PipeRegionIdentity nextPipeRegion =
+        getNextEligibleTsFileParserPipeRegion(perPipeRegionLimit, 
!isSoftMemoryEnough);
+    if (nextPipeRegion == null) {
+      return;
+    }
+
+    final LinkedHashSet<TsFileParserMemoryReservation> requestsOfPipeRegion =
+        waitingTsFileParserRequestsByPipeRegion.get(nextPipeRegion);
+    if (requestsOfPipeRegion != null && !requestsOfPipeRegion.isEmpty()) {
+      requestsOfPipeRegion.iterator().next().signal();
+    }
+  }
+
+  private PipeRegionIdentity getNextEligibleTsFileParserPipeRegion(
+      final int perPipeRegionLimit, final boolean 
requirePipeWithoutReservedParser) {
+    for (final PipeIdentity pipeIdentity : waitingTsFileParserPipeOrder) {
+      // Under soft memory pressure, reserve the hard-threshold headroom for a 
pipe that has no
+      // parser yet. Otherwise a busy pipe at the queue head can block every 
pipe behind it.
+      if (requirePipeWithoutReservedParser
+          && reservedTsFileParserCountByPipe.getOrDefault(pipeIdentity, 0) > 
0) {
+        continue;
+      }
+
+      final ArrayDeque<PipeRegionIdentity> regionOrder =
+          waitingTsFileParserRegionOrderByPipe.get(pipeIdentity);
+      if (regionOrder == null) {
+        continue;
+      }
+      for (final PipeRegionIdentity pipeRegionIdentity : regionOrder) {
+        if 
(reservedTsFileParserCountByPipeRegion.getOrDefault(pipeRegionIdentity, 0)
+            < perPipeRegionLimit) {
+          return pipeRegionIdentity;
+        }
+      }
+    }
+    return null;
+  }
+
+  private void removeTsFileParserReservationRequest(
+      final PipeRegionIdentity pipeRegionIdentity,
+      final TsFileParserMemoryReservation reservationKey,
+      final boolean rotateAfterAdmission) {
+    final LinkedHashSet<TsFileParserMemoryReservation> requestsOfPipeRegion =
+        waitingTsFileParserRequestsByPipeRegion.get(pipeRegionIdentity);
+    if (requestsOfPipeRegion == null || 
!requestsOfPipeRegion.remove(reservationKey)) {
+      return;
+    }
+
+    final PipeIdentity pipeIdentity = pipeRegionIdentity.pipeIdentity;
+    final ArrayDeque<PipeRegionIdentity> regionOrder =
+        waitingTsFileParserRegionOrderByPipe.get(pipeIdentity);
+    if (requestsOfPipeRegion.isEmpty()) {
+      waitingTsFileParserRequestsByPipeRegion.remove(pipeRegionIdentity);
+      if (regionOrder != null) {
+        regionOrder.remove(pipeRegionIdentity);
+        if (regionOrder.isEmpty()) {
+          waitingTsFileParserRegionOrderByPipe.remove(pipeIdentity);
+          waitingTsFileParserPipeOrder.remove(pipeIdentity);
+          return;
+        }
+      }
+    } else if (rotateAfterAdmission && regionOrder != null) {
+      regionOrder.remove(pipeRegionIdentity);
+      regionOrder.addLast(pipeRegionIdentity);
+    }
+
+    if (rotateAfterAdmission) {
+      waitingTsFileParserPipeOrder.remove(pipeIdentity);
+      waitingTsFileParserPipeOrder.addLast(pipeIdentity);
+    }
+  }
+
+  public static final class TsFileParserMemoryReservation {
+
+    private boolean isSignaled;
+
+    public synchronized void await(final long timeoutInMs) throws 
InterruptedException {
+      if (!isSignaled) {
+        wait(timeoutInMs);
+      }
+      isSignaled = false;
+    }
+
+    private synchronized void signal() {
+      isSignaled = true;
+      notify();
+    }
   }
 
   public boolean shouldReleaseTsFileParserOnOutOfMemory(
@@ -194,7 +415,7 @@ public class PipeMemoryManager {
       return elapsedTimeInMs >= maxRetryTimeInMs;
     }
 
-    if (!isHardEnough4TabletParsingWithReservedParserMemory()) {
+    if (!isHardEnough4TabletParsingWithReservedParserMemory(0)) {
       return true;
     }
 
@@ -442,6 +663,7 @@ public class PipeMemoryManager {
         allocatedBlocks.remove(block);
       }
 
+      notifyNextTsFileParserMemoryReservationInternal();
       this.notifyAll();
       return;
     }
@@ -732,6 +954,7 @@ public class PipeMemoryManager {
     }
     block.markAsReleased();
 
+    notifyNextTsFileParserMemoryReservationInternal();
     this.notifyAll();
   }
 
@@ -749,6 +972,7 @@ public class PipeMemoryManager {
     }
     block.setMemoryUsageInBytes(block.getMemoryUsageInBytes() - sizeInBytes);
 
+    notifyNextTsFileParserMemoryReservationInternal();
     this.notifyAll();
 
     return true;
@@ -785,4 +1009,61 @@ public class PipeMemoryManager {
   public long getTotalMemorySizeInBytes() {
     return memoryBlock.getTotalMemorySizeInBytes();
   }
+
+  private static class PipeIdentity {
+
+    private final String pipeName;
+    private final long creationTime;
+
+    private PipeIdentity(final String pipeName, final long creationTime) {
+      this.pipeName = pipeName;
+      this.creationTime = creationTime;
+    }
+
+    @Override
+    public boolean equals(final Object object) {
+      if (this == object) {
+        return true;
+      }
+      if (!(object instanceof PipeIdentity)) {
+        return false;
+      }
+      final PipeIdentity that = (PipeIdentity) object;
+      return creationTime == that.creationTime && Objects.equals(pipeName, 
that.pipeName);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(pipeName, creationTime);
+    }
+  }
+
+  private static class PipeRegionIdentity {
+
+    private final PipeIdentity pipeIdentity;
+    private final String dataRegionId;
+
+    private PipeRegionIdentity(final PipeIdentity pipeIdentity, final String 
dataRegionId) {
+      this.pipeIdentity = pipeIdentity;
+      this.dataRegionId = dataRegionId;
+    }
+
+    @Override
+    public boolean equals(final Object object) {
+      if (this == object) {
+        return true;
+      }
+      if (!(object instanceof PipeRegionIdentity)) {
+        return false;
+      }
+      final PipeRegionIdentity that = (PipeRegionIdentity) object;
+      return Objects.equals(pipeIdentity, that.pipeIdentity)
+          && Objects.equals(dataRegionId, that.dataRegionId);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(pipeIdentity, dataRegionId);
+    }
+  }
 }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/conf/PropertiesTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/conf/PropertiesTest.java
index 24cb6a1b8c5..aad4851f6bc 100755
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/conf/PropertiesTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/conf/PropertiesTest.java
@@ -19,6 +19,8 @@
 
 package org.apache.iotdb.db.conf;
 
+import org.apache.iotdb.commons.conf.CommonConfig;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.conf.ConfigurationFileUtils;
 import org.apache.iotdb.commons.conf.TrimProperties;
 import org.apache.iotdb.commons.utils.RegionMigrationFileRemoveRateLimiter;
@@ -39,6 +41,33 @@ import java.util.Properties;
 import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
 
 public class PropertiesTest {
+  @Test
+  public void testHotReloadTsFileParserInFlightLimits() throws Exception {
+    final IoTDBDescriptor descriptor = IoTDBDescriptor.getInstance();
+    final CommonConfig commonConfig = 
CommonDescriptor.getInstance().getConfig();
+    final int originalGlobalLimit = 
commonConfig.getPipeTsFileParserInFlightMaxNum();
+    final int originalPerPipeRegionLimit =
+        commonConfig.getPipeTsFileParserInFlightMaxNumPerPipeRegion();
+
+    try {
+      final TrimProperties properties = new TrimProperties();
+      properties.setProperty("pipe_tsfile_parser_in_flight_max_num", "3");
+      
properties.setProperty("pipe_tsfile_parser_in_flight_max_num_per_pipe_region", 
"2");
+      descriptor.loadHotModifiedProps(properties);
+
+      Assert.assertEquals(3, commonConfig.getPipeTsFileParserInFlightMaxNum());
+      Assert.assertEquals(2, 
commonConfig.getPipeTsFileParserInFlightMaxNumPerPipeRegion());
+    } finally {
+      final TrimProperties properties = new TrimProperties();
+      properties.setProperty(
+          "pipe_tsfile_parser_in_flight_max_num", 
Integer.toString(originalGlobalLimit));
+      properties.setProperty(
+          "pipe_tsfile_parser_in_flight_max_num_per_pipe_region",
+          Integer.toString(originalPerPipeRegionLimit));
+      descriptor.loadHotModifiedProps(properties);
+    }
+  }
+
   @Test
   public void testHotReloadRegionMigrationFileRemoveSpeedLimit() throws 
Exception {
     IoTDBDescriptor descriptor = IoTDBDescriptor.getInstance();
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEventAdmissionTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEventAdmissionTest.java
index d3d7ae9cf8e..74bea8a5ea5 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEventAdmissionTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEventAdmissionTest.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.pipe.datastructure.pattern.PrefixTreePattern;
 import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
 import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager;
+import 
org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager.TsFileParserMemoryReservation;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
 import 
org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResourceStatus;
 import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
@@ -42,54 +43,28 @@ import java.util.concurrent.TimeoutException;
 
 public class PipeTsFileInsertionEventAdmissionTest {
 
-  @Test
-  public void testParserAdmissionBackoffIsBoundedAndJittered() {
-    Assert.assertEquals(100, 
PipeTsFileInsertionEvent.getMaxMemoryCheckIntervalMs(10, 10));
-    Assert.assertEquals(10, 
PipeTsFileInsertionEvent.getMaxMemoryCheckIntervalMs(10, 0));
-    Assert.assertEquals(
-        Long.MAX_VALUE, 
PipeTsFileInsertionEvent.getMaxMemoryCheckIntervalMs(Long.MAX_VALUE, 10));
-
-    Assert.assertEquals(20, 
PipeTsFileInsertionEvent.getNextMemoryCheckIntervalMs(10, 100));
-    Assert.assertEquals(100, 
PipeTsFileInsertionEvent.getNextMemoryCheckIntervalMs(80, 100));
-    Assert.assertEquals(100, 
PipeTsFileInsertionEvent.getNextMemoryCheckIntervalMs(100, 100));
-
-    for (int i = 0; i < 100; i++) {
-      final long intervalWithJitter =
-          PipeTsFileInsertionEvent.getMemoryCheckIntervalWithJitter(100);
-      Assert.assertTrue(intervalWithJitter >= 50);
-      Assert.assertTrue(intervalWithJitter <= 100);
-    }
-  }
-
   @Test(timeout = 10000)
   public void testParserAdmissionIsWokenWhenMemoryIsReleased() throws 
Exception {
     final CommonConfig commonConfig = 
CommonDescriptor.getInstance().getConfig();
     final PipeMemoryManager memoryManager = 
PipeDataNodeResourceManager.memory();
     final long originalParserMemoryInBytes = 
commonConfig.getPipeTsFileParserMemory();
-    final long originalMemoryCheckIntervalMs = 
commonConfig.getPipeCheckMemoryEnoughIntervalMs();
-    final int originalMemoryAllocateMaxRetries = 
commonConfig.getPipeMemoryAllocateMaxRetries();
+    final int originalGlobalLimit = 
commonConfig.getPipeTsFileParserInFlightMaxNum();
+    final int originalPerPipeRegionLimit =
+        commonConfig.getPipeTsFileParserInFlightMaxNumPerPipeRegion();
+    final TsFileParserMemoryReservation blockerReservation = new 
TsFileParserMemoryReservation();
 
     File tsFile = null;
     PipeTsFileInsertionEvent event = null;
     ExecutorService executor = null;
     Future<Iterable<TabletInsertionEvent>> parsingFuture = null;
-    int blockerReservationCount = 0;
+    boolean isBlockerReserved = false;
     try {
-      commonConfig.setPipeTsFileParserMemory(
-          Math.max(1, memoryManager.getTotalNonFloatingMemorySizeInBytes() / 
8));
-      commonConfig.setPipeCheckMemoryEnoughIntervalMs(10000);
-      commonConfig.setPipeMemoryAllocateMaxRetries(10);
-
-      boolean parserMemoryExhausted = false;
-      for (int i = 0; i < 100; i++) {
-        if (!memoryManager.tryReserveTsFileParserMemory()) {
-          parserMemoryExhausted = true;
-          break;
-        }
-        blockerReservationCount++;
-      }
-      Assert.assertTrue(blockerReservationCount > 0);
-      Assert.assertTrue(parserMemoryExhausted);
+      commonConfig.setPipeTsFileParserMemory(1);
+      commonConfig.setPipeTsFileParserInFlightMaxNum(1);
+      commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+      isBlockerReserved =
+          memoryManager.tryReserveTsFileParserMemory("blocker", 0, "0", 
blockerReservation);
+      Assert.assertTrue(isBlockerReserved);
 
       tsFile =
           TsFileGeneratorUtils.generateNonAlignedTsFile(
@@ -109,7 +84,7 @@ public class PipeTsFileInsertionEventAdmissionTest {
               false,
               false,
               null,
-              null,
+              "testPipe",
               0,
               null,
               new PrefixTreePattern("root"),
@@ -130,8 +105,8 @@ public class PipeTsFileInsertionEventAdmissionTest {
           TimeoutException.class, () -> blockedParsingFuture.get(200, 
TimeUnit.MILLISECONDS));
 
       final long releaseTimeInNanos = System.nanoTime();
-      memoryManager.releaseTsFileParserMemory();
-      blockerReservationCount--;
+      memoryManager.releaseTsFileParserMemory("blocker", 0, "0");
+      isBlockerReserved = false;
 
       Assert.assertNotNull(parsingFuture.get(3, TimeUnit.SECONDS));
       Assert.assertTrue(
@@ -147,13 +122,13 @@ public class PipeTsFileInsertionEventAdmissionTest {
       if (event != null) {
         event.close();
       }
-      while (blockerReservationCount > 0) {
-        memoryManager.releaseTsFileParserMemory();
-        blockerReservationCount--;
+      memoryManager.cancelTsFileParserMemoryReservation("blocker", 0, "0", 
blockerReservation);
+      if (isBlockerReserved) {
+        memoryManager.releaseTsFileParserMemory("blocker", 0, "0");
       }
       commonConfig.setPipeTsFileParserMemory(originalParserMemoryInBytes);
-      
commonConfig.setPipeCheckMemoryEnoughIntervalMs(originalMemoryCheckIntervalMs);
-      
commonConfig.setPipeMemoryAllocateMaxRetries(originalMemoryAllocateMaxRetries);
+      commonConfig.setPipeTsFileParserInFlightMaxNum(originalGlobalLimit);
+      
commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(originalPerPipeRegionLimit);
       if (tsFile != null) {
         tsFile.delete();
       }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerTest.java
new file mode 100644
index 00000000000..72bffbc2c61
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerTest.java
@@ -0,0 +1,347 @@
+/*
+ * 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.resource.memory;
+
+import org.apache.iotdb.commons.conf.CommonConfig;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
+import 
org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager.TsFileParserMemoryReservation;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+public class PipeMemoryManagerTest {
+
+  private final PipeMemoryManager memoryManager = 
PipeDataNodeResourceManager.memory();
+  private final CommonConfig commonConfig = 
CommonDescriptor.getInstance().getConfig();
+  private final List<Reservation> reservations = new ArrayList<>();
+  private int originalGlobalLimit;
+  private int originalPerPipeRegionLimit;
+  private long originalParserMemoryInBytes;
+
+  @Before
+  public void setUp() {
+    originalGlobalLimit = commonConfig.getPipeTsFileParserInFlightMaxNum();
+    originalPerPipeRegionLimit = 
commonConfig.getPipeTsFileParserInFlightMaxNumPerPipeRegion();
+    originalParserMemoryInBytes = commonConfig.getPipeTsFileParserMemory();
+    commonConfig.setPipeTsFileParserMemory(1);
+  }
+
+  @After
+  public void tearDown() {
+    for (final Reservation reservation : reservations) {
+      memoryManager.cancelTsFileParserMemoryReservation(
+          reservation.pipeName,
+          reservation.creationTime,
+          reservation.dataRegionId,
+          reservation.key);
+      if (reservation.acquired) {
+        memoryManager.releaseTsFileParserMemory(
+            reservation.pipeName, reservation.creationTime, 
reservation.dataRegionId);
+      }
+    }
+    commonConfig.setPipeTsFileParserInFlightMaxNum(originalGlobalLimit);
+    
commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(originalPerPipeRegionLimit);
+    commonConfig.setPipeTsFileParserMemory(originalParserMemoryInBytes);
+  }
+
+  @Test
+  public void testWaitingPipesAreAdmittedInRoundRobinOrder() {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(1);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+
+    final Reservation pipeAActive = new Reservation("pipeA", 1);
+    final Reservation pipeAFirstWaiting = new Reservation("pipeA", 1);
+    final Reservation pipeASecondWaiting = new Reservation("pipeA", 1);
+    final Reservation pipeBWaiting = new Reservation("pipeB", 2);
+
+    Assert.assertTrue(tryAcquire(pipeAActive));
+    Assert.assertFalse(tryAcquire(pipeAFirstWaiting));
+    Assert.assertFalse(tryAcquire(pipeBWaiting));
+    Assert.assertFalse(tryAcquire(pipeASecondWaiting));
+
+    release(pipeAActive);
+    Assert.assertTrue(tryAcquire(pipeAFirstWaiting));
+    release(pipeAFirstWaiting);
+
+    // Pipe A still has another waiting TsFile, but it was rotated behind pipe 
B after admission.
+    Assert.assertFalse(tryAcquire(pipeASecondWaiting));
+    Assert.assertTrue(tryAcquire(pipeBWaiting));
+    release(pipeBWaiting);
+
+    Assert.assertTrue(tryAcquire(pipeASecondWaiting));
+  }
+
+  @Test
+  public void testGlobalAndPerPipeRegionLimitsAreBothEnforced() {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(2);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+
+    final Reservation pipeAFirst = new Reservation("pipeA", 1);
+    final Reservation pipeASecond = new Reservation("pipeA", 1);
+    final Reservation pipeB = new Reservation("pipeB", 2);
+    final Reservation pipeC = new Reservation("pipeC", 3);
+
+    Assert.assertTrue(tryAcquire(pipeAFirst));
+    Assert.assertFalse(tryAcquire(pipeASecond));
+    Assert.assertTrue(tryAcquire(pipeB));
+    Assert.assertFalse(tryAcquire(pipeC));
+
+    release(pipeAFirst);
+    Assert.assertTrue(tryAcquire(pipeASecond));
+    Assert.assertFalse(tryAcquire(pipeC));
+
+    release(pipeB);
+    Assert.assertTrue(tryAcquire(pipeC));
+  }
+
+  @Test
+  public void testDifferentRegionsOfSamePipeCanRunConcurrently() {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(2);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+
+    final Reservation pipeARegion1First = new Reservation("pipeA", 1, "1");
+    final Reservation pipeARegion1Second = new Reservation("pipeA", 1, "1");
+    final Reservation pipeARegion2 = new Reservation("pipeA", 1, "2");
+
+    Assert.assertTrue(tryAcquire(pipeARegion1First));
+    Assert.assertFalse(tryAcquire(pipeARegion1Second));
+    Assert.assertTrue(tryAcquire(pipeARegion2));
+  }
+
+  @Test
+  public void testWaitingRegionsWithinPipeAreAdmittedInRoundRobinOrder() {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(1);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+
+    final Reservation blocker = new Reservation("blocker", 0);
+    final Reservation pipeARegion1First = new Reservation("pipeA", 1, "1");
+    final Reservation pipeARegion1Second = new Reservation("pipeA", 1, "1");
+    final Reservation pipeARegion2 = new Reservation("pipeA", 1, "2");
+
+    Assert.assertTrue(tryAcquire(blocker));
+    Assert.assertFalse(tryAcquire(pipeARegion1First));
+    Assert.assertFalse(tryAcquire(pipeARegion1Second));
+    Assert.assertFalse(tryAcquire(pipeARegion2));
+
+    release(blocker);
+    Assert.assertTrue(tryAcquire(pipeARegion1First));
+    release(pipeARegion1First);
+
+    Assert.assertFalse(tryAcquire(pipeARegion1Second));
+    Assert.assertTrue(tryAcquire(pipeARegion2));
+    release(pipeARegion2);
+
+    Assert.assertTrue(tryAcquire(pipeARegion1Second));
+  }
+
+  @Test
+  public void testPipeFairnessIsNotWeightedByRegionCount() {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(1);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+
+    final Reservation blocker = new Reservation("blocker", 0);
+    final Reservation pipeARegion1 = new Reservation("pipeA", 1, "1");
+    final Reservation pipeARegion2 = new Reservation("pipeA", 1, "2");
+    final Reservation pipeARegion3 = new Reservation("pipeA", 1, "3");
+    final Reservation pipeBRegion1 = new Reservation("pipeB", 2, "1");
+
+    Assert.assertTrue(tryAcquire(blocker));
+    Assert.assertFalse(tryAcquire(pipeARegion1));
+    Assert.assertFalse(tryAcquire(pipeARegion2));
+    Assert.assertFalse(tryAcquire(pipeARegion3));
+    Assert.assertFalse(tryAcquire(pipeBRegion1));
+
+    release(blocker);
+    Assert.assertTrue(tryAcquire(pipeARegion1));
+    release(pipeARegion1);
+
+    Assert.assertFalse(tryAcquire(pipeARegion2));
+    Assert.assertTrue(tryAcquire(pipeBRegion1));
+    release(pipeBRegion1);
+
+    Assert.assertTrue(tryAcquire(pipeARegion2));
+  }
+
+  @Test
+  public void testSoftMemoryHeadroomIsReservedForPipeWithoutParser() {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(2);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(2);
+
+    final double tabletMemoryLimit =
+        
(commonConfig.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
+                + 
commonConfig.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold() / 
2)
+            * memoryManager.getTotalNonFloatingMemorySizeInBytes();
+    final double tabletAndTsFileMemoryLimit =
+        
(commonConfig.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
+                + 
commonConfig.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold())
+            * memoryManager.getTotalNonFloatingMemorySizeInBytes();
+    commonConfig.setPipeTsFileParserMemory(
+        Math.max(1, (long) (Math.min(tabletMemoryLimit, 
tabletAndTsFileMemoryLimit) * 0.49)));
+
+    final Reservation pipeAActive = new Reservation("pipeA", 1, "1");
+    final Reservation pipeAWaiting = new Reservation("pipeA", 1, "2");
+    final Reservation pipeBWaiting = new Reservation("pipeB", 2, "1");
+
+    Assert.assertTrue(tryAcquire(pipeAActive));
+    Assert.assertFalse(tryAcquire(pipeAWaiting));
+
+    // The second parser would fit only below the hard threshold. Pipe A 
already has a parser, so
+    // the headroom must go to pipe B even though pipe A is ahead in the 
waiting queue.
+    Assert.assertTrue(tryAcquire(pipeBWaiting));
+  }
+
+  @Test
+  public void testConcurrentTsFilesFromMultiplePipesAreNotStarved() throws 
Exception {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(1);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+
+    final Reservation blocker = new Reservation("blocker", 0);
+    Assert.assertTrue(tryAcquire(blocker));
+
+    // Each reservation represents a distinct TsFile event. Pipe A 
deliberately has more waiting
+    // TsFiles so the test can detect whether it monopolizes the single parser 
slot.
+    final List<Reservation> waitingTsFiles = new ArrayList<>();
+    for (int i = 0; i < 5; i++) {
+      waitingTsFiles.add(new Reservation("pipeA", 1));
+    }
+    for (int i = 0; i < 2; i++) {
+      waitingTsFiles.add(new Reservation("pipeB", 2));
+      waitingTsFiles.add(new Reservation("pipeC", 3));
+    }
+    reservations.addAll(waitingTsFiles);
+
+    final List<String> acquisitionOrder = Collections.synchronizedList(new 
ArrayList<>());
+    final CountDownLatch ready = new CountDownLatch(waitingTsFiles.size());
+    final CountDownLatch start = new CountDownLatch(1);
+    final CountDownLatch enqueued = new CountDownLatch(waitingTsFiles.size());
+    final ExecutorService executor = 
Executors.newFixedThreadPool(waitingTsFiles.size());
+    final List<Future<Boolean>> futures = new ArrayList<>();
+
+    try {
+      for (final Reservation reservation : waitingTsFiles) {
+        futures.add(
+            executor.submit(
+                () -> {
+                  ready.countDown();
+                  start.await();
+
+                  boolean acquired = tryAcquireWithoutTracking(reservation);
+                  enqueued.countDown();
+                  final long deadline = System.nanoTime() + 
TimeUnit.SECONDS.toNanos(10);
+                  while (!acquired && System.nanoTime() < deadline) {
+                    reservation.key.await(
+                        Math.max(
+                            1,
+                            TimeUnit.NANOSECONDS.toMillis(
+                                Math.max(1, deadline - System.nanoTime()))));
+                    acquired = tryAcquireWithoutTracking(reservation);
+                  }
+                  if (!acquired) {
+                    return false;
+                  }
+
+                  acquisitionOrder.add(reservation.pipeName);
+                  Thread.sleep(5);
+                  release(reservation);
+                  return true;
+                }));
+      }
+
+      Assert.assertTrue(ready.await(5, TimeUnit.SECONDS));
+      start.countDown();
+      Assert.assertTrue(enqueued.await(5, TimeUnit.SECONDS));
+      release(blocker);
+
+      for (final Future<Boolean> future : futures) {
+        Assert.assertTrue(future.get(15, TimeUnit.SECONDS));
+      }
+      Assert.assertEquals(waitingTsFiles.size(), acquisitionOrder.size());
+      Assert.assertEquals(3, new HashSet<>(acquisitionOrder.subList(0, 
3)).size());
+    } finally {
+      release(blocker);
+      executor.shutdownNow();
+      Assert.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
+    }
+  }
+
+  private boolean tryAcquire(final Reservation reservation) {
+    if (!reservations.contains(reservation)) {
+      reservations.add(reservation);
+    }
+    reservation.acquired =
+        memoryManager.tryReserveTsFileParserMemory(
+            reservation.pipeName,
+            reservation.creationTime,
+            reservation.dataRegionId,
+            reservation.key);
+    return reservation.acquired;
+  }
+
+  private boolean tryAcquireWithoutTracking(final Reservation reservation) {
+    reservation.acquired =
+        memoryManager.tryReserveTsFileParserMemory(
+            reservation.pipeName,
+            reservation.creationTime,
+            reservation.dataRegionId,
+            reservation.key);
+    return reservation.acquired;
+  }
+
+  private void release(final Reservation reservation) {
+    if (!reservation.acquired) {
+      return;
+    }
+    memoryManager.releaseTsFileParserMemory(
+        reservation.pipeName, reservation.creationTime, 
reservation.dataRegionId);
+    reservation.acquired = false;
+  }
+
+  private static class Reservation {
+
+    private final String pipeName;
+    private final long creationTime;
+    private final String dataRegionId;
+    private final TsFileParserMemoryReservation key = new 
TsFileParserMemoryReservation();
+    private volatile boolean acquired;
+
+    private Reservation(final String pipeName, final long creationTime) {
+      this(pipeName, creationTime, "0");
+    }
+
+    private Reservation(final String pipeName, final long creationTime, final 
String dataRegionId) {
+      this.pipeName = pipeName;
+      this.creationTime = creationTime;
+      this.dataRegionId = dataRegionId;
+    }
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
index dd3ba2d295e..02dcb1b3b5d 100644
--- 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
+++ 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
@@ -2036,6 +2036,19 @@ pipe_lib_dir=ext/pipe
 # Datatype: int
 pipe_subtask_executor_max_thread_num=0
 
+# The maximum number of TsFile parsers that can run concurrently across all 
Pipes on this DataNode.
+# When <= 0, use max(1, CPU core number / 2).
+# effectiveMode: hot_reload
+# Datatype: int
+pipe_tsfile_parser_in_flight_max_num=0
+
+# The maximum number of TsFile parsers that can run concurrently for one 
DataRegion of one Pipe.
+# Different DataRegions of the same Pipe have independent limits.
+# When <= 0, use 1.
+# effectiveMode: hot_reload
+# Datatype: int
+pipe_tsfile_parser_in_flight_max_num_per_pipe_region=1
+
 # The connection timeout (in milliseconds) for the thrift client.
 # effectiveMode: restart
 # Datatype: int
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 e69dc15c1dd..742239bc1ca 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
@@ -48,6 +48,10 @@ public final class PipeMessages {
   public static final String CONFIG_IS_PIPE_ENABLE_MEMORY_CHECK =
       "IsPipeEnableMemoryCheck: {}";
   public static final String CONFIG_PIPE_TSFILE_PARSER_MEMORY = 
"PipeTsFileParserMemory: {}";
+  public static final String CONFIG_PIPE_TSFILE_PARSER_IN_FLIGHT_MAX_NUM =
+      "PipeTsFileParserInFlightMaxNum: {}";
+  public static final String 
CONFIG_PIPE_TSFILE_PARSER_IN_FLIGHT_MAX_NUM_PER_PIPE_REGION =
+      "PipeTsFileParserInFlightMaxNumPerPipeRegion: {}";
   public static final String CONFIG_SINK_BATCH_MEMORY_INSERT_NODE =
       "SinkBatchMemoryInsertNode: {}";
   public static final String CONFIG_SINK_BATCH_MEMORY_TSFILE = 
"SinkBatchMemoryTsFile: {}";
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 ca3243cdc01..0a8c0badb35 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
@@ -47,6 +47,10 @@ public final class PipeMessages {
   public static final String CONFIG_IS_PIPE_ENABLE_MEMORY_CHECK =
       "IsPipeEnableMemoryCheck: {}";
   public static final String CONFIG_PIPE_TSFILE_PARSER_MEMORY = 
"PipeTsFileParserMemory: {}";
+  public static final String CONFIG_PIPE_TSFILE_PARSER_IN_FLIGHT_MAX_NUM =
+      "PipeTsFileParserInFlightMaxNum: {}";
+  public static final String 
CONFIG_PIPE_TSFILE_PARSER_IN_FLIGHT_MAX_NUM_PER_PIPE_REGION =
+      "PipeTsFileParserInFlightMaxNumPerPipeRegion: {}";
   public static final String CONFIG_SINK_BATCH_MEMORY_INSERT_NODE =
       "SinkBatchMemoryInsertNode: {}";
   public static final String CONFIG_SINK_BATCH_MEMORY_TSFILE = 
"SinkBatchMemoryTsFile: {}";
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
index 14675b8b19e..73f5bf52fc3 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java
@@ -248,6 +248,13 @@ public class CommonConfig {
   // Note: Pipes that do not decompose pattern/time do not need this part of 
memory
   private long pipeTsFileParserMemory = 17 * MB;
 
+  // Limit concurrently active TsFile parsers globally and for each region 
task of a pipe. The
+  // per-pipe-region limit also serves as an approximate parser memory quota 
because every admitted
+  // parser reserves pipeTsFileParserMemory bytes.
+  private int pipeTsFileParserInFlightMaxNum =
+      Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
+  private int pipeTsFileParserInFlightMaxNumPerPipeRegion = 1;
+
   // Memory for Sink batch sending (InsertNode/TsFile, choose one)
   // 1. InsertNode: 15MB, used for batch sending data to the downstream system
   private long pipeSinkBatchMemoryInsertNode = 15 * MB;
@@ -1041,6 +1048,39 @@ public class CommonConfig {
     logger.info(ConfigMessages.CONFIG_SET_TO, "pipeTsFileParserMemory", 
pipeTsFileParserMemory);
   }
 
+  public int getPipeTsFileParserInFlightMaxNum() {
+    return pipeTsFileParserInFlightMaxNum;
+  }
+
+  public void setPipeTsFileParserInFlightMaxNum(final int 
pipeTsFileParserInFlightMaxNum) {
+    final int validatedValue =
+        pipeTsFileParserInFlightMaxNum > 0
+            ? pipeTsFileParserInFlightMaxNum
+            : Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
+    if (this.pipeTsFileParserInFlightMaxNum == validatedValue) {
+      return;
+    }
+    this.pipeTsFileParserInFlightMaxNum = validatedValue;
+    logger.info(ConfigMessages.CONFIG_SET_TO, 
"pipeTsFileParserInFlightMaxNum", validatedValue);
+  }
+
+  public int getPipeTsFileParserInFlightMaxNumPerPipeRegion() {
+    return pipeTsFileParserInFlightMaxNumPerPipeRegion;
+  }
+
+  public void setPipeTsFileParserInFlightMaxNumPerPipeRegion(
+      final int pipeTsFileParserInFlightMaxNumPerPipeRegion) {
+    final int validatedValue = Math.max(1, 
pipeTsFileParserInFlightMaxNumPerPipeRegion);
+    if (this.pipeTsFileParserInFlightMaxNumPerPipeRegion == validatedValue) {
+      return;
+    }
+    this.pipeTsFileParserInFlightMaxNumPerPipeRegion = validatedValue;
+    logger.info(
+        ConfigMessages.CONFIG_SET_TO,
+        "pipeTsFileParserInFlightMaxNumPerPipeRegion",
+        validatedValue);
+  }
+
   public long getPipeSinkBatchMemoryInsertNode() {
     return pipeSinkBatchMemoryInsertNode;
   }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeConfig.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeConfig.java
index 6e85519a85f..561d2923c07 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeConfig.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeConfig.java
@@ -84,6 +84,14 @@ public class PipeConfig {
     return COMMON_CONFIG.getPipeTsFileParserMemory();
   }
 
+  public int getPipeTsFileParserInFlightMaxNum() {
+    return COMMON_CONFIG.getPipeTsFileParserInFlightMaxNum();
+  }
+
+  public int getPipeTsFileParserInFlightMaxNumPerPipeRegion() {
+    return COMMON_CONFIG.getPipeTsFileParserInFlightMaxNumPerPipeRegion();
+  }
+
   public long getSinkBatchMemoryInsertNode() {
     return COMMON_CONFIG.getPipeSinkBatchMemoryInsertNode();
   }
@@ -506,6 +514,12 @@ public class PipeConfig {
 
     LOGGER.info(PipeMessages.CONFIG_IS_PIPE_ENABLE_MEMORY_CHECK, 
isPipeEnableMemoryCheck());
     LOGGER.info(PipeMessages.CONFIG_PIPE_TSFILE_PARSER_MEMORY, 
getTsFileParserMemory());
+    LOGGER.info(
+        PipeMessages.CONFIG_PIPE_TSFILE_PARSER_IN_FLIGHT_MAX_NUM,
+        getPipeTsFileParserInFlightMaxNum());
+    LOGGER.info(
+        
PipeMessages.CONFIG_PIPE_TSFILE_PARSER_IN_FLIGHT_MAX_NUM_PER_PIPE_REGION,
+        getPipeTsFileParserInFlightMaxNumPerPipeRegion());
     LOGGER.info(PipeMessages.CONFIG_SINK_BATCH_MEMORY_INSERT_NODE, 
getSinkBatchMemoryInsertNode());
     LOGGER.info(PipeMessages.CONFIG_SINK_BATCH_MEMORY_TSFILE, 
getSinkBatchMemoryTsFile());
     LOGGER.info(PipeMessages.CONFIG_SEND_TSFILE_READ_BUFFER, 
getSendTsFileReadBuffer());
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeDescriptor.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeDescriptor.java
index 7909ef89e80..3a1da5f18ba 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeDescriptor.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeDescriptor.java
@@ -238,6 +238,16 @@ public class PipeDescriptor {
         Long.parseLong(
             properties.getProperty(
                 "pipe_tsfile_parser_memory", 
String.valueOf(config.getPipeTsFileParserMemory()))));
+    config.setPipeTsFileParserInFlightMaxNum(
+        Integer.parseInt(
+            properties.getProperty(
+                "pipe_tsfile_parser_in_flight_max_num",
+                String.valueOf(config.getPipeTsFileParserInFlightMaxNum()))));
+    config.setPipeTsFileParserInFlightMaxNumPerPipeRegion(
+        Integer.parseInt(
+            properties.getProperty(
+                "pipe_tsfile_parser_in_flight_max_num_per_pipe_region",
+                
String.valueOf(config.getPipeTsFileParserInFlightMaxNumPerPipeRegion()))));
     config.setPipeSinkBatchMemoryInsertNode(
         Long.parseLong(
             properties.getProperty(

Reply via email to