Skip to content

Commit 37cf3ca

Browse files
authored
[Performance] Reduce temporary allocations on insert path (#18240)
* Reduce temporary allocations on insert path * Add opt-in write path performance benchmarks
1 parent 55271d7 commit 37cf3ca

13 files changed

Lines changed: 963 additions & 100 deletions

File tree

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/InsertNodeMemoryEstimator.java‎

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.iotdb.commons.path.PartialPath;
2929
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
3030
import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
31+
import org.apache.iotdb.commons.utils.TestOnly;
3132
import org.apache.iotdb.db.i18n.DataNodePipeMessages;
3233
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertMultiTabletsNode;
3334
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertNode;
@@ -155,6 +156,12 @@ public class InsertNodeMemoryEstimator {
155156
// from the actual result because the properties of the parent class are not added.
156157
private static final double INSERT_ROW_NODE_EXPANSION_FACTOR = 1.3;
157158

159+
// Composite insert nodes are estimated on write threads. Reuse the identity set between events,
160+
// but discard unusually large sets to avoid retaining a large table on every write thread.
161+
private static final int MAX_RETAINED_DEDUPLICATED_OBJECTS = 1024;
162+
private static final ThreadLocal<Set<Object>> REUSABLE_DEDUPLICATED_OBJECTS =
163+
ThreadLocal.withInitial(InsertNodeMemoryEstimator::newDeduplicatedObjectSet);
164+
158165
public static long sizeOf(final InsertNode insertNode) {
159166
try {
160167
final String className = insertNode.getClass().getSimpleName();
@@ -248,46 +255,62 @@ private static long sizeOfInsertRowNode(
248255

249256
private static long sizeOfInsertRowsNode(final InsertRowsNode node) {
250257
final Set<Object> deduplicatedObjects =
251-
newDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
252-
long size = INSERT_ROWS_NODE_SIZE;
253-
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
254-
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), deduplicatedObjects);
255-
size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
256-
size += sizeOfResults(node.getResults());
257-
return size;
258+
acquireDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
259+
try {
260+
long size = INSERT_ROWS_NODE_SIZE;
261+
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
262+
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), deduplicatedObjects);
263+
size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
264+
size += sizeOfResults(node.getResults());
265+
return size;
266+
} finally {
267+
releaseDeduplicatedObjectSet(deduplicatedObjects);
268+
}
258269
}
259270

260271
private static long sizeOfInsertRowsOfOneDeviceNode(final InsertRowsOfOneDeviceNode node) {
261272
final Set<Object> deduplicatedObjects =
262-
newDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
263-
long size = INSERT_ROWS_OF_ONE_DEVICE_NODE_SIZE;
264-
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
265-
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), deduplicatedObjects);
266-
size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
267-
size += sizeOfResults(node.getResults());
268-
return size;
273+
acquireDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
274+
try {
275+
long size = INSERT_ROWS_OF_ONE_DEVICE_NODE_SIZE;
276+
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
277+
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), deduplicatedObjects);
278+
size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
279+
size += sizeOfResults(node.getResults());
280+
return size;
281+
} finally {
282+
releaseDeduplicatedObjectSet(deduplicatedObjects);
283+
}
269284
}
270285

271286
private static long sizeOfInsertMultiTabletsNode(final InsertMultiTabletsNode node) {
272287
final Set<Object> deduplicatedObjects =
273-
newDeduplicatedObjectSetIfNeeded(node.getInsertTabletNodeList());
274-
long size = INSERT_MULTI_TABLETS_NODE_SIZE;
275-
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
276-
size += sizeOfInsertTabletNodeList(node.getInsertTabletNodeList(), deduplicatedObjects);
277-
size += sizeOfIntegerList(node.getParentInsertTabletNodeIndexList());
278-
size += sizeOfResults(node.getResults());
279-
return size;
288+
acquireDeduplicatedObjectSetIfNeeded(node.getInsertTabletNodeList());
289+
try {
290+
long size = INSERT_MULTI_TABLETS_NODE_SIZE;
291+
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
292+
size += sizeOfInsertTabletNodeList(node.getInsertTabletNodeList(), deduplicatedObjects);
293+
size += sizeOfIntegerList(node.getParentInsertTabletNodeIndexList());
294+
size += sizeOfResults(node.getResults());
295+
return size;
296+
} finally {
297+
releaseDeduplicatedObjectSet(deduplicatedObjects);
298+
}
280299
}
281300

