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

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


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new 197c137acc1 Pipe: throttle TsFile parser retries under memory pressure 
(#18141) (#18152)
197c137acc1 is described below

commit 197c137acc15d3ace55a31a49758fd3f5260884e
Author: Caideyipi <[email protected]>
AuthorDate: Wed Jul 8 18:51:05 2026 +0800

    Pipe: throttle TsFile parser retries under memory pressure (#18141) (#18152)
    
    (cherry picked from commit 82690873b64ffb19d23cbd688cd86610b70ad231)
---
 .../common/tsfile/PipeTsFileInsertionEvent.java    | 101 +++++++++++++++++++--
 .../db/pipe/resource/memory/PipeMemoryManager.java |  80 ++++++++++++++++
 .../event/TsFileInsertionDataContainerTest.java    |  62 +++++++++++++
 3 files changed, 233 insertions(+), 10 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
index 2416e6caf82..98c660ea351 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
@@ -85,6 +85,7 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
 
   protected final AtomicBoolean isClosed;
   protected final AtomicReference<TsFileInsertionDataContainer> dataContainer;
+  private final AtomicBoolean isTsFileParserMemoryReserved = new 
AtomicBoolean(false);
 
   // The point count of the TsFile. Used for metrics on PipeConsensus' 
receiver side.
   // May be updated after it is flushed. Should be negative if not set.
@@ -487,12 +488,8 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
       while (iterator.hasNext()) {
         final TabletInsertionEvent parsedEvent = iterator.next();
         tabletEventCount++;
-        try {
-          consumer.consume((PipeRawTabletInsertionEvent) parsedEvent);
-        } catch (final PipeRuntimeOutOfMemoryCriticalException e) {
-          releaseParsedTabletEvent(parsedEvent);
-          throw e;
-        }
+        consumeParsedTabletInsertionEventWithRetry(
+            consumer, callerName, tabletEventCount, parsedEvent);
       }
     } catch (final PipeRuntimeOutOfMemoryCriticalException e) {
       close();
@@ -506,6 +503,57 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
     }
   }
 
