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 a9f1d5258e1 Fix pipe sink isolation between pipes (#18549)
a9f1d5258e1 is described below

commit a9f1d5258e1681258dbeb4c0ff326f48830451c5
Author: Caideyipi <[email protected]>
AuthorDate: Mon Aug 31 17:08:56 2026 +0800

    Fix pipe sink isolation between pipes (#18549)
---
 .../runtime/heartbeat/PipeHeartbeatParser.java     |  45 ----
 .../runtime/heartbeat/PipeHeartbeatParserTest.java |  42 ++++
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |   4 +
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |   3 +
 .../agent/runtime/PipeDataNodeRuntimeAgent.java    |  17 +-
 .../db/pipe/agent/task/PipeDataNodeTaskAgent.java  |   9 +
 .../pipe/agent/task/stage/PipeTaskSinkStage.java   |   7 +-
 .../agent/task/subtask/sink/PipeSinkSubtask.java   |  46 ++++
 .../task/subtask/sink/PipeSinkSubtaskManager.java  | 219 +++++++++++++----
 .../metric/schema/PipeSchemaRegionSinkMetrics.java |  69 +++---
 .../metric/sink/PipeDataRegionSinkMetrics.java     | 271 +++++++--------------
 .../pipe/agent/task/PipeDataNodeTaskAgentTest.java | 207 ++++++++++++++++
 .../subtask/sink/PipeSinkSubtaskManagerTest.java   |  89 +++++++
 .../schema/PipeSchemaRegionSinkMetricsTest.java    |  77 ++++++
 .../commons/pipe/agent/task/PipeTaskAgent.java     | 246 ++++++++++++++-----
 .../pipe/agent/task/meta/PipeRuntimeMeta.java      |   4 +-
 .../commons/pipe/agent/task/meta/PipeTaskMeta.java |   4 +-
 .../iotdb/commons/service/metric/enums/Tag.java    |   1 +
 18 files changed, 984 insertions(+), 376 deletions(-)

diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
index f702312e5dc..f8374934811 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
@@ -24,7 +24,6 @@ import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
 import org.apache.iotdb.commons.consensus.index.ProgressIndex;
 import org.apache.iotdb.commons.exception.pipe.PipeRuntimeCriticalException;
 import org.apache.iotdb.commons.exception.pipe.PipeRuntimeException;
-import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
 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;
@@ -299,50 +298,6 @@ public class PipeHeartbeatParser {
                   exception,
                   pipeName);
             }
-
-            if (exception instanceof PipeRuntimeSinkCriticalException) {
-              pipeTaskInfo
-                  .get()
-                  .getPipeMetaList()
-                  .forEach(
-                      pipeMeta -> {
-                        final PipeStaticMeta affectedStaticMeta = 
pipeMeta.getStaticMeta();
-                        if (!affectedStaticMeta
-                                .getSinkParameters()
-                                
.equals(pipeMetaFromCoordinator.getStaticMeta().getSinkParameters())
-                            || 
affectedStaticMeta.equals(pipeMetaFromCoordinator.getStaticMeta())) {
-                          return;
-                        }
-
-                        final PipeRuntimeMeta runtimeMeta = 
pipeMeta.getRuntimeMeta();
-                        if 
(PipeStatus.PRE_DELETE.equals(runtimeMeta.getStatus().get())) {
-                          return;
-                        }
-                        if 
(!runtimeMeta.getStatus().get().equals(PipeStatus.STOPPED)) {
-                          // Record the connector exception for each pipe 
affected
-                          Map<Integer, PipeRuntimeException> exceptionMap =
-                              runtimeMeta.getNodeId2PipeRuntimeExceptionMap();
-                          if (!exceptionMap.containsKey(nodeId)
-                              || exceptionMap.get(nodeId).getTimeStamp()
-                                  < exception.getTimeStamp()) {
-                            exceptionMap.put(nodeId, exception);
-                          }
-                          runtimeMeta.getStatus().set(PipeStatus.STOPPED);
-                          runtimeMeta.setIsStoppedByRuntimeException(true);
-
-                          needWriteConsensusOnConfigNodes.set(true);
-                          needPushPipeMetaToDataNodes.set(false);
-
-                          PipeLogger.log(
-                              LOGGER::warn,
-                              exception,
-                              ManagerMessages
-                                  
.DETECT_PIPERUNTIMESINKCRITICALEXCEPTION_FROM_AGENT_STOP_PIPE,
-                              exception,
-                              pipeName);
-                        }
-                      });
-            }
           }
         }
       }
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
index c36a09a7def..b76156bb619 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
@@ -24,6 +24,7 @@ import 
org.apache.iotdb.common.rpc.thrift.TPipeCompletedDataRegion;
 import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
 import org.apache.iotdb.commons.exception.pipe.PipeRuntimeCriticalException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
 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;
@@ -218,6 +219,47 @@ public class PipeHeartbeatParserTest {
     verify(context.procedureManager, times(1)).pipeHandleMetaChange(true, 
false);
   }
 
+  @Test
+  public void testParseHeartbeatDoesNotPropagateSinkExceptionToOtherPipes() 
throws Exception {
+    
CommonDescriptor.getInstance().getConfig().setSeperatedPipeHeartbeatEnabled(false);
+
+    final String failedPipeName = "failedPipe";
+    final String unaffectedPipeName = "unaffectedPipe";
+    final PipeTaskInfo pipeTaskInfo = new PipeTaskInfo();
+    createPipe(pipeTaskInfo, failedPipeName, PipeStatus.RUNNING);
+    createPipe(pipeTaskInfo, unaffectedPipeName, PipeStatus.RUNNING);
+
+    final PipeMeta failedPipeMeta = 
pipeTaskInfo.getPipeMetaByPipeName(failedPipeName);
+    final PipeRuntimeMeta failedRuntimeMeta = failedPipeMeta.getRuntimeMeta();
+    final PipeRuntimeMeta unaffectedRuntimeMeta =
+        
pipeTaskInfo.getPipeMetaByPipeName(unaffectedPipeName).getRuntimeMeta();
+
+    final PipeTaskMeta agentTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, DATA_NODE_ID);
+    agentTaskMeta.trackExceptionMessage(new 
PipeRuntimeSinkCriticalException("sink failure", 300L));
+    final ConcurrentMap<Integer, PipeTaskMeta> agentPipeTasks = new 
ConcurrentHashMap<>();
+    agentPipeTasks.put(DATA_NODE_ID, agentTaskMeta);
+    final PipeHeartbeat heartbeat =
+        new PipeHeartbeat(
+            Collections.singletonList(
+                new PipeMeta(failedPipeMeta.getStaticMeta(), new 
PipeRuntimeMeta(agentPipeTasks))
+                    .serialize()),
+            Collections.singletonList(false),
+            Collections.singletonList(0L),
+            Collections.singletonList(0D),
+            null);
+
+    final ParserTestContext context = createParserTestContext(1, pipeTaskInfo);
+    context.parser.parseHeartbeat(DATA_NODE_ID, heartbeat);
+
+    Assert.assertEquals(PipeStatus.STOPPED, 
failedRuntimeMeta.getStatus().get());
+    Assert.assertTrue(failedRuntimeMeta.getIsStoppedByRuntimeException());
+    Assert.assertEquals(PipeStatus.RUNNING, 
unaffectedRuntimeMeta.getStatus().get());
+    Assert.assertFalse(unaffectedRuntimeMeta.getIsStoppedByRuntimeException());
+    
Assert.assertTrue(unaffectedRuntimeMeta.getNodeId2PipeRuntimeExceptionMap().isEmpty());
+    verify(context.procedureManager, times(1)).pipeHandleMetaChange(true, 
false);
+  }
+
   @Test
   public void testParseHeartbeatDoesNotOverwritePreDeleteStatus() throws 
