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 cebe6639993 Fix hybrid pipe queue memory admission across data regions 
(#19080)
cebe6639993 is described below

commit cebe6639993ec641599b7b4e27403557b25c2f7e
Author: Caideyipi <[email protected]>
AuthorDate: Mon Nov 2 18:39:39 2026 +0800

    Fix hybrid pipe queue memory admission across data regions (#19080)
---
 .../db/pipe/agent/task/PipeDataNodeTaskAgent.java  |  19 ++-
 .../task/PipeDataNodeTaskAgentMemoryTest.java      | 186 +++++++++++++++++++++
 2 files changed, 204 insertions(+), 1 deletion(-)

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 82e8e2da608..91116fe1942 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
@@ -1232,6 +1232,16 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent 
{
       return;
     }
 
+    // Snapshot/query and non-streaming sources do not retain InsertNode 
tablets.
+    if (isSnapshotMode(sourceParameters)
+        || !sourceParameters.getBooleanOrDefault(
+            Arrays.asList(
+                PipeSourceConstant.EXTRACTOR_MODE_STREAMING_KEY,
+                PipeSourceConstant.SOURCE_MODE_STREAMING_KEY),
+            PipeSourceConstant.EXTRACTOR_MODE_STREAMING_DEFAULT_VALUE)) {
+      return;
+    }
+
     // If the realtime mode is batch or file, we do not need to allocate memory
     final String realtimeMode =
         sourceParameters.getStringByKeys(
@@ -1242,8 +1252,15 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent 
{
       return;
     }
 
+    // Hybrid sources share a pipe-wide floating-memory limit and downgrade to 
TsFiles when it is
+    // reached. Charging a full queue for every region rejects an idle pipe as 
its region count
+    // grows.
+    // Only forced-log sources cannot downgrade and retain the per-region 
estimate.
     final long needFloatingMemory =
-        PipeConfig.getInstance().getPipeInsertNodeQueueMemory() * 
dataRegionTaskCount;
+        PipeConfig.getInstance().getPipeInsertNodeQueueMemory()
+            * 
(PipeSourceConstant.EXTRACTOR_REALTIME_MODE_FORCED_LOG_VALUE.equals(realtimeMode)
+                ? dataRegionTaskCount
+                : 1);
     final long allocatedMemorySizeInBytes = 
this.getAllFloatingMemoryUsageInByte();
     final long remainingMemory =
         
PipeDataNodeResourceManager.memory().getTotalFloatingMemorySizeInBytes()
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentMemoryTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentMemoryTest.java
new file mode 100644
index 00000000000..910af73646a
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentMemoryTest.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.pipe.agent.task;
+
+import org.apache.iotdb.commons.conf.CommonConfig;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
+import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
+import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
+import org.apache.iotdb.pipe.api.exception.PipeException;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+
+public class PipeDataNodeTaskAgentMemoryTest {
+
+  private static final int DATA_REGION_COUNT = 14;
+
+  private final CommonConfig config = 
CommonDescriptor.getInstance().getConfig();
+  private final PipeDataNodeTaskAgent taskAgent = new PipeDataNodeTaskAgent();
+  private long originalQueueMemory;
+  private double originalFloatingMemoryProportion;
+  private long floatingMemorySizeInBytes;
+  private Method memoryCheck;
+
+  @Before
+  public void setUp() throws Exception {
+    originalQueueMemory = config.getPipeInsertNodeQueueMemory();
+    originalFloatingMemoryProportion = 
config.getPipeTotalFloatingMemoryProportion();
+    config.setPipeTotalFloatingMemoryProportion(0.5);
+    floatingMemorySizeInBytes =
+        
PipeDataNodeResourceManager.memory().getTotalFloatingMemorySizeInBytes();
+    Assert.assertTrue(floatingMemorySizeInBytes > 1);
+    config.setPipeInsertNodeQueueMemory(floatingMemorySizeInBytes / 2);
+    memoryCheck =
+        PipeDataNodeTaskAgent.class.getDeclaredMethod(
+            "calculateInsertNodeQueueMemory", PipeParameters.class, int.class);
+    memoryCheck.setAccessible(true);
+  }
+
+  @After
+  public void tearDown() {
+    config.setPipeInsertNodeQueueMemory(originalQueueMemory);
+    
config.setPipeTotalFloatingMemoryProportion(originalFloatingMemoryProportion);
+  }
+
+  @Test
+  public void testHybridQueueBudgetIsSharedAcrossDataRegions() throws 
Exception {
+    checkMemory(Collections.emptyMap(), DATA_REGION_COUNT);
+    for (final String key :
+        new String[] {
+          PipeSourceConstant.SOURCE_MODE_STREAMING_KEY,
+          PipeSourceConstant.EXTRACTOR_MODE_STREAMING_KEY
+        }) {
+      checkMemory(Collections.singletonMap(key, "true"), DATA_REGION_COUNT);
+    }
+    for (final String key :
+        new String[] {
+          PipeSourceConstant.SOURCE_REALTIME_MODE_KEY,
+          PipeSourceConstant.EXTRACTOR_REALTIME_MODE_KEY
+        }) {
+      for (final String mode :
+          new String[] {
+            PipeSourceConstant.EXTRACTOR_REALTIME_MODE_HYBRID_VALUE,
+            PipeSourceConstant.EXTRACTOR_REALTIME_MODE_STREAM_MODE_VALUE,
+            PipeSourceConstant.EXTRACTOR_REALTIME_MODE_LOG_VALUE
+          }) {
+        checkMemory(Collections.singletonMap(key, mode), DATA_REGION_COUNT);
+      }
+    }
+  }
+
+  @Test
+  public void testHybridQueueStillRequiresAvailableFloatingMemory() {
+    config.setPipeInsertNodeQueueMemory(floatingMemorySizeInBytes + 1);
+    final PipeException exception =
+        Assert.assertThrows(
+            PipeException.class, () -> checkMemory(Collections.emptyMap(), 
DATA_REGION_COUNT));
+    Assert.assertTrue(
+        
exception.getMessage().contains(Long.toString(floatingMemorySizeInBytes + 1)));
+  }
+
+  @Test
+  public void testForcedLogStillRequiresPerRegionMemory() throws Exception {
+    for (final String key :
+        new String[] {
+          PipeSourceConstant.SOURCE_REALTIME_MODE_KEY,
+          PipeSourceConstant.EXTRACTOR_REALTIME_MODE_KEY
+        }) {
+      final Map<String, String> attributes =
+          Collections.singletonMap(
+              key, 
PipeSourceConstant.EXTRACTOR_REALTIME_MODE_FORCED_LOG_VALUE);
+      checkMemory(attributes, 1);
+      final PipeException exception =
+          Assert.assertThrows(
+              PipeException.class, () -> checkMemory(attributes, 
DATA_REGION_COUNT));
+      Assert.assertTrue(
+          exception
+              .getMessage()
+              .contains(Long.toString(config.getPipeInsertNodeQueueMemory() * 
DATA_REGION_COUNT)));
+    }
+  }
+
+  @Test
+  public void testSourcesWithoutTabletQueuesSkipFloatingMemoryCheck() throws 
Exception {
+    config.setPipeInsertNodeQueueMemory(floatingMemorySizeInBytes + 1);
+    checkMemory(Collections.emptyMap(), 0);
+    for (final String key :
+        new String[] {
+          PipeSourceConstant.SOURCE_REALTIME_ENABLE_KEY,
+          PipeSourceConstant.EXTRACTOR_REALTIME_ENABLE_KEY
+        }) {
+      checkMemory(Collections.singletonMap(key, "false"), DATA_REGION_COUNT);
+    }
+    for (final String mode :
+        new String[] {
+          PipeSourceConstant.EXTRACTOR_REALTIME_MODE_FILE_VALUE,
+          PipeSourceConstant.EXTRACTOR_REALTIME_MODE_BATCH_MODE_VALUE
+        }) {
+      for (final String key :
+          new String[] {
+            PipeSourceConstant.SOURCE_REALTIME_MODE_KEY,
+            PipeSourceConstant.EXTRACTOR_REALTIME_MODE_KEY
+          }) {
+        checkMemory(Collections.singletonMap(key, mode), DATA_REGION_COUNT);
+      }
+    }
+    for (final String key :
+        new String[] {
+          PipeSourceConstant.SOURCE_MODE_STREAMING_KEY,
+          PipeSourceConstant.EXTRACTOR_MODE_STREAMING_KEY
+        }) {
+      checkMemory(Collections.singletonMap(key, "false"), DATA_REGION_COUNT);
+    }
+    for (final String key :
+        new String[] {
+          PipeSourceConstant.SOURCE_MODE_SNAPSHOT_KEY,
+          PipeSourceConstant.EXTRACTOR_MODE_SNAPSHOT_KEY
+        }) {
+      checkMemory(Collections.singletonMap(key, "true"), DATA_REGION_COUNT);
+    }
+    for (final String key :
+        new String[] {PipeSourceConstant.SOURCE_MODE_KEY, 
PipeSourceConstant.EXTRACTOR_MODE_KEY}) {
+      for (final String mode :
+          new String[] {
+            PipeSourceConstant.EXTRACTOR_MODE_SNAPSHOT_VALUE,
+            PipeSourceConstant.EXTRACTOR_MODE_QUERY_VALUE
+          }) {
+        checkMemory(Collections.singletonMap(key, mode), DATA_REGION_COUNT);
+      }
+    }
+  }
+
+  private void checkMemory(final Map<String, String> attributes, final int 
dataRegionCount)
+      throws Exception {
+    try {
+      memoryCheck.invoke(taskAgent, new PipeParameters(attributes), 
dataRegionCount);
+    } catch (final InvocationTargetException e) {
+      throw (Exception) e.getCause();
+    }
+  }
+}

Reply via email to