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 d1f362d3ca3 Pipe: Handle transient resource failures locally (#18381)
d1f362d3ca3 is described below

commit d1f362d3ca311b6121c782a423369e7b0998be24
Author: Caideyipi <[email protected]>
AuthorDate: Mon Aug 10 09:28:40 2026 +0800

    Pipe: Handle transient resource failures locally (#18381)
---
 .../relational/it/schema/IoTDBDatabaseIT.java      |  5 +-
 .../heartbeat/DataNodeHeartbeatHandler.java        |  3 +-
 .../response/pipe/task/PipeTableResp.java          |  1 +
 .../iotdb/confignode/manager/ConfigManager.java    |  3 +-
 .../pipe/agent/task/PipeConfigNodeSubtask.java     |  8 ++
 .../pipe/agent/task/PipeConfigNodeTaskAgent.java   |  5 ++
 .../runtime/PipeRuntimeCoordinator.java            |  7 +-
 .../runtime/heartbeat/PipeHeartbeat.java           | 29 +++++++
 .../runtime/heartbeat/PipeHeartbeatParser.java     |  1 +
 .../runtime/heartbeat/PipeHeartbeatScheduler.java  |  6 +-
 .../consensus/response/pipe/PipeTableRespTest.java | 19 +++++
 .../runtime/heartbeat/PipeHeartbeatParserTest.java | 52 +++++++++++-
 .../db/pipe/agent/task/PipeDataNodeTaskAgent.java  |  5 ++
 .../subtask/processor/PipeProcessorSubtask.java    | 12 +++
 .../agent/task/subtask/sink/PipeSinkSubtask.java   |  8 ++
 .../iotconsensusv2/IoTConsensusV2SyncSink.java     | 21 +++--
 .../thrift/async/IoTDBDataRegionAsyncSink.java     | 57 ++++++++++++-
 .../InformationSchemaContentSupplierFactory.java   |  6 ++
 .../execution/config/sys/pipe/ShowPipeTask.java    |  9 +++
 .../task/subtask/sink/PipeSinkSubtaskTest.java     | 49 ++++++++++++
 .../config/sys/pipe/ShowPipeTaskTest.java          | 13 ++-
 .../pipe/PipeRuntimeSinkResourceException.java     | 51 ++++++++++++
 ...RuntimeSinkRetryTimesConfigurableException.java |  6 ++
 .../commons/pipe/agent/task/PipeTaskAgent.java     | 10 +++
 .../agent/task/meta/PipeTemporaryMetaInAgent.java  | 19 ++++-
 .../task/meta/PipeTemporaryMetaInCoordinator.java  | 81 ++++++++++++++++++-
 .../task/subtask/PipeAbstractSinkSubtask.java      | 23 ++++--
 .../pipe/receiver/PipeReceiverStatusHandler.java   | 19 +++--
 .../pipe/resource/PipeRecentFailureCounter.java    | 93 ++++++++++++++++++++++
 .../pipe/resource/PipeResourceFailureType.java     | 36 +++++++++
 .../commons/pipe/resource/PipeStopStrategy.java    | 76 ++++++++++++++++++
 .../schema/column/ColumnHeaderConstant.java        |  5 +-
 .../commons/schema/table/InformationSchema.java    |  3 +
 .../agent/task/meta/PipeTemporaryMetaTest.java     | 32 ++++++++
 .../resource/PipeRecentFailureCounterTest.java     | 48 +++++++++++
 .../pipe/resource/PipeStopStrategyTest.java        | 84 +++++++++++++++++++
 .../thrift-commons/src/main/thrift/common.thrift   |  3 +-
 .../src/main/thrift/confignode.thrift              |  1 +
 .../src/main/thrift/datanode.thrift                |  1 +
 39 files changed, 874 insertions(+), 36 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
index e011915de0e..a6124c547a6 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBDatabaseIT.java
@@ -555,7 +555,8 @@ public class IoTDBDatabaseIT {
                   "exception_message,STRING,ATTRIBUTE,",
                   "remaining_event_count,INT64,ATTRIBUTE,",
                   "estimated_remaining_seconds,DOUBLE,ATTRIBUTE,",
-                  "is_degraded,BOOLEAN,ATTRIBUTE,")));
+                  "is_degraded,BOOLEAN,ATTRIBUTE,",
+                  "recent_failures,STRING,ATTRIBUTE,")));
       TestUtils.assertResultSetEqual(
           statement.executeQuery("desc pipe_plugins"),
           "ColumnName,DataType,Category,",
@@ -677,7 +678,7 @@ public class IoTDBDatabaseIT {
       // Filter out not self-created pipes
       TestUtils.assertResultSetEqual(
           statement.executeQuery("select * from pipes"),
-          
"id,creation_time,state,pipe_source,pipe_processor,pipe_sink,exception_message,remaining_event_count,estimated_remaining_seconds,is_degraded,",
+          
"id,creation_time,state,pipe_source,pipe_processor,pipe_sink,exception_message,remaining_event_count,estimated_remaining_seconds,is_degraded,recent_failures,",
           Collections.emptySet());
 
       // No auth needed
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
index 4d9df123520..9c7810dabe2 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/heartbeat/DataNodeHeartbeatHandler.java
@@ -191,7 +191,8 @@ public class DataNodeHeartbeatHandler implements 
AsyncMethodCallback<TDataNodeHe
           heartbeatResp.getPipeCompletedList(),
           heartbeatResp.getPipeRemainingEventCountList(),
           heartbeatResp.getPipeRemainingTimeList(),
-          heartbeatResp.getPipeDegradedStatusList());
+          heartbeatResp.getPipeDegradedStatusList(),
+          heartbeatResp.getPipeRecentFailureList());
     }
   }
 
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
index 15167a57ab3..153fae0f9b7 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/response/pipe/task/PipeTableResp.java
@@ -293,6 +293,7 @@ public class PipeTableResp implements DataSet {
       if (Objects.nonNull(isDegraded)) {
         showPipeInfo.setIsDegraded(isDegraded);
       }
+      showPipeInfo.setRecentFailures(temporaryMeta.getGlobalRecentFailures());
       showPipeInfoList.add(showPipeInfo);
     }
 
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
index d646b359e1f..c0276fd2cd5 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java
@@ -3338,7 +3338,8 @@ public class ConfigManager implements IManager {
             resp.getPipeCompletedList(),
             resp.getPipeRemainingEventCountList(),
             resp.getPipeRemainingTimeList(),
-            resp.getPipeDegradedStatusList());
+            resp.getPipeDegradedStatusList(),
+            resp.getPipeRecentFailureList());
     return StatusUtils.OK;
   }
 
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
index 854f39c22bd..dbc67befc6b 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeSubtask.java
@@ -32,6 +32,7 @@ import 
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSinkRuntimeEnviro
 import 
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSourceRuntimeEnvironment;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
 import org.apache.iotdb.commons.pipe.event.ProgressReportEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
 import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
 import org.apache.iotdb.confignode.i18n.ManagerMessages;
 import org.apache.iotdb.confignode.manager.pipe.agent.PipeConfigNodeAgent;
@@ -243,6 +244,13 @@ public class PipeConfigNodeSubtask extends 
PipeAbstractSinkSubtask {
     PipeConfigNodeAgent.runtime().report(event, exception);
   }
 
+  @Override
+  protected void reportResourceFailure(
+      final EnrichedEvent event, final PipeResourceFailureType failureType) {
+    PipeConfigNodeAgent.task()
+        .recordPipeResourceFailure(event.getPipeName(), 
event.getCreationTime(), failureType);
+  }
+
   //////////////////////////// APIs provided for metric framework 