Exception {
     
CommonDescriptor.getInstance().getConfig().setSeperatedPipeHeartbeatEnabled(false);
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 a61028e25da..41f5c2831bb 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
@@ -134,6 +134,10 @@ public final class DataNodePipeMessages {
       "Failed to decrease reference count for event {} in 
PipeRealtimePriorityBlockingQueue";
   public static final String FAILED_TO_GET_PENDINGQUEUE_NO_SUCH_SUBTASK =
       "Failed to get PendingQueue. No such subtask: ";
+  public static final String
+      
EXCEPTION_MULTIPLE_PIPES_MATCH_THE_REQUESTED_SINK_SUBTASK_USE_THE_PIPE_SPECIFIC_PIPESINKSUBTASKMANAGER_API_C180D94C
 =
+          "Multiple pipes match the requested sink subtask. Use the 
pipe-specific "
+              + "PipeSinkSubtaskManager API.";
   public static final String FAILED_TO_GET_PIPE_INFO_FROM_CONFIG_NODE_STATUS =
       "Failed to get pipe info from config node, status is %s.";
   public static final String FAILED_TO_GET_PIPE_METAS_WILL_BE =
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 4de5ee9aa99..5a4d9c4c568 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
@@ -124,6 +124,9 @@ public final class DataNodePipeMessages {
       "减少 reference count for event {} in PipeRealtimePriorityBlockingQueue 
失败";
   public static final String FAILED_TO_GET_PENDINGQUEUE_NO_SUCH_SUBTASK =
       "获取 PendingQueue 失败,没有该子任务:  ";
+  public static final String
+      
EXCEPTION_MULTIPLE_PIPES_MATCH_THE_REQUESTED_SINK_SUBTASK_USE_THE_PIPE_SPECIFIC_PIPESINKSUBTASKMANAGER_API_C180D94C
 =
+          "多个 Pipe 匹配请求的 sink 子任务。请使用指定 Pipe 的 PipeSinkSubtaskManager API。";
   public static final String FAILED_TO_GET_PIPE_INFO_FROM_CONFIG_NODE_STATUS =
       "从 CN 处获取 pipe 信息失败,状态是 %s。";
   public static final String FAILED_TO_GET_PIPE_METAS_WILL_BE =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/runtime/PipeDataNodeRuntimeAgent.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/runtime/PipeDataNodeRuntimeAgent.java
index 1a589d5970d..83c115a704e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/runtime/PipeDataNodeRuntimeAgent.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/runtime/PipeDataNodeRuntimeAgent.java
@@ -232,7 +232,11 @@ public class PipeDataNodeRuntimeAgent implements IService {
 
   public void report(EnrichedEvent event, PipeRuntimeException 
pipeRuntimeException) {
     if (event.getPipeTaskMeta() != null) {
-      report(event.getPipeTaskMeta(), pipeRuntimeException);
+      report(
+          event.getPipeName(),
+          event.getCreationTime(),
+          event.getPipeTaskMeta(),
+          pipeRuntimeException);
     } else {
       PipeLogger.log(
           LOGGER::warn,
@@ -242,6 +246,14 @@ public class PipeDataNodeRuntimeAgent implements IService {
   }
 
   public void report(PipeTaskMeta pipeTaskMeta, PipeRuntimeException 
pipeRuntimeException) {
+    report(null, Long.MIN_VALUE, pipeTaskMeta, pipeRuntimeException);
+  }
+
+  private void report(
+      final String pipeName,
+      final long creationTime,
+      final PipeTaskMeta pipeTaskMeta,
+      final PipeRuntimeException pipeRuntimeException) {
     PipeLogger.log(
         LOGGER::warn,
         pipeRuntimeException,
@@ -253,7 +265,8 @@ public class PipeDataNodeRuntimeAgent implements IService {
     // no need to wait for the next heartbeat cycle.
     if (pipeRuntimeException instanceof PipeRuntimeCriticalException) {
       PipeDataNodeAgent.task()
-          .stopAllPipesWithCriticalExceptionAndTrackException(pipeTaskMeta, 
pipeRuntimeException);
+          .stopAllPipesWithCriticalExceptionAndTrackException(
+              pipeName, creationTime, pipeTaskMeta, pipeRuntimeException);
     }
   }
 
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 1f6416ad20f..b0f527110d3 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
@@ -475,6 +475,15 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
         CONFIG.getDataNodeId(), pipeTaskMeta, pipeRuntimeException);
   }
 
+  public void stopAllPipesWithCriticalExceptionAndTrackException(
+      final String pipeName,
+      final long creationTime,
+      final PipeTaskMeta pipeTaskMeta,
+      final PipeRuntimeException pipeRuntimeException) {
+    super.stopAllPipesWithCriticalException(
+        CONFIG.getDataNodeId(), pipeName, creationTime, pipeTaskMeta, 
pipeRuntimeException);
+  }
+
   ///////////////////////// Heartbeat /////////////////////////
 
   public void collectPipeMetaList(final TDataNodeHeartbeatResp resp) throws 
TException {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/stage/PipeTaskSinkStage.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/stage/PipeTaskSinkStage.java
index 88eac560cde..2bbcc1248d8 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/stage/PipeTaskSinkStage.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/stage/PipeTaskSinkStage.java
@@ -71,12 +71,12 @@ public class PipeTaskSinkStage extends PipeTaskStage {
 
   @Override
   public void startSubtask() throws PipeException {
-    PipeSinkSubtaskManager.instance().start(sinkSubtaskId);
+    PipeSinkSubtaskManager.instance().start(pipeName, creationTime, 
sinkSubtaskId);
   }
 
   @Override
   public void stopSubtask() throws PipeException {
-    PipeSinkSubtaskManager.instance().stop(sinkSubtaskId);
+    PipeSinkSubtaskManager.instance().stop(pipeName, creationTime, 
sinkSubtaskId);
   }
 
   @Override
@@ -85,6 +85,7 @@ public class PipeTaskSinkStage extends PipeTaskStage {
   }
 
   public UnboundedBlockingPendingQueue<Event> getPipeSinkPendingQueue() {
-    return 
PipeSinkSubtaskManager.instance().getPipeSinkPendingQueue(sinkSubtaskId);
+    return PipeSinkSubtaskManager.instance()
+        .getPipeSinkPendingQueue(pipeName, creationTime, sinkSubtaskId);
   }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
index 83de468cba5..e65c2d3f619 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
@@ -68,6 +68,7 @@ public class PipeSinkSubtask extends PipeAbstractSinkSubtask {
   protected final UnboundedBlockingPendingQueue<Event> inputPendingQueue;
 
   // Record these variables to provide corresponding value to tag key of 
monitoring metrics
+  private final String pipeName;
   private final String attributeSortedString;
   private final String attributeDisplayString;
   private final int sinkIndex;
@@ -88,6 +89,7 @@ public class PipeSinkSubtask extends PipeAbstractSinkSubtask {
       final UnboundedBlockingPendingQueue<Event> inputPendingQueue,
       final PipeConnector outputPipeConnector) {
     this(
+        null,
         taskID,
         creationTime,
         attributeSortedString,
@@ -98,6 +100,45 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
   }
 
   public PipeSinkSubtask(
+      final String pipeName,
+      final String taskID,
+      final long creationTime,
+      final String attributeSortedString,
+      final int sinkIndex,
+      final UnboundedBlockingPendingQueue<Event> inputPendingQueue,
+      final PipeConnector outputPipeConnector) {
+    this(
+        pipeName,
+        taskID,
+        creationTime,
+        attributeSortedString,
+        attributeSortedString,
+        sinkIndex,
+        inputPendingQueue,
+        outputPipeConnector);
+  }
+
+  public PipeSinkSubtask(
+      final String taskID,
+      final long creationTime,
+      final String attributeSortedString,
+      final String attributeDisplayString,
+      final int sinkIndex,
+      final UnboundedBlockingPendingQueue<Event> inputPendingQueue,
+      final PipeConnector outputPipeConnector) {
+    this(
+        null,
+        taskID,
+        creationTime,
+        attributeSortedString,
+        attributeDisplayString,
+        sinkIndex,
+        inputPendingQueue,
+        outputPipeConnector);
+  }
+
+  public PipeSinkSubtask(
+      final String pipeName,
       final String taskID,
       final long creationTime,
       final String attributeSortedString,
@@ -106,6 +147,7 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       final UnboundedBlockingPendingQueue<Event> inputPendingQueue,
       final PipeConnector outputPipeConnector) {
     super(taskID, creationTime, outputPipeConnector);
+    this.pipeName = pipeName;
     this.attributeSortedString = attributeSortedString;
     this.attributeDisplayString = attributeDisplayString;
     this.sinkIndex = sinkIndex;
@@ -467,6 +509,10 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
 
   //////////////////////////// APIs provided for metric framework 
////////////////////////////
 
+  public String getPipeName() {
+    return pipeName;
+  }
+
   public String getAttributeSortedString() {
     return attributeSortedString;
   }
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 1d94d4d9419..27a38c76d52 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
@@ -31,6 +31,7 @@ import 
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSinkRuntimeEnviro
 import org.apache.iotdb.db.i18n.DataNodePipeMessages;
 import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
 import org.apache.iotdb.db.pipe.agent.task.execution.PipeSinkSubtaskExecutor;
+import org.apache.iotdb.db.pipe.consensus.IoTConsensusV2SubtaskExecutor;
 import org.apache.iotdb.db.pipe.consensus.ReplicateProgressDataNodeManager;
 import org.apache.iotdb.db.pipe.metric.source.PipeDataRegionEventCounter;
 import 
org.apache.iotdb.db.pipe.sink.protocol.thrift.async.IoTDBDataRegionAsyncSink;
@@ -49,6 +50,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
@@ -60,10 +62,10 @@ public class PipeSinkSubtaskManager {
   private static final String FAILED_TO_DEREGISTER_EXCEPTION_MESSAGE =
       "Failed to deregister PipeConnectorSubtask. No such subtask: ";
 
-  private final Map<String, List<PipeSinkSubtaskLifeCycle>>
-      attributeSortedString2SubtaskLifeCycleMap = new HashMap<>();
+  private final Map<PipeSinkSubtaskKey, List<PipeSinkSubtaskLifeCycle>>
+      pipeSinkSubtaskKey2SubtaskLifeCycleMap = new HashMap<>();
 
-  private final Map<String, String> attributeSortedString2DisplayString = new 
HashMap<>();
+  private final Map<PipeSinkSubtaskKey, String> 
pipeSinkSubtaskKey2DisplayString = new HashMap<>();
 
   public synchronized String register(
       final Supplier<? extends PipeSinkSubtaskExecutor> executorSupplier,
@@ -98,8 +100,11 @@ public class PipeSinkSubtaskManager {
                 : "data_" + attributeDisplayString
             : "schema_" + attributeDisplayString;
     environment.setAttributeSortedString(attributeDisplayStringWithPrefix);
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        new PipeSinkSubtaskKey(
+            environment.getPipeName(), environment.getCreationTime(), 
attributeSortedString);
 
-    if 
(!attributeSortedString2SubtaskLifeCycleMap.containsKey(attributeSortedString)) 
{
+    if 
(!pipeSinkSubtaskKey2SubtaskLifeCycleMap.containsKey(pipeSinkSubtaskKey)) {
       final PipeSinkSubtaskExecutor executor = executorSupplier.get();
       final List<PipeSinkSubtaskLifeCycle> pipeSinkSubtaskLifeCycleList = new 
ArrayList<>(sinkNum);
 
@@ -117,7 +122,11 @@ public class PipeSinkSubtaskManager {
       for (int sinkIndex = 0; sinkIndex < sinkNum; sinkIndex++) {
         final String taskID =
             String.format(
-                "%s_%s_%s", attributeSortedString, 
environment.getCreationTime(), sinkIndex);
+                "%s_%s_%s_%s",
+                environment.getPipeName(),
+                attributeSortedString,
+                environment.getCreationTime(),
+                sinkIndex);
         environment.setSinkTaskId(taskID);
 
         final PipeConnector pipeSink =
@@ -148,6 +157,7 @@ public class PipeSinkSubtaskManager {
         // 2. Construct PipeConnectorSubtaskLifeCycle to manage 
PipeConnectorSubtask's life cycle
         final PipeSinkSubtask pipeSinkSubtask =
             new PipeSinkSubtask(
+                environment.getPipeName(),
                 taskID,
                 environment.getCreationTime(),
                 attributeSortedString,
@@ -165,14 +175,12 @@ public class PipeSinkSubtaskManager {
           attributeDisplayStringWithPrefix,
           executor.getWorkingThreadName(),
           executor.getCallbackThreadName());
-      attributeSortedString2SubtaskLifeCycleMap.put(
-          attributeSortedString, pipeSinkSubtaskLifeCycleList);
-      attributeSortedString2DisplayString.put(
-          attributeSortedString, attributeDisplayStringWithPrefix);
+      pipeSinkSubtaskKey2SubtaskLifeCycleMap.put(pipeSinkSubtaskKey, 
pipeSinkSubtaskLifeCycleList);
+      pipeSinkSubtaskKey2DisplayString.put(pipeSinkSubtaskKey, 
attributeDisplayStringWithPrefix);
     }
 
     for (final PipeSinkSubtaskLifeCycle lifeCycle :
-        attributeSortedString2SubtaskLifeCycleMap.get(attributeSortedString)) {
+        pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey)) {
       lifeCycle.register();
     }
 
@@ -184,12 +192,14 @@ public class PipeSinkSubtaskManager {
       final long creationTime,
       final int regionId,
       final String attributeSortedString) {
-    if 
(!attributeSortedString2SubtaskLifeCycleMap.containsKey(attributeSortedString)) 
{
-      throwNoSuchSubtaskException(attributeSortedString);
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        new PipeSinkSubtaskKey(pipeName, creationTime, attributeSortedString);
+    if 
(!pipeSinkSubtaskKey2SubtaskLifeCycleMap.containsKey(pipeSinkSubtaskKey)) {
+      throwNoSuchSubtaskException(pipeSinkSubtaskKey);
     }
 
     final List<PipeSinkSubtaskLifeCycle> lifeCycles =
-        attributeSortedString2SubtaskLifeCycleMap.get(attributeSortedString);
+        pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey);
 
     // Shall not be empty
     final PipeSinkSubtaskExecutor executor = lifeCycles.get(0).executor;
@@ -200,13 +210,17 @@ public class PipeSinkSubtaskManager {
     lifeCycles.removeIf(o -> o.deregister(committerKey));
 
     if (lifeCycles.isEmpty()) {
-      attributeSortedString2SubtaskLifeCycleMap.remove(attributeSortedString);
-      attributeSortedString2DisplayString.remove(attributeSortedString);
-      executor.shutdown();
-      LOGGER.info(
-          DataNodePipeMessages.THE_EXECUTOR_AND_HAS_BEEN_SUCCESSFULLY_SHUTDOWN,
-          executor.getWorkingThreadName(),
-          executor.getCallbackThreadName());
+      pipeSinkSubtaskKey2SubtaskLifeCycleMap.remove(pipeSinkSubtaskKey);
+      pipeSinkSubtaskKey2DisplayString.remove(pipeSinkSubtaskKey);
+      // The IoTConsensusV2 executor is owned by the node-wide global 
component container and is
+      // shared by all consensus pipes. A single pipe must not shut it down 
for the others.
+      if (!(executor instanceof IoTConsensusV2SubtaskExecutor)) {
+        executor.shutdown();
+        LOGGER.info(
+            
DataNodePipeMessages.THE_EXECUTOR_AND_HAS_BEEN_SUCCESSFULLY_SHUTDOWN,
+            executor.getWorkingThreadName(),
+            executor.getCallbackThreadName());
+      }
     }
 
     PipeEventCommitManager.getInstance().deregister(pipeName, creationTime, 
regionId);
@@ -215,47 +229,122 @@ public class PipeSinkSubtaskManager {
     ReplicateProgressDataNodeManager.resetReplicateIndexForIoTV2(pipeName);
   }
 
+  public synchronized void start(
+      final String pipeName, final long creationTime, final String 
attributeSortedString) {
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        new PipeSinkSubtaskKey(pipeName, creationTime, attributeSortedString);
+    if 
(!pipeSinkSubtaskKey2SubtaskLifeCycleMap.containsKey(pipeSinkSubtaskKey)) {
+      throwNoSuchSubtaskException(pipeSinkSubtaskKey);
+    }
+
+    for (final PipeSinkSubtaskLifeCycle lifeCycle :
+        pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey)) {
+      lifeCycle.start();
+    }
+  }
+
+  /**
+   * @deprecated Use {@link #start(String, long, String)} to identify the pipe 
explicitly.
+   */
+  @Deprecated
   public synchronized void start(final String attributeSortedString) {
-    if 
(!attributeSortedString2SubtaskLifeCycleMap.containsKey(attributeSortedString)) 
{
-      throwNoSuchSubtaskException(attributeSortedString);
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        getUniquePipeSinkSubtaskKey(attributeSortedString);
+    if (pipeSinkSubtaskKey == null) {
+      throwNoSuchSubtaskException(
+          new PipeSinkSubtaskKey(null, Long.MIN_VALUE, attributeSortedString));
     }
 
     for (final PipeSinkSubtaskLifeCycle lifeCycle :
-        attributeSortedString2SubtaskLifeCycleMap.get(attributeSortedString)) {
+        pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey)) {
       lifeCycle.start();
     }
   }
 
+  public synchronized void stop(
+      final String pipeName, final long creationTime, final String 
attributeSortedString) {
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        new PipeSinkSubtaskKey(pipeName, creationTime, attributeSortedString);
+    if 
(!pipeSinkSubtaskKey2SubtaskLifeCycleMap.containsKey(pipeSinkSubtaskKey)) {
+      throwNoSuchSubtaskException(pipeSinkSubtaskKey);
+    }
+
+    for (final PipeSinkSubtaskLifeCycle lifeCycle :
+        pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey)) {
+      lifeCycle.stop();
+    }
+  }
+
+  /**
+   * @deprecated Use {@link #stop(String, long, String)} to identify the pipe 
explicitly.
+   */
+  @Deprecated
   public synchronized void stop(final String attributeSortedString) {
-    if 
(!attributeSortedString2SubtaskLifeCycleMap.containsKey(attributeSortedString)) 
{
-      throwNoSuchSubtaskException(attributeSortedString);
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        getUniquePipeSinkSubtaskKey(attributeSortedString);
+    if (pipeSinkSubtaskKey == null) {
+      throwNoSuchSubtaskException(
+          new PipeSinkSubtaskKey(null, Long.MIN_VALUE, attributeSortedString));
     }
 
     for (final PipeSinkSubtaskLifeCycle lifeCycle :
-        attributeSortedString2SubtaskLifeCycleMap.get(attributeSortedString)) {
+        pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey)) {
       lifeCycle.stop();
     }
   }
 
-  public UnboundedBlockingPendingQueue<Event> getPipeSinkPendingQueue(
-      final String attributeSortedString) {
-    if 
(!attributeSortedString2SubtaskLifeCycleMap.containsKey(attributeSortedString)) 
{
+  public synchronized UnboundedBlockingPendingQueue<Event> 
getPipeSinkPendingQueue(
+      final String pipeName, final long creationTime, final String 
attributeSortedString) {
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        new PipeSinkSubtaskKey(pipeName, creationTime, attributeSortedString);
+    if 
(!pipeSinkSubtaskKey2SubtaskLifeCycleMap.containsKey(pipeSinkSubtaskKey)) {
       throw new PipeException(
           DataNodePipeMessages.FAILED_TO_GET_PENDINGQUEUE_NO_SUCH_SUBTASK
-              + getDisplayStringForException(attributeSortedString));
+              + getDisplayStringForException(pipeSinkSubtaskKey));
     }
 
     // All subtasks share the same pending queue
-    return attributeSortedString2SubtaskLifeCycleMap
-        .get(attributeSortedString)
-        .get(0)
-        .getPendingQueue();
+    return 
pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey).get(0).getPendingQueue();
+  }
+
+  /**
+   * @deprecated Use {@link #getPipeSinkPendingQueue(String, long, String)} to 
identify the pipe
+   *     explicitly.
+   */
+  @Deprecated
+  public synchronized UnboundedBlockingPendingQueue<Event> 
getPipeSinkPendingQueue(
+      final String attributeSortedString) {
+    final PipeSinkSubtaskKey pipeSinkSubtaskKey =
+        getUniquePipeSinkSubtaskKey(attributeSortedString);
+    if (pipeSinkSubtaskKey == null) {
+      throw new PipeException(
+          DataNodePipeMessages.FAILED_TO_GET_PENDINGQUEUE_NO_SUCH_SUBTASK
+              + getDisplayStringForException(
+                  new PipeSinkSubtaskKey(null, Long.MIN_VALUE, 
attributeSortedString)));
+    }
+
+    return 
pipeSinkSubtaskKey2SubtaskLifeCycleMap.get(pipeSinkSubtaskKey).get(0).getPendingQueue();
   }
 
+  public synchronized boolean hasRegisteredSubtasks(
+      final String pipeName,
+      final long creationTime,
+      final PipeParameters pipeSinkParameters,
+      final int regionId) {
+    return pipeSinkSubtaskKey2SubtaskLifeCycleMap.containsKey(
+        new PipeSinkSubtaskKey(
+            pipeName, creationTime, 
generateAttributeSortedString(pipeSinkParameters, regionId)));
+  }
+
+  /**
+   * @deprecated Use {@link #hasRegisteredSubtasks(String, long, 
PipeParameters, int)} to identify
+   *     the pipe explicitly.
+   */
+  @Deprecated
   public synchronized boolean hasRegisteredSubtasks(
       final PipeParameters pipeSinkParameters, final int regionId) {
-    return attributeSortedString2SubtaskLifeCycleMap.containsKey(
-        generateAttributeSortedString(pipeSinkParameters, regionId));
+    return 
getUniquePipeSinkSubtaskKey(generateAttributeSortedString(pipeSinkParameters, 
regionId))
+        != null;
   }
 
   public static int calculateSinkSubtaskNum(
@@ -324,14 +413,62 @@ public class PipeSinkSubtaskManager {
     return filteredAttributes.toString();
   }
 
-  private void throwNoSuchSubtaskException(final String attributeSortedString) 
{
+  private void throwNoSuchSubtaskException(final PipeSinkSubtaskKey 
pipeSinkSubtaskKey) {
     throw new PipeException(
-        FAILED_TO_DEREGISTER_EXCEPTION_MESSAGE
-            + getDisplayStringForException(attributeSortedString));
+        FAILED_TO_DEREGISTER_EXCEPTION_MESSAGE + 
getDisplayStringForException(pipeSinkSubtaskKey));
+  }
+
+  private String getDisplayStringForException(final PipeSinkSubtaskKey 
pipeSinkSubtaskKey) {
+    return pipeSinkSubtaskKey2DisplayString.getOrDefault(pipeSinkSubtaskKey, 
"unknown");
   }
 
-  private String getDisplayStringForException(final String 
attributeSortedString) {
-    return 
attributeSortedString2DisplayString.getOrDefault(attributeSortedString, 
"unknown");
+  private PipeSinkSubtaskKey getUniquePipeSinkSubtaskKey(final String 
attributeSortedString) {
+    PipeSinkSubtaskKey matchedKey = null;
+    for (final PipeSinkSubtaskKey key : 
pipeSinkSubtaskKey2SubtaskLifeCycleMap.keySet()) {
+      if (!Objects.equals(attributeSortedString, key.attributeSortedString)) {
+        continue;
+      }
+      if (matchedKey != null) {
+        throw new PipeException(
+            DataNodePipeMessages
+                
.EXCEPTION_MULTIPLE_PIPES_MATCH_THE_REQUESTED_SINK_SUBTASK_USE_THE_PIPE_SPECIFIC_PIPESINKSUBTASKMANAGER_API_C180D94C);
+      }
+      matchedKey = key;
+    }
+    return matchedKey;
+  }
+
+  private static final class PipeSinkSubtaskKey {
+
+    private final String pipeName;
+    private final long creationTime;
+    private final String attributeSortedString;
+
+    private PipeSinkSubtaskKey(
+        final String pipeName, final long creationTime, final String 
attributeSortedString) {
+      this.pipeName = pipeName;
+      this.creationTime = creationTime;
+      this.attributeSortedString = attributeSortedString;
+    }
+
+    @Override
+    public boolean equals(final Object object) {
+      if (this == object) {
+        return true;
+      }
+      if (!(object instanceof PipeSinkSubtaskKey)) {
+        return false;
+      }
+      final PipeSinkSubtaskKey that = (PipeSinkSubtaskKey) object;
+      return creationTime == that.creationTime
+          && Objects.equals(pipeName, that.pipeName)
+          && Objects.equals(attributeSortedString, that.attributeSortedString);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(pipeName, creationTime, attributeSortedString);
+    }
   }
 
   /////////////////////////  Singleton Instance Holder  
/////////////////////////
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java
index 869b2616e60..cc8c74e2984 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetrics.java
@@ -61,6 +61,37 @@ public class PipeSchemaRegionSinkMetrics implements 
IMetricSet {
     createHistogram(taskID);
   }
 
+  private static String[] getCreationTimeTags(final PipeSinkSubtask connector) 
{
+    return connector.getPipeName() == null
+        ? new String[] {
+          Tag.NAME.toString(),
+          connector.getAttributeSortedString(),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(connector.getCreationTime())
+        }
+        : new String[] {
+          Tag.NAME.toString(),
+          connector.getAttributeSortedString(),
+          Tag.PIPE.toString(),
+          connector.getPipeName(),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(connector.getCreationTime())
+        };
+  }
+
+  private static String[] getNameTags(final PipeSinkSubtask connector) {
+    return connector.getPipeName() == null
+        ? new String[] {Tag.NAME.toString(), 
connector.getAttributeSortedString()}
+        : new String[] {
+          Tag.NAME.toString(),
+          connector.getAttributeSortedString(),
+          Tag.PIPE.toString(),
+          connector.getPipeName(),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(connector.getCreationTime())
+        };
+  }
+
   private void createRate(final String taskID) {
     final PipeSinkSubtask connector = connectorMap.get(taskID);
     // Transfer event rate
@@ -69,10 +100,7 @@ public class PipeSchemaRegionSinkMetrics implements 
IMetricSet {
         metricService.getOrCreateRate(
             Metric.PIPE_CONNECTOR_SCHEMA_TRANSFER.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            connector.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(connector.getCreationTime())));
+            getCreationTimeTags(connector)));
   }
 
   private void createHistogram(final String taskID) {
@@ -82,28 +110,21 @@ public class PipeSchemaRegionSinkMetrics implements 
IMetricSet {
         metricService.getOrCreateHistogram(
             Metric.PIPE_SCHEMA_BATCH_SIZE.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            connector.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(connector.getCreationTime()));
+            getCreationTimeTags(connector));
     connector.setSchemaBatchSizeHistogram(schemaBatchSizeHistogram);
 
     final Histogram schemaBatchTimeIntervalHistogram =
         metricService.getOrCreateHistogram(
             Metric.PIPE_SCHEMA_BATCH_TIME_COST.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            connector.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(connector.getCreationTime()));
+            getCreationTimeTags(connector));
     
connector.setSchemaBatchTimeIntervalHistogram(schemaBatchTimeIntervalHistogram);
 
     final Histogram schemaBatchEventSizeHistogram =
         metricService.getOrCreateHistogram(
             Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            connector.getAttributeSortedString());
+            getNameTags(connector));
     connector.setEventSizeHistogram(schemaBatchEventSizeHistogram);
   }
 
@@ -126,10 +147,7 @@ public class PipeSchemaRegionSinkMetrics implements 
IMetricSet {
     metricService.remove(
         MetricType.RATE,
         Metric.PIPE_CONNECTOR_SCHEMA_TRANSFER.toString(),
-        Tag.NAME.toString(),
-        connector.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(connector.getCreationTime()));
+        getCreationTimeTags(connector));
     schemaRateMap.remove(taskID);
   }
 
@@ -138,22 +156,13 @@ public class PipeSchemaRegionSinkMetrics implements 
IMetricSet {
     metricService.remove(
         MetricType.HISTOGRAM,
         Metric.PIPE_SCHEMA_BATCH_SIZE.toString(),
-        Tag.NAME.toString(),
-        connector.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(connector.getCreationTime()));
+        getCreationTimeTags(connector));
     metricService.remove(
         MetricType.HISTOGRAM,
         Metric.PIPE_SCHEMA_BATCH_TIME_COST.toString(),
-        Tag.NAME.toString(),
-        connector.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(connector.getCreationTime()));
+        getCreationTimeTags(connector));
     metricService.remove(
-        MetricType.HISTOGRAM,
-        Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(),
-        Tag.NAME.toString(),
-        connector.getAttributeSortedString());
+        MetricType.HISTOGRAM, Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(), 
getNameTags(connector));
   }
 
   //////////////////////////// Register & deregister (pipe integration) 
////////////////////////////
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java
index e2a391e8f70..a52d32ed0d7 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/sink/PipeDataRegionSinkMetrics.java
@@ -74,6 +74,59 @@ public class PipeDataRegionSinkMetrics implements IMetricSet 
{
     createHistogram(taskID);
   }
 
+  private static String[] getIndexedTags(final PipeSinkSubtask sink) {
+    return sink.getPipeName() == null
+        ? new String[] {
+          Tag.NAME.toString(),
+          sink.getAttributeSortedString(),
+          Tag.INDEX.toString(),
+          String.valueOf(sink.getSinkIndex()),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(sink.getCreationTime())
+        }
+        : new String[] {
+          Tag.NAME.toString(),
+          sink.getAttributeSortedString(),
+          Tag.PIPE.toString(),
+          sink.getPipeName(),
+          Tag.INDEX.toString(),
+          String.valueOf(sink.getSinkIndex()),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(sink.getCreationTime())
+        };
+  }
+
+  private static String[] getCreationTimeTags(final PipeSinkSubtask sink) {
+    return sink.getPipeName() == null
+        ? new String[] {
+          Tag.NAME.toString(),
+          sink.getAttributeSortedString(),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(sink.getCreationTime())
+        }
+        : new String[] {
+          Tag.NAME.toString(),
+          sink.getAttributeSortedString(),
+          Tag.PIPE.toString(),
+          sink.getPipeName(),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(sink.getCreationTime())
+        };
+  }
+
+  private static String[] getNameTags(final PipeSinkSubtask sink) {
+    return sink.getPipeName() == null
+        ? new String[] {Tag.NAME.toString(), sink.getAttributeSortedString()}
+        : new String[] {
+          Tag.NAME.toString(),
+          sink.getAttributeSortedString(),
+          Tag.PIPE.toString(),
+          sink.getPipeName(),
+          Tag.CREATION_TIME.toString(),
+          String.valueOf(sink.getCreationTime())
+        };
+  }
+
   private void createAutoGauge(final String taskID) {
     final PipeSinkSubtask sink = sinkMap.get(taskID);
     // Pending event count
@@ -82,80 +135,45 @@ public class PipeDataRegionSinkMetrics implements 
IMetricSet {
         MetricLevel.IMPORTANT,
         sink,
         PipeSinkSubtask::getTabletInsertionEventCount,
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     metricService.createAutoGauge(
         Metric.UNTRANSFERRED_TSFILE_COUNT.toString(),
         MetricLevel.IMPORTANT,
         sink,
         PipeSinkSubtask::getTsFileInsertionEventCount,
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     metricService.createAutoGauge(
         Metric.UNTRANSFERRED_HEARTBEAT_COUNT.toString(),
         MetricLevel.IMPORTANT,
         sink,
         PipeSinkSubtask::getPipeHeartbeatEventCount,
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     // Metrics related to IoTDBThriftAsyncSink
     metricService.createAutoGauge(
         Metric.PIPE_ASYNC_CONNECTOR_RETRY_EVENT_QUEUE_SIZE.toString(),
         MetricLevel.IMPORTANT,
         sink,
         PipeSinkSubtask::getAsyncSinkRetryEventQueueSize,
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     metricService.createAutoGauge(
         Metric.PIPE_PENDING_HANDLERS_SIZE.toString(),
         MetricLevel.IMPORTANT,
         sink,
         PipeSinkSubtask::getPendingHandlersSize,
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     // Metrics related to IoTDB sink
     metricService.createAutoGauge(
         Metric.PIPE_TOTAL_UNCOMPRESSED_SIZE.toString(),
         MetricLevel.IMPORTANT,
         sink,
         PipeSinkSubtask::getTotalUncompressedSize,
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     metricService.createAutoGauge(
         Metric.PIPE_TOTAL_COMPRESSED_SIZE.toString(),
         MetricLevel.IMPORTANT,
         sink,
         PipeSinkSubtask::getTotalCompressedSize,
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
   }
 
   private void createRate(final String taskID) {
@@ -166,34 +184,19 @@ public class PipeDataRegionSinkMetrics implements 
IMetricSet {
         metricService.getOrCreateRate(
             Metric.PIPE_CONNECTOR_TABLET_TRANSFER.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.INDEX.toString(),
-            String.valueOf(sink.getSinkIndex()),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime())));
+            getIndexedTags(sink)));
     tsFileRateMap.put(
         taskID,
         metricService.getOrCreateRate(
             Metric.PIPE_CONNECTOR_TSFILE_TRANSFER.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.INDEX.toString(),
-            String.valueOf(sink.getSinkIndex()),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime())));
+            getIndexedTags(sink)));
     pipeHeartbeatRateMap.put(
         taskID,
         metricService.getOrCreateRate(
             Metric.PIPE_CONNECTOR_HEARTBEAT_TRANSFER.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.INDEX.toString(),
-            String.valueOf(sink.getSinkIndex()),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime())));
+            getIndexedTags(sink)));
   }
 
   private void createTimer(final String taskID) {
@@ -203,10 +206,7 @@ public class PipeDataRegionSinkMetrics implements 
IMetricSet {
         metricService.getOrCreateTimer(
             Metric.PIPE_COMPRESSION_TIME.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime())));
+            getCreationTimeTags(sink)));
   }
 
   private void createHistogram(final String taskID) {
@@ -216,48 +216,33 @@ public class PipeDataRegionSinkMetrics implements 
IMetricSet {
         metricService.getOrCreateHistogram(
             Metric.PIPE_INSERT_NODE_BATCH_SIZE.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime()));
+            getCreationTimeTags(sink));
     sink.setTabletBatchSizeHistogram(tabletBatchSizeHistogram);
 
     final Histogram tsFileBatchSizeHistogram =
         metricService.getOrCreateHistogram(
             Metric.PIPE_TSFILE_BATCH_SIZE.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime()));
+            getCreationTimeTags(sink));
     sink.setTsFileBatchSizeHistogram(tsFileBatchSizeHistogram);
 
     final Histogram tabletBatchTimeIntervalHistogram =
         metricService.getOrCreateHistogram(
             Metric.PIPE_INSERT_NODE_BATCH_TIME_COST.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime()));
+            getCreationTimeTags(sink));
     sink.setTabletBatchTimeIntervalHistogram(tabletBatchTimeIntervalHistogram);
 
     final Histogram tsFileBatchTimeIntervalHistogram =
         metricService.getOrCreateHistogram(
             Metric.PIPE_TSFILE_BATCH_TIME_COST.toString(),
             MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString(),
-            Tag.CREATION_TIME.toString(),
-            String.valueOf(sink.getCreationTime()));
+            getCreationTimeTags(sink));
     sink.setTsFileBatchTimeIntervalHistogram(tsFileBatchTimeIntervalHistogram);
 
     Histogram eventSizeHistogram =
         metricService.getOrCreateHistogram(
-            Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(),
-            MetricLevel.IMPORTANT,
-            Tag.NAME.toString(),
-            sink.getAttributeSortedString());
+            Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(), 
MetricLevel.IMPORTANT, getNameTags(sink));
     sink.setEventSizeHistogram(eventSizeHistogram);
   }
 
