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 94694d92f25 Avoid redundant TsFile table schema registration (#18731)
94694d92f25 is described below

commit 94694d92f25531264ca2373a43e9268be19dfd6b
Author: Caideyipi <[email protected]>
AuthorDate: Wed Sep 30 10:03:32 2026 +0800

    Avoid redundant TsFile table schema registration (#18731)
---
 .../db/schemaengine/table/DataNodeTableCache.java  |   1 +
 .../iotdb/db/schemaengine/table/ITableCache.java   |   2 +
 .../db/storageengine/dataregion/DataRegion.java    |   6 +-
 .../dataregion/memtable/TsFileProcessor.java       |  18 +-
 .../dataregion/memtable/TsFileProcessorTest.java   |  53 +++++
 ...FileTableSchemaRegistrationPerformanceTest.java | 230 +++++++++++++++++++++
 6 files changed, 303 insertions(+), 7 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
index 23daf268206..807387f8c29 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
@@ -435,6 +435,7 @@ public class DataNodeTableCache implements ITableCache {
     }
   }
 
+  @Override
   public long getInstanceVersion() {
     return instanceVersion.get();
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/ITableCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/ITableCache.java
index 13b05058955..13193fbff13 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/ITableCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/ITableCache.java
@@ -29,6 +29,8 @@ import java.util.Map;
 
 public interface ITableCache {
 
+  long getInstanceVersion();
+
   void init(final byte[] tableInitializationBytes);
 
   void preUpdateTable(final String database, final TsTable table, final 
@Nullable String oldName);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
index e8da377db6b..802d440cc44 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
@@ -98,6 +98,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.La
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.TableDeviceSchemaCache;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.TreeDeviceSchemaCacheManager;
 import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache;
+import org.apache.iotdb.db.schemaengine.table.ITableCache;
 import org.apache.iotdb.db.service.SettleService;
 import org.apache.iotdb.db.service.metrics.CompactionMetrics;
 import org.apache.iotdb.db.service.metrics.DataNodeExceptionMetrics;
@@ -1730,14 +1731,15 @@ public class DataRegion implements IDataRegionForQuery {
   private void registerToTsFile(InsertNode node, TsFileProcessor 
tsFileProcessor) {
     final String tableName = node.getTableName();
     if (tableName != null) {
+      final ITableCache tableCache = DataNodeTableCache.getInstance();
       tsFileProcessor.registerToTsFile(
           tableName,
+          tableCache.getInstanceVersion(),
           t -> {
             final String database = getDatabaseName();
 
             TsTable tsTable =
-                DataNodeTableCache.getInstance()
-                    .getTable(database, t, false, 
LeaseFencedRetryPolicy.RETRY_UNTIL_SUCCESS);
+                tableCache.getTable(database, t, false, 
LeaseFencedRetryPolicy.RETRY_UNTIL_SUCCESS);
             if (tsTable == null) {
               // There is a high probability that the leader node has been 
executed and is currently
               // located in the follower node.
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
index 6724d196443..78c48a2702b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
@@ -113,6 +113,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutionException;
@@ -156,6 +157,8 @@ public class TsFileProcessor {
   /** Writer for restore tsfile and flushing. */
   private RestorableTsFileIOWriter writer;
 
+  private final Map<String, Long> registeredTableSchemaVersions = new 
ConcurrentHashMap<>();
+
   /** Tsfile resource for index this tsfile. */
   private final TsFileResource tsFileResource;
 
@@ -2736,11 +2739,16 @@ public class TsFileProcessor {
   }
 
   public void registerToTsFile(
-      String tableName, Function<String, TableSchema> tableSchemaFunction) {
-    getWriter()
-        .getSchema()
-        .getTableSchemaMap()
-        .put(tableName, tableSchemaFunction.apply(tableName));
+      String tableName, long tableCacheVersion, Function<String, TableSchema> 
tableSchemaFunction) {
+    registeredTableSchemaVersions.compute(
+        tableName,
+        (name, registeredVersion) -> {
+          if (registeredVersion != null && registeredVersion == 
tableCacheVersion) {
+            return registeredVersion;
+          }
+          getWriter().getSchema().getTableSchemaMap().put(name, 
tableSchemaFunction.apply(name));
+          return tableCacheVersion;
+        });
   }
 
   public void writeLock() {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
index f1ca1d597c3..b4117ec121a 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
@@ -54,6 +54,7 @@ import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.external.commons.io.FileUtils;
 import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.TableSchema;
 import org.apache.tsfile.file.metadata.enums.CompressionType;
 import org.apache.tsfile.file.metadata.enums.TSEncoding;
 import org.apache.tsfile.read.TimeValuePair;
@@ -145,6 +146,58 @@ public class TsFileProcessorTest {
     config.setTargetChunkSize(defaultTargetChunkSize);
   }
 
+  @Test
+  public void testRegisterToTsFileCachesTableSchemaByVersion() throws 
IOException {
+    processor =
+        new TsFileProcessor(
+            storageGroup,
+            SystemFileFactory.INSTANCE.getFile(filePath),
+            sgInfo,
+            this::closeTsFileProcessor,
+            (tsFileProcessor, updateMap, systemFlushTime) -> {},
+            true);
+
+    final AtomicInteger supplierCalls = new AtomicInteger();
+    final AtomicInteger secondTableSupplierCalls = new AtomicInteger();
+    final TableSchema firstSchema = new TableSchema("table1");
+    final TableSchema updatedSchema = new TableSchema("table1");
+    final TableSchema secondTableSchema = new TableSchema("table2");
+
+    processor.registerToTsFile(
+        "table1",
+        1,
+        tableName -> supplierCalls.incrementAndGet() == 1 ? firstSchema : 
updatedSchema);
+    processor.registerToTsFile(
+        "table1",
+        1,
+        tableName -> supplierCalls.incrementAndGet() == 1 ? firstSchema : 
updatedSchema);
+
+    Assert.assertEquals(1, supplierCalls.get());
+    Assert.assertSame(
+        firstSchema, 
processor.getWriter().getSchema().getTableSchemaMap().get("table1"));
+
+    processor.registerToTsFile(
+        "table2",
+        1,
+        tableName -> {
+          secondTableSupplierCalls.incrementAndGet();
+          return secondTableSchema;
+        });
+
+    Assert.assertEquals(1, secondTableSupplierCalls.get());
+    Assert.assertSame(
+        secondTableSchema, 
processor.getWriter().getSchema().getTableSchemaMap().get("table2"));
+
+    processor.registerToTsFile(
+        "table1",
+        2,
+        tableName -> supplierCalls.incrementAndGet() == 1 ? firstSchema : 
updatedSchema);
+
+    Assert.assertEquals(2, supplierCalls.get());
+    Assert.assertSame(
+        updatedSchema, 
processor.getWriter().getSchema().getTableSchemaMap().get("table1"));
+  }
+
   @Test
   public void testWriteAndFlush()
       throws IOException, WriteProcessException, MetadataException, 
ExecutionException {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileTableSchemaRegistrationPerformanceTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileTableSchemaRegistrationPerformanceTest.java
new file mode 100644
index 00000000000..ad4b2dc84af
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileTableSchemaRegistrationPerformanceTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.storageengine.dataregion.memtable;
+
+import org.apache.iotdb.commons.file.SystemFileFactory;
+import org.apache.iotdb.db.storageengine.dataregion.DataRegionInfo;
+import org.apache.iotdb.db.storageengine.dataregion.DataRegionTest;
+import org.apache.iotdb.db.storageengine.rescon.memory.SystemInfo;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
+import org.apache.iotdb.db.utils.constant.TestConstant;
+
+import org.apache.tsfile.file.metadata.TableSchema;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+
+public class TsFileTableSchemaRegistrationPerformanceTest {
+
+  private static final String ENABLED_PROPERTY = 
"iotdb.tsfile.table.schema.perf.enabled";
+  private static final String WARMUP_ITERATIONS_PROPERTY =
+      "iotdb.tsfile.table.schema.perf.warmup.iterations";
+  private static final String ITERATIONS_PROPERTY = 
"iotdb.tsfile.table.schema.perf.iterations";
+  private static final String ROUNDS_PROPERTY = 
"iotdb.tsfile.table.schema.perf.rounds";
+  private static volatile int benchmarkBlackhole;
+
+  @Test
+  public void tableSchemaRegistrationBenchmark() throws Exception {
+    Assume.assumeTrue(
+        String.format(
+            "Manual performance UT. Enable with -D%s=true, optionally tune 
-D%s, -D%s and -D%s.",
+            ENABLED_PROPERTY, WARMUP_ITERATIONS_PROPERTY, ITERATIONS_PROPERTY, 
ROUNDS_PROPERTY),
+        Boolean.getBoolean(ENABLED_PROPERTY));
+    Assume.assumeTrue(
+        "Current-thread CPU time and allocation metrics are required.",
+        ManualPerformanceTestUtils.enableThreadMetrics());
+
+    final int warmupIterations = 
Integer.getInteger(WARMUP_ITERATIONS_PROPERTY, 20_000);
+    final int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 100_000);
+    final int rounds = Integer.getInteger(ROUNDS_PROPERTY, 7);
+    Assert.assertTrue(warmupIterations > 0);
+    Assert.assertTrue(iterations > 0);
+    Assert.assertTrue(rounds > 0);
+
+    final String storageGroup = "root.vehicle";
+    final String systemDir = TestConstant.OUTPUT_DATA_DIR.concat("info");
+    final String filePath = TestConstant.getTestTsFilePath(storageGroup, 0, 0, 
0);
+    TsFileProcessor processor = null;
+    EnvironmentUtils.envSetUp();
+    try {
+      final File file = SystemFileFactory.INSTANCE.getFile(filePath);
+      if (!file.getParentFile().exists()) {
+        Assert.assertTrue(file.getParentFile().mkdirs());
+      }
+      final DataRegionInfo regionInfo =
+          new DataRegionInfo(new DataRegionTest.DummyDataRegion(systemDir, 
storageGroup));
+      processor =
+          new TsFileProcessor(
+              storageGroup,
+              file,
+              regionInfo,
+              tsFileProcessor -> {},
+              (tsFileProcessor, updateMap, systemFlushTime) -> {},
+              true);
+      final TsFileProcessorInfo processorInfo = new 
TsFileProcessorInfo(regionInfo);
+      processor.setTsFileProcessorInfo(processorInfo);
+      regionInfo.initTsFileProcessorInfo(processor);
+      SystemInfo.getInstance().reportStorageGroupStatus(regionInfo, processor);
+
+      final AtomicLong legacySupplierCalls = new AtomicLong();
+      final AtomicLong cachedSupplierCalls = new AtomicLong();
+      final Function<String, TableSchema> legacySupplier =
+          tableName -> {
+            legacySupplierCalls.incrementAndGet();
+            return new TableSchema(tableName);
+          };
+      final Function<String, TableSchema> cachedSupplier =
+          tableName -> {
+            cachedSupplierCalls.incrementAndGet();
+            return new TableSchema(tableName);
+          };
+
+      runLegacy(processor, warmupIterations, legacySupplier);
+      runCached(processor, warmupIterations, cachedSupplier);
+      legacySupplierCalls.set(0);
+      cachedSupplierCalls.set(0);
+
+      final Measurement[] legacyMeasurements = new Measurement[rounds];
+      final Measurement[] cachedMeasurements = new Measurement[rounds];
+      for (int round = 0; round < rounds; ++round) {
+        if ((round & 1) == 0) {
+          legacyMeasurements[round] = measureLegacy(processor, iterations, 
legacySupplier);
+          cachedMeasurements[round] = measureCached(processor, iterations, 
cachedSupplier);
+        } else {
+          cachedMeasurements[round] = measureCached(processor, iterations, 
cachedSupplier);
+          legacyMeasurements[round] = measureLegacy(processor, iterations, 
legacySupplier);
+        }
+      }
+
+      final Summary legacySummary =
+          ManualPerformanceTestUtils.summarize(legacyMeasurements, iterations);
+      final Summary cachedSummary =
+          ManualPerformanceTestUtils.summarize(cachedMeasurements, iterations);
+      Assert.assertEquals((long) iterations * rounds, 
legacySupplierCalls.get());
+      Assert.assertEquals(0, cachedSupplierCalls.get());
+      printResult(
+          warmupIterations,
+          iterations,
+          rounds,
+          legacySummary,
+          cachedSummary,
+          legacySupplierCalls.get(),
+          cachedSupplierCalls.get());
+    } finally {
+      if (processor != null) {
+        processor.syncClose();
+      }
+      EnvironmentUtils.cleanEnv();
+      EnvironmentUtils.cleanDir(TestConstant.OUTPUT_DATA_DIR);
+    }
+  }
+
+  private static Measurement measureLegacy(
+      TsFileProcessor processor,
+      int iterations,
+      Function<String, TableSchema> tableSchemaFunction) {
+    return ManualPerformanceTestUtils.measure(
+        iterations, () -> legacyRegister(processor, tableSchemaFunction));
+  }
+
+  private static Measurement measureCached(
+      TsFileProcessor processor,
+      int iterations,
+      Function<String, TableSchema> tableSchemaFunction) {
+    return ManualPerformanceTestUtils.measure(
+        iterations, () -> cachedRegister(processor, tableSchemaFunction));
+  }
+
+  private static void runLegacy(
+      TsFileProcessor processor,
+      int iterations,
+      Function<String, TableSchema> tableSchemaFunction) {
+    for (int i = 0; i < iterations; ++i) {
+      legacyRegister(processor, tableSchemaFunction);
+    }
+  }
+
+  private static void runCached(
+      TsFileProcessor processor,
+      int iterations,
+      Function<String, TableSchema> tableSchemaFunction) {
+    for (int i = 0; i < iterations; ++i) {
+      cachedRegister(processor, tableSchemaFunction);
+    }
+  }
+
+  private static void legacyRegister(
+      TsFileProcessor processor, Function<String, TableSchema> 
tableSchemaFunction) {
+    final String tableName = "legacy_table";
+    processor
+        .getWriter()
+        .getSchema()
+        .getTableSchemaMap()
+        .put(tableName, tableSchemaFunction.apply(tableName));
+    benchmarkBlackhole = 
processor.getWriter().getSchema().getTableSchemaMap().size();
+  }
+
+  private static void cachedRegister(
+      TsFileProcessor processor, Function<String, TableSchema> 
tableSchemaFunction) {
+    processor.registerToTsFile("cached_table", 1, tableSchemaFunction);
+    benchmarkBlackhole = 
processor.getWriter().getSchema().getTableSchemaMap().size();
+  }
+
+  private static void printResult(
+      int warmupIterations,
+      int iterations,
+      int rounds,
+      Summary legacySummary,
+      Summary cachedSummary,
+      long legacySupplierCalls,
+      long cachedSupplierCalls) {
+    System.out.printf(
+        Locale.ROOT,
+        "TsFile table schema registration benchmark: warmup=%d, iterations=%d, 
rounds=%d%n"
+            + "legacy: cpu=%.2f ns/op, allocation=%.2f bytes/op, 
supplierCalls=%d%n"
+            + "cached: cpu=%.2f ns/op, allocation=%.2f bytes/op, 
supplierCalls=%d%n"
+            + "reduction: cpu=%.2f%%, allocation=%.2f%%%n",
+        warmupIterations,
+        iterations,
+        rounds,
+        legacySummary.getCpuNanosPerOperation(),
+        legacySummary.getAllocatedBytesPerOperation(),
+        legacySupplierCalls,
+        cachedSummary.getCpuNanosPerOperation(),
+        cachedSummary.getAllocatedBytesPerOperation(),
+        cachedSupplierCalls,
+        reduction(legacySummary.getCpuNanosPerOperation(), 
cachedSummary.getCpuNanosPerOperation()),
+        reduction(
+            legacySummary.getAllocatedBytesPerOperation(),
+            cachedSummary.getAllocatedBytesPerOperation()));
+  }
+
+  private static double reduction(double before, double after) {
+    return before == 0 ? 0 : (before - after) * 100 / before;
+  }
+}

Reply via email to