////////////////////////////
 
   public String getPipeName() {
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
index 8668debe222..323fdb8ca00 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/task/PipeConfigNodeTaskAgent.java
@@ -29,6 +29,7 @@ import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInAgent;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor;
 import org.apache.iotdb.confignode.i18n.ManagerMessages;
@@ -225,6 +226,7 @@ public class PipeConfigNodeTaskAgent extends PipeTaskAgent {
     final List<Long> pipeRemainingEventCountList = new ArrayList<>();
     final List<Double> pipeRemainingTimeList = new ArrayList<>();
     final List<Integer> pipeDegradedStatusList = new ArrayList<>();
+    final List<Map<String, Long>> pipeRecentFailureList = new ArrayList<>();
     try {
       for (final PipeMeta pipeMeta : pipeMetaKeeper.getPipeMetaList()) {
         pipeMetaBinaryList.add(pipeMeta.serialize());
@@ -240,6 +242,8 @@ public class PipeConfigNodeTaskAgent extends PipeTaskAgent {
         pipeRemainingEventCountList.add(remainingEventCount);
         pipeRemainingTimeList.add(estimatedRemainingTime);
         
pipeDegradedStatusList.add(PipeTemporaryMeta.TS_FILE_EPOCH_DEGRADED_STATUS_UNKNOWN);
+        pipeRecentFailureList.add(
+            ((PipeTemporaryMetaInAgent) 
pipeMeta.getTemporaryMeta()).getRecentFailures());
 
         logger.ifPresent(
             l ->
@@ -258,6 +262,7 @@ public class PipeConfigNodeTaskAgent extends PipeTaskAgent {
     resp.setPipeRemainingEventCountList(pipeRemainingEventCountList);
     resp.setPipeRemainingTimeList(pipeRemainingTimeList);
     resp.setPipeDegradedStatusList(pipeDegradedStatusList);
+    resp.setPipeRecentFailureList(pipeRecentFailureList);
   }
 
   @Override
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
index d9a578c379e..ec00adcd302 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/PipeRuntimeCoordinator.java
@@ -29,6 +29,7 @@ import 
org.apache.iotdb.confignode.manager.pipe.coordinator.runtime.heartbeat.Pi
 
 import java.nio.ByteBuffer;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -96,7 +97,8 @@ public class PipeRuntimeCoordinator implements 
IClusterStatusSubscriber {
       /* @Nullable */ final List<Boolean> pipeCompletedListFromAgent,
       /* @Nullable */ final List<Long> pipeRemainingEventCountListFromAgent,
       /* @Nullable */ final List<Double> pipeRemainingTimeListFromAgent,
-      /* @Nullable */ final List<Integer> pipeDegradedStatusListFromAgent) {
+      /* @Nullable */ final List<Integer> pipeDegradedStatusListFromAgent,
+      /* @Nullable */ final List<Map<String, Long>> 
pipeRecentFailureListFromAgent) {
     pipeHeartbeatScheduler.parseHeartbeat(
         dataNodeId,
         new PipeHeartbeat(
@@ -104,6 +106,7 @@ public class PipeRuntimeCoordinator implements 
IClusterStatusSubscriber {
             pipeCompletedListFromAgent,
             pipeRemainingEventCountListFromAgent,
             pipeRemainingTimeListFromAgent,
-            pipeDegradedStatusListFromAgent));
+            pipeDegradedStatusListFromAgent,
+            pipeRecentFailureListFromAgent));
   }
 }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
index 31e5020bf89..7aa75b2d78d 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeat.java
@@ -24,6 +24,7 @@ import 
org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMeta;
 
 import java.nio.ByteBuffer;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -35,6 +36,7 @@ public class PipeHeartbeat {
   private final Map<PipeStaticMeta, Long> remainingEventCountMap = new 
HashMap<>();
   private final Map<PipeStaticMeta, Double> remainingTimeMap = new HashMap<>();
   private final Map<PipeStaticMeta, Boolean> isDegradedMap = new HashMap<>();
+  private final Map<PipeStaticMeta, Map<String, Long>> recentFailuresMap = new 
HashMap<>();
 
   public PipeHeartbeat(
       final List<ByteBuffer> pipeMetaByteBufferListFromAgent,
@@ -42,6 +44,22 @@ public class PipeHeartbeat {
       /* @Nullable */ final List<Long> pipeRemainingEventCountListFromAgent,
       /* @Nullable */ final List<Double> pipeRemainingTimeListFromAgent,
       /* @Nullable */ final List<Integer> pipeDegradedStatusListFromAgent) {
+    this(
+        pipeMetaByteBufferListFromAgent,
+        pipeCompletedListFromAgent,
+        pipeRemainingEventCountListFromAgent,
+        pipeRemainingTimeListFromAgent,
+        pipeDegradedStatusListFromAgent,
+        null);
+  }
+
+  public PipeHeartbeat(
+      final List<ByteBuffer> pipeMetaByteBufferListFromAgent,
+      /* @Nullable */ final List<Boolean> pipeCompletedListFromAgent,
+      /* @Nullable */ final List<Long> pipeRemainingEventCountListFromAgent,
+      /* @Nullable */ final List<Double> pipeRemainingTimeListFromAgent,
+      /* @Nullable */ final List<Integer> pipeDegradedStatusListFromAgent,
+      /* @Nullable */ final List<Map<String, Long>> 
pipeRecentFailureListFromAgent) {
     // Shall not reach here, just in case
     if (Objects.isNull(pipeMetaByteBufferListFromAgent)) {
       return;
@@ -77,6 +95,13 @@ public class PipeHeartbeat {
                       && i < pipeDegradedStatusListFromAgent.size()
                   ? pipeDegradedStatusListFromAgent.get(i)
                   : null));
+      recentFailuresMap.put(
+          pipeMeta.getStaticMeta(),
+          Objects.nonNull(pipeRecentFailureListFromAgent)
+                  && i < pipeRecentFailureListFromAgent.size()
+                  && Objects.nonNull(pipeRecentFailureListFromAgent.get(i))
+              ? new HashMap<>(pipeRecentFailureListFromAgent.get(i))
+              : Collections.emptyMap());
     }
   }
 
@@ -104,6 +129,10 @@ public class PipeHeartbeat {
     return isDegradedMap.get(pipeStaticMeta);
   }
 
+  public Map<String, Long> getRecentFailures(final PipeStaticMeta 
pipeStaticMeta) {
+    return recentFailuresMap.get(pipeStaticMeta);
+  }
+
   public boolean isEmpty() {
     return pipeMetaMap.isEmpty();
   }
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 c33beed69c0..a8734469d0c 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
@@ -197,6 +197,7 @@ public class PipeHeartbeatParser {
       temporaryMeta.setRemainingEvent(nodeId, 
pipeHeartbeat.getRemainingEventCount(staticMeta));
       temporaryMeta.setRemainingTime(nodeId, 
pipeHeartbeat.getRemainingTime(staticMeta));
       temporaryMeta.setDegraded(nodeId, pipeHeartbeat.getDegraded(staticMeta));
+      temporaryMeta.setRecentFailures(nodeId, 
pipeHeartbeat.getRecentFailures(staticMeta));
 
       final Map<Integer, PipeTaskMeta> pipeTaskMetaMapFromCoordinator =
           
pipeMetaFromCoordinator.getRuntimeMeta().getConsensusGroupId2TaskMetaMap();
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
index 00368c0472a..209b08cff15 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatScheduler.java
@@ -116,7 +116,8 @@ public class PipeHeartbeatScheduler {
                         resp.getPipeCompletedList(),
                         resp.getPipeRemainingEventCountList(),
                         resp.getPipeRemainingTimeList(),
-                        resp.getPipeDegradedStatusList())));
+                        resp.getPipeDegradedStatusList(),
+                        resp.getPipeRecentFailureList())));
 
     // config node heartbeat
     try {
@@ -129,7 +130,8 @@ public class PipeHeartbeatScheduler {
               null,
               configNodeResp.getPipeRemainingEventCountList(),
               configNodeResp.getPipeRemainingTimeList(),
-              configNodeResp.getPipeDegradedStatusList()));
+              configNodeResp.getPipeDegradedStatusList(),
+              configNodeResp.getPipeRecentFailureList()));
     } catch (final Exception e) {
       PipeLogger.log(
           LOGGER::warn, e, 
ManagerMessages.FAILED_TO_COLLECT_PIPE_META_LIST_FROM_CONFIG_NODE_TASK);
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
index f4437fd2c34..b3ae595a5ca 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
@@ -169,6 +169,25 @@ public class PipeTableRespTest {
     Assert.assertFalse(showPipeResult.get(2).isSetIsDegraded());
   }
 
+  @Test
+  public void testConvertToTShowPipeRespAggregatesRecentFailures() {
+    final PipeTableResp pipeTableResp = constructPipeTableResp();
+    final PipeTemporaryMetaInCoordinator temporaryMeta =
+        (PipeTemporaryMetaInCoordinator) 
pipeTableResp.getAllPipeMeta().get(0).getTemporaryMeta();
+    final Map<String, Long> firstNodeFailures = new HashMap<>();
+    firstNodeFailures.put("network_timeout", 10L);
+    firstNodeFailures.put("memory_timeout", 15L);
+    temporaryMeta.setRecentFailures(1, firstNodeFailures);
+    final Map<String, Long> secondNodeFailures = new HashMap<>();
+    secondNodeFailures.put("network_timeout", 2L);
+    temporaryMeta.setRecentFailures(2, secondNodeFailures);
+
+    final TShowPipeInfo showPipeInfo =
+        pipeTableResp.convertToTShowPipeResp().getPipeInfoList().get(0);
+    Assert.assertEquals(Long.valueOf(12), 
showPipeInfo.getRecentFailures().get("network_timeout"));
+    Assert.assertEquals(Long.valueOf(15), 
showPipeInfo.getRecentFailures().get("memory_timeout"));
+  }
+
   @Test
   public void testConvertToTShowPipeRespIncludesPreDeleteStatus() {
     final PipeTableResp pipeTableResp = constructPipeTableResp();
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 04a898629fd..c7d4e3b5d8d 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
@@ -317,6 +317,49 @@ public class PipeHeartbeatParserTest {
     verify(context.procedureManager, 
never()).pipeHandleMetaChange(anyBoolean(), anyBoolean());
   }
 
+  @Test
+  public void testParseHeartbeatAggregatesRecentFailuresFromAllDataNodes() 
throws Exception {
+    
CommonDescriptor.getInstance().getConfig().setSeperatedPipeHeartbeatEnabled(false);
+
+    final PipeTaskInfo pipeTaskInfo = new PipeTaskInfo();
+    final PipeMeta pipeMeta = createPipeMeta();
+    pipeTaskInfo.createPipe(
+        new CreatePipePlanV2(pipeMeta.getStaticMeta(), 
pipeMeta.getRuntimeMeta()));
+
+    final ParserTestContext context = createParserTestContext(2, pipeTaskInfo);
+    final Map<String, Long> firstNodeFailures = new HashMap<>();
+    firstNodeFailures.put("network_timeout", 10L);
+    final Map<String, Long> secondNodeFailures = new HashMap<>();
+    secondNodeFailures.put("network_timeout", 2L);
+    secondNodeFailures.put("memory_timeout", 15L);
+
+    context.parser.parseHeartbeat(1, createPipeHeartbeat(pipeMeta, false, 
firstNodeFailures));
+    context.parser.parseHeartbeat(2, createPipeHeartbeat(pipeMeta, false, 
secondNodeFailures));
+
+    Assert.assertEquals(
+        Long.valueOf(12),
+        
getTemporaryMeta(pipeTaskInfo).getGlobalRecentFailures().get("network_timeout"));
+    Assert.assertEquals(
+        Long.valueOf(15),
+        
getTemporaryMeta(pipeTaskInfo).getGlobalRecentFailures().get("memory_timeout"));
+    verify(context.procedureManager, 
never()).pipeHandleMetaChange(anyBoolean(), anyBoolean());
+  }
+
+  @Test
+  public void testPipeHeartbeatTreatsNullRecentFailureMapAsEmpty() throws 
Exception {
+    final PipeMeta pipeMeta = createPipeMeta();
+    final PipeHeartbeat heartbeat =
+        new PipeHeartbeat(
+            Collections.singletonList(pipeMeta.serialize()),
+            Collections.singletonList(false),
+            Collections.singletonList(0L),
+            Collections.singletonList(0d),
+            
Collections.singletonList(PipeTemporaryMeta.TS_FILE_EPOCH_DEGRADED_STATUS_UNKNOWN),
+            Collections.singletonList(null));
+
+    
Assert.assertTrue(heartbeat.getRecentFailures(pipeMeta.getStaticMeta()).isEmpty());
+  }
+
   private ParserTestContext createParserTestContext(final int 
registeredDataNodeCount) {
     return createParserTestContext(registeredDataNodeCount, new 
PipeTaskInfo());
   }
@@ -385,12 +428,19 @@ public class PipeHeartbeatParserTest {
 
   private PipeHeartbeat createPipeHeartbeat(final PipeMeta pipeMeta, final 
boolean isDegraded)
       throws Exception {
+    return createPipeHeartbeat(pipeMeta, isDegraded, Collections.emptyMap());
+  }
+
+  private PipeHeartbeat createPipeHeartbeat(
+      final PipeMeta pipeMeta, final boolean isDegraded, final Map<String, 
Long> recentFailures)
+      throws Exception {
     return new PipeHeartbeat(
         Collections.singletonList(pipeMeta.serialize()),
         Collections.singletonList(false),
         Collections.singletonList(0L),
         Collections.singletonList(0d),
-        
Collections.singletonList(PipeTemporaryMeta.encodeTsFileEpochDegradedStatus(isDegraded)));
+        
Collections.singletonList(PipeTemporaryMeta.encodeTsFileEpochDegradedStatus(isDegraded)),
+        Collections.singletonList(recentFailures));
   }
 
   private PipeTemporaryMetaInCoordinator getTemporaryMeta(final PipeTaskInfo 
pipeTaskInfo) {
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 3adad062db7..1fa7bfb5786 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
@@ -473,6 +473,8 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
             PipeTemporaryMeta.encodeTsFileEpochDegradedStatus(
                 ((PipeTemporaryMetaInAgent) pipeMeta.getTemporaryMeta())
                     .getGlobalTsFileEpochDegraded()));
+        report.pipeRecentFailureList.add(
+            ((PipeTemporaryMetaInAgent) 
pipeMeta.getTemporaryMeta()).getRecentFailures());
 
         logger.ifPresent(
             l ->
@@ -522,6 +524,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
     private final List<Long> pipeRemainingEventCountList = new ArrayList<>();
     private final List<Double> pipeRemainingTimeList = new ArrayList<>();
     private final List<Integer> pipeDegradedStatusList = new ArrayList<>();
+    private final List<Map<String, Long>> pipeRecentFailureList = new 
ArrayList<>();
 
     private void setTo(final TDataNodeHeartbeatResp resp) {
       resp.setPipeMetaList(pipeMetaBinaryList);
@@ -529,6 +532,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
       resp.setPipeRemainingEventCountList(pipeRemainingEventCountList);
       resp.setPipeRemainingTimeList(pipeRemainingTimeList);
       resp.setPipeDegradedStatusList(pipeDegradedStatusList);
+      resp.setPipeRecentFailureList(pipeRecentFailureList);
     }
 
     private void setTo(final TPipeHeartbeatResp resp) {
@@ -537,6 +541,7 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
       resp.setPipeRemainingEventCountList(pipeRemainingEventCountList);
       resp.setPipeRemainingTimeList(pipeRemainingTimeList);
       resp.setPipeDegradedStatusList(pipeDegradedStatusList);
+      resp.setPipeRecentFailureList(pipeRecentFailureList);
     }
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
index a95c99ebd52..8c49e03cb06 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
@@ -28,6 +28,7 @@ import 
org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
 import 
org.apache.iotdb.commons.pipe.agent.task.progress.PipeEventCommitManager;
 import org.apache.iotdb.commons.pipe.agent.task.subtask.PipeReportableSubtask;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
 import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
 import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils;
 import org.apache.iotdb.db.i18n.DataNodePipeMessages;
@@ -242,6 +243,7 @@ public class PipeProcessorSubtask extends 
PipeReportableSubtask {
       }
       decreaseReferenceCountAndReleaseLastEvent(event, shouldReport);
     } catch (final PipeRuntimeOutOfMemoryCriticalException e) {
+      recordResourceFailure(event, PipeResourceFailureType.MEMORY_TIMEOUT);
       PipeLogger.log(
           LOGGER::info,
           
DataNodePipeMessages.TEMPORARILY_OUT_OF_MEMORY_IN_PIPE_EVENT_PROCESSING,
@@ -249,6 +251,7 @@ public class PipeProcessorSubtask extends 
PipeReportableSubtask {
       return false;
     } catch (final Exception e) {
       if (ExceptionUtils.getRootCause(e) instanceof 
PipeRuntimeOutOfMemoryCriticalException) {
+        recordResourceFailure(event, PipeResourceFailureType.MEMORY_TIMEOUT);
         PipeLogger.log(
             LOGGER::info,
             
DataNodePipeMessages.TEMPORARILY_OUT_OF_MEMORY_IN_PIPE_EVENT_PROCESSING,
@@ -352,4 +355,13 @@ public class PipeProcessorSubtask extends 
PipeReportableSubtask {
   protected void report(final EnrichedEvent event, final PipeRuntimeException 
exception) {
     PipeDataNodeAgent.runtime().report(event, exception);
   }
+
+  private void recordResourceFailure(final Event event, final 
PipeResourceFailureType failureType) {
+    if (event instanceof EnrichedEvent) {
+      final EnrichedEvent enrichedEvent = (EnrichedEvent) event;
+      PipeDataNodeAgent.task()
+          .recordPipeResourceFailure(
+              enrichedEvent.getPipeName(), enrichedEvent.getCreationTime(), 
failureType);
+    }
+  }
 }
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 365f211fb51..83de468cba5 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
@@ -27,6 +27,7 @@ import 
org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
 import 
org.apache.iotdb.commons.pipe.agent.task.subtask.PipeAbstractSinkSubtask;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
 import org.apache.iotdb.commons.pipe.sink.protocol.IoTDBSink;
 import 
org.apache.iotdb.commons.pipe.sink.protocol.PipeConnectorWithEventDiscard;
 import org.apache.iotdb.commons.pipe.sink.protocol.PipeSinkWithSchedulingDelay;
@@ -584,6 +585,13 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     PipeDataNodeAgent.runtime().report(event, exception);
   }
 
+  @Override
+  protected void reportResourceFailure(
+      final EnrichedEvent event, final PipeResourceFailureType failureType) {
+    PipeDataNodeAgent.task()
+        .recordPipeResourceFailure(event.getPipeName(), 
event.getCreationTime(), failureType);
+  }
+
   @Override
   public String getDisplayTaskID() {
     return generateDisplayTaskID(attributeDisplayString, creationTime, 
sinkIndex);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/iotconsensusv2/IoTConsensusV2SyncSink.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/iotconsensusv2/IoTConsensusV2SyncSink.java
index 03e589e2f26..2f274189809 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/iotconsensusv2/IoTConsensusV2SyncSink.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/iotconsensusv2/IoTConsensusV2SyncSink.java
@@ -156,7 +156,8 @@ public class IoTConsensusV2SyncSink extends IoTDBSink {
                   
.PIPE_EXCEPTION_FAILED_TO_TRANSFER_TABLET_INSERTION_EVENT_S_BECAUSE_S_9710318F,
               tabletInsertionEvent,
               e.getMessage()),
-          Integer.MAX_VALUE);
+          Integer.MAX_VALUE,
+          e);
     }
   }
 
@@ -181,7 +182,8 @@ public class IoTConsensusV2SyncSink extends IoTDBSink {
                   
.PIPE_EXCEPTION_FAILED_TO_TRANSFER_TSFILE_INSERTION_EVENT_S_BECAUSE_S_21AD3263,
               tsFileInsertionEvent,
               e.getMessage()),
-          Integer.MAX_VALUE);
+          Integer.MAX_VALUE,
+          e);
     }
   }
 