+  private void consumeParsedTabletInsertionEventWithRetry(
+      final TabletInsertionEventConsumer consumer,
+      final String callerName,
+      final int tabletEventCount,
+      final TabletInsertionEvent parsedEvent)
+      throws Exception {
+    final PipeMemoryManager memoryManager = 
PipeDataNodeResourceManager.memory();
+    long firstOutOfMemoryTimeInMs = Long.MIN_VALUE;
+    int retryCount = 0;
+    while (true) {
+      try {
+        consumer.consume((PipeRawTabletInsertionEvent) parsedEvent);
+        return;
+      } catch (final PipeRuntimeOutOfMemoryCriticalException e) {
+        if (firstOutOfMemoryTimeInMs == Long.MIN_VALUE) {
+          firstOutOfMemoryTimeInMs = System.currentTimeMillis();
+        }
+        if (memoryManager.shouldReleaseTsFileParserOnOutOfMemory(
+            firstOutOfMemoryTimeInMs, ++retryCount)) {
+          releaseParsedTabletEvent(parsedEvent);
+          throw e;
+        }
+        logParserRetryOnOutOfMemory(callerName, tabletEventCount, retryCount, 
e);
+        try {
+          
Thread.sleep(PipeConfig.getInstance().getPipeMemoryAllocateRetryIntervalInMs());
+        } catch (final InterruptedException interruptedException) {
+          Thread.currentThread().interrupt();
+          releaseParsedTabletEvent(parsedEvent);
+          throw e;
+        }
+      }
+    }
+  }
+
+  private void logParserRetryOnOutOfMemory(
+      final String callerName,
+      final int tabletEventCount,
+      final int retryCount,
+      final PipeRuntimeOutOfMemoryCriticalException e) {
+    if (retryCount != 1 && retryCount % 10 != 0) {
+      return;
+    }
+    LOGGER.warn(
+        "{}: failed to consume parsed tablet from TsFile {}, tablet event no. 
{}, retry count is {}, will keep parser and retry locally for a short time.",
+        callerName,
+        getTsFile(),
+        tabletEventCount,
+        retryCount,
+        e);
+  }
+
   private void releaseParsedTabletEvent(final TabletInsertionEvent 
parsedEvent) {
     if (parsedEvent instanceof PipeRawTabletInsertionEvent
         && ((PipeRawTabletInsertionEvent) parsedEvent).getReferenceCount() == 0
@@ -556,7 +604,7 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
 
   private void waitForResourceEnough4Parsing(final long timeoutMs) throws 
InterruptedException {
     final PipeMemoryManager memoryManager = 
PipeDataNodeResourceManager.memory();
-    if (memoryManager.isEnough4TabletParsing()) {
+    if (tryReserveTsFileParserMemory(memoryManager)) {
       return;
     }
 
@@ -565,7 +613,7 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
 
     final long memoryCheckIntervalMs =
         PipeConfig.getInstance().getPipeCheckMemoryEnoughIntervalMs();
-    while (!memoryManager.isEnough4TabletParsing()) {
+    while (!tryReserveTsFileParserMemory(memoryManager)) {
       Thread.sleep(memoryCheckIntervalMs);
 
       final long currentTime = System.currentTimeMillis();
@@ -600,6 +648,29 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
         waitTimeSeconds);
   }
 
+  private boolean tryReserveTsFileParserMemory(final PipeMemoryManager 
memoryManager) {
+    synchronized (isTsFileParserMemoryReserved) {
+      if (isTsFileParserMemoryReserved.get()) {
+        return true;
+      }
+
+      if (!memoryManager.tryReserveTsFileParserMemory()) {
+        return false;
+      }
+
+      isTsFileParserMemoryReserved.set(true);
+      return true;
+    }
+  }
+
+  private void releaseTsFileParserMemoryIfReserved() {
+    synchronized (isTsFileParserMemoryReserved) {
+      if (isTsFileParserMemoryReserved.compareAndSet(true, false)) {
+        PipeDataNodeResourceManager.memory().releaseTsFileParserMemory();
+      }
+    }
+  }
+
   /** The method is used to prevent circular replication in PipeConsensus */
   public boolean isGeneratedByPipeConsensus() {
     return isGeneratedByPipeConsensus;
@@ -668,6 +739,7 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
           }
           return null;
         });
+    releaseTsFileParserMemoryIfReserved();
   }
 
   /////////////////////////// Object ///////////////////////////
@@ -706,7 +778,8 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
         this.tsFile,
         this.isWithMod,
         this.modFile,
-        this.dataContainer);
+        this.dataContainer,
+        this.isTsFileParserMemoryReserved);
   }
 
   private static class PipeTsFileInsertionEventResource extends 
PipeEventResource {
@@ -716,6 +789,7 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
     private final File modFile;
     private final AtomicReference<TsFileInsertionDataContainer> dataContainer;
     private final String pipeName;
+    private final AtomicBoolean isTsFileParserMemoryReserved;
 
     private PipeTsFileInsertionEventResource(
         final AtomicBoolean isReleased,
@@ -724,13 +798,15 @@ public class PipeTsFileInsertionEvent extends 
EnrichedEvent
         final File tsFile,
         final boolean isWithMod,
         final File modFile,
-        final AtomicReference<TsFileInsertionDataContainer> dataContainer) {
+        final AtomicReference<TsFileInsertionDataContainer> dataContainer,
+        final AtomicBoolean isTsFileParserMemoryReserved) {
       super(isReleased, referenceCount);
       this.pipeName = pipeName;
       this.tsFile = tsFile;
       this.isWithMod = isWithMod;
       this.modFile = modFile;
       this.dataContainer = dataContainer;
+      this.isTsFileParserMemoryReserved = isTsFileParserMemoryReserved;
     }
 
     @Override
@@ -750,6 +826,11 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
               }
               return null;
             });
+        synchronized (isTsFileParserMemoryReserved) {
+          if (isTsFileParserMemoryReserved.compareAndSet(true, false)) {
+            PipeDataNodeResourceManager.memory().releaseTsFileParserMemory();
+          }
+        }
       } catch (final Exception e) {
         LOGGER.warn("Decrease reference count for TsFile {} error.", 
tsFile.getPath(), e);
       }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