@@ -283,102 +268,38 @@ public class PipeDataRegionSinkMetrics implements 
IMetricSet {
     final PipeSinkSubtask sink = sinkMap.get(taskID);
     // Pending event count
     metricService.remove(
-        MetricType.AUTO_GAUGE,
-        Metric.UNTRANSFERRED_TABLET_COUNT.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.AUTO_GAUGE, Metric.UNTRANSFERRED_TABLET_COUNT.toString(), 
getIndexedTags(sink));
     metricService.remove(
-        MetricType.AUTO_GAUGE,
-        Metric.UNTRANSFERRED_TSFILE_COUNT.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.AUTO_GAUGE, Metric.UNTRANSFERRED_TSFILE_COUNT.toString(), 
getIndexedTags(sink));
     metricService.remove(
         MetricType.AUTO_GAUGE,
         Metric.UNTRANSFERRED_HEARTBEAT_COUNT.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     // Metrics related to IoTDBThriftAsyncSink
     metricService.remove(
         MetricType.AUTO_GAUGE,
         Metric.PIPE_ASYNC_CONNECTOR_RETRY_EVENT_QUEUE_SIZE.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     metricService.remove(
-        MetricType.AUTO_GAUGE,
-        Metric.PIPE_PENDING_HANDLERS_SIZE.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.AUTO_GAUGE, Metric.PIPE_PENDING_HANDLERS_SIZE.toString(), 
getIndexedTags(sink));
     // Metrics related to IoTDB sink
     metricService.remove(
         MetricType.AUTO_GAUGE,
         Metric.PIPE_TOTAL_UNCOMPRESSED_SIZE.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getIndexedTags(sink));
     metricService.remove(
-        MetricType.AUTO_GAUGE,
-        Metric.PIPE_TOTAL_COMPRESSED_SIZE.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.AUTO_GAUGE, Metric.PIPE_TOTAL_COMPRESSED_SIZE.toString(), 
getIndexedTags(sink));
   }
 
   private void removeRate(final String taskID) {
     final PipeSinkSubtask sink = sinkMap.get(taskID);
     // Transfer event rate
     metricService.remove(
-        MetricType.RATE,
-        Metric.PIPE_CONNECTOR_TABLET_TRANSFER.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.RATE, Metric.PIPE_CONNECTOR_TABLET_TRANSFER.toString(), 
getIndexedTags(sink));
     metricService.remove(
-        MetricType.RATE,
-        Metric.PIPE_CONNECTOR_TSFILE_TRANSFER.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.RATE, Metric.PIPE_CONNECTOR_TSFILE_TRANSFER.toString(), 
getIndexedTags(sink));
     metricService.remove(
-        MetricType.RATE,
-        Metric.PIPE_CONNECTOR_HEARTBEAT_TRANSFER.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.INDEX.toString(),
-        String.valueOf(sink.getSinkIndex()),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.RATE, Metric.PIPE_CONNECTOR_HEARTBEAT_TRANSFER.toString(), 
getIndexedTags(sink));
     tabletRateMap.remove(taskID);
     tsFileRateMap.remove(taskID);
     pipeHeartbeatRateMap.remove(taskID);
@@ -387,12 +308,7 @@ public class PipeDataRegionSinkMetrics implements 
IMetricSet {
   private void removeTimer(final String taskID) {
     final PipeSinkSubtask sink = sinkMap.get(taskID);
     metricService.remove(
-        MetricType.TIMER,
-        Metric.PIPE_COMPRESSION_TIME.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.TIMER, Metric.PIPE_COMPRESSION_TIME.toString(), 
getCreationTimeTags(sink));
     compressionTimerMap.remove(taskID);
   }
 
@@ -401,37 +317,20 @@ public class PipeDataRegionSinkMetrics implements 
IMetricSet {
     metricService.remove(
         MetricType.HISTOGRAM,
         Metric.PIPE_INSERT_NODE_BATCH_SIZE.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getCreationTimeTags(sink));
     metricService.remove(
-        MetricType.HISTOGRAM,
-        Metric.PIPE_TSFILE_BATCH_SIZE.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        MetricType.HISTOGRAM, Metric.PIPE_TSFILE_BATCH_SIZE.toString(), 
getCreationTimeTags(sink));
     metricService.remove(
         MetricType.HISTOGRAM,
         Metric.PIPE_INSERT_NODE_BATCH_TIME_COST.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getCreationTimeTags(sink));
     metricService.remove(
         MetricType.HISTOGRAM,
         Metric.PIPE_TSFILE_BATCH_TIME_COST.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString(),
-        Tag.CREATION_TIME.toString(),
-        String.valueOf(sink.getCreationTime()));
+        getCreationTimeTags(sink));
 
     metricService.remove(
-        MetricType.HISTOGRAM,
-        Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(),
-        Tag.NAME.toString(),
-        sink.getAttributeSortedString());
+        MetricType.HISTOGRAM, Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(), 
getNameTags(sink));
   }
 
   //////////////////////////// register & deregister (pipe integration) 
////////////////////////////
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
index 018ec371d91..83709d41c3f 100644
--- 
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
@@ -23,24 +23,32 @@ import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.consensus.index.ProgressIndex;
 import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
 import org.apache.iotdb.commons.consensus.index.impl.SimpleProgressIndex;
+import org.apache.iotdb.commons.exception.pipe.PipeRuntimeCriticalException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
 import org.apache.iotdb.commons.pipe.agent.task.PipeTaskAgent;
+import org.apache.iotdb.commons.pipe.agent.task.PipeTaskManager;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMetaKeeper;
 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.config.constant.PipeSourceConstant;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
 import org.apache.iotdb.pipe.api.exception.PipeException;
 
+import org.awaitility.Awaitility;
 import org.junit.Assert;
 import org.junit.Test;
 
 import java.lang.reflect.Field;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
 
 public class PipeDataNodeTaskAgentTest {
 
@@ -103,6 +111,197 @@ public class PipeDataNodeTaskAgentTest {
     }
   }
 
+  @Test
+  public void testSinkCriticalExceptionIsPropagatedOnlyWithinItsPipe() throws 
Exception {
+    final PipeDataNodeTaskAgent taskAgent = new PipeDataNodeTaskAgent();
+    final PipeMetaKeeper pipeMetaKeeper = getField(taskAgent, 
"pipeMetaKeeper");
+    final int localNodeId = 
IoTDBDescriptor.getInstance().getConfig().getDataNodeId();
+
+    final PipeTaskMeta failedTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final PipeTaskMeta failedPipeSecondTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final ConcurrentMap<Integer, PipeTaskMeta> failedPipeTaskMetaMap = new 
ConcurrentHashMap<>();
+    failedPipeTaskMetaMap.put(REGION_ID, failedTaskMeta);
+    failedPipeTaskMetaMap.put(REGION_ID + 1, failedPipeSecondTaskMeta);
+    final PipeMeta failedPipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta("failedPipe", 1L, new HashMap<>(), new 
HashMap<>(), new HashMap<>()),
+            new PipeRuntimeMeta(failedPipeTaskMetaMap));
+    failedPipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+
+    final PipeTaskMeta unaffectedTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final ConcurrentMap<Integer, PipeTaskMeta> unaffectedPipeTaskMetaMap =
+        new ConcurrentHashMap<>();
+    unaffectedPipeTaskMetaMap.put(REGION_ID, unaffectedTaskMeta);
+    final PipeMeta unaffectedPipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta(
+                "unaffectedPipe", 1L, new HashMap<>(), new HashMap<>(), new 
HashMap<>()),
+            new PipeRuntimeMeta(unaffectedPipeTaskMetaMap));
+    unaffectedPipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+
+    pipeMetaKeeper.addPipeMeta(failedPipeMeta);
+    pipeMetaKeeper.addPipeMeta(unaffectedPipeMeta);
+
+    final PipeRuntimeSinkCriticalException exception =
+        new PipeRuntimeSinkCriticalException("sink failure", 1L);
+    
taskAgent.stopAllPipesWithCriticalExceptionAndTrackException(failedTaskMeta, 
exception);
+
+    Awaitility.await()
+        .atMost(5, TimeUnit.SECONDS)
+        .until(
+            () ->
+                
PipeStatus.STOPPED.equals(failedPipeMeta.getRuntimeMeta().getStatus().get())
+                    && 
failedPipeSecondTaskMeta.containsExceptionMessage(exception));
+
+    Assert.assertEquals(PipeStatus.RUNNING, 
unaffectedPipeMeta.getRuntimeMeta().getStatus().get());
+    Assert.assertFalse(unaffectedTaskMeta.hasExceptionMessages());
+  }
+
+  @Test
+  public void testExplicitPipeIdentityDoesNotFallBackToTaskMeta() throws 
Exception {
+    final PipeDataNodeTaskAgent taskAgent = new PipeDataNodeTaskAgent();
+    final PipeMetaKeeper pipeMetaKeeper = getField(taskAgent, 
"pipeMetaKeeper");
+    final int localNodeId = 
IoTDBDescriptor.getInstance().getConfig().getDataNodeId();
+
+    final PipeTaskMeta taskMeta = new 
PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final ConcurrentMap<Integer, PipeTaskMeta> taskMetaMap = new 
ConcurrentHashMap<>();
+    taskMetaMap.put(REGION_ID, taskMeta);
+    final PipeMeta pipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta(
+                "existingPipe", 1L, new HashMap<>(), new HashMap<>(), new 
HashMap<>()),
+            new PipeRuntimeMeta(taskMetaMap));
+    pipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+    pipeMetaKeeper.addPipeMeta(pipeMeta);
+
+    taskAgent.stopAllPipesWithCriticalExceptionAndTrackException(
+        "existingPipe",
+        Long.MIN_VALUE,
+        taskMeta,
+        new PipeRuntimeCriticalException("stale pipe identity", 1L));
+
+    Awaitility.await()
+        .during(500, TimeUnit.MILLISECONDS)
+        .atMost(2, TimeUnit.SECONDS)
+        .until(
+            () ->
+                
PipeStatus.RUNNING.equals(pipeMeta.getRuntimeMeta().getStatus().get())
+                    && !taskMeta.hasExceptionMessages());
+  }
+
+  @Test
+  public void testDetachedTaskMetaMustIdentifyOnePipeUniquely() throws 
Exception {
+    final PipeDataNodeTaskAgent taskAgent = new PipeDataNodeTaskAgent();
+    final PipeMetaKeeper pipeMetaKeeper = getField(taskAgent, 
"pipeMetaKeeper");
+    final int localNodeId = 
IoTDBDescriptor.getInstance().getConfig().getDataNodeId();
+
+    final PipeTaskMeta firstTaskMeta = new 
PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final PipeTaskMeta secondTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final ConcurrentMap<Integer, PipeTaskMeta> firstTaskMetaMap = new 
ConcurrentHashMap<>();
+    firstTaskMetaMap.put(REGION_ID, firstTaskMeta);
+    final ConcurrentMap<Integer, PipeTaskMeta> secondTaskMetaMap = new 
ConcurrentHashMap<>();
+    secondTaskMetaMap.put(REGION_ID, secondTaskMeta);
+    final PipeMeta firstPipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta("firstPipe", 1L, new HashMap<>(), new 
HashMap<>(), new HashMap<>()),
+            new PipeRuntimeMeta(firstTaskMetaMap));
+    final PipeMeta secondPipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta("secondPipe", 1L, new HashMap<>(), new 
HashMap<>(), new HashMap<>()),
+            new PipeRuntimeMeta(secondTaskMetaMap));
+    firstPipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+    secondPipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+    pipeMetaKeeper.addPipeMeta(firstPipeMeta);
+    pipeMetaKeeper.addPipeMeta(secondPipeMeta);
+
+    final PipeTaskMeta detachedTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    taskAgent.stopAllPipesWithCriticalExceptionAndTrackException(
+        detachedTaskMeta, new PipeRuntimeCriticalException("ambiguous task 
meta", 1L));
+
+    Awaitility.await().atMost(2, 
TimeUnit.SECONDS).until(detachedTaskMeta::hasExceptionMessages);
+    pipeMetaKeeper.acquireWriteLock();
+    pipeMetaKeeper.releaseWriteLock();
+
+    Assert.assertEquals(PipeStatus.RUNNING, 
firstPipeMeta.getRuntimeMeta().getStatus().get());
+    Assert.assertEquals(PipeStatus.RUNNING, 
secondPipeMeta.getRuntimeMeta().getStatus().get());
+    Assert.assertFalse(firstTaskMeta.hasExceptionMessages());
+    Assert.assertFalse(secondTaskMeta.hasExceptionMessages());
+  }
+
+  @Test
+  public void testDetachedTaskMetaIsRecordedOnIdentifiedPipe() throws 
Exception {
+    final PipeDataNodeTaskAgent taskAgent = new PipeDataNodeTaskAgent();
+    final PipeMetaKeeper pipeMetaKeeper = getField(taskAgent, 
"pipeMetaKeeper");
+    final PipeTaskManager pipeTaskManager = getField(taskAgent, 
"pipeTaskManager");
+    final int localNodeId = 
IoTDBDescriptor.getInstance().getConfig().getDataNodeId();
+
+    final PipeTaskMeta localTaskMeta = new 
PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final ConcurrentMap<Integer, PipeTaskMeta> taskMetaMap = new 
ConcurrentHashMap<>();
+    taskMetaMap.put(REGION_ID, localTaskMeta);
+    final PipeMeta pipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta(
+                "detachedPipe", 2L, new HashMap<>(), new HashMap<>(), new 
HashMap<>()),
+            new PipeRuntimeMeta(taskMetaMap));
+    pipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+    pipeMetaKeeper.addPipeMeta(pipeMeta);
+    pipeTaskManager.addPipeTasks(pipeMeta.getStaticMeta(), 
Collections.emptyMap());
+
+    final PipeTaskMeta detachedTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, localNodeId);
+    final PipeRuntimeCriticalException exception =
+        new PipeRuntimeCriticalException("detached failure", 2L);
+    taskAgent.stopAllPipesWithCriticalExceptionAndTrackException(
+        "detachedPipe", 2L, detachedTaskMeta, exception);
+
+    Awaitility.await()
+        .atMost(5, TimeUnit.SECONDS)
+        .until(
+            () ->
+                
PipeStatus.STOPPED.equals(pipeMeta.getRuntimeMeta().getStatus().get())
+                    && localTaskMeta.containsExceptionMessage(exception));
+    
Assert.assertTrue(pipeMeta.getRuntimeMeta().getIsStoppedByRuntimeException());
+  }
+
+  @Test
+  public void testCriticalExceptionWithNullTaskMetaStopsIdentifiedPipe() 
throws Exception {
+    final PipeDataNodeTaskAgent taskAgent = new PipeDataNodeTaskAgent();
+    final PipeMetaKeeper pipeMetaKeeper = getField(taskAgent, 
"pipeMetaKeeper");
+
+    final PipeMeta failedPipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta(
+                "nullTaskMetaPipe", 3L, new HashMap<>(), new HashMap<>(), new 
HashMap<>()),
+            new PipeRuntimeMeta());
+    failedPipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+    final PipeMeta unaffectedPipeMeta =
+        new PipeMeta(
+            new PipeStaticMeta(
+                "stillRunningPipe", 3L, new HashMap<>(), new HashMap<>(), new 
HashMap<>()),
+            new PipeRuntimeMeta());
+    unaffectedPipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+    pipeMetaKeeper.addPipeMeta(failedPipeMeta);
+    pipeMetaKeeper.addPipeMeta(unaffectedPipeMeta);
+
+    final PipeRuntimeSinkCriticalException exception =
+        new PipeRuntimeSinkCriticalException("null task meta failure", 3L);
+    taskAgent.stopAllPipesWithCriticalExceptionAndTrackException(
+        "nullTaskMetaPipe", 3L, null, exception);
+
+    Awaitility.await()
+        .atMost(5, TimeUnit.SECONDS)
+        .until(
+            () ->
+                
PipeStatus.STOPPED.equals(failedPipeMeta.getRuntimeMeta().getStatus().get())
+                    && 
failedPipeMeta.getRuntimeMeta().getIsStoppedByRuntimeException());
+    Assert.assertEquals(PipeStatus.RUNNING, 
unaffectedPipeMeta.getRuntimeMeta().getStatus().get());
+  }
+
   @Test
   public void testCarryOverCommittedProgressForResumeAlter() {
     final PipeStaticMeta oldStaticMeta = createStaticMeta(1, false);
@@ -250,4 +449,12 @@ public class PipeDataNodeTaskAgentTest {
     taskMetaMap.put(REGION_ID, new PipeTaskMeta(progressIndex, leaderId));
     return new PipeMeta(staticMeta, new PipeRuntimeMeta(taskMetaMap));
   }
+
+  @SuppressWarnings("unchecked")
+  private <T> T getField(final PipeDataNodeTaskAgent taskAgent, final String 
fieldName)
+      throws ReflectiveOperationException {
+    final Field field = PipeTaskAgent.class.getDeclaredField(fieldName);
+    field.setAccessible(true);
+    return (T) field.get(taskAgent);
+  }
 }
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
index dad19044251..8c2fd176587 100644
--- 
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
@@ -20,9 +20,15 @@
 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.agent.task.connection.UnboundedBlockingPendingQueue;
 import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant;
 import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