@@ -230,7 +232,8 @@ public class IoTConsensusV2SyncSink extends IoTDBSink {
               getFollowerUrl().getPort(),
               TABLET_BATCH_SCENARIO,
               e.getMessage()),
-          Integer.MAX_VALUE);
+          Integer.MAX_VALUE,
+          e);
     }
   }
 
@@ -278,7 +281,8 @@ public class IoTConsensusV2SyncSink extends IoTDBSink {
               getFollowerUrl().getPort(),
               DELETION_SCENARIO,
               e.getMessage()),
-          Integer.MAX_VALUE);
+          Integer.MAX_VALUE,
+          e);
     }
 
     final TSStatus status = resp.getStatus();
@@ -346,7 +350,8 @@ public class IoTConsensusV2SyncSink extends IoTDBSink {
               getFollowerUrl().getPort(),
               TABLET_INSERTION_NODE_SCENARIO,
               e.getMessage()),
-          Integer.MAX_VALUE);
+          Integer.MAX_VALUE,
+          e);
     }
 
     final TSStatus status = resp.getStatus();
@@ -420,7 +425,8 @@ public class IoTConsensusV2SyncSink extends IoTDBSink {
               getFollowerUrl().getPort(),
               TSFILE_SCENARIO,
               e.getMessage()),