index afdbafad1a7..387a4228a2c 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
@@ -54,6 +54,8 @@ public class PipeMemoryManager {
 
   private volatile long usedMemorySizeInBytesOfTsFiles;
 
+  private volatile long reservedTsFileParserCount;
+
   // Only non-zero memory blocks will be added to this set.
   private final Set<PipeMemoryBlock> allocatedBlocks = new HashSet<>();
   private final Set<PipeMemoryBlock> shrinkableBlocks = new HashSet<>();
@@ -103,6 +105,34 @@ public class PipeMemoryManager {
         * getTotalNonFloatingMemorySizeInBytes();
   }
 
+  private static long getTsFileParserMemorySizeInBytes() {
+    return Math.max(
+        PipeConfig.getInstance().getTsFileParserMemory(), 
MEMORY_ALLOCATE_MIN_SIZE_IN_BYTES);
+  }
+
+  private long getReservedTsFileParserMemorySizeInBytes() {
+    return reservedTsFileParserCount * getTsFileParserMemorySizeInBytes();
+  }
+
+  private boolean isEnough4TabletParsingWithReservedParserMemory(final long 
extraMemoryInBytes) {
+    final double tabletMemoryWithParserMemory =
+        (double) usedMemorySizeInBytesOfTablets
+            + getReservedTsFileParserMemorySizeInBytes()
+            + extraMemoryInBytes;
+    return tabletMemoryWithParserMemory + (double) 
usedMemorySizeInBytesOfTsFiles
+            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
+        && tabletMemoryWithParserMemory
+            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTablets();
+  }
+
+  private boolean isHardEnough4TabletParsingWithReservedParserMemory() {
+    final double tabletMemoryWithParserMemory =
+        (double) usedMemorySizeInBytesOfTablets + 
getReservedTsFileParserMemorySizeInBytes();
+    return tabletMemoryWithParserMemory + (double) 
usedMemorySizeInBytesOfTsFiles
+            < allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
+        && tabletMemoryWithParserMemory < 
allowedMaxMemorySizeInBytesOfTablets();
+  }
+
   public boolean isEnough4TabletParsing() {
     return (double) usedMemorySizeInBytesOfTablets + (double) 
usedMemorySizeInBytesOfTsFiles
             < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
@@ -116,6 +146,56 @@ public class PipeMemoryManager {
         && (double) usedMemorySizeInBytesOfTablets < 
allowedMaxMemorySizeInBytesOfTablets();
   }
 
+  public synchronized boolean tryReserveTsFileParserMemory() {
+    if (!PIPE_MEMORY_MANAGEMENT_ENABLED) {
+      return true;
+    }
+
+    final long parserMemorySizeInBytes = getTsFileParserMemorySizeInBytes();
+    if 
(isEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes)) {
+      reservedTsFileParserCount++;
+      return true;
+    }
+
+    return false;
+  }
+
+  public synchronized void releaseTsFileParserMemory() {
+    if (!PIPE_MEMORY_MANAGEMENT_ENABLED) {
+      return;
+    }
+
+    reservedTsFileParserCount = Math.max(0, reservedTsFileParserCount - 1);
+    this.notifyAll();
+  }
+
+  public boolean shouldReleaseTsFileParserOnOutOfMemory(
+      final long firstOutOfMemoryTimeInMs, final int retryCount) {
+    final long retryIntervalInMs =
+        PipeConfig.getInstance().getPipeMemoryAllocateRetryIntervalInMs();
+    final long minRetryTimeInMs = Math.max(retryIntervalInMs * 2, 1);
+    final long maxRetryTimeInMs =
+        Math.max(
+            minRetryTimeInMs,
+            retryIntervalInMs * 
PipeConfig.getInstance().getPipeMemoryAllocateMaxRetries());
+
+    final long elapsedTimeInMs = System.currentTimeMillis() - 
firstOutOfMemoryTimeInMs;
+    if (elapsedTimeInMs < minRetryTimeInMs) {
+      return false;
+    }
+
+    if (!PIPE_MEMORY_MANAGEMENT_ENABLED) {
+      return elapsedTimeInMs >= maxRetryTimeInMs;
+    }
+
+    if (!isHardEnough4TabletParsingWithReservedParserMemory()) {
+      return true;
+    }
+
+    return retryCount >= 
PipeConfig.getInstance().getPipeMemoryAllocateMaxRetries()
+        || elapsedTimeInMs >= maxRetryTimeInMs;
+  }
+
   public boolean isEnough4TsFileSlicing() {
     return (double) usedMemorySizeInBytesOfTablets + (double) 
usedMemorySizeInBytesOfTsFiles
             < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/TsFileInsertionDataContainerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/TsFileInsertionDataContainerTest.java
index fa82d15db61..862e2b553fe 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/TsFileInsertionDataContainerTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/TsFileInsertionDataContainerTest.java
@@ -105,11 +105,19 @@ public class TsFileInsertionDataContainerTest {
   private File nonalignedTsFile;
   private TsFileResource resource;
   private boolean isPipeMemoryManagementEnabled;
+  private long originalPipeMemoryAllocateRetryIntervalInMs;
+  private int originalPipeMemoryAllocateMaxRetries;
 
   @Before
   public void setUp() throws Exception {
     isPipeMemoryManagementEnabled = 
PipeConfig.getInstance().getPipeMemoryManagementEnabled();
+    originalPipeMemoryAllocateRetryIntervalInMs =
+        PipeConfig.getInstance().getPipeMemoryAllocateRetryIntervalInMs();
+    originalPipeMemoryAllocateMaxRetries =
+        PipeConfig.getInstance().getPipeMemoryAllocateMaxRetries();
     
CommonDescriptor.getInstance().getConfig().setPipeMemoryManagementEnabled(false);
+    
CommonDescriptor.getInstance().getConfig().setPipeMemoryAllocateRetryIntervalInMs(1);
+    
CommonDescriptor.getInstance().getConfig().setPipeMemoryAllocateMaxRetries(2);
   }
 
   @After
@@ -117,6 +125,12 @@ public class TsFileInsertionDataContainerTest {
     CommonDescriptor.getInstance()
         .getConfig()
         .setPipeMemoryManagementEnabled(isPipeMemoryManagementEnabled);
+    CommonDescriptor.getInstance()
+        .getConfig()
+        
.setPipeMemoryAllocateRetryIntervalInMs(originalPipeMemoryAllocateRetryIntervalInMs);
+    CommonDescriptor.getInstance()
+        .getConfig()
+        .setPipeMemoryAllocateMaxRetries(originalPipeMemoryAllocateMaxRetries);
     if (alignedTsFile != null) {
       alignedTsFile.delete();
     }
@@ -216,6 +230,54 @@ public class TsFileInsertionDataContainerTest {
     Assert.assertNull(getDataContainer(event).get());
   }
 
+  @Test
+  public void 
testConsumeTabletInsertionEventsWithRetryKeepsParserForTransientOutOfMemory()
+      throws Exception {
+    nonalignedTsFile =
+        TsFileGeneratorUtils.generateNonAlignedTsFile(
+            "nonaligned-consume-transient-oom.tsfile", 1, 1, 10, 0, 100, 10, 
10);
+    resource = new TsFileResource(nonalignedTsFile);
+    resource.setStatusForTest(TsFileResourceStatus.NORMAL);
+
+    final IDeviceID deviceID = new PlainDeviceID("root.testsg.d0");
+    resource.updateStartTime(deviceID, 0);
+    resource.updateEndTime(deviceID, 9);
+
+    final PipeTsFileInsertionEvent event =
+        new PipeTsFileInsertionEvent(
+            resource,
+            null,
+            false,
+            false,
+            false,
+            null,
+            0,
+            null,
+            new PrefixPipePattern("root"),
+            Long.MIN_VALUE,
+            Long.MAX_VALUE);
+    final AtomicInteger retryCount = new AtomicInteger(0);
+    final AtomicReference<PipeRawTabletInsertionEvent> parsedEventReference =
+        new AtomicReference<>();
+
+    event.consumeTabletInsertionEventsWithRetry(
+        parsedEvent -> {
+          parsedEventReference.set(parsedEvent);
+          if (retryCount.getAndIncrement() == 0) {
+            throw new PipeRuntimeOutOfMemoryCriticalException("transient oom");
+          }
+          parsedEvent.clearReferenceCount(getClass().getName());
+        },
+        "test");
+
+    Assert.assertEquals(2, retryCount.get());
+    Assert.assertNotNull(parsedEventReference.get());
+    Assert.assertTrue(parsedEventReference.get().isReleased());
+    Assert.assertNotNull(getDataContainer(event).get());
+
+    event.close();
+  }
+
   @Test
   public void 
testScanParserSplitNonAlignedSinglePageChunkByEstimatedPageMemory() throws 
Exception {
     final long originalPipeMaxReaderChunkSize =

Reply via email to