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

JackieTien97 pushed a commit to branch rc/2.0.11
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/rc/2.0.11 by this push:
     new ba3d598b9c7 [Performance] Reduce temporary allocations on insert path 
(#18240)
ba3d598b9c7 is described below

commit ba3d598b9c7b72821bc86ec2dfd222a04bd77f76
Author: Caideyipi <[email protected]>
AuthorDate: Mon Aug 24 16:21:16 2026 +0800

    [Performance] Reduce temporary allocations on insert path (#18240)
---
 .../resource/memory/InsertNodeMemoryEstimator.java | 121 ++++++++++++++------
 .../planner/plan/node/write/InsertRowNode.java     |  29 +++--
 .../plan/node/write/RelationalInsertRowNode.java   |   7 +-
 .../fetcher/cache/LastCacheUpdateSource.java       |  37 ++++++
 .../fetcher/cache/TableDeviceCacheEntry.java       |  35 ++++--
 .../fetcher/cache/TableDeviceLastCache.java        | 124 ++++++++++++++-------
 .../fetcher/cache/TableDeviceSchemaCache.java      |  36 +++++-
 .../cache/TreeDeviceSchemaCacheManager.java        |  11 ++
 8 files changed, 304 insertions(+), 96 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..49561cdba19 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
@@ -155,6 +155,12 @@ public class InsertNodeMemoryEstimator {
   // from the actual result because the properties of the parent class are not 
added.
   private static final double INSERT_ROW_NODE_EXPANSION_FACTOR = 1.3;
 
+  // Insert nodes are estimated on write threads. Reuse the identity set 
between events,
+  // but discard unusually large sets to avoid retaining a large table on 
every write thread.
+  private static final int MAX_RETAINED_DEDUPLICATED_OBJECTS = 1024;
+  private static final ThreadLocal<Set<Object>> REUSABLE_DEDUPLICATED_OBJECTS =
+      
ThreadLocal.withInitial(InsertNodeMemoryEstimator::newDeduplicatedObjectSet);
+
   public static long sizeOf(final InsertNode insertNode) {
     try {
       final String className = insertNode.getClass().getSimpleName();
@@ -220,7 +226,12 @@ public class InsertNodeMemoryEstimator {
   }
 
   private static long sizeOfInsertTabletNode(final InsertTabletNode node) {
-    return sizeOfInsertTabletNode(node, newDeduplicatedObjectSet());
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      return sizeOfInsertTabletNode(node, deduplicatedObjects);
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfInsertTabletNode(
@@ -235,7 +246,12 @@ public class InsertNodeMemoryEstimator {
   }
 
   private static long sizeOfInsertRowNode(final InsertRowNode node) {
-    return sizeOfInsertRowNode(node, newDeduplicatedObjectSet());
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      return sizeOfInsertRowNode(node, deduplicatedObjects);
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfInsertRowNode(
@@ -247,47 +263,68 @@ public class InsertNodeMemoryEstimator {
   }
 
   private static long sizeOfInsertRowsNode(final InsertRowsNode node) {
-    final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
-    long size = INSERT_ROWS_NODE_SIZE;
-    size += calculateFullInsertNodeSize(node, deduplicatedObjects);
-    size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), 
deduplicatedObjects);
-    size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
-    size += sizeOfResults(node.getResults());
-    return size;
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      long size = INSERT_ROWS_NODE_SIZE;
+      size += calculateFullInsertNodeSize(node, deduplicatedObjects);
+      size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), 
deduplicatedObjects);
+      size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
+      size += sizeOfResults(node.getResults());
+      return size;
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfInsertRowsOfOneDeviceNode(final 
InsertRowsOfOneDeviceNode node) {
-    final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
-    long size = INSERT_ROWS_OF_ONE_DEVICE_NODE_SIZE;
-    size += calculateFullInsertNodeSize(node, deduplicatedObjects);
-    size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), 
deduplicatedObjects);
-    size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
-    size += sizeOfResults(node.getResults());
-    return size;
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      long size = INSERT_ROWS_OF_ONE_DEVICE_NODE_SIZE;
+      size += calculateFullInsertNodeSize(node, deduplicatedObjects);
+      size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), 
deduplicatedObjects);
+      size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
+      size += sizeOfResults(node.getResults());
+      return size;
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfInsertMultiTabletsNode(final 
InsertMultiTabletsNode node) {
-    final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
-    long size = INSERT_MULTI_TABLETS_NODE_SIZE;
-    size += calculateFullInsertNodeSize(node, deduplicatedObjects);
-    size += sizeOfInsertTabletNodeList(node.getInsertTabletNodeList(), 
deduplicatedObjects);
-    size += sizeOfIntegerList(node.getParentInsertTabletNodeIndexList());
-    size += sizeOfResults(node.getResults());
-    return size;
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      long size = INSERT_MULTI_TABLETS_NODE_SIZE;
+      size += calculateFullInsertNodeSize(node, deduplicatedObjects);
+      size += sizeOfInsertTabletNodeList(node.getInsertTabletNodeList(), 
deduplicatedObjects);
+      size += sizeOfIntegerList(node.getParentInsertTabletNodeIndexList());
+      size += sizeOfResults(node.getResults());
+      return size;
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfRelationalInsertRowsNode(final 
RelationalInsertRowsNode node) {
-    final Set<Object> deduplicatedObjects = newDeduplicatedObjectSet();
-    long size = RELATIONAL_INSERT_ROWS_NODE_SIZE;
-    size += calculateFullInsertNodeSize(node, deduplicatedObjects);
-    size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), 
deduplicatedObjects);
-    size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
-    // ignore deviceIDs
-    return size;
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      long size = RELATIONAL_INSERT_ROWS_NODE_SIZE;
+      size += calculateFullInsertNodeSize(node, deduplicatedObjects);
+      size += sizeOfInsertRowNodeList(node.getInsertRowNodeList(), 
deduplicatedObjects);
+      size += sizeOfIntegerList(node.getInsertRowNodeIndexList());
+      // ignore deviceIDs
+      return size;
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfRelationalInsertRowNode(final 
RelationalInsertRowNode node) {
-    return sizeOfRelationalInsertRowNode(node, newDeduplicatedObjectSet());
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      return sizeOfRelationalInsertRowNode(node, deduplicatedObjects);
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfRelationalInsertRowNode(
@@ -299,7 +336,12 @@ public class InsertNodeMemoryEstimator {
   }
 
   private static long sizeOfRelationalInsertTabletNode(final 
RelationalInsertTabletNode node) {
-    return sizeOfRelationalInsertTabletNode(node, newDeduplicatedObjectSet());
+    final Set<Object> deduplicatedObjects = acquireDeduplicatedObjectSet();
+    try {
+      return sizeOfRelationalInsertTabletNode(node, deduplicatedObjects);
+    } finally {
+      releaseDeduplicatedObjectSet(deduplicatedObjects);
+    }
   }
 
   private static long sizeOfRelationalInsertTabletNode(
@@ -771,6 +813,21 @@ public class InsertNodeMemoryEstimator {
     return Collections.newSetFromMap(new IdentityHashMap<>());
   }
 
+  private static Set<Object> acquireDeduplicatedObjectSet() {
+    return REUSABLE_DEDUPLICATED_OBJECTS.get();
+  }
+
+  private static void releaseDeduplicatedObjectSet(final Set<Object> 
deduplicatedObjects) {
+    if (deduplicatedObjects == null) {
+      return;
+    }
+    final boolean oversized = deduplicatedObjects.size() > 
MAX_RETAINED_DEDUPLICATED_OBJECTS;
+    deduplicatedObjects.clear();
+    if (oversized) {
+      REUSABLE_DEDUPLICATED_OBJECTS.remove();
+    }
+  }
+
   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/queryengine/plan/planner/plan/node/write/InsertRowNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertRowNode.java
index 04768c57b50..5a3628ac06c 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertRowNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertRowNode.java
@@ -33,6 +33,7 @@ import org.apache.iotdb.db.i18n.DataNodeQueryMessages;
 import org.apache.iotdb.db.queryengine.plan.analyze.IAnalysis;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
 import org.apache.iotdb.db.queryengine.plan.planner.plan.node.WritePlanNode;
+import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.LastCacheUpdateSource;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.TreeDeviceSchemaCacheManager;
 import org.apache.iotdb.db.storageengine.dataregion.memtable.AbstractMemTable;
 import 
org.apache.iotdb.db.storageengine.dataregion.memtable.IWritableMemChunkGroup;
@@ -60,7 +61,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
-public class InsertRowNode extends InsertNode implements WALEntryValue {
+public class InsertRowNode extends InsertNode implements WALEntryValue, 
LastCacheUpdateSource {
 
   private static final byte TYPE_RAW_STRING = -1;
 
@@ -1010,6 +1011,21 @@ public class InsertRowNode extends InsertNode implements 
WALEntryValue {
     return new TimeValuePair(time, 
TsPrimitiveType.getByType(dataTypes[columnIndex], value));
   }
 
+  @Override
+  public long getLastCacheTimestamp() {
+    return time;
+  }
+
+  @Override
+  public boolean hasLastCacheValue(final int index) {
+    return canComposeTimeValuePair(index);
+  }
+
+  @Override
+  public TimeValuePair getLastCacheValue(final int index) {
+    return composeTimeValuePair(index);
+  }
+
   private boolean canComposeTimeValuePair(final int columnIndex) {
     return measurements != null
         && columnIndex >= 0
@@ -1026,18 +1042,9 @@ public class InsertRowNode extends InsertNode implements 
WALEntryValue {
   }
 
   public void updateLastCache(String databaseName) {
-    TimeValuePair[] timeValuePairs = new TimeValuePair[measurements.length];
-    for (int i = 0; i < measurements.length; i++) {
-      timeValuePairs[i] = composeTimeValuePair(i);
-    }
     TreeDeviceSchemaCacheManager.getInstance()
         .updateLastCacheIfExists(
-            databaseName,
-            getDeviceID(),
-            measurements,
-            timeValuePairs,
-            isAligned,
-            measurementSchemas);
+            databaseName, getDeviceID(), measurements, this, isAligned, 
measurementSchemas);
   }
 
   @Override
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowNode.java
index b11c0f6784e..f0d0d8de7d6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertRowNode.java
@@ -35,7 +35,6 @@ import 
org.apache.iotdb.db.storageengine.dataregion.wal.buffer.IWALByteBufferVie
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.IDeviceID;
 import org.apache.tsfile.file.metadata.IDeviceID.Factory;
-import org.apache.tsfile.read.TimeValuePair;
 import org.apache.tsfile.utils.ReadWriteIOUtils;
 import org.apache.tsfile.write.schema.MeasurementSchema;
 
@@ -281,13 +280,9 @@ public class RelationalInsertRowNode extends InsertRowNode 
{
 
   @Override
   public void updateLastCache(String databaseName) {
-    TimeValuePair[] timeValuePairs = new TimeValuePair[measurements.length];
-    for (int i = 0; i < measurements.length; i++) {
-      timeValuePairs[i] = composeTimeValuePair(i);
-    }
     TableDeviceSchemaCache.getInstance()
         .updateLastCacheIfExists(
-            databaseName, getDeviceID(), measurements, measurementSchemas, 
timeValuePairs);
+            databaseName, getDeviceID(), measurements, measurementSchemas, 
this);
   }
 
   @Override
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/LastCacheUpdateSource.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/LastCacheUpdateSource.java
new file mode 100644
index 00000000000..2cd45460024
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/LastCacheUpdateSource.java
@@ -0,0 +1,37 @@
+/*
+ * 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.queryengine.plan.relational.metadata.fetcher.cache;
+
+import org.apache.tsfile.read.TimeValuePair;
+
+/**
+ * Provides row values lazily when updating last cache on the write path.
+ *
+ * <p>{@link #getLastCacheValue(int)} is called only when the corresponding 
cache entry exists and
+ * its timestamp is eligible for update.
+ */
+public interface LastCacheUpdateSource {
+
+  long getLastCacheTimestamp();
+
+  boolean hasLastCacheValue(int index);
+
+  TimeValuePair getLastCacheValue(int index);
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceCacheEntry.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceCacheEntry.java
index 5ea51a5809f..394cd509571 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceCacheEntry.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceCacheEntry.java
@@ -140,13 +140,22 @@ public class TableDeviceCacheEntry {
       return 0;
     }
     // Safe here because tree schema is invalidated by the whole entry
-    final int result =
-        (deviceSchema.compareAndSet(null, new TreeDeviceNormalSchema(database, 
isAligned))
-            ? TreeDeviceNormalSchema.INSTANCE_SIZE
-            : 0);
-    return deviceSchema.get() instanceof TreeDeviceNormalSchema
-        ? result + ((TreeDeviceNormalSchema) 
deviceSchema.get()).update(measurements, schemas)
-        : 0;
+    IDeviceSchema schema = deviceSchema.get();
+    int result = 0;
+    if (schema == null) {
+      final TreeDeviceNormalSchema newSchema = new 
TreeDeviceNormalSchema(database, isAligned);
+      if (deviceSchema.compareAndSet(null, newSchema)) {
+        schema = newSchema;
+        result = TreeDeviceNormalSchema.INSTANCE_SIZE;
+      } else {
+        schema = deviceSchema.get();
+      }
+    }
+    if (!(schema instanceof TreeDeviceNormalSchema)) {
+      return 0;
+    }
+    result += ((TreeDeviceNormalSchema) schema).update(measurements, schemas);
+    return deviceSchema.get() == schema ? result : 0;
   }
 
   IDeviceSchema getDeviceSchema() {
@@ -203,6 +212,18 @@ public class TableDeviceCacheEntry {
     return Objects.nonNull(lastCache.get()) ? result : 0;
   }
 
+  int tryUpdateLastCache(
+      final String[] measurements,
+      final @Nullable IMeasurementSchema[] measurementSchemas,
+      final LastCacheUpdateSource updateSource) {
+    final TableDeviceLastCache cache = lastCache.get();
+    final int result =
+        Objects.nonNull(cache)
+            ? cache.tryUpdate(measurements, measurementSchemas, updateSource)
+            : 0;
+    return Objects.nonNull(lastCache.get()) ? result : 0;
+  }
+
   int tryUpdateLastCache(final String[] measurements, final TimeValuePair[] 
timeValuePairs) {
     return tryUpdateLastCache(measurements, timeValuePairs, false);
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceLastCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceLastCache.java
index 325484f6f68..5c79457bada 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceLastCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceLastCache.java
@@ -33,11 +33,11 @@ import javax.annotation.concurrent.GuardedBy;
 import javax.annotation.concurrent.ThreadSafe;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.OptionalLong;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -73,8 +73,9 @@ public class TableDeviceLastCache {
       new TimeValuePair(Long.MIN_VALUE, PLACEHOLDER_NO_VALUE);
 
   // Time is seen as "" as a measurement
-  private final Map<String, TimeValuePair> measurement2CachedLastMap = new 
ConcurrentHashMap<>();
-  private final Map<String, Long> measurement2CachedLastKnownNullTimeMap =
+  private final ConcurrentMap<String, TimeValuePair> measurement2CachedLastMap 
=
+      new ConcurrentHashMap<>();
+  private final ConcurrentMap<String, Long> 
measurement2CachedLastKnownNullTimeMap =
       new ConcurrentHashMap<>();
   private final boolean isTableModel;
 
@@ -143,7 +144,7 @@ public class TableDeviceLastCache {
       final @Nullable IMeasurementSchema[] measurementSchemas,
       final @Nonnull TimeValuePair[] timeValuePairs,
       final boolean invalidateNull) {
-    final AtomicInteger diff = new AtomicInteger(0);
+    int diff = 0;
     long lastTime = Long.MIN_VALUE;
 
     for (int i = 0; i < measurements.length; ++i) {
@@ -151,49 +152,93 @@ public class TableDeviceLastCache {
       if (Objects.isNull(measurement)) {
         continue;
       }
-      if (Objects.isNull(timeValuePairs[i])) {
+      final TimeValuePair timeValuePair = timeValuePairs[i];
+      if (Objects.isNull(timeValuePair)) {
         if (invalidateNull) {
-          diff.addAndGet(removeKnownNullTime(measurement));
-          diff.addAndGet(
-              -((int) RamUsageEstimator.sizeOf(measurement)
-                  + 
getTvPairEntrySize(measurement2CachedLastMap.remove(measurement))));
+          diff += removeKnownNullTime(measurement);
+          diff -=
+              (int) RamUsageEstimator.sizeOf(measurement)
+                  + 
getTvPairEntrySize(measurement2CachedLastMap.remove(measurement));
         }
         continue;
       }
 
-      if (isKnownNullAtAlignedTime(measurement, timeValuePairs[i])) {
-        if (lastTime < timeValuePairs[i].getTimestamp()) {
-          lastTime = timeValuePairs[i].getTimestamp();
-        }
-        diff.addAndGet(tryUpdateKnownNullTime(measurement, 
timeValuePairs[i].getTimestamp()));
+      if (lastTime < timeValuePair.getTimestamp()) {
+        lastTime = timeValuePair.getTimestamp();
+      }
+      if (isKnownNullAtAlignedTime(measurement, timeValuePair)) {
+        diff += tryUpdateKnownNullTime(measurement, 
timeValuePair.getTimestamp());
+      } else {
+        diff += tryUpdateCachedLast(measurement, timeValuePair);
+      }
+    }
+    tryUpdateLastTime(lastTime);
+    return diff;
+  }
+
+  int tryUpdate(
+      final @Nonnull String[] measurements,
+      final @Nullable IMeasurementSchema[] measurementSchemas,
+      final LastCacheUpdateSource updateSource) {
+    int diff = 0;
+    boolean hasValue = false;
+
+    for (int i = 0; i < measurements.length; ++i) {
+      final String measurement = getRawMeasurement(measurements, 
measurementSchemas, i);
+      if (Objects.isNull(measurement) || !updateSource.hasLastCacheValue(i)) {
         continue;
       }
+      hasValue = true;
+      diff += tryUpdateCachedLast(measurement, updateSource, i);
+    }
+    if (hasValue) {
+      tryUpdateLastTime(updateSource.getLastCacheTimestamp());
+    }
+    return diff;
+  }
 
-      final int finalI = i;
-      if (lastTime < timeValuePairs[i].getTimestamp()) {
-        lastTime = timeValuePairs[i].getTimestamp();
+  private int tryUpdateCachedLast(
+      final String measurement, final LastCacheUpdateSource updateSource, 
final int index) {
+    final TimeValuePair cachedPair = 
measurement2CachedLastMap.get(measurement);
+    if (Objects.isNull(cachedPair)
+        || cachedPair.getTimestamp() > updateSource.getLastCacheTimestamp()) {
+      return 0;
+    }
+    final TimeValuePair newPair = updateSource.getLastCacheValue(index);
+    return Objects.nonNull(newPair) ? tryUpdateCachedLast(measurement, 
cachedPair, newPair) : 0;
+  }
+
+  private int tryUpdateCachedLast(final String measurement, final 
TimeValuePair newPair) {
+    return tryUpdateCachedLast(measurement, 
measurement2CachedLastMap.get(measurement), newPair);
+  }
+
+  private int tryUpdateCachedLast(
+      final String measurement, TimeValuePair cachedPair, final TimeValuePair 
newPair) {
+    while (Objects.nonNull(cachedPair) && cachedPair.getTimestamp() <= 
newPair.getTimestamp()) {
+      if (measurement2CachedLastMap.replace(measurement, cachedPair, newPair)) 
{
+        return getDiffSize(cachedPair, newPair)
+            + clearKnownNullTimeIfCovered(measurement, newPair.getTimestamp());
       }
-      measurement2CachedLastMap.computeIfPresent(
-          measurement,
-          (measurementName, tvPair) -> {
-            if (tvPair.getTimestamp() <= 
timeValuePairs[finalI].getTimestamp()) {
-              diff.addAndGet(
-                  getDiffSize(tvPair, timeValuePairs[finalI])
-                      + clearKnownNullTimeIfCovered(
-                          measurementName, 
timeValuePairs[finalI].getTimestamp()));
-              return timeValuePairs[finalI];
-            }
-            return tvPair;
-          });
+      cachedPair = measurement2CachedLastMap.get(measurement);
+    }
+    return 0;
+  }
+
+  private void tryUpdateLastTime(final long lastTime) {
+    if (lastTime == Long.MIN_VALUE) {
+      return;
+    }
+    TimeValuePair cachedPair = measurement2CachedLastMap.get("");
+    if (Objects.isNull(cachedPair) || cachedPair.getTimestamp() >= lastTime) {
+      return;
+    }
+    final TimeValuePair newPair = new TimeValuePair(lastTime, 
PLACEHOLDER_NO_VALUE);
+    while (Objects.nonNull(cachedPair) && cachedPair.getTimestamp() < 
lastTime) {
+      if (measurement2CachedLastMap.replace("", cachedPair, newPair)) {
+        return;
+      }
+      cachedPair = measurement2CachedLastMap.get("");
     }
-    final long finalLastTime = lastTime;
-    measurement2CachedLastMap.computeIfPresent(
-        "",
-        (time, tvPair) ->
-            tvPair.getTimestamp() < finalLastTime
-                ? new TimeValuePair(finalLastTime, PLACEHOLDER_NO_VALUE)
-                : tvPair);
-    return diff.get();
   }
 
   @Nullable
@@ -360,8 +405,9 @@ public class TableDeviceLastCache {
       return 0;
     }
     final Long knownNullTime = 
measurement2CachedLastKnownNullTimeMap.get(measurement);
-    if (knownNullTime != null && knownNullTime <= coveredTime) {
-      measurement2CachedLastKnownNullTimeMap.remove(measurement);
+    if (knownNullTime != null
+        && knownNullTime <= coveredTime
+        && measurement2CachedLastKnownNullTimeMap.remove(measurement, 
knownNullTime)) {
       return -getKnownNullTimeEntrySize();
     }
     return 0;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceSchemaCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceSchemaCache.java
index f712b668689..15bd7ab6eca 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceSchemaCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TableDeviceSchemaCache.java
@@ -305,6 +305,20 @@ public class TableDeviceSchemaCache {
         false);
   }
 
+  public void updateLastCacheIfExists(
+      final String database,
+      final IDeviceID deviceId,
+      final String[] measurements,
+      final @Nullable IMeasurementSchema[] measurementSchemas,
+      final LastCacheUpdateSource updateSource) {
+    dualKeyCache.update(
+        new TableId(database, deviceId.getTableName()),
+        deviceId,
+        null,
+        entry -> entry.tryUpdateLastCache(measurements, measurementSchemas, 
updateSource),
+        false);
+  }
+
   /**
    * Update the last cache in writing or the second push of last cache query. 
If a measurement is
    * with all {@code null}s or is a tag/attribute column, its {@link 
TimeValuePair}[] shall be
@@ -457,7 +471,7 @@ public class TableDeviceSchemaCache {
     dualKeyCache.update(
         new TableId(null, deviceID.getTableName()),
         deviceID,
-        new TableDeviceCacheEntry(),
+        Objects.isNull(timeValuePairs) ? new TableDeviceCacheEntry() : null,
         initOrInvalidate
             ? entry ->
                 entry.setMeasurementSchema(
@@ -475,6 +489,26 @@ public class TableDeviceSchemaCache {
         Objects.isNull(timeValuePairs));
   }
 
+  void updateLastCache(
+      final String database,
+      final IDeviceID deviceID,
+      final String[] measurements,
+      final LastCacheUpdateSource updateSource,
+      final boolean isAligned,
+      final IMeasurementSchema[] measurementSchemas) {
+    final String previousDatabase = 
treeModelDatabasePool.putIfAbsent(database, database);
+    final String database2Use = Objects.nonNull(previousDatabase) ? 
previousDatabase : database;
+
+    dualKeyCache.update(
+        new TableId(null, deviceID.getTableName()),
+        deviceID,
+        null,
+        entry ->
+            entry.setMeasurementSchema(database2Use, isAligned, measurements, 
measurementSchemas)
+                + entry.tryUpdateLastCache(measurements, measurementSchemas, 
updateSource),
+        false);
+  }
+
   public boolean getLastCache(
       final Map<TableId, Map<IDeviceID, Map<String, Pair<TSDataType, 
TimeValuePair>>>> inputMap) {
     return dualKeyCache.batchApply(inputMap, 
TableDeviceCacheEntry::updateInputMap);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TreeDeviceSchemaCacheManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TreeDeviceSchemaCacheManager.java
index 8cabd8ca43f..799f938edd4 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TreeDeviceSchemaCacheManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/fetcher/cache/TreeDeviceSchemaCacheManager.java
@@ -353,6 +353,17 @@ public class TreeDeviceSchemaCacheManager {
         database, deviceID, measurements, timeValuePairs, isAligned, 
measurementSchemas, false);
   }
 
+  public void updateLastCacheIfExists(
+      final String database,
+      final IDeviceID deviceID,
+      final String[] measurements,
+      final LastCacheUpdateSource updateSource,
+      final boolean isAligned,
+      final IMeasurementSchema[] measurementSchemas) {
+    tableDeviceSchemaCache.updateLastCache(
+        database, deviceID, measurements, updateSource, isAligned, 
measurementSchemas);
+  }
+
   /**
    * Update the {@link TableDeviceLastCache} on query in tree model.
    *

Reply via email to