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 079d2ee66de Fix snapshot pipe completion wait on unmatched DataRegions
and add IT (#18523)
079d2ee66de is described below
commit 079d2ee66de4e589fee9f783b3a083c8f95221b7
Author: Zhenyu Luo <[email protected]>
AuthorDate: Wed Aug 26 10:25:20 2026 +0800
Fix snapshot pipe completion wait on unmatched DataRegions and add IT
(#18523)
---
.../auto/enhanced/IoTDBPipeAutoDropIT.java | 107 +++++++++++++++++++++
.../runtime/heartbeat/PipeHeartbeatParser.java | 79 +++++++++++++--
2 files changed, 176 insertions(+), 10 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
index 405c5f72371..28928653ef9 100644
---
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
@@ -24,10 +24,14 @@ import
org.apache.iotdb.commons.client.sync.SyncConfigNodeIServiceClient;
import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
import org.apache.iotdb.commons.schema.column.ColumnHeaderConstant;
import org.apache.iotdb.confignode.rpc.thrift.TCreatePipeReq;
+import org.apache.iotdb.confignode.rpc.thrift.TShowPipeInfo;
+import org.apache.iotdb.confignode.rpc.thrift.TShowPipeReq;
import org.apache.iotdb.db.it.utils.TestUtils;
+import org.apache.iotdb.isession.SessionConfig;
import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
import org.apache.iotdb.it.framework.IoTDBTestRunner;
import org.apache.iotdb.itbase.category.MultiClusterIT2DualTreeAutoEnhanced;
+import org.apache.iotdb.pipe.it.dual.tablemodel.TableModelUtils;
import
org.apache.iotdb.pipe.it.dual.treemodel.auto.AbstractPipeDualTreeModelAutoIT;
import org.apache.iotdb.rpc.TSStatusCode;
@@ -40,9 +44,12 @@ import org.junit.runner.RunWith;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static org.apache.iotdb.util.MagicUtils.makeItCloseQuietly;
@@ -130,6 +137,106 @@ public class IoTDBPipeAutoDropIT extends
AbstractPipeDualTreeModelAutoIT {
}
}
+ @Test
+ public void testAutoDropIgnoredUnmatchedDataRegions() throws Exception {
+ final DataNodeWrapper receiverDataNode = receiverEnv.getDataNodeWrapper(0);
+
+ final String receiverIp = receiverDataNode.getIp();
+ final int receiverPort = receiverDataNode.getPort();
+
+ TableModelUtils.createDataBaseAndTable(senderEnv, "t1", "table_db");
+ TableModelUtils.insertData("table_db", "t1", 0, 1, senderEnv);
+
+ TestUtils.executeNonQueries(
+ senderEnv,
+ Arrays.asList(
+ "create database root.other",
+ "insert into root.other.d1(time, s1) values (1, 1)",
+ "create database root.db",
+ "insert into root.db.d1(time, s1) values (1, 1)",
+ "flush"),
+ null);
+
+ try (final SyncConfigNodeIServiceClient client =
+ (SyncConfigNodeIServiceClient)
senderEnv.getLeaderConfigNodeConnection()) {
+ final Map<String, String> processorAttributes = new HashMap<>();
+ final Map<String, String> sinkAttributes = new HashMap<>();
+
+ sinkAttributes.put("sink", "iotdb-thrift-sink");
+ sinkAttributes.put("sink.batch.enable", "false");
+ sinkAttributes.put("sink.ip", receiverIp);
+ sinkAttributes.put("sink.port", Integer.toString(receiverPort));
+
+ // Tree-model pipe: only listens to root.db. root.other and the table
database are
+ // user-visible DataRegions that must not block the historical snapshot
pipe from being
+ // auto-dropped.
+ final Map<String, String> treeSourceAttributes = new HashMap<>();
+ treeSourceAttributes.put("source.mode", "query");
+ treeSourceAttributes.put("source.path", "root.db.**");
+ treeSourceAttributes.put("source.capture.tree", "true");
+ treeSourceAttributes.put("source.capture.table", "false");
+ treeSourceAttributes.put("user", "root");
+
+ TSStatus status =
+ client.createPipe(
+ new TCreatePipeReq("p_tree", sinkAttributes)
+ .setExtractorAttributes(treeSourceAttributes)
+ .setProcessorAttributes(processorAttributes));
+
+ Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(),
status.getCode());
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
client.startPipe("p_tree").getCode());
+
+ // Table-model pipe: only listens to table_db, which must not be blocked
by the tree
+ // DataRegions either.
+ final Map<String, String> tableSourceAttributes = new HashMap<>();
+ tableSourceAttributes.put("source.mode", "query");
+ tableSourceAttributes.put("source.database-name", "table_db");
+ tableSourceAttributes.put("source.table-name", "t1");
+ tableSourceAttributes.put("source.capture.tree", "false");
+ tableSourceAttributes.put("source.capture.table", "true");
+ tableSourceAttributes.put("__system.sql-dialect", "table");
+ tableSourceAttributes.put("user", "root");
+
+ status =
+ client.createPipe(
+ new TCreatePipeReq("p_table", sinkAttributes)
+ .setExtractorAttributes(tableSourceAttributes)
+ .setProcessorAttributes(processorAttributes));
+
+ Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(),
status.getCode());
+ Assert.assertEquals(
+ TSStatusCode.SUCCESS_STATUS.getStatusCode(),
client.startPipe("p_table").getCode());
+
+ TestUtils.assertDataEventuallyOnEnv(
+ receiverEnv,
+ "select count(*) from root.db.**",
+ "count(root.db.d1.s1),",
+ Collections.singleton("1,"));
+ TableModelUtils.assertCountData("table_db", "t1", 1, receiverEnv);
+
+ await()
+ .pollInSameThread()
+ .pollDelay(1L, TimeUnit.SECONDS)
+ .pollInterval(1L, TimeUnit.SECONDS)
+ .atMost(600, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ final List<TShowPipeInfo> showPipeResult =
+ client.showPipe(new
TShowPipeReq().setUserName(SessionConfig.DEFAULT_USER))
+ .pipeInfoList;
+ showPipeResult.removeIf(
+ i ->
i.getId().startsWith(PipeStaticMeta.CONSENSUS_PIPE_PREFIX));
+ Assert.assertTrue(
+ showPipeResult.stream()
+ .noneMatch(
+ i ->
+ Objects.equals(i.getId(), "p_tree")
+ || Objects.equals(i.getId(), "p_table")));
+ });
+ }
+ }
+
@Test
public void testAutoDropInHistoricalTransferWithTimeRange() throws Exception
{
final DataNodeWrapper receiverDataNode = receiverEnv.getDataNodeWrapper(0);
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 1149c8e4444..db3d67e86c3 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
@@ -37,6 +37,8 @@ import org.apache.iotdb.confignode.i18n.ManagerMessages;
import org.apache.iotdb.confignode.manager.ConfigManager;
import
org.apache.iotdb.confignode.manager.pipe.resource.PipeConfigNodeResourceManager;
import org.apache.iotdb.confignode.persistence.pipe.PipeTaskInfo;
+import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.db.pipe.source.dataregion.DataRegionListeningFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -169,16 +171,12 @@ public class PipeHeartbeatParser {
}
}
- final Set<Integer> requiredDataRegionIds = new HashSet<>();
- for (final Map.Entry<Integer, PipeTaskMeta> entry :
-
pipeMetaFromCoordinator.getRuntimeMeta().getConsensusGroupId2TaskMetaMap().entrySet())
{
- if (configManager
- .getPartitionManager()
- .isRegionGroupExists(
- new TConsensusGroupId(TConsensusGroupType.DataRegion,
entry.getKey()))) {
- requiredDataRegionIds.add(entry.getKey());
- }
- }
+ // Align with copyAndFilterOutNonWorkingDataRegionPipeTasks: CN's task
table contains every
+ // user-visible DataRegion, but DataNodes only create / complete tasks
for regions that match
+ // the source pattern. Waiting on unmatched regions would prevent
snapshot pipes from
+ // dropping.
+ final Set<Integer> requiredDataRegionIds =
+ collectRequiredDataRegionIds(pipeMetaFromCoordinator);
// Remove completed pipes only when every required DataRegion has been
reported complete.
// Relying on the region-level reports (instead of the DataNode-level
boolean) prevents a
@@ -341,4 +339,65 @@ public class PipeHeartbeatParser {
}
}
}
+
+ /**
+ * Collect DataRegion ids that this pipe must wait on before auto-drop.
Schema / Config regions
+ * are skipped; DataRegions that the source pattern will not listen to are
skipped as well, using
+ * the same {@link DataRegionListeningFilter} as task push-down.
+ *
+ * <p>If database / schema lookup fails, the region is kept (same
conservative behavior as {@code
+ * copyAndFilterOutNonWorkingDataRegionPipeTasks}).
+ */
+ private Set<Integer> collectRequiredDataRegionIds(final PipeMeta
pipeMetaFromCoordinator) {
+ final PipeStaticMeta staticMeta = pipeMetaFromCoordinator.getStaticMeta();
+ final Set<Integer> requiredDataRegionIds = new HashSet<>();
+ for (final Map.Entry<Integer, PipeTaskMeta> entry :
+
pipeMetaFromCoordinator.getRuntimeMeta().getConsensusGroupId2TaskMetaMap().entrySet())
{
+ final TConsensusGroupId dataRegionId =
+ new TConsensusGroupId(TConsensusGroupType.DataRegion,
entry.getKey());
+ if
(!configManager.getPartitionManager().isRegionGroupExists(dataRegionId)) {
+ continue;
+ }
+ if (shouldKeepDataRegionAsRequired(staticMeta, dataRegionId)) {
+ requiredDataRegionIds.add(entry.getKey());
+ }
+ }
+ return requiredDataRegionIds;
+ }
+
+ private boolean shouldKeepDataRegionAsRequired(
+ final PipeStaticMeta staticMeta, final TConsensusGroupId dataRegionId) {
+ if (staticMeta.isSourceExternal()) {
+ return true;
+ }
+
+ final String database;
+ try {
+ database =
configManager.getPartitionManager().getRegionDatabase(dataRegionId);
+ if (database == null) {
+ return true;
+ }
+ } catch (final Exception ignored) {
+ return true;
+ }
+
+ final boolean isTableModel;
+ try {
+ final TDatabaseSchema schema =
+
configManager.getClusterSchemaManager().getDatabaseSchemaByName(database);
+ if (schema == null) {
+ return true;
+ }
+ isTableModel = schema.isIsTableModel();
+ } catch (final Exception ignored) {
+ return true;
+ }
+
+ try {
+ return DataRegionListeningFilter.shouldDatabaseBeListened(
+ staticMeta.getSourceParameters(), isTableModel, database,
staticMeta.getPipeType());
+ } catch (final Exception ignored) {
+ return true;
+ }
+ }
}