-          Integer.MAX_VALUE);
+          Integer.MAX_VALUE,
+          e);
     }
 
     final TSStatus status = resp.getStatus();
@@ -487,7 +493,8 @@ public class IoTConsensusV2SyncSink extends IoTDBSink {
                       
.PIPE_EXCEPTION_NETWORK_ERROR_WHEN_TRANSFER_FILE_S_BECAUSE_S_3C673B7A,
                   file,
                   e.getMessage()),
-              Integer.MAX_VALUE);
+              Integer.MAX_VALUE,
+              e);
         }
 
         position += readLength;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
index 689a5bb1b7b..422dd70976b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
@@ -25,13 +25,17 @@ import org.apache.iotdb.commons.audit.UserEntity;
 import org.apache.iotdb.commons.client.ThriftClient;
 import 
org.apache.iotdb.commons.client.async.AsyncPipeDataTransferServiceClient;
 import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfigurableException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
 import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+import org.apache.iotdb.commons.pipe.resource.PipeStopStrategy;
 import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
 import org.apache.iotdb.commons.pipe.sink.protocol.IoTDBSink;
 import org.apache.iotdb.commons.pipe.sink.protocol.PipeSinkWithSchedulingDelay;
 import org.apache.iotdb.db.i18n.DataNodePipeMessages;
+import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
 import org.apache.iotdb.db.pipe.event.common.deletion.PipeDeleteDataNodeEvent;
 import org.apache.iotdb.db.pipe.event.common.heartbeat.PipeHeartbeatEvent;
 import 
org.apache.iotdb.db.pipe.event.common.tablet.PipeInsertNodeTabletInsertionEvent;
@@ -81,6 +85,8 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -129,6 +135,9 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
   private final BlockingQueue<TsFileInsertionEvent> retryTsFileQueue = new 
LinkedBlockingQueue<>();
   private final PipeDataRegionEventCounter retryEventQueueEventCounter =
       new PipeDataRegionEventCounter();
+  // Guarded by this. Events need identity semantics because the same payload 
may compare equal.
+  private final Map<Event, PipeResourceFailureType> 
retryEvent2ResourceFailureType =
+      new IdentityHashMap<>();
 
   private IoTDBDataNodeAsyncClientManager clientManager;
   private IoTDBDataNodeAsyncClientManager transferTsFileClientManager;
@@ -670,6 +679,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
         final Event polledEvent;
         if (!retryEventQueue.isEmpty()) {
           peekedEvent = retryEventQueue.peek();
+          retryEvent2ResourceFailureType.remove(peekedEvent);
 
           if (peekedEvent instanceof PipeInsertNodeTabletInsertionEvent) {
             retryTransfer((PipeInsertNodeTabletInsertionEvent) peekedEvent);
@@ -689,6 +699,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
             return;
           }
           peekedEvent = retryTsFileQueue.peek();
+          retryEvent2ResourceFailureType.remove(peekedEvent);
           retryTransfer((PipeTsFileInsertionEvent) peekedEvent);
           polledEvent = retryTsFileQueue.poll();
         }
@@ -726,6 +737,12 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
                   + ", tsfile events: "
                   + retryEventQueueEventCounter.getTsFileInsertionEventCount()
                   + ").";
+          final PipeResourceFailureType retryQueueResourceFailureType =
+              getRetryQueueResourceFailureType();
+          if (retryQueueResourceFailureType != null) {
+            throw new PipeRuntimeSinkResourceException(
+                message, retryQueueResourceFailureType, true);
+          }
           throw isConnectionException
               ? new PipeConnectionException(message)
               : new PipeException(message);
@@ -788,6 +805,13 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
    */
   @SuppressWarnings("java:S899")
   public void addFailureEventToRetryQueue(final Event event, final Exception 
e) {
+    addFailureEventToRetryQueue(event, e, null);
+  }
+
+  private synchronized void addFailureEventToRetryQueue(
+      final Event event, final Exception e, final Set<Pair<String, Long>> 
failureRecordedPipes) {
+    final PipeResourceFailureType resourceFailureType =
+        PipeStopStrategy.getResourceFailureType(e, null);
     isConnectionException =
         e instanceof PipeConnectionException || 
ThriftClient.isConnectionBroken(e);
     if (event instanceof EnrichedEvent) {
@@ -808,6 +832,23 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
       return;
     }
 
+    if (resourceFailureType != null && event instanceof EnrichedEvent) {
+      final EnrichedEvent enrichedEvent = (EnrichedEvent) event;
+      final Pair<String, Long> pipeKey =
+          new Pair<>(enrichedEvent.getPipeName(), 
enrichedEvent.getCreationTime());
+      if (failureRecordedPipes == null || failureRecordedPipes.add(pipeKey)) {
+        PipeDataNodeAgent.task()
+            .recordPipeResourceFailure(
+                enrichedEvent.getPipeName(), enrichedEvent.getCreationTime(), 
resourceFailureType);
+      }
+    }
+
+    if (resourceFailureType == null) {
+      retryEvent2ResourceFailureType.remove(event);
+    } else {
+      retryEvent2ResourceFailureType.put(event, resourceFailureType);
+    }
+
     if (event instanceof PipeTsFileInsertionEvent) {
       retryTsFileQueue.offer((PipeTsFileInsertionEvent) event);
       retryEventQueueEventCounter.increaseEventCount(event);
@@ -834,7 +875,17 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
    */
   public void addFailureEventsToRetryQueue(
       final Iterable<EnrichedEvent> events, final Exception e) {
-    events.forEach(event -> addFailureEventToRetryQueue(event, e));
+    final Set<Pair<String, Long>> failureRecordedPipes = new HashSet<>();
+    events.forEach(event -> addFailureEventToRetryQueue(event, e, 
failureRecordedPipes));
+  }
+
+  private synchronized PipeResourceFailureType 
getRetryQueueResourceFailureType() {
+    for (final PipeResourceFailureType failureType : 
PipeResourceFailureType.values()) {
+      if (retryEvent2ResourceFailureType.containsValue(failureType)) {
+        return failureType;
+      }
+    }
+    return null;
   }
 
   public boolean isEnableSendTsFileLimit() {
@@ -986,6 +1037,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
               && isDroppedPipe((EnrichedEvent) event, committerKey)) {
             ((EnrichedEvent) 
event).clearReferenceCount(IoTDBDataRegionAsyncSink.class.getName());
             retryEventQueueEventCounter.decreaseEventCount(event);
+            retryEvent2ResourceFailureType.remove(event);
             return true;
           }
           return false;
@@ -997,6 +1049,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
               && isDroppedPipe((EnrichedEvent) event, committerKey)) {
             ((EnrichedEvent) 
event).clearReferenceCount(IoTDBDataRegionAsyncSink.class.getName());
             retryEventQueueEventCounter.decreaseEventCount(event);
+            retryEvent2ResourceFailureType.remove(event);
             return true;
           }
           return false;
@@ -1049,10 +1102,12 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink 
implements PipeSinkWithS
       final Event event =
           retryTsFileQueue.isEmpty() ? retryEventQueue.poll() : 
retryTsFileQueue.poll();
       retryEventQueueEventCounter.decreaseEventCount(event);
+      retryEvent2ResourceFailureType.remove(event);
       if (event instanceof EnrichedEvent) {
         ((EnrichedEvent) 
event).clearReferenceCount(IoTDBDataRegionAsyncSink.class.getName());
       }
     }
+    retryEvent2ResourceFailureType.clear();
   }
 
   //////////////////////// APIs provided for metric framework 
////////////////////////
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
index 7ca14b38284..4f01b5230ad 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java
@@ -709,6 +709,12 @@ public class InformationSchemaContentSupplierFactory {
       } else {
         columnBuilders[9].appendNull();
       }
+      columnBuilders[10].writeBinary(
+          new Binary(
+              tPipeInfo.isSetRecentFailures()
+                  ? new TreeMap<>(tPipeInfo.getRecentFailures()).toString()
+                  : "{}",
+              TSFileConfig.STRING_CHARSET));
 
       resultBuilder.declarePosition();
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
index 6698ae88fdc..71a6e6ef4d3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTask.java
@@ -42,6 +42,7 @@ import org.apache.tsfile.utils.Binary;
 import org.apache.tsfile.utils.Pair;
 
 import java.util.List;