282301
private static long sizeOfRelationalInsertRowsNode(final RelationalInsertRowsNode node) {
283302
final Set<Object> deduplicatedObjects =
284-
newDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
285-
long size = RELATIONAL_INSERT_ROWS_NODE_SIZE;
286-
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
287-
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), deduplicatedObjects);
288-
size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
289-
// ignore deviceIDs
290-
return size;
303+
acquireDeduplicatedObjectSetIfNeeded(node.getInsertRowNodeList());
304+
try {
305+
long size = RELATIONAL_INSERT_ROWS_NODE_SIZE;
306+
size += calculateFullInsertNodeSize(node, deduplicatedObjects);
307+
size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), deduplicatedObjects);
308+
size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
309+
// ignore deviceIDs
310+
return size;
311+
} finally {
312+
releaseDeduplicatedObjectSet(deduplicatedObjects);
313+
}
291314
}
292315

293316
private static long sizeOfRelationalInsertRowNode(final RelationalInsertRowNode node) {
@@ -775,8 +798,24 @@ private static Set<Object> newDeduplicatedObjectSet() {
775798
return Collections.newSetFromMap(new IdentityHashMap<>());
776799
}
777800

778-
private static Set<Object> newDeduplicatedObjectSetIfNeeded(final List<?> children) {
779-
return children != null && children.size() > 1 ? newDeduplicatedObjectSet() : null;
801+
private static Set<Object> acquireDeduplicatedObjectSetIfNeeded(final List<?> children) {
802+
return children != null && children.size() > 1 ? REUSABLE_DEDUPLICATED_OBJECTS.get() : null;
803+
}
804+
805+
private static void releaseDeduplicatedObjectSet(final Set<Object> deduplicatedObjects) {
806+
if (deduplicatedObjects == null) {
807+
return;
808+
}
809+
final boolean oversized = deduplicatedObjects.size() > MAX_RETAINED_DEDUPLICATED_OBJECTS;
810+
deduplicatedObjects.clear();
811+
if (oversized) {
812+
REUSABLE_DEDUPLICATED_OBJECTS.remove();
813+
}
814+
}
815+
816+
@TestOnly
817+
static void clearReusableDeduplicatedObjectsForTest() {
818+
REUSABLE_DEDUPLICATED_OBJECTS.remove();
780819
}
781820

782821
private static boolean shouldCountObject(

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertRowNode.java‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.iotdb.db.queryengine.plan.analyze.IAnalysis;
3434
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
3535
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.WritePlanNode;
36+
import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.LastCacheUpdateSource;
3637
import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.TreeDeviceSchemaCacheManager;
3738
import org.apache.iotdb.db.storageengine.dataregion.memtable.AbstractMemTable;
3839
import org.apache.iotdb.db.storageengine.dataregion.memtable.IWritableMemChunkGroup;
@@ -60,7 +61,7 @@
6061
import java.util.List;
6162
import java.util.Objects;
6263

63-
public class InsertRowNode extends InsertNode implements WALEntryValue {
64+
public class InsertRowNode extends InsertNode implements WALEntryValue, LastCacheUpdateSource {
6465

6566
private static final byte TYPE_RAW_STRING = -1;
6667

@@ -1010,6 +1011,21 @@ public TimeValuePair composeTimeValuePair(int columnIndex) {
10101011
return new TimeValuePair(time, TsPrimitiveType.getByType(dataTypes[columnIndex], value));
10111012
}
10121013

1014+
@Override
1015+
public long getLastCacheTimestamp() {
1016+
return time;
1017+
}
1018+
1019+
@Override
1020+
public boolean hasLastCacheValue(final int index) {
1021+
return canComposeTimeValuePair(index);
1022+
}
1023+
1024+
@Override
1025+
public TimeValuePair getLastCacheValue(final int index) {
1026+
return composeTimeValuePair(index);
1027+
}
1028+
10131029
private boolean canComposeTimeValuePair(final int columnIndex) {
10141030
return measurements != null
10151031
&& columnIndex >= 0
@@ -1026,18 +1042,9 @@ private boolean canComposeTimeValuePair(final int columnIndex) {
10261042
}
10271043

10281044
public void updateLastCache(String databaseName) {
1029-
TimeValuePair[] timeValuePairs = new TimeValuePair[measurements.length];
1030-
for (int i = 0; i < measurements.length; i++) {
1031-
timeValuePairs[i] = composeTimeValuePair(i);
1032-
}
10331045
TreeDeviceSchemaCacheManager.getInstance()
10341046
.updateLastCacheIfExists(
1035-
databaseName,
1036-
getDeviceID(),
1037-
measurements,
1038-
timeValuePairs,
1039-
isAligned,
1040-
measurementSchemas);
1047+
databaseName, getDeviceID(), measurements, this, isAligned, measurementSchemas);
10411048
}
10421049

10431050
@Override

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowNode.java‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import org.apache.tsfile.enums.TSDataType;
3636
import org.apache.tsfile.file.metadata.IDeviceID;
3737
import org.apache.tsfile.file.metadata.IDeviceID.Factory;
38-
import org.apache.tsfile.read.TimeValuePair;
3938
import org.apache.tsfile.utils.ReadWriteIOUtils;
4039
import org.apache.tsfile.write.schema.MeasurementSchema;
4140

@@ -281,13 +280,9 @@ protected PartialPath readTargetPath(DataInputStream stream)
281280

282281
@Override
283282
public void updateLastCache(String databaseName) {
284-
TimeValuePair[] timeValuePairs = new TimeValuePair[measurements.length];
285-
for (int i = 0; i < measurements.length; i++) {
286-
timeValuePairs[i] = composeTimeValuePair(i);
287-
}
288283
TableDeviceSchemaCache.getInstance()
289284
.updateLastCacheIfExists(
290-
databaseName, getDeviceID(), measurements, measurementSchemas, timeValuePairs);
285+
databaseName, getDeviceID(), measurements, measurementSchemas, this);
291286
}
292287

293288
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache;
21+
22+
import org.apache.tsfile.read.TimeValuePair;
23+
24+
/**
25+
* Provides row values lazily when updating last cache on the write path.
26+
*
27+
* <p>{@link #getLastCacheValue(int)} is called only when the corresponding cache entry exists and
28+
* its timestamp is eligible for update.
29+
*/
30+
public interface LastCacheUpdateSource {
31+
32+
long getLastCacheTimestamp();
33+
34+
boolean hasLastCacheValue(int index);
35+
36+
TimeValuePair getLastCacheValue(int index);
37+
}

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceCacheEntry.java‎

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,22 @@ int setMeasurementSchema(
140140
return 0;
141141
}
142142
// Safe here because tree schema is invalidated by the whole entry
143-
final int result =
144-
(deviceSchema.compareAndSet(null, new TreeDeviceNormalSchema(database, isAligned))
145-
? TreeDeviceNormalSchema.INSTANCE_SIZE
146-
: 0);
147-
return deviceSchema.get() instanceof TreeDeviceNormalSchema
148-
? result + ((TreeDeviceNormalSchema) deviceSchema.get()).update(measurements, schemas)
149-
: 0;
143+
IDeviceSchema schema = deviceSchema.get();
144+
int result = 0;
145+
if (schema == null) {
146+
final TreeDeviceNormalSchema newSchema = new TreeDeviceNormalSchema(database, isAligned);
147+
if (deviceSchema.compareAndSet(null, newSchema)) {
148+
schema = newSchema;
149+
result = TreeDeviceNormalSchema.INSTANCE_SIZE;
150+
} else {
151+
schema = deviceSchema.get();
152+
}
153+
}
154+
if (!(schema instanceof TreeDeviceNormalSchema)) {
155+
return 0;
156+
}
157+
result += ((TreeDeviceNormalSchema) schema).update(measurements, schemas);
158+
return deviceSchema.get() == schema ? result : 0;
150159
}
151160

152161
IDeviceSchema getDeviceSchema() {
@@ -203,6 +212,18 @@ int tryUpdateLastCache(
203212
return Objects.nonNull(lastCache.get()) ? result : 0;
204213
}
205214

215+
int tryUpdateLastCache(
216+
final String[] measurements,
217+
final @Nullable IMeasurementSchema[] measurementSchemas,
218+
final LastCacheUpdateSource updateSource) {
219+
final TableDeviceLastCache cache = lastCache.get();
220+
final int result =
221+
Objects.nonNull(cache)
222+
? cache.tryUpdate(measurements, measurementSchemas, updateSource)
223+
: 0;
224+
return Objects.nonNull(lastCache.get()) ? result : 0;
225+
}
226+
206227
int tryUpdateLastCache(final String[] measurements, final TimeValuePair[] timeValuePairs) {
207228
return tryUpdateLastCache(measurements, timeValuePairs, false);
208229
}

0 commit comments

Comments
 (0)