+import 
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSinkRuntimeEnvironment;
+import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
+import org.apache.iotdb.db.pipe.agent.task.execution.PipeSinkSubtaskExecutor;
 import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
+import org.apache.iotdb.pipe.api.event.Event;
+import org.apache.iotdb.pipe.api.exception.PipeException;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -32,6 +38,89 @@ import java.util.Map;
 
 public class PipeSinkSubtaskManagerTest {
 
+  @Test
+  public void testSubtasksAreSharedOnlyWithinSamePipe() {
+    // Initialize the task agent used by PipeEventCommitManager before 
registering subtasks.
+    PipeDataNodeAgent.task();
+
+    final String firstPipeName = "firstPipe";
+    final String secondPipeName = "secondPipe";
+    final long creationTime = 1L;
+    final int firstRegionId = -1;
+    final int secondRegionId = -2;
+    final Map<String, String> attributes = new HashMap<>();
+    attributes.put(
+        PipeSinkConstant.CONNECTOR_KEY, 
BuiltinPipePlugin.DO_NOTHING_CONNECTOR.getPipePluginName());
+    attributes.put(PipeSinkConstant.CONNECTOR_IOTDB_PARALLEL_TASKS_KEY, "1");
+    final PipeParameters parameters = new PipeParameters(attributes);
+    final PipeSinkSubtaskManager manager = PipeSinkSubtaskManager.instance();
+
+    boolean firstRegionRegistered = false;
+    boolean secondRegionRegistered = false;
+    boolean secondPipeRegistered = false;
+    try {
+      final String firstPipeSubtaskId =
+          manager.register(
+              PipeSinkSubtaskExecutor::new,
+              parameters,
+              new PipeTaskSinkRuntimeEnvironment(firstPipeName, creationTime, 
firstRegionId));
+      firstRegionRegistered = true;
+      final String firstPipeSecondRegionSubtaskId =
+          manager.register(
+              PipeSinkSubtaskExecutor::new,
+              parameters,
+              new PipeTaskSinkRuntimeEnvironment(firstPipeName, creationTime, 
secondRegionId));
+      secondRegionRegistered = true;
+      final UnboundedBlockingPendingQueue<Event> firstPipeQueue =
+          manager.getPipeSinkPendingQueue(firstPipeName, creationTime, 
firstPipeSubtaskId);
+      Assert.assertSame(firstPipeQueue, 
manager.getPipeSinkPendingQueue(firstPipeSubtaskId));
+      Assert.assertTrue(manager.hasRegisteredSubtasks(parameters, 
firstRegionId));
+
+      final String secondPipeSubtaskId =
+          manager.register(
+              PipeSinkSubtaskExecutor::new,
+              parameters,
+              new PipeTaskSinkRuntimeEnvironment(secondPipeName, creationTime, 
firstRegionId));
+      secondPipeRegistered = true;
+
+      Assert.assertSame(
+          firstPipeQueue,
+          manager.getPipeSinkPendingQueue(
+              firstPipeName, creationTime, firstPipeSecondRegionSubtaskId));
+      Assert.assertNotSame(
+          firstPipeQueue,
+          manager.getPipeSinkPendingQueue(secondPipeName, creationTime, 
secondPipeSubtaskId));
+      Assert.assertThrows(
+          PipeException.class, () -> 
manager.getPipeSinkPendingQueue(firstPipeSubtaskId));
+      Assert.assertThrows(
+          PipeException.class, () -> manager.hasRegisteredSubtasks(parameters, 
firstRegionId));
+      Assert.assertThrows(PipeException.class, () -> 
manager.start(firstPipeSubtaskId));
+      Assert.assertThrows(PipeException.class, () -> 
manager.stop(firstPipeSubtaskId));
+    } finally {
+      if (secondRegionRegistered) {
+        manager.deregister(
+            firstPipeName,
+            creationTime,
+            secondRegionId,
+            PipeSinkSubtaskManager.generateAttributeSortedString(parameters, 
secondRegionId));
+      }
+      if (firstRegionRegistered) {
+        manager.deregister(
+            firstPipeName,
+            creationTime,
+            firstRegionId,
+            PipeSinkSubtaskManager.generateAttributeSortedString(parameters, 
firstRegionId));
+      }
+      if (secondPipeRegistered) {
+        manager.deregister(
+            secondPipeName,
+            creationTime,
+            firstRegionId,
+            PipeSinkSubtaskManager.generateAttributeSortedString(parameters, 
firstRegionId));
+      }
+    }
+  }
+
   @Test
   public void 
testGenerateAttributeSortedStringUsesSerializeByRegionAndIgnoresRestartFlag() {
     final Map<String, String> attributes = new HashMap<>();
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetricsTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetricsTest.java
index 744e640189c..f695c604ea1 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetricsTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/metric/schema/PipeSchemaRegionSinkMetricsTest.java
@@ -151,4 +151,81 @@ public class PipeSchemaRegionSinkMetricsTest {
       metricServiceField.set(metrics, null);
     }
   }
+
+  @Test
+  public void testBatchHistogramIsIsolatedByPipeIdentity() throws Exception {
+    final String taskId = "schema-pipe-task-" + System.nanoTime();
+    boolean deregistered = false;
+    final AbstractMetricService metricService = 
Mockito.mock(AbstractMetricService.class);
+    final PipeSinkSubtask subtask = Mockito.mock(PipeSinkSubtask.class);
+    final Rate rate = Mockito.mock(Rate.class);
+    final Histogram eventSizeHistogram = Mockito.mock(Histogram.class);
+
+    when(subtask.getTaskID()).thenReturn(taskId);
+    when(subtask.getAttributeSortedString()).thenReturn("schema_test");
+    when(subtask.getPipeName()).thenReturn("pipe");
+    when(subtask.getCreationTime()).thenReturn(2L);
+    when(metricService.getOrCreateRate(
+            eq(Metric.PIPE_CONNECTOR_SCHEMA_TRANSFER.toString()),
+            eq(MetricLevel.IMPORTANT),
+            eq(Tag.NAME.toString()),
+            eq("schema_test"),
+            eq(Tag.PIPE.toString()),
+            eq("pipe"),
+            eq(Tag.CREATION_TIME.toString()),
+            eq("2")))
+        .thenReturn(rate);
+    when(metricService.getOrCreateHistogram(
+            eq(Metric.PIPE_CONNECTOR_BATCH_SIZE.toString()),
+            eq(MetricLevel.IMPORTANT),
+            eq(Tag.NAME.toString()),
+            eq("schema_test"),
+            eq(Tag.PIPE.toString()),
+            eq("pipe"),
+            eq(Tag.CREATION_TIME.toString()),
+            eq("2")))
+        .thenReturn(eventSizeHistogram);
+
+    final PipeSchemaRegionSinkMetrics metrics = 
PipeSchemaRegionSinkMetrics.getInstance();
+    final Field metricServiceField =
+        PipeSchemaRegionSinkMetrics.class.getDeclaredField("metricService");
+    metricServiceField.setAccessible(true);
+    final Field connectorMapField =
+        PipeSchemaRegionSinkMetrics.class.getDeclaredField("connectorMap");
+    connectorMapField.setAccessible(true);
+    final Field schemaRateMapField =
+        PipeSchemaRegionSinkMetrics.class.getDeclaredField("schemaRateMap");
+    schemaRateMapField.setAccessible(true);
+
+    ((Map<?, ?>) connectorMapField.get(metrics)).clear();
+    ((Map<?, ?>) schemaRateMapField.get(metrics)).clear();
+    metricServiceField.set(metrics, null);
+
+    try {
+      metrics.register(subtask);
+      metrics.bindTo(metricService);
+
+      verify(subtask).setEventSizeHistogram(eventSizeHistogram);
+
+      metrics.deregister(taskId);
+      verify(metricService)
+          .remove(
+              MetricType.HISTOGRAM,
+              Metric.PIPE_CONNECTOR_BATCH_SIZE.toString(),
+              Tag.NAME.toString(),
+              "schema_test",
+              Tag.PIPE.toString(),
+              "pipe",
+              Tag.CREATION_TIME.toString(),
+              "2");
+      deregistered = true;
+    } finally {
+      if (!deregistered) {
+        metrics.deregister(taskId);
+      }
+      ((Map<?, ?>) connectorMapField.get(metrics)).clear();
+      ((Map<?, ?>) schemaRateMapField.get(metrics)).clear();
+      metricServiceField.set(metrics, null);
+    }
+  }
 }
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 5523717b093..ceb4f257733 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
@@ -54,7 +54,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -970,6 +969,21 @@ public abstract class PipeTaskAgent {
       final int currentNodeId,
       final PipeTaskMeta pipeTaskMeta,
       final PipeRuntimeException pipeRuntimeException) {
+    stopAllPipesWithCriticalException(
+        currentNodeId, null, Long.MIN_VALUE, pipeTaskMeta, 
pipeRuntimeException);
+  }
+
+  /**
+   * Stops the pipe that owns a critical exception and, for sink failures, its 
other region tasks.
+   * The pipe identity is passed separately because the task meta in an event 
may be a serialized
+   * copy rather than the object currently held by the task agent.
+   */
+  protected void stopAllPipesWithCriticalException(
+      final int currentNodeId,
+      final String pipeName,
+      final long creationTime,
+      final PipeTaskMeta pipeTaskMeta,
+      final PipeRuntimeException pipeRuntimeException) {
     // To avoid deadlock, we use a new thread to stop all pipes.
     CompletableFuture.runAsync(
         () -> {
@@ -978,8 +992,41 @@ public abstract class PipeTaskAgent {
             while (true) {
               if (tryWriteLockWithTimeOut(5)) {
                 try {
-                  pipeTaskMeta.trackExceptionMessage(pipeRuntimeException);
-                  stopAllPipesWithCriticalExceptionInternal(currentNodeId);
+                  final PipeMeta failedPipeMeta =
+                      findPipeMeta(pipeName, creationTime, pipeTaskMeta);
+                  final PipeTaskMeta localFailedPipeTaskMeta =
+                      findLocalPipeTaskMeta(failedPipeMeta, pipeTaskMeta, 
currentNodeId);
+
+                  // An explicit pipe identity is authoritative. If it is 
stale, do not fall back
+                  // to the supplied task meta because identical task metadata 
is valid in another
+                  // pipe.
+                  if (pipeName != null && failedPipeMeta == null) {
+                    return;
+                  }
+
+                  if (failedPipeMeta != null
+                      && failedPipeMeta.getRuntimeMeta().getStatus().get() == 
PipeStatus.RUNNING) {
+                    
failedPipeMeta.getRuntimeMeta().setIsStoppedByRuntimeException(true);
+                  }
+
+                  if (failedPipeMeta == null) {
+                    // Keep the legacy behavior for callers that only provide 
a task meta. When
+                    // the object is detached and cannot be mapped to a unique 
local pipe, there
+                    // is no safe pipe to stop.
+                    if (pipeTaskMeta != null) {
+                      pipeTaskMeta.trackExceptionMessage(pipeRuntimeException);
+                    }
+                  } else if (localFailedPipeTaskMeta != null) {
+                    
localFailedPipeTaskMeta.trackExceptionMessage(pipeRuntimeException);
+                  }
+
+                  stopAllPipesWithCriticalExceptionInternal(
+                      currentNodeId, failedPipeMeta, pipeRuntimeException);
+                  if (failedPipeMeta != null) {
+                    // stopPipe intentionally returns without changing the 
runtime status if its
+                    // task map was concurrently removed. The identified pipe 
still has to stop.
+                    stopPipeWithRuntimeException(failedPipeMeta);
+                  }
                   
LOGGER.info(PipeMessages.STOPPED_ALL_PIPES_WITH_CRITICAL_EXCEPTION);
                   return;
                 } finally {
@@ -999,69 +1046,40 @@ public abstract class PipeTaskAgent {
         });
   }
 
-  private void stopAllPipesWithCriticalExceptionInternal(final int 
currentNodeId) {
-    // 1. track exception in all pipe tasks that share the same connector that 
have critical
-    // exceptions.
-    final Map<PipeParameters, PipeRuntimeSinkCriticalException>
-        reusedConnectorParameters2ExceptionMap = new HashMap<>();
-
-    pipeMetaKeeper
-        .getPipeMetaList()
-        .forEach(
-            pipeMeta -> {
-              final PipeStaticMeta staticMeta = pipeMeta.getStaticMeta();
-              final PipeRuntimeMeta runtimeMeta = pipeMeta.getRuntimeMeta();
-
-              runtimeMeta
-                  .getConsensusGroupId2TaskMetaMap()
-                  .values()
-                  .forEach(
-                      pipeTaskMeta -> {
-                        if (pipeTaskMeta.getLeaderNodeId() != currentNodeId) {
-                          return;
-                        }
-
-                        for (final PipeRuntimeException e : 
pipeTaskMeta.getExceptionMessages()) {
-                          if (e instanceof PipeRuntimeSinkCriticalException) {
-                            reusedConnectorParameters2ExceptionMap.putIfAbsent(
-                                staticMeta.getSinkParameters(),
-                                (PipeRuntimeSinkCriticalException) e);
-                          }
-                        }
-                      });
-            });
-    pipeMetaKeeper
-        .getPipeMetaList()
-        .forEach(
-            pipeMeta -> {
-              final PipeStaticMeta staticMeta = pipeMeta.getStaticMeta();
-              final PipeRuntimeMeta runtimeMeta = pipeMeta.getRuntimeMeta();
+  private void stopAllPipesWithCriticalExceptionInternal(
+      final int currentNodeId,
+      final PipeMeta failedPipeMeta,
+      final PipeRuntimeException pipeRuntimeException) {
+    // 1. A sink subtask is shared only by regions of one pipe. Locate that 
pipe through its
+    // explicit identity, then propagate the exception only inside that pipe.
+    if (pipeRuntimeException instanceof PipeRuntimeSinkCriticalException) {
+      if (failedPipeMeta != null) {
+        final PipeRuntimeMeta runtimeMeta = failedPipeMeta.getRuntimeMeta();
+        final PipeStaticMeta staticMeta = failedPipeMeta.getStaticMeta();
+        boolean hasLocalTask = false;
+        for (final PipeTaskMeta pipeTaskMeta :
+            runtimeMeta.getConsensusGroupId2TaskMetaMap().values()) {
+          if (pipeTaskMeta.getLeaderNodeId() == currentNodeId
+              && !pipeTaskMeta.containsExceptionMessage(pipeRuntimeException)) 
{
+            hasLocalTask = true;
+            pipeTaskMeta.trackExceptionMessage(pipeRuntimeException);
+            PipeLogger.log(
+                LOGGER::warn,
+                PipeMessages.PIPE_STOPPED_CRITICAL_EXCEPTION,
+                staticMeta.getPipeName(),
+                staticMeta.getCreationTime(),
+                pipeRuntimeException.getTimeStamp(),
+                staticMeta.getSinkParameters());
+          }
+        }
 
-              runtimeMeta
-                  .getConsensusGroupId2TaskMetaMap()
-                  .values()
-                  .forEach(
-                      pipeTaskMeta -> {
-                        if (pipeTaskMeta.getLeaderNodeId() == currentNodeId
-                            && 
reusedConnectorParameters2ExceptionMap.containsKey(
-                                staticMeta.getSinkParameters())
-                            && !pipeTaskMeta.containsExceptionMessage(
-                                reusedConnectorParameters2ExceptionMap.get(
-                                    staticMeta.getSinkParameters()))) {
-                          final PipeRuntimeSinkCriticalException exception =
-                              reusedConnectorParameters2ExceptionMap.get(
-                                  staticMeta.getSinkParameters());
-                          pipeTaskMeta.trackExceptionMessage(exception);
-                          PipeLogger.log(
-                              LOGGER::warn,
-                              PipeMessages.PIPE_STOPPED_CRITICAL_EXCEPTION,
-                              staticMeta.getPipeName(),
-                              staticMeta.getCreationTime(),
-                              exception.getTimeStamp(),
-                              staticMeta.getSinkParameters());
-                        }
-                      });
-            });
+        if (!hasLocalTask) {
+          // The pipe may have no local region task when the sink callback 
races with task
+          // removal. The explicit pipe identity still lets us stop only this 
pipe.
+          stopPipeWithRuntimeException(failedPipeMeta);
+        }
+      }
+    }
 
     // 2. stop all pipes that have critical exceptions.
     pipeMetaKeeper
@@ -1094,6 +1112,104 @@ public abstract class PipeTaskAgent {
             });
   }
 
+  private void stopPipeWithRuntimeException(final PipeMeta pipeMeta) {
+    final PipeRuntimeMeta runtimeMeta = pipeMeta.getRuntimeMeta();
+    if (runtimeMeta.getStatus().get() != PipeStatus.RUNNING) {
+      return;
+    }
+
+    runtimeMeta.setIsStoppedByRuntimeException(true);
+    final PipeStaticMeta staticMeta = pipeMeta.getStaticMeta();
+    try {
+      stopPipe(staticMeta.getPipeName(), staticMeta.getCreationTime());
+    } finally {
+      // stopPipe intentionally does nothing when its task map has already 
been removed. Keep the
+      // runtime state consistent with the exception in that race.
+      if (runtimeMeta.getStatus().get() == PipeStatus.RUNNING) {
+        runtimeMeta.getStatus().set(PipeStatus.STOPPED);
+      }
+    }
+  }
+
+  private PipeMeta findPipeMeta(
+      final String pipeName, final long creationTime, final PipeTaskMeta 
pipeTaskMeta) {
+    if (pipeName != null) {
+      // An explicitly supplied identity is authoritative. Never fall back to 
task-meta content
+      // matching with a different pipe, since identical task metadata is 
valid across pipes.
+      return pipeMetaKeeper.getPipeMeta(pipeName, creationTime);
+    }
+
+    for (final PipeMeta pipeMeta : pipeMetaKeeper.getPipeMetaList()) {
+      if (pipeTaskMeta != null
+          && 
pipeMeta.getRuntimeMeta().getConsensusGroupId2TaskMetaMap().values().stream()
+              .anyMatch(taskMeta -> taskMeta == pipeTaskMeta)) {
+        return pipeMeta;
+      }
+    }
+
+    // A task meta may have been deserialized before it reaches this agent. 
Only use content
+    // matching when it identifies one pipe uniquely; identical 
progress/leader metadata is valid
+    // for multiple pipes and must not be used to cross their boundaries.
+    PipeMeta matchedPipeMeta = null;
+    if (pipeTaskMeta != null) {
+      for (final PipeMeta pipeMeta : pipeMetaKeeper.getPipeMetaList()) {
+        if 
(pipeMeta.getRuntimeMeta().getConsensusGroupId2TaskMetaMap().values().stream()
+            .anyMatch(pipeTaskMeta::equals)) {
+          if (matchedPipeMeta != null) {
+            return null;
+          }
+          matchedPipeMeta = pipeMeta;
+        }
+      }
+    }
+    return matchedPipeMeta;
+  }
+
+  private PipeTaskMeta findLocalPipeTaskMeta(
+      final PipeMeta pipeMeta, final PipeTaskMeta pipeTaskMeta, final int 
currentNodeId) {
+    if (pipeMeta == null || pipeTaskMeta == null) {
+      return null;
+    }
+
+    final Collection<PipeTaskMeta> taskMetas =
+        pipeMeta.getRuntimeMeta().getConsensusGroupId2TaskMetaMap().values();
+
+    // Object identity is the normal in-process path and is the most precise 
match.
+    for (final PipeTaskMeta localTaskMeta : taskMetas) {
+      if (localTaskMeta == pipeTaskMeta) {
+        return localTaskMeta;
+      }
+    }
+
+    // Prefer a unique local leader when matching a detached task meta. This 
avoids recording a
+    // processor failure on a task owned by another DataNode.
+    PipeTaskMeta matchedLocalLeaderTaskMeta = null;
+    for (final PipeTaskMeta localTaskMeta : taskMetas) {
+      if (localTaskMeta.getLeaderNodeId() == currentNodeId && 
localTaskMeta.equals(pipeTaskMeta)) {
+        if (matchedLocalLeaderTaskMeta != null) {
+          matchedLocalLeaderTaskMeta = null;
+          break;
+        }
+        matchedLocalLeaderTaskMeta = localTaskMeta;
+      }
+    }
+    if (matchedLocalLeaderTaskMeta != null) {
+      return matchedLocalLeaderTaskMeta;
+    }
+
+    // Fall back to a unique content match if the leader metadata is stale or 
unavailable.
+    PipeTaskMeta matchedTaskMeta = null;
+    for (final PipeTaskMeta localTaskMeta : taskMetas) {
+      if (localTaskMeta.equals(pipeTaskMeta)) {
+        if (matchedTaskMeta != null) {
+          return null;
+        }
+        matchedTaskMeta = localTaskMeta;
+      }
+    }
+    return matchedTaskMeta;
+  }
+
   public void collectPipeMetaList(final TPipeHeartbeatReq req, final 
TPipeHeartbeatResp resp)
       throws TException {
     if (!tryReadLockWithTimeOutInMs(
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeRuntimeMeta.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeRuntimeMeta.java
index 223eb69c346..1f2e92254f3 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeRuntimeMeta.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeRuntimeMeta.java
@@ -89,8 +89,8 @@ public class PipeRuntimeMeta {
    * <p>1. {@link PipeRuntimeCriticalException}, to record the failure of 
pushing {@link PipeMeta},
    * and will result in the halt of pipe execution.
    *
-   * <p>2. {@link PipeRuntimeSinkCriticalException}, to record the exception 
reported by other pipes
-   * sharing the same connector, and will stop the pipe likewise.
+   * <p>2. {@link PipeRuntimeSinkCriticalException}, retained for 
compatibility with runtime meta
+   * written before sink subtasks were isolated by pipe.
    */
   private final ConcurrentMap<Integer, PipeRuntimeException> 
nodeId2PipeRuntimeExceptionMap =
       new ConcurrentHashMap<>();
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTaskMeta.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTaskMeta.java
index e9939d7b2c6..1e860383edd 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTaskMeta.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTaskMeta.java
@@ -54,8 +54,8 @@ public class PipeTaskMeta {
    * <p>The exceptions are instances of {@link PipeRuntimeCriticalException}, 
{@link
    * PipeRuntimeSinkCriticalException} and {@link 
PipeRuntimeNonCriticalException}.
    *
-   * <p>The failure of them, respectively, will lead to the stop of the pipe, 
the stop of the pipes
-   * sharing the same connector, and nothing.
+   * <p>The failure of them, respectively, will lead to the stop of the pipe, 
the stop of the pipe
+   * that owns the failed sink, and nothing.
    */
   private final Set<PipeRuntimeException> exceptionMessages =
       Collections.newSetFromMap(new ConcurrentHashMap<>());
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Tag.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Tag.java
index 5fecc6b3122..91b4307e02d 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Tag.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Tag.java
@@ -29,6 +29,7 @@ public enum Tag {
   STAGE("stage"),
   OPERATION("operation"),
   INTERFACE("interface"),
+  PIPE("pipe"),
   CREATION_TIME("creation_time"),
   INDEX("index"),
   MODULE("module"),

Reply via email to