+import java.util.TreeMap;
 import java.util.stream.Collectors;
 
 public class ShowPipeTask implements IConfigTask {
@@ -135,6 +136,14 @@ public class ShowPipeTask implements IConfigTask {
       } else {
         builder.getColumnBuilder(9).appendNull();
       }
+      builder
+          .getColumnBuilder(10)
+          .writeBinary(
+              new Binary(
+                  tPipeInfo.isSetRecentFailures()
+                      ? new TreeMap<>(tPipeInfo.getRecentFailures()).toString()
+                      : "{}",
+                  TSFileConfig.STRING_CHARSET));
       builder.declarePosition();
     }
     final DatasetHeader datasetHeader = 
DatasetHeaderFactory.getShowPipeHeader();
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
index 673fa3e91e4..3d5427d39b3 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.db.pipe.agent.task.subtask.sink;
 
+import org.apache.iotdb.commons.client.exception.ClientManagerException;
 import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.exception.pipe.PipeRuntimeException;
 import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
@@ -26,6 +27,7 @@ import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfi
 import 
org.apache.iotdb.commons.pipe.agent.task.connection.UnboundedBlockingPendingQueue;
 import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
 import 
org.apache.iotdb.commons.pipe.sink.protocol.PipeConnectorWithEventDiscard;
 import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils;
 import org.apache.iotdb.db.pipe.event.common.heartbeat.PipeHeartbeatEvent;
@@ -371,9 +373,42 @@ public class PipeSinkSubtaskTest {
     }
   }
 
