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 fe1777bfa6d Optimize pipe tablet memory accounting (#18211)
fe1777bfa6d is described below
commit fe1777bfa6d54f6847e910a9f5aebdaaf5a103e2
Author: Caideyipi <[email protected]>
AuthorDate: Wed Jul 15 17:14:19 2026 +0800
Optimize pipe tablet memory accounting (#18211)
---
.../resource/memory/InsertNodeMemoryEstimator.java | 24 ++--
.../request/PipeTransferTabletBatchReqV2.java | 13 +-
.../request/PipeTransferTabletRawReqV2.java | 16 +--
.../InsertNodeMemoryEstimatorPerformanceTest.java | 104 ++++++++++++++++
.../memory/InsertNodeMemoryEstimatorTest.java | 130 ++++++++++++++++++++
.../pipe/sink/PipeDataNodeThriftRequestTest.java | 45 +++++++
...ipeTransferTabletBatchReqV2PerformanceTest.java | 133 +++++++++++++++++++++
7 files changed, 444 insertions(+), 21 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimator.java
index 54655704c1c..b7b660f638c 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimator.java
@@ -220,7 +220,7 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfInsertTabletNode(final InsertTabletNode node) {
- return sizeOfInsertTabletNode(node, newDeduplicatedObjectSet());
+ return sizeOfInsertTabletNode(node, null);
}
private static long sizeOfInsertTabletNode(
@@ -235,7 +235,7 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfInsertRowNode(final InsertRowNode node) {
- return sizeOfInsertRowNode(node, newDeduplicatedObjectSet());
+ return sizeOfInsertRowNode(node, null);
}
private static long sizeOfInsertRowNode(
@@ -247,7 +247,8 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfInsertRowsNode(final InsertRowsNode node) {
- final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
+ final Set<Object> deduplicatedObjects =
+ newDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
long size = INSERT_ROWS_NODE_SIZE;
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(),
deduplicatedObjects);
@@ -257,7 +258,8 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfInsertRowsOfOneDeviceNode(final
InsertRowsOfOneDeviceNode node) {
- final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
+ final Set<Object> deduplicatedObjects =
+ newDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
long size = INSERT_ROWS_OF_ONE_DEVICE_NODE_SIZE;
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(),
deduplicatedObjects);
@@ -267,7 +269,8 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfInsertMultiTabletsNode(final
InsertMultiTabletsNode node) {
- final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
+ final Set<Object> deduplicatedObjects =
+ newDeduplicatedObjectSetIfNeeded(node.getInsertTabletNodeList());
long size = INSERT_MULTI_TABLETS_NODE_SIZE;
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
size += sizeOfInsertTabletNodeList(node.getInsertTabletNodeList(),
deduplicatedObjects);
@@ -277,7 +280,8 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfRelationalInsertRowsNode(final
RelationalInsertRowsNode node) {
- final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
+ final Set<Object> deduplicatedObjects =
+ newDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
long size = RELATIONAL_INSERT_ROWS_NODE_SIZE;
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(),
deduplicatedObjects);
@@ -287,7 +291,7 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfRelationalInsertRowNode(final
RelationalInsertRowNode node) {
- return sizeOfRelationalInsertRowNode(node, newDeduplicatedObjectSet());
+ return sizeOfRelationalInsertRowNode(node, null);
}
private static long sizeOfRelationalInsertRowNode(
@@ -299,7 +303,7 @@ public class InsertNodeMemoryEstimator {
}
private static long sizeOfRelationalInsertTabletNode(final
RelationalInsertTabletNode node) {
- return sizeOfRelationalInsertTabletNode(node, newDeduplicatedObjectSet());
+ return sizeOfRelationalInsertTabletNode(node, null);
}
private static long sizeOfRelationalInsertTabletNode(
@@ -771,6 +775,10 @@ public class InsertNodeMemoryEstimator {
return Collections.newSetFromMap(new IdentityHashMap<>());
}
+ private static Set<Object> newDeduplicatedObjectSetIfNeeded(final List<?>
children) {
+ return children != null && children.size() > 1 ?
newDeduplicatedObjectSet() : null;
+ }
+
private static boolean shouldCountObject(
final Object object, final Set<Object> deduplicatedObjects) {
return object != null && (deduplicatedObjects == null ||
deduplicatedObjects.add(object));
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java
index 6c4607518b4..4dd044cf273 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java
@@ -215,21 +215,22 @@ public class PipeTransferTabletBatchReqV2 extends
TPipeTransferReq {
public static PipeTransferTabletBatchReqV2 fromTPipeTransferReq(
final org.apache.iotdb.service.rpc.thrift.TPipeTransferReq transferReq) {
final PipeTransferTabletBatchReqV2 batchReq = new
PipeTransferTabletBatchReqV2();
- final TabletStringInternPool tabletStringInternPool = new
TabletStringInternPool();
// Binary req, for rolling upgrade
ReadWriteIOUtils.readInt(transferReq.body);
- int size = ReadWriteIOUtils.readInt(transferReq.body);
- for (int i = 0; i < size; ++i) {
+ final int insertNodeCount = ReadWriteIOUtils.readInt(transferReq.body);
+ for (int i = 0; i < insertNodeCount; ++i) {
batchReq.insertNodeReqs.add(
PipeTransferTabletInsertNodeReqV2.toTabletInsertNodeReq(
(InsertNode) PlanFragment.deserializeHelper(transferReq.body,
null),
-
tabletStringInternPool.intern(ReadWriteIOUtils.readString(transferReq.body))));
+ ReadWriteIOUtils.readString(transferReq.body)));
}
- size = ReadWriteIOUtils.readInt(transferReq.body);
- for (int i = 0; i < size; ++i) {
+ final int rawTabletCount = ReadWriteIOUtils.readInt(transferReq.body);
+ final TabletStringInternPool tabletStringInternPool =
+ rawTabletCount > 1 ? new TabletStringInternPool() : null;
+ for (int i = 0; i < rawTabletCount; ++i) {
batchReq.tabletReqs.add(
PipeTransferTabletRawReqV2.toTPipeTransferRawReq(
transferReq.body, tabletStringInternPool));
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java
index d395bf6cf5f..3537d9ddd00 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java
@@ -214,15 +214,12 @@ public class PipeTransferTabletRawReqV2 extends
PipeTransferTabletRawReq {
public void deserializeTPipeTransferRawReq(
final ByteBuffer buffer, final TabletStringInternPool
tabletStringInternPool) {
- final TabletStringInternPool internPool =
- Objects.nonNull(tabletStringInternPool)
- ? tabletStringInternPool
- : new TabletStringInternPool();
final int startPosition = buffer.position();
try {
// V2: read databaseName, readDatabaseName = true
final InsertTabletStatement insertTabletStatement =
-
TabletStatementConverter.deserializeStatementFromTabletFormat(buffer, true,
internPool);
+ TabletStatementConverter.deserializeStatementFromTabletFormat(
+ buffer, true, tabletStringInternPool);
this.isAligned = insertTabletStatement.isAligned();
// databaseName is already set in deserializeStatementFromTabletFormat
when
// readDatabaseName=true
@@ -232,9 +229,14 @@ public class PipeTransferTabletRawReqV2 extends
PipeTransferTabletRawReq {
// If Statement deserialization fails, fallback to Tablet format
// Reset buffer position for Tablet deserialization
buffer.position(startPosition);
- this.tablet = PipeTabletUtils.internTablet(Tablet.deserialize(buffer),
internPool);
+ this.tablet =
+ PipeTabletUtils.internTablet(Tablet.deserialize(buffer),
tabletStringInternPool);
this.isAligned = ReadWriteIOUtils.readBool(buffer);
- this.dataBaseName =
internPool.intern(ReadWriteIOUtils.readString(buffer));
+ final String dataBaseName = ReadWriteIOUtils.readString(buffer);
+ this.dataBaseName =
+ Objects.nonNull(tabletStringInternPool)
+ ? tabletStringInternPool.intern(dataBaseName)
+ : dataBaseName;
}
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimatorPerformanceTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimatorPerformanceTest.java
new file mode 100644
index 00000000000..d7c01a5b676
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimatorPerformanceTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.resource.memory;
+
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class InsertNodeMemoryEstimatorPerformanceTest {
+
+ private static final String ENABLED_PROPERTY =
+ "iotdb.pipe.insert.node.memory.estimator.perf.enabled";
+ private static final String COLUMNS_PROPERTY =
+ "iotdb.pipe.insert.node.memory.estimator.perf.columns";
+ private static final String WARMUP_ITERATIONS_PROPERTY =
+ "iotdb.pipe.insert.node.memory.estimator.perf.warmup.iterations";
+ private static final String ITERATIONS_PROPERTY =
+ "iotdb.pipe.insert.node.memory.estimator.perf.iterations";
+
+ private static volatile long benchmarkBlackhole;
+
+ @Test
+ public void wideTabletSizeOfBenchmark() throws IllegalPathException {
+ Assume.assumeTrue(
+ String.format(
+ "Manual performance UT. Enable with -D%s=true, optionally tune
-D%s, -D%s and -D%s.",
+ ENABLED_PROPERTY, COLUMNS_PROPERTY, WARMUP_ITERATIONS_PROPERTY,
ITERATIONS_PROPERTY),
+ Boolean.getBoolean(ENABLED_PROPERTY));
+
+ final int columnCount = Integer.getInteger(COLUMNS_PROPERTY, 100_000);
+ final int warmupIterations =
Integer.getInteger(WARMUP_ITERATIONS_PROPERTY, 3);
+ final int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 10);
+ Assert.assertTrue(columnCount > 0);
+ Assert.assertTrue(warmupIterations > 0);
+ Assert.assertTrue(iterations > 0);
+
+ final InsertTabletNode insertTabletNode =
createWideInsertTabletNode(columnCount);
+ runBenchmark(insertTabletNode, warmupIterations);
+ final long elapsedNanos = runBenchmark(insertTabletNode, iterations);
+
+ Assert.assertTrue(benchmarkBlackhole > 0);
+ System.out.printf(
+ "InsertNode memory estimate benchmark: columns=%d, warmups=%d,
iterations=%d, %.3f ms/op%n",
+ columnCount,
+ warmupIterations,
+ iterations,
+ elapsedNanos / (double) iterations / 1_000_000.0);
+ }
+
+ private static long runBenchmark(final InsertTabletNode insertTabletNode,
final int iterations) {
+ final long startTime = System.nanoTime();
+ for (int i = 0; i < iterations; ++i) {
+ benchmarkBlackhole = InsertNodeMemoryEstimator.sizeOf(insertTabletNode);
+ }
+ return System.nanoTime() - startTime;
+ }
+
+ private static InsertTabletNode createWideInsertTabletNode(final int
columnCount)
+ throws IllegalPathException {
+ final String[] measurements = new String[columnCount];
+ final TSDataType[] dataTypes = new TSDataType[columnCount];
+ final MeasurementSchema[] measurementSchemas = new
MeasurementSchema[columnCount];
+ for (int i = 0; i < columnCount; ++i) {
+ measurements[i] = "s" + i;
+ dataTypes[i] = TSDataType.INT32;
+ measurementSchemas[i] = new MeasurementSchema(measurements[i],
TSDataType.INT32);
+ }
+ return new InsertTabletNode(
+ new PlanNodeId("wide-memory-estimator"),
+ new PartialPath("root.sg.d1"),
+ false,
+ measurements,
+ dataTypes,
+ measurementSchemas,
+ new long[] {0L},
+ null,
+ new Object[columnCount],
+ 1);
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimatorTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimatorTest.java
index 06db08aaf57..ad809bd4abb 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimatorTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimatorTest.java
@@ -23,11 +23,15 @@ import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertMultiTabletsNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowsOfOneDeviceNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertTabletNode;
+import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertRowNode;
+import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertTabletNode;
import org.apache.iotdb.rpc.TSStatusCode;
import org.apache.tsfile.common.conf.TSFileConfig;
@@ -162,6 +166,89 @@ public class InsertNodeMemoryEstimatorTest {
> InsertNodeMemoryEstimator.sizeOf(shortPlanNodeIdRow));
}
+ @Test
+ public void testLeafNodesDoNotDeduplicateRepeatedMeasurementSchemas()
+ throws IllegalPathException {
+ assertLeafNodeDoesNotDeduplicateRepeatedMeasurementSchemas(
+ createTextInsertRowNode(
+ "row", "root.sg.d1", new String[] {"s0", "s1"}, new String[]
{"v0", "v1"}));
+ assertLeafNodeDoesNotDeduplicateRepeatedMeasurementSchemas(
+ createTextRelationalInsertRowNode(
+ "relational-row", "root.sg.d1", new String[] {"s0", "s1"}, new
String[] {"v0", "v1"}));
+ assertLeafNodeDoesNotDeduplicateRepeatedMeasurementSchemas(
+ createTextInsertTabletNode("tablet", "root.sg.d1", 2, 1, 1));
+ assertLeafNodeDoesNotDeduplicateRepeatedMeasurementSchemas(
+ createTextRelationalInsertTabletNode("relational-tablet",
"root.sg.d1", 2, 1, 1));
+ }
+
+ @Test
+ public void
testSingleChildCompositesDoNotDeduplicateRepeatedMeasurementSchemas()
+ throws IllegalPathException {
+ final InsertRowNode rowWithDistinctSchemas =
+ createTextInsertRowNode(
+ "row", "root.sg.d1", new String[] {"s0", "s1"}, new String[]
{"v0", "v1"});
+ final InsertRowNode rowWithRepeatedSchema =
+ createTextInsertRowNode(
+ "row", "root.sg.d1", new String[] {"s0", "s1"}, new String[]
{"v0", "v1"});
+ replaceSecondSchemaWithEquivalentDistinctSchema(rowWithDistinctSchemas);
+ rowWithRepeatedSchema.getMeasurementSchemas()[1] =
+ rowWithRepeatedSchema.getMeasurementSchemas()[0];
+ Assert.assertEquals(
+ InsertNodeMemoryEstimator.sizeOf(createInsertRowsNode("parent",
rowWithDistinctSchemas)),
+ InsertNodeMemoryEstimator.sizeOf(createInsertRowsNode("parent",
rowWithRepeatedSchema)));
+
+ final InsertTabletNode tabletWithDistinctSchemas =
+ createTextInsertTabletNode("tablet", "root.sg.d1", 2, 1, 1);
+ final InsertTabletNode tabletWithRepeatedSchema =
+ createTextInsertTabletNode("tablet", "root.sg.d1", 2, 1, 1);
+ replaceSecondSchemaWithEquivalentDistinctSchema(tabletWithDistinctSchemas);
+ tabletWithRepeatedSchema.getMeasurementSchemas()[1] =
+ tabletWithRepeatedSchema.getMeasurementSchemas()[0];
+ Assert.assertEquals(
+ InsertNodeMemoryEstimator.sizeOf(
+ createInsertMultiTabletsNode("parent", tabletWithDistinctSchemas)),
+ InsertNodeMemoryEstimator.sizeOf(
+ createInsertMultiTabletsNode("parent", tabletWithRepeatedSchema)));
+ }
+
+ @Test
+ public void testMultiChildCompositeDeduplicatesSharedMeasurementSchemas()
+ throws IllegalPathException {
+ final long sizeWithDistinctSchemas =
+ InsertNodeMemoryEstimator.sizeOf(
+ createInsertMultiTabletsNode(
+ "parent",
+ createTextInsertTabletNode("tablet-1", "root.sg.d1", 2, 1, 1),
+ createTextInsertTabletNode("tablet-2", "root.sg.d2", 2, 1,
1)));
+
+ final InsertTabletNode firstTablet =
+ createTextInsertTabletNode("tablet-1", "root.sg.d1", 2, 1, 1);
+ final InsertTabletNode secondTablet =
+ createTextInsertTabletNode("tablet-2", "root.sg.d2", 2, 1, 1);
+ secondTablet.setMeasurementSchemas(firstTablet.getMeasurementSchemas());
+
+ Assert.assertTrue(
+ InsertNodeMemoryEstimator.sizeOf(
+ createInsertMultiTabletsNode("parent", firstTablet,
secondTablet))
+ < sizeWithDistinctSchemas);
+ }
+
+ private static void
assertLeafNodeDoesNotDeduplicateRepeatedMeasurementSchemas(
+ final InsertNode node) {
+ replaceSecondSchemaWithEquivalentDistinctSchema(node);
+ final long sizeWithDistinctSchemas =
InsertNodeMemoryEstimator.sizeOf(node);
+ node.getMeasurementSchemas()[1] = node.getMeasurementSchemas()[0];
+
+ Assert.assertEquals(sizeWithDistinctSchemas,
InsertNodeMemoryEstimator.sizeOf(node));
+ }
+
+ private static void replaceSecondSchemaWithEquivalentDistinctSchema(final
InsertNode node) {
+ final MeasurementSchema firstMeasurementSchema =
node.getMeasurementSchemas()[0];
+ node.getMeasurementSchemas()[1] =
+ new MeasurementSchema(
+ firstMeasurementSchema.getMeasurementName(),
firstMeasurementSchema.getType());
+ }
+
private static InsertRowsNode createInsertRowsNode(
String planNodeId, InsertRowNode... insertRowNodes) {
InsertRowsNode node = new InsertRowsNode(new PlanNodeId(planNodeId));
@@ -220,6 +307,24 @@ public class InsertNodeMemoryEstimatorTest {
false);
}
+ private static RelationalInsertRowNode createTextRelationalInsertRowNode(
+ String planNodeId, String devicePath, String[] measurements, String[]
values)
+ throws IllegalPathException {
+ final InsertRowNode insertRowNode =
+ createTextInsertRowNode(planNodeId, devicePath, measurements, values);
+ return new RelationalInsertRowNode(
+ new PlanNodeId(planNodeId),
+ new PartialPath(devicePath),
+ false,
+ insertRowNode.getMeasurements(),
+ insertRowNode.getDataTypes(),
+ insertRowNode.getMeasurementSchemas(),
+ 1L,
+ insertRowNode.getValues(),
+ false,
+ createFieldColumnCategories(measurements.length));
+ }
+
private static InsertTabletNode createTextInsertTabletNode(
String planNodeId, String devicePath, int measurementCount, int
rowCount, int repeatCount)
throws IllegalPathException {
@@ -260,6 +365,31 @@ public class InsertNodeMemoryEstimatorTest {
rowCount);
}
+ private static RelationalInsertTabletNode
createTextRelationalInsertTabletNode(
+ String planNodeId, String devicePath, int measurementCount, int
rowCount, int repeatCount)
+ throws IllegalPathException {
+ final InsertTabletNode insertTabletNode =
+ createTextInsertTabletNode(planNodeId, devicePath, measurementCount,
rowCount, repeatCount);
+ return new RelationalInsertTabletNode(
+ new PlanNodeId(planNodeId),
+ new PartialPath(devicePath),
+ false,
+ insertTabletNode.getMeasurements(),
+ insertTabletNode.getDataTypes(),
+ insertTabletNode.getMeasurementSchemas(),
+ insertTabletNode.getTimes(),
+ insertTabletNode.getBitMaps(),
+ insertTabletNode.getColumns(),
+ insertTabletNode.getRowCount(),
+ createFieldColumnCategories(measurementCount));
+ }
+
+ private static TsTableColumnCategory[] createFieldColumnCategories(final int
measurementCount) {
+ final TsTableColumnCategory[] columnCategories = new
TsTableColumnCategory[measurementCount];
+ Arrays.fill(columnCategories, TsTableColumnCategory.FIELD);
+ return columnCategories;
+ }
+
private static TSStatus createStatus(String message) {
TSStatus status = new TSStatus();
status.setCode(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeDataNodeThriftRequestTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeDataNodeThriftRequestTest.java
index ab08dc6b96e..07b7661e2cb 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeDataNodeThriftRequestTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeDataNodeThriftRequestTest.java
@@ -911,6 +911,51 @@ public class PipeDataNodeThriftRequestTest {
Assert.assertEquals("test",
deserializedReq.getInsertNodeReqs().get(0).getDataBaseName());
}
+ @Test
+ public void
testPipeTransferTabletBatchReqV2SkipsStringInterningForSingleRawTablet()
+ throws IOException {
+ final PipeTransferTabletBatchReqV2 deserializedReq =
+ PipeTransferTabletBatchReqV2.fromTPipeTransferReq(
+ PipeTransferTabletBatchReqV2.toTPipeTransferReq(
+ Collections.emptyList(),
+ Collections.singletonList(
+ serializeTablet(
+ createSingleValueTablet(new String("root.sg.d"), new
String("s1")), false)),
+ Collections.emptyList(),
+ Collections.singletonList(new String("root.sg.d"))));
+
+ final PipeTransferTabletRawReqV2 tabletReq =
deserializedReq.getTabletReqs().get(0);
+ Assert.assertEquals(tabletReq.getTablet().getDeviceId(),
tabletReq.getDataBaseName());
+ Assert.assertNotSame(tabletReq.getTablet().getDeviceId(),
tabletReq.getDataBaseName());
+ }
+
+ @Test
+ public void
testPipeTransferTabletBatchReqV2InternsStringsAcrossMultipleRawTablets()
+ throws IOException {
+ final List<ByteBuffer> tabletBuffers = new ArrayList<>();
+ tabletBuffers.add(
+ serializeTablet(createSingleValueTablet(new String("root.sg.d"), new
String("s1")), false));
+ tabletBuffers.add(
+ serializeTablet(createSingleValueTablet(new String("root.sg.d"), new
String("s1")), false));
+
+ final PipeTransferTabletBatchReqV2 deserializedReq =
+ PipeTransferTabletBatchReqV2.fromTPipeTransferReq(
+ PipeTransferTabletBatchReqV2.toTPipeTransferReq(
+ Collections.emptyList(),
+ tabletBuffers,
+ Collections.emptyList(),
+ Arrays.asList(new String("root.sg.d"), new
String("root.sg.d"))));
+
+ final PipeTransferTabletRawReqV2 firstTabletReq =
deserializedReq.getTabletReqs().get(0);
+ final PipeTransferTabletRawReqV2 secondTabletReq =
deserializedReq.getTabletReqs().get(1);
+ Assert.assertSame(
+ firstTabletReq.getTablet().getDeviceId(),
secondTabletReq.getTablet().getDeviceId());
+ Assert.assertSame(firstTabletReq.getDataBaseName(),
secondTabletReq.getDataBaseName());
+ Assert.assertSame(
+ ((MeasurementSchema)
firstTabletReq.getTablet().getSchemas().get(0)).getMeasurementName(),
+ ((MeasurementSchema)
secondTabletReq.getTablet().getSchemas().get(0)).getMeasurementName());
+ }
+
@Test
public void testPipeTransferTabletBatchReqV2WithMultipleTreeModelDatabases()
throws IOException {
final List<ByteBuffer> insertNodeBuffers = new ArrayList<>();
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2PerformanceTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2PerformanceTest.java
new file mode 100644
index 00000000000..96be39ba21e
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2PerformanceTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.sink.payload.evolvable.request;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.utils.PublicBAOS;
+import org.apache.tsfile.utils.ReadWriteIOUtils;
+import org.apache.tsfile.write.record.Tablet;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class PipeTransferTabletBatchReqV2PerformanceTest {
+
+ private static final String ENABLED_PROPERTY =
+ "iotdb.pipe.tablet.batch.v2.deserialize.perf.enabled";
+ private static final String COLUMNS_PROPERTY =
+ "iotdb.pipe.tablet.batch.v2.deserialize.perf.columns";
+ private static final String WARMUP_ITERATIONS_PROPERTY =
+ "iotdb.pipe.tablet.batch.v2.deserialize.perf.warmup.iterations";
+ private static final String ITERATIONS_PROPERTY =
+ "iotdb.pipe.tablet.batch.v2.deserialize.perf.iterations";
+
+ private static volatile PipeTransferTabletBatchReqV2 benchmarkBlackhole;
+
+ @Test
+ public void singleWideRawTabletDeserializationBenchmark() throws IOException
{
+ Assume.assumeTrue(
+ String.format(
+ "Manual performance UT. Enable with -D%s=true, optionally tune
-D%s, -D%s and -D%s.",
+ ENABLED_PROPERTY, COLUMNS_PROPERTY, WARMUP_ITERATIONS_PROPERTY,
ITERATIONS_PROPERTY),
+ Boolean.getBoolean(ENABLED_PROPERTY));
+
+ final int columnCount = Integer.getInteger(COLUMNS_PROPERTY, 100_000);
+ final int warmupIterations =
Integer.getInteger(WARMUP_ITERATIONS_PROPERTY, 1);
+ final int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 5);
+ Assert.assertTrue(columnCount > 0);
+ Assert.assertTrue(warmupIterations >= 0);
+ Assert.assertTrue(iterations > 0);
+
+ final PipeTransferTabletBatchReqV2 request =
createSingleWideRawTabletBatch(columnCount);
+ final PipeTransferTabletBatchReqV2 verificationResult =
deserialize(request);
+ Assert.assertEquals(1, verificationResult.getTabletReqs().size());
+ Assert.assertEquals(
+ columnCount,
verificationResult.getTabletReqs().get(0).getTablet().getSchemas().size());
+
+ for (int i = 0; i < warmupIterations; ++i) {
+ deserialize(request);
+ }
+
+ final long[] elapsedNanos = new long[iterations];
+ for (int i = 0; i < iterations; ++i) {
+ final long startTime = System.nanoTime();
+ deserialize(request);
+ elapsedNanos[i] = System.nanoTime() - startTime;
+ }
+
+ System.out.printf(
+ Locale.ROOT,
+ "Batch V2 single raw tablet deserialization benchmark: columns=%d,
warmups=%d, iterations=%d, median=%.3f ms/op%n",
+ columnCount,
+ warmupIterations,
+ iterations,
+ median(elapsedNanos) / 1_000_000.0);
+ }
+
+ private static PipeTransferTabletBatchReqV2
createSingleWideRawTabletBatch(final int columnCount)
+ throws IOException {
+ final List<IMeasurementSchema> schemas = new ArrayList<>(columnCount);
+ for (int i = 0; i < columnCount; ++i) {
+ schemas.add(new MeasurementSchema("s" + i, TSDataType.INT32));
+ }
+
+ final Tablet tablet = new Tablet("root.sg.d", schemas, 1);
+ return PipeTransferTabletBatchReqV2.toTPipeTransferReq(
+ Collections.emptyList(),
+ Collections.singletonList(serializeTablet(tablet)),
+ Collections.emptyList(),
+ Collections.singletonList("root.sg"));
+ }
+
+ private static ByteBuffer serializeTablet(final Tablet tablet) throws
IOException {
+ try (final PublicBAOS byteArrayOutputStream = new PublicBAOS();
+ final DataOutputStream outputStream = new
DataOutputStream(byteArrayOutputStream)) {
+ tablet.serialize(outputStream);
+ ReadWriteIOUtils.write(false, outputStream);
+ return ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0,
byteArrayOutputStream.size());
+ }
+ }
+
+ private static PipeTransferTabletBatchReqV2 deserialize(
+ final PipeTransferTabletBatchReqV2 request) {
+ request.body.position(0);
+ benchmarkBlackhole =
PipeTransferTabletBatchReqV2.fromTPipeTransferReq(request);
+ return benchmarkBlackhole;
+ }
+
+ private static double median(final long[] values) {
+ Arrays.sort(values);
+ final int middle = values.length / 2;
+ return (values.length & 1) == 1
+ ? values[middle]
+ : values[middle - 1] + (values[middle] - values[middle - 1]) / 2.0;
+ }
+}