+  @Test
+  public void 
testClientBorrowFailureRetriesLocallyWithoutReportingCriticalException() {
+    final long originalSleepIntervalInitMs =
+        
CommonDescriptor.getInstance().getConfig().getPipeSinkSubtaskSleepIntervalInitMs();
+    final long originalSleepIntervalMaxMs =
+        
CommonDescriptor.getInstance().getConfig().getPipeSinkSubtaskSleepIntervalMaxMs();
+    
CommonDescriptor.getInstance().getConfig().setPipeSinkSubtaskSleepIntervalInitMs(1);
+    
CommonDescriptor.getInstance().getConfig().setPipeSinkSubtaskSleepIntervalMaxMs(2);
+
+    final PipeConnector connector = mock(PipeConnector.class);
+    final UnboundedBlockingPendingQueue<Event> pendingQueue =
+        mock(UnboundedBlockingPendingQueue.class);
+    final CapturingPipeSinkSubtask subtask = new 
CapturingPipeSinkSubtask(pendingQueue, connector);
+
+    try {
+      subtask.handleExceptionForTest(
+          mock(EnrichedEvent.class), new ClientManagerException("client pool 
exhausted"));
+
+      Assert.assertEquals(
+          PipeResourceFailureType.NETWORK_TIMEOUT, 
subtask.getReportedResourceFailureType());
+      Assert.assertNull(subtask.getReportedException());
+    } finally {
+      subtask.close();
+      CommonDescriptor.getInstance()
+          .getConfig()
+          .setPipeSinkSubtaskSleepIntervalInitMs(originalSleepIntervalInitMs);
+      CommonDescriptor.getInstance()
+          .getConfig()
+          .setPipeSinkSubtaskSleepIntervalMaxMs(originalSleepIntervalMaxMs);
+    }
+  }
+
   private static class CapturingPipeSinkSubtask extends PipeSinkSubtask {
 
     private PipeRuntimeException reportedException;
+    private PipeResourceFailureType reportedResourceFailureType;
 
     private CapturingPipeSinkSubtask(
         final UnboundedBlockingPendingQueue<Event> pendingQueue, final 
PipeConnector connector) {
@@ -397,10 +432,24 @@ public class PipeSinkSubtaskTest {
       return reportedException;
     }
 
+    private PipeResourceFailureType getReportedResourceFailureType() {
+      return reportedResourceFailureType;
+    }
+
+    private void handleExceptionForTest(final EnrichedEvent event, final 
Exception exception) {
+      handleException(event, exception);
+    }
+
     @Override
     protected void report(final EnrichedEvent event, final 
PipeRuntimeException exception) {
       reportedException = exception;
     }
+
+    @Override
+    protected void reportResourceFailure(
+        final EnrichedEvent event, final PipeResourceFailureType failureType) {
+      reportedResourceFailureType = failureType;
+    }
   }
 
   private static class BlockingHandshakeConnector
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTaskTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTaskTest.java
index 3f89e8ee9cc..0822b51f1cb 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTaskTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/sys/pipe/ShowPipeTaskTest.java
@@ -29,6 +29,8 @@ import org.apache.tsfile.read.common.block.TsBlock;
 import org.junit.Test;
 
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -37,9 +39,13 @@ import static org.junit.Assert.assertTrue;
 public class ShowPipeTaskTest {
 
   @Test
-  public void testBuildTSBlockWritesDegradedColumn() throws Exception {
+  public void testBuildTSBlockWritesRuntimeColumns() throws Exception {
     final TShowPipeInfo degradedPipe = createPipeInfo("degraded_pipe");
     degradedPipe.setIsDegraded(true);
+    final Map<String, Long> recentFailures = new HashMap<>();
+    recentFailures.put("network_timeout", 10L);
+    recentFailures.put("memory_timeout", 15L);
+    degradedPipe.setRecentFailures(recentFailures);
     final TShowPipeInfo normalPipe = createPipeInfo("normal_pipe");
     normalPipe.setIsDegraded(false);
     final TShowPipeInfo unknownPipe = createPipeInfo("unknown_pipe");
@@ -53,10 +59,15 @@ public class ShowPipeTaskTest {
     assertEquals(TSStatusCode.SUCCESS_STATUS, result.getStatusCode());
     assertEquals(
         ColumnHeaderConstant.IS_DEGRADED, 
result.getResultSetHeader().getRespColumns().get(9));
+    assertEquals(
+        ColumnHeaderConstant.RECENT_FAILURES, 
result.getResultSetHeader().getRespColumns().get(10));
     assertEquals(3, resultSet.getPositionCount());
     assertTrue(resultSet.getColumn(9).getBoolean(0));
     assertFalse(resultSet.getColumn(9).getBoolean(1));
     assertTrue(resultSet.getColumn(9).isNull(2));
+    assertEquals(
+        "{memory_timeout=15, network_timeout=10}", 
resultSet.getColumn(10).getBinary(0).toString());
+    assertEquals("{}", resultSet.getColumn(10).getBinary(1).toString());
   }
 
   private TShowPipeInfo createPipeInfo(final String pipeName) {
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkResourceException.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkResourceException.java
new file mode 100644
index 00000000000..efc41d88654
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkResourceException.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.exception.pipe;
+
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+
+public class PipeRuntimeSinkResourceException
+    extends PipeRuntimeSinkNonReportTimeConfigurableException {
+
+  private final PipeResourceFailureType failureType;
+  private final boolean failureRecorded;
+
+  public PipeRuntimeSinkResourceException(
+      final String message, final PipeResourceFailureType failureType) {
+    this(message, failureType, false);
+  }
+
+  public PipeRuntimeSinkResourceException(
+      final String message,
+      final PipeResourceFailureType failureType,
+      final boolean failureRecorded) {
+    super(message, Long.MAX_VALUE);
+    this.failureType = failureType;
+    this.failureRecorded = failureRecorded;
+  }
+
+  public PipeResourceFailureType getFailureType() {
+    return failureType;
+  }
+
+  public boolean isFailureRecorded() {
+    return failureRecorded;
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkRetryTimesConfigurableException.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkRetryTimesConfigurableException.java
index aa64e533528..6db893421aa 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkRetryTimesConfigurableException.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/exception/pipe/PipeRuntimeSinkRetryTimesConfigurableException.java
@@ -32,6 +32,12 @@ public class PipeRuntimeSinkRetryTimesConfigurableException
     this.retryTimes = retryTimes;
   }
 
+  public PipeRuntimeSinkRetryTimesConfigurableException(
+      final String message, final int retryTimes, final Throwable cause) {
+    super(message, cause);
+    this.retryTimes = retryTimes;
+  }
+
   public int getRetryTimes() {
     return retryTimes;
   }
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 f2f912cbc61..5523717b093 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
@@ -36,6 +36,7 @@ import 
org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInAgent;
 import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
 import 
org.apache.iotdb.commons.pipe.agent.task.progress.PipeEventCommitManager;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
 import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
 import org.apache.iotdb.commons.pipe.sink.limiter.PipeEndPointRateLimiter;
 import org.apache.iotdb.commons.subscription.config.SubscriptionConfig;
@@ -1217,6 +1218,15 @@ public abstract class PipeTaskAgent {
     }
   }
 
+  public void recordPipeResourceFailure(
+      final String pipeName, final long creationTime, final 
PipeResourceFailureType failureType) {
+    final PipeMeta pipeMeta = pipeMetaKeeper.getPipeMeta(pipeName, 
creationTime);
+    // To avoid recording a failure for the stale pipe before alter
+    if (Objects.nonNull(pipeMeta) && 
pipeMeta.getStaticMeta().getCreationTime() == creationTime) {
+      ((PipeTemporaryMetaInAgent) 
pipeMeta.getTemporaryMeta()).recordResourceFailure(failureType);
+    }
+  }
+
   public void setPipeTsFileEpochDegraded(
       final String pipeName,
       final long creationTime,
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
index c28bb51236d..f525c015706 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInAgent.java
@@ -20,6 +20,8 @@
 package org.apache.iotdb.commons.pipe.agent.task.meta;
 
 import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
+import org.apache.iotdb.commons.pipe.resource.PipeRecentFailureCounter;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
 
 import java.util.Map;
 import java.util.Objects;
@@ -31,6 +33,7 @@ public class PipeTemporaryMetaInAgent implements 
PipeTemporaryMeta {
 
   // Statistics
   private final AtomicLong floatingMemoryUsageInByte = new AtomicLong(0L);
+  private final PipeRecentFailureCounter recentFailureCounter = new 
PipeRecentFailureCounter();
   private final ConcurrentMap<Integer, Boolean> 
regionId2TsFileEpochDegradedMap =
       new ConcurrentHashMap<>();
 
@@ -71,6 +74,14 @@ public class PipeTemporaryMetaInAgent implements 
PipeTemporaryMeta {
     return regionId2TsFileEpochDegradedMap.isEmpty() ? null : false;
   }
 
+  public void recordResourceFailure(final PipeResourceFailureType failureType) 
{
+    recentFailureCounter.record(failureType);
+  }
+
+  public Map<String, Long> getRecentFailures() {
+    return recentFailureCounter.getRecentFailures();
+  }
+
   public String getPipeNameWithCreationTime() {
     return pipeNameWithCreationTime;
   }
@@ -107,13 +118,17 @@ public class PipeTemporaryMetaInAgent implements 
PipeTemporaryMeta {
             this.floatingMemoryUsageInByte.get(), 
that.floatingMemoryUsageInByte.get())
         && Objects.equals(
             this.regionId2TsFileEpochDegradedMap, 
that.regionId2TsFileEpochDegradedMap)
+        && Objects.equals(this.getRecentFailures(), that.getRecentFailures())
         && Objects.equals(this.regionId2CommitterKeyMap, 
that.regionId2CommitterKeyMap);
   }
 
   @Override
   public int hashCode() {
     return Objects.hash(
-        floatingMemoryUsageInByte.get(), regionId2TsFileEpochDegradedMap, 
regionId2CommitterKeyMap);
+        floatingMemoryUsageInByte.get(),
+        regionId2TsFileEpochDegradedMap,
+        getRecentFailures(),
+        regionId2CommitterKeyMap);
   }
 
   @Override
@@ -123,6 +138,8 @@ public class PipeTemporaryMetaInAgent implements 
PipeTemporaryMeta {
         + floatingMemoryUsageInByte
         + ", regionId2TsFileEpochDegradedMap="
         + regionId2TsFileEpochDegradedMap
+        + ", recentFailures="
+        + getRecentFailures()
         + ", regionId2CommitterKeyMap="
         + regionId2CommitterKeyMap
         + '}';
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
index b44649ff30c..5c3a1ea17eb 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaInCoordinator.java
@@ -19,9 +19,14 @@
 
 package org.apache.iotdb.commons.pipe.agent.task.meta;
 
+import org.apache.iotdb.commons.pipe.resource.PipeRecentFailureCounter;
+
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
@@ -33,6 +38,8 @@ public class PipeTemporaryMetaInCoordinator implements 
PipeTemporaryMeta {
   private final ConcurrentMap<Integer, Long> nodeId2RemainingEventMap = new 
ConcurrentHashMap<>();
   private final ConcurrentMap<Integer, Double> nodeId2RemainingTimeMap = new 
ConcurrentHashMap<>();
   private final ConcurrentMap<Integer, Boolean> nodeId2IsDegradedMap = new 
ConcurrentHashMap<>();
+  private final ConcurrentMap<Integer, RecentFailureSnapshot> 
nodeId2RecentFailuresMap =
+      new ConcurrentHashMap<>();
 
   public void markDataNodeCompleted(final int dataNodeId) {
     completedDataNodeIds.add(dataNodeId);
@@ -58,6 +65,27 @@ public class PipeTemporaryMetaInCoordinator implements 
PipeTemporaryMeta {
     }
   }
 
+  public void setRecentFailures(final int dataNodeId, final Map<String, Long> 
recentFailures) {
+    if (Objects.isNull(recentFailures) || recentFailures.isEmpty()) {
+      nodeId2RecentFailuresMap.remove(dataNodeId);
+      return;
+    }
+
+    final Map<String, Long> sanitizedFailures = new HashMap<>();
+    recentFailures.forEach(
+        (failureType, count) -> {
+          if (Objects.nonNull(failureType) && Objects.nonNull(count) && count 
> 0) {
+            sanitizedFailures.put(failureType, count);
+          }
+        });
+    if (sanitizedFailures.isEmpty()) {
+      nodeId2RecentFailuresMap.remove(dataNodeId);
+    } else {
+      nodeId2RecentFailuresMap.put(
+          dataNodeId, new RecentFailureSnapshot(sanitizedFailures, 
System.currentTimeMillis()));
+    }
+  }
+
   public Set<Integer> getCompletedDataNodeIds() {
     return completedDataNodeIds;
   }
@@ -77,6 +105,23 @@ public class PipeTemporaryMetaInCoordinator implements 
PipeTemporaryMeta {
     return nodeId2IsDegradedMap.isEmpty() ? null : false;
   }
 
+  public Map<String, Long> getGlobalRecentFailures() {
+    final long earliestIncludedTime =
+        System.currentTimeMillis() - PipeRecentFailureCounter.WINDOW_MILLIS;
+    nodeId2RecentFailuresMap
+        .entrySet()
+        .removeIf(entry -> entry.getValue().reportTime < earliestIncludedTime);
+
+    final Map<String, Long> result = new TreeMap<>();
+    nodeId2RecentFailuresMap
+        .values()
+        .forEach(
+            snapshot ->
+                snapshot.recentFailures.forEach(
+                    (failureType, count) -> result.merge(failureType, count, 
Long::sum)));
+    return result;
+  }
+
   @Override
   public boolean equals(final Object o) {
     if (this == o) {
@@ -89,7 +134,8 @@ public class PipeTemporaryMetaInCoordinator implements 
PipeTemporaryMeta {
     return Objects.equals(this.completedDataNodeIds, that.completedDataNodeIds)
         && Objects.equals(this.nodeId2RemainingEventMap, 
that.nodeId2RemainingEventMap)
         && Objects.equals(this.nodeId2RemainingTimeMap, 
that.nodeId2RemainingTimeMap)
-        && Objects.equals(this.nodeId2IsDegradedMap, 
that.nodeId2IsDegradedMap);
+        && Objects.equals(this.nodeId2IsDegradedMap, that.nodeId2IsDegradedMap)
+        && Objects.equals(this.nodeId2RecentFailuresMap, 
that.nodeId2RecentFailuresMap);
   }
 
   @Override
@@ -98,7 +144,8 @@ public class PipeTemporaryMetaInCoordinator implements 
PipeTemporaryMeta {
         completedDataNodeIds,
         nodeId2RemainingEventMap,
         nodeId2RemainingTimeMap,
-        nodeId2IsDegradedMap);
+        nodeId2IsDegradedMap,
+        nodeId2RecentFailuresMap);
   }
 
   @Override
@@ -112,6 +159,36 @@ public class PipeTemporaryMetaInCoordinator implements 
PipeTemporaryMeta {
         + nodeId2RemainingTimeMap
         + ", nodeId2IsDegradedMap="
         + nodeId2IsDegradedMap
+        + ", nodeId2RecentFailuresMap="
+        + nodeId2RecentFailuresMap
         + '}';
   }
+
+  private static class RecentFailureSnapshot {
+
+    private final Map<String, Long> recentFailures;
+    private final long reportTime;
+
+    private RecentFailureSnapshot(final Map<String, Long> recentFailures, 
final long reportTime) {
+      this.recentFailures = recentFailures;
+      this.reportTime = reportTime;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      final RecentFailureSnapshot that = (RecentFailureSnapshot) o;
+      return Objects.equals(recentFailures, that.recentFailures);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(recentFailures);
+    }
+  }
 }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
index 99ec79badab..7a07345c4ae 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
@@ -19,13 +19,14 @@
 
 package org.apache.iotdb.commons.pipe.agent.task.subtask;
 
-import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
 import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkCriticalException;
 import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfigurableException;
 import org.apache.iotdb.commons.i18n.PipeMessages;
 import org.apache.iotdb.commons.pipe.agent.task.execution.PipeSubtaskScheduler;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+import org.apache.iotdb.commons.pipe.resource.PipeStopStrategy;
 import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
 import org.apache.iotdb.commons.utils.ErrorHandlingCommonUtils;
 import org.apache.iotdb.pipe.api.PipeConnector;
@@ -37,7 +38,6 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
-import org.apache.tsfile.external.commons.lang3.exception.ExceptionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -353,9 +353,17 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
 
   @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity 
warning
   protected void handleException(final Event event, final Exception e) {
-    if (e instanceof PipeRuntimeOutOfMemoryCriticalException
-        || ExceptionUtils.getRootCause(e) instanceof 
PipeRuntimeOutOfMemoryCriticalException) {
-      PipeLogger.log(LOGGER::info, e, PipeMessages.TEMPORARILY_OUT_OF_MEMORY);
+    if (!PipeStopStrategy.accept(e, null)) {
+      final PipeResourceFailureType failureType = 
PipeStopStrategy.getResourceFailureType(e, null);
+      if (event instanceof EnrichedEvent && 
!PipeStopStrategy.isResourceFailureRecorded(e)) {
+        reportResourceFailure((EnrichedEvent) event, failureType);
+      }
+
+      if (failureType == PipeResourceFailureType.MEMORY_TIMEOUT) {
+        PipeLogger.log(LOGGER::info, e, 
PipeMessages.TEMPORARILY_OUT_OF_MEMORY);
+      } else {
+        sleep4NonReportException();
+      }
     } else if (e instanceof PipeRuntimeSinkNonReportTimeConfigurableException) 
{
       if (lastExceptionTime == Long.MAX_VALUE) {
         lastExceptionTime = System.currentTimeMillis();
@@ -401,4 +409,9 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
       clearReferenceCountAndReleaseLastEvent(event);
     }
   }
+
+  protected void reportResourceFailure(
+      final EnrichedEvent event, final PipeResourceFailureType failureType) {
+    // Do nothing by default for subtasks that do not expose resource failure 
metrics.
+  }
 }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
index d5024ddd50f..2cb31c81fd0 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/receiver/PipeReceiverStatusHandler.java
@@ -22,8 +22,11 @@ package org.apache.iotdb.commons.pipe.receiver;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import 
org.apache.iotdb.commons.exception.pipe.IoTConsensusV2RetryWithIncreasingIntervalException;
 import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkNonReportTimeConfigurableException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
 import org.apache.iotdb.commons.i18n.PipeMessages;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+import org.apache.iotdb.commons.pipe.resource.PipeStopStrategy;
 import org.apache.iotdb.commons.pipe.resource.log.PipeLogger;
 import org.apache.iotdb.commons.utils.RetryUtils;
 import org.apache.iotdb.commons.utils.TestOnly;
@@ -120,6 +123,14 @@ public class PipeReceiverStatusHandler {
       return;
     }
 
+    if (!PipeStopStrategy.accept(null, status)) {
+      PipeLogger.log(
+          LOGGER::info, PipeMessages.TEMPORARY_UNAVAILABLE_RETRY, status, 
exceptionMessage);
+      final PipeResourceFailureType failureType =
+          PipeStopStrategy.getResourceFailureType(null, status);
+      throw new PipeRuntimeSinkResourceException(exceptionMessage, 
failureType);
+    }
+
     switch (status.getCode()) {
       case 200: // SUCCESS_STATUS
       case 400: // REDIRECTION_RECOMMEND
@@ -133,14 +144,6 @@ public class PipeReceiverStatusHandler {
           return;
         }
 
-      case 1808: // PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION
-        {
-          PipeLogger.log(
-              LOGGER::info, PipeMessages.TEMPORARY_UNAVAILABLE_RETRY, status, 
exceptionMessage);
-          throw new PipeRuntimeSinkNonReportTimeConfigurableException(
-              exceptionMessage, Long.MAX_VALUE);
-        }
-
       case 1810: // PIPE_RECEIVER_USER_CONFLICT_EXCEPTION
       case 1815: // PIPE_RECEIVER_PARALLEL_OR_USER_CONFLICT_EXCEPTION
         if (!isRetryAllowedWhenConflictOccurs) {
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounter.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounter.java
new file mode 100644
index 00000000000..ee00946316d
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounter.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.resource;
+
+import org.apache.iotdb.commons.utils.TestOnly;
+
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class PipeRecentFailureCounter {
+
+  public static final long WINDOW_MILLIS = TimeUnit.MINUTES.toMillis(1);
+  private static final long BUCKET_MILLIS = TimeUnit.SECONDS.toMillis(1);
+  // Keep one extra slot for a failure exactly WINDOW_MILLIS old.
+  private static final int BUCKET_COUNT = (int) (WINDOW_MILLIS / 
BUCKET_MILLIS) + 1;
+
+  private final Map<PipeResourceFailureType, FailureBucket[]> failureBuckets =
+      new EnumMap<>(PipeResourceFailureType.class);
+
+  public PipeRecentFailureCounter() {
+    for (final PipeResourceFailureType failureType : 
PipeResourceFailureType.values()) {
+      final FailureBucket[] buckets = new FailureBucket[BUCKET_COUNT];
+      for (int i = 0; i < BUCKET_COUNT; ++i) {
+        buckets[i] = new FailureBucket();
+      }
+      failureBuckets.put(failureType, buckets);
+    }
+  }
+
+  public void record(final PipeResourceFailureType failureType) {
+    record(failureType, System.currentTimeMillis());
+  }
+
+  @TestOnly
+  synchronized void record(final PipeResourceFailureType failureType, final 
long timestamp) {
+    final long bucketStartTime = Math.floorDiv(timestamp, BUCKET_MILLIS) * 
BUCKET_MILLIS;
+    final int bucketIndex = Math.floorMod(Math.floorDiv(timestamp, 
BUCKET_MILLIS), BUCKET_COUNT);
+    final FailureBucket bucket = failureBuckets.get(failureType)[bucketIndex];
+    if (bucket.startTime != bucketStartTime) {
+      bucket.startTime = bucketStartTime;
+      bucket.count = 0;
+    }
+    ++bucket.count;
+  }
+
+  public Map<String, Long> getRecentFailures() {
+    return getRecentFailures(System.currentTimeMillis());
+  }
+
+  @TestOnly
+  synchronized Map<String, Long> getRecentFailures(final long currentTime) {
+    final Map<String, Long> result = new LinkedHashMap<>();
+    final long earliestIncludedTime = currentTime - WINDOW_MILLIS;
+    for (final PipeResourceFailureType failureType : 
PipeResourceFailureType.values()) {
+      long count = 0;
+      for (final FailureBucket bucket : failureBuckets.get(failureType)) {
+        if (bucket.startTime >= earliestIncludedTime && bucket.startTime <= 
currentTime) {
+          count += bucket.count;
+        }
+      }
+      if (count > 0) {
+        result.put(failureType.getDisplayName(), count);
+      }
+    }
+    return Collections.unmodifiableMap(result);
+  }
+
+  private static class FailureBucket {
+
+    private long startTime = Long.MIN_VALUE;
+    private long count;
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeResourceFailureType.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeResourceFailureType.java
new file mode 100644
index 00000000000..722da021cbd
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeResourceFailureType.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.resource;
+
+public enum PipeResourceFailureType {
+  NETWORK_TIMEOUT("network_timeout"),
+  MEMORY_TIMEOUT("memory_timeout"),
+  RECEIVER_UNAVAILABLE("receiver_unavailable");
+
+  private final String displayName;
+
+  PipeResourceFailureType(final String displayName) {
+    this.displayName = displayName;
+  }
+
+  public String getDisplayName() {
+    return displayName;
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategy.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategy.java
new file mode 100644
index 00000000000..4535bf57fe5
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategy.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.resource;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.exception.ClientManagerException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import javax.annotation.Nullable;
+
+public final class PipeStopStrategy {
+
+  private PipeStopStrategy() {}
+
+  /**
+   * @return {@code true} if the failure may follow the normal stop/report 
path, or {@code false} if
+   *     it is a transient resource failure that must only be retried locally
+   */
+  public static boolean accept(
+      final @Nullable Exception exception, final @Nullable TSStatus status) {
+    return getResourceFailureType(exception, status) == null;
+  }
+
+  public static PipeResourceFailureType getResourceFailureType(
+      final @Nullable Exception exception, final @Nullable TSStatus status) {
+    Throwable current = exception;
+    while (current != null) {
+      if (current instanceof PipeRuntimeSinkResourceException) {
+        return ((PipeRuntimeSinkResourceException) current).getFailureType();
+      }
+      if (current instanceof PipeRuntimeOutOfMemoryCriticalException) {
+        return PipeResourceFailureType.MEMORY_TIMEOUT;
+      }
+      if (current instanceof ClientManagerException) {
+        return PipeResourceFailureType.NETWORK_TIMEOUT;
+      }
+      current = current.getCause();
+    }
+
+    return status != null
+            && status.getCode()
+                == 
TSStatusCode.PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode()
+        ? PipeResourceFailureType.RECEIVER_UNAVAILABLE
+        : null;
+  }
+
+  public static boolean isResourceFailureRecorded(final @Nullable Exception 
exception) {
+    Throwable current = exception;
+    while (current != null) {
+      if (current instanceof PipeRuntimeSinkResourceException) {
+        return ((PipeRuntimeSinkResourceException) 
current).isFailureRecorded();
+      }
+      current = current.getCause();
+    }
+    return false;
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
index 87c6a01097b..23ccb5c91db 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/column/ColumnHeaderConstant.java
@@ -197,6 +197,7 @@ public class ColumnHeaderConstant {
   public static final String REMAINING_EVENT_COUNT = "RemainingEventCount";
   public static final String ESTIMATED_REMAINING_SECONDS = 
"EstimatedRemainingSeconds";
   public static final String IS_DEGRADED = "IsDegraded";
+  public static final String RECENT_FAILURES = "RecentFailures";
 
   // column names for show repair data partition table progress
   public static final String REPAIR_DATA_PARTITION_TABLE_STATUS = "Status";
@@ -279,6 +280,7 @@ public class ColumnHeaderConstant {
   public static final String ESTIMATED_REMAINING_SECONDS_TABLE_MODEL =
       "estimated_remaining_seconds";
   public static final String IS_DEGRADED_TABLE_MODEL = "is_degraded";
+  public static final String RECENT_FAILURES_TABLE_MODEL = "recent_failures";
 
   public static final String PLUGIN_NAME_TABLE_MODEL = "plugin_name";
   public static final String PLUGIN_TYPE_TABLE_MODEL = "plugin_type";
@@ -616,7 +618,8 @@ public class ColumnHeaderConstant {
           new ColumnHeader(EXCEPTION_MESSAGE, TSDataType.TEXT),
           new ColumnHeader(REMAINING_EVENT_COUNT, TSDataType.TEXT),
           new ColumnHeader(ESTIMATED_REMAINING_SECONDS, TSDataType.TEXT),
-          new ColumnHeader(IS_DEGRADED, TSDataType.BOOLEAN));
+          new ColumnHeader(IS_DEGRADED, TSDataType.BOOLEAN),
+          new ColumnHeader(RECENT_FAILURES, TSDataType.TEXT));
 
   public static final List<ColumnHeader> 
showRepairDataPartitionTableProgressColumnHeaders =
       ImmutableList.of(
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
index 32f51a173be..645ce6d2b85 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java
@@ -228,6 +228,9 @@ public class InformationSchema {
     pipeTable.addColumnSchema(
         new AttributeColumnSchema(
             ColumnHeaderConstant.IS_DEGRADED_TABLE_MODEL, TSDataType.BOOLEAN));
+    pipeTable.addColumnSchema(
+        new AttributeColumnSchema(
+            ColumnHeaderConstant.RECENT_FAILURES_TABLE_MODEL, 
TSDataType.STRING));
     schemaTables.put(PIPES, pipeTable);
 
     final TsTable pipePluginTable = new TsTable(PIPE_PLUGINS);
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaTest.java
index b6091ab76f7..eddd1d29fad 100644
--- 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaTest.java
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeTemporaryMetaTest.java
@@ -19,9 +19,14 @@
 
 package org.apache.iotdb.commons.pipe.agent.task.meta;
 
+import org.apache.iotdb.commons.pipe.resource.PipeResourceFailureType;
+
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+
 public class PipeTemporaryMetaTest {
 
   @Test
@@ -89,4 +94,31 @@ public class PipeTemporaryMetaTest {
     temporaryMeta.setDegraded(1, null);
     Assert.assertNull(temporaryMeta.getGlobalDegraded());
   }
+
+  @Test
+  public void testRecentFailuresAreRecordedAndAggregated() {
+    final PipeTemporaryMetaInAgent agentMeta = new 
PipeTemporaryMetaInAgent("test_pipe", 1L);
+    agentMeta.recordResourceFailure(PipeResourceFailureType.NETWORK_TIMEOUT);
+    agentMeta.recordResourceFailure(PipeResourceFailureType.NETWORK_TIMEOUT);
+    agentMeta.recordResourceFailure(PipeResourceFailureType.MEMORY_TIMEOUT);
+
+    Assert.assertEquals(Long.valueOf(2), 
agentMeta.getRecentFailures().get("network_timeout"));
+    Assert.assertEquals(Long.valueOf(1), 
agentMeta.getRecentFailures().get("memory_timeout"));
+
+    final PipeTemporaryMetaInCoordinator coordinatorMeta = new 
PipeTemporaryMetaInCoordinator();
+    coordinatorMeta.setRecentFailures(1, agentMeta.getRecentFailures());
+    final Map<String, Long> secondNodeFailures = new HashMap<>();
+    secondNodeFailures.put("network_timeout", 3L);
+    coordinatorMeta.setRecentFailures(2, secondNodeFailures);
+
+    Assert.assertEquals(
+        Long.valueOf(5), 
coordinatorMeta.getGlobalRecentFailures().get("network_timeout"));
+    Assert.assertEquals(
+        Long.valueOf(1), 
coordinatorMeta.getGlobalRecentFailures().get("memory_timeout"));
+
+    coordinatorMeta.setRecentFailures(1, null);
+    Assert.assertEquals(
+        Long.valueOf(3), 
coordinatorMeta.getGlobalRecentFailures().get("network_timeout"));
+    
Assert.assertFalse(coordinatorMeta.getGlobalRecentFailures().containsKey("memory_timeout"));
+  }
 }
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounterTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounterTest.java
new file mode 100644
index 00000000000..422dcbf2633
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeRecentFailureCounterTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.resource;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Map;
+
+public class PipeRecentFailureCounterTest {
+
+  @Test
+  public void testCountsOnlyFailuresWithinOneMinute() {
+    final PipeRecentFailureCounter counter = new PipeRecentFailureCounter();
+    final long now = 100_000L;
+
+    counter.record(
+        PipeResourceFailureType.NETWORK_TIMEOUT, now - 
PipeRecentFailureCounter.WINDOW_MILLIS - 1);
+    counter.record(
+        PipeResourceFailureType.NETWORK_TIMEOUT, now - 
PipeRecentFailureCounter.WINDOW_MILLIS);
+    counter.record(PipeResourceFailureType.NETWORK_TIMEOUT, now);
+    counter.record(PipeResourceFailureType.MEMORY_TIMEOUT, now);
+
+    final Map<String, Long> failures = counter.getRecentFailures(now);
+    Assert.assertEquals(Long.valueOf(2), failures.get("network_timeout"));
+    Assert.assertEquals(Long.valueOf(1), failures.get("memory_timeout"));
+
+    Assert.assertTrue(
+        counter.getRecentFailures(now + PipeRecentFailureCounter.WINDOW_MILLIS 
+ 1).isEmpty());
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategyTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategyTest.java
new file mode 100644
index 00000000000..5798ec192ff
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/resource/PipeStopStrategyTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.resource;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.client.exception.ClientManagerException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkResourceException;
+import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeSinkRetryTimesConfigurableException;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class PipeStopStrategyTest {
+
+  @Test
+  public void testClientBorrowFailureDoesNotStopPipe() {
+    final Exception failure =
+        new PipeRuntimeSinkRetryTimesConfigurableException(
+            "transfer failed",
+            Integer.MAX_VALUE,
+            new IOException(new ClientManagerException(new IOException("client 
pool exhausted"))));
+
+    Assert.assertFalse(PipeStopStrategy.accept(failure, null));
+    Assert.assertEquals(
+        PipeResourceFailureType.NETWORK_TIMEOUT,
+        PipeStopStrategy.getResourceFailureType(failure, null));
+  }
+
+  @Test
+  public void testMemoryFailuresDoNotStopPipe() {
+    final PipeRuntimeOutOfMemoryCriticalException exception =
+        new PipeRuntimeOutOfMemoryCriticalException("memory unavailable");
+    Assert.assertFalse(PipeStopStrategy.accept(exception, null));
+    Assert.assertEquals(
+        PipeResourceFailureType.MEMORY_TIMEOUT,
+        PipeStopStrategy.getResourceFailureType(exception, null));
+
+    final TSStatus status =
+        new 
TSStatus(TSStatusCode.PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode());
+    Assert.assertFalse(PipeStopStrategy.accept(null, status));
+    Assert.assertEquals(
+        PipeResourceFailureType.RECEIVER_UNAVAILABLE,
+        PipeStopStrategy.getResourceFailureType(null, status));
+  }
+
+  @Test
+  public void testOtherFailuresKeepExistingStopPolicy() {
+    Assert.assertTrue(PipeStopStrategy.accept(new IOException("network 
disconnected"), null));
+    Assert.assertTrue(
+        PipeStopStrategy.accept(
+            null, new 
TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode())));
+  }
+
+  @Test
+  public void testRecordedMarkerIsFoundWhenResourceFailureIsWrapped() {
+    final Exception failure =
+        new IOException(
+            new PipeRuntimeSinkResourceException(
+                "retry queue exhausted", 
PipeResourceFailureType.NETWORK_TIMEOUT, true));
+
+    Assert.assertTrue(PipeStopStrategy.isResourceFailureRecorded(failure));
+  }
+}
diff --git a/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift 
b/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift
index bdde4fbea5d..edc824a6b43 100644
--- a/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift
+++ b/iotdb-protocol/thrift-commons/src/main/thrift/common.thrift
@@ -203,6 +203,7 @@ struct TPipeHeartbeatResp {
   3: optional list<i64> pipeRemainingEventCountList
   4: optional list<double> pipeRemainingTimeList
   5: optional list<i32> pipeDegradedStatusList
+  6: optional list<map<string, i64>> pipeRecentFailureList
 }
 
 struct TLicense {
@@ -359,4 +360,4 @@ enum FunctionType{
   SCALAR=1,
   AGGREGATE=2,
   TABLE=3
-}
\ No newline at end of file
+}
diff --git a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift 
b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
index 7aa75731545..1111740f759 100644
--- a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
+++ b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift
@@ -883,6 +883,7 @@ struct TShowPipeInfo {
   8: optional i64 remainingEventCount
   9: optional double EstimatedRemainingTime
   10: optional bool isDegraded
+  11: optional map<string, i64> recentFailures
 }
 
 struct TGetAllPipeInfoResp {
diff --git a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift 
b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
index ea0f9467a2a..d66ed10ccf9 100644
--- a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
+++ b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift
@@ -318,6 +318,7 @@ struct TDataNodeHeartbeatResp {
   16: optional list<double> pipeRemainingTimeList
   17: optional map<i32, i64> dataRegionRawDataSize
   18: optional list<i32> pipeDegradedStatusList
+  19: optional list<map<string, i64>> pipeRecentFailureList
 }
 
 struct TPipeHeartbeatReq {

Reply via email to