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

jiangtian pushed a commit to branch cache_non_exist_timeseries_metadata
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 1cd27fa2c17b3f5d38a30ca42b2c9bc7819b6b14
Author: Tian Jiang <[email protected]>
AuthorDate: Fri Oct 17 12:01:35 2025 +0800

    Cache non-exist timeseries metadata to reduce IO
---
 .../apache/iotdb/db/conf/DataNodeMemoryConfig.java |  18 +++
 .../buffer/TimeSeriesMetadataCache.java            |  19 ++-
 .../buffer/TimeSeriesMetadataCacheTest.java        | 139 +++++++++++++++++++++
 .../conf/iotdb-system.properties.template          |   5 +
 4 files changed, 180 insertions(+), 1 deletion(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/DataNodeMemoryConfig.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/DataNodeMemoryConfig.java
index efb6758066f..d07bcc61d74 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/DataNodeMemoryConfig.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/DataNodeMemoryConfig.java
@@ -56,6 +56,12 @@ public class DataNodeMemoryConfig {
   /** whether to cache metadata(ChunkMetaData and TsFileMetaData) or not. */
   private boolean metaDataCacheEnable = true;
 
+  /**
+   * If a timeseries is not found in a TsFile, also cache a placeholder to 
indicate the
+   * non-existence.
+   */
+  private boolean mayCacheNonExistSeries = true;
+
   /** How many threads can concurrently execute query statement. When <= 0, 
use CPU core number. */
   private int queryThreadCount = Runtime.getRuntime().availableProcessors();
 
@@ -407,6 +413,10 @@ public class DataNodeMemoryConfig {
         Boolean.parseBoolean(
             properties.getProperty(
                 "meta_data_cache_enable", 
Boolean.toString(isMetaDataCacheEnable()))));
+    setMayCacheNonExistSeries(
+        Boolean.parseBoolean(
+            properties.getProperty(
+                "may_cache_nonexist_series", 
Boolean.toString(isMetaDataCacheEnable()))));
     setQueryThreadCount(
         Integer.parseInt(
             properties.getProperty("query_thread_count", 
Integer.toString(getQueryThreadCount()))));
@@ -560,6 +570,14 @@ public class DataNodeMemoryConfig {
     this.metaDataCacheEnable = metaDataCacheEnable;
   }
 
+  public boolean isMayCacheNonExistSeries() {
+    return mayCacheNonExistSeries;
+  }
+
+  public void setMayCacheNonExistSeries(boolean mayCacheNonExistSeries) {
+    this.mayCacheNonExistSeries = mayCacheNonExistSeries;
+  }
+
   public int getQueryThreadCount() {
     return queryThreadCount;
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/buffer/TimeSeriesMetadataCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/buffer/TimeSeriesMetadataCache.java
index c3fef67b5f1..052d4b0b34f 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/buffer/TimeSeriesMetadataCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/buffer/TimeSeriesMetadataCache.java
@@ -35,10 +35,13 @@ import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.github.benmanes.caffeine.cache.Weigher;
 import org.apache.tsfile.common.constant.TsFileConstant;
+import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.IDeviceID;
 import org.apache.tsfile.file.metadata.TimeseriesMetadata;
+import org.apache.tsfile.file.metadata.statistics.Statistics;
 import org.apache.tsfile.read.TsFileSequenceReader;
 import org.apache.tsfile.utils.BloomFilter;
+import org.apache.tsfile.utils.PublicBAOS;
 import org.apache.tsfile.utils.RamUsageEstimator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -69,6 +72,14 @@ public class TimeSeriesMetadataCache {
       IoTDBDescriptor.getInstance().getMemoryConfig();
   private static final IMemoryBlock CACHE_MEMORY_BLOCK;
   private static final boolean CACHE_ENABLE = 
memoryConfig.isMetaDataCacheEnable();
+  private static final TimeseriesMetadata NULL_EXISTS_CACHE_PLACE_HOLDER =
+      new TimeseriesMetadata(
+          (byte) 0,
+          0,
+          "",
+          TSDataType.INT32,
+          Statistics.getStatsByType(TSDataType.INT32),
+          new PublicBAOS());
 
   private final Cache<TimeSeriesMetadataCacheKey, TimeseriesMetadata> lruCache;
 
@@ -188,6 +199,9 @@ public class TimeSeriesMetadataCache {
                 DEBUG_LOGGER.info("TimeSeries meta data {} is filter by 
bloomFilter!", key);
               }
               loadBloomFilterTime = System.nanoTime() - 
loadBloomFilterStartTime;
+              if (memoryConfig.isMayCacheNonExistSeries()) {
+                lruCache.put(key, NULL_EXISTS_CACHE_PLACE_HOLDER);
+              }
               return null;
             }
 
@@ -217,10 +231,13 @@ public class TimeSeriesMetadataCache {
           }
         }
       }
-      if (timeseriesMetadata == null) {
+      if (timeseriesMetadata == null || timeseriesMetadata == 
NULL_EXISTS_CACHE_PLACE_HOLDER) {
         if (debug) {
           DEBUG_LOGGER.info("The file doesn't have this time series {}.", key);
         }
+        if (timeseriesMetadata == null && 
memoryConfig.isMayCacheNonExistSeries()) {
+          lruCache.put(key, NULL_EXISTS_CACHE_PLACE_HOLDER);
+        }
         return null;
       } else {
         if (debug) {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/buffer/TimeSeriesMetadataCacheTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/buffer/TimeSeriesMetadataCacheTest.java
new file mode 100644
index 00000000000..49fb6c555f6
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/buffer/TimeSeriesMetadataCacheTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.buffer;
+
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.queryengine.execution.fragment.QueryContext;
+import 
org.apache.iotdb.db.storageengine.buffer.TimeSeriesMetadataCache.TimeSeriesMetadataCacheKey;
+import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileID;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.exception.write.WriteProcessException;
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.IDeviceID.Factory;
+import org.apache.tsfile.write.TsFileWriter;
+import org.apache.tsfile.write.record.TSRecord;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TimeSeriesMetadataCacheTest {
+
+  private void testCachePlaceHolderInternal() throws IOException, 
WriteProcessException {
+    TimeSeriesMetadataCache.getInstance().clear();
+    File file = new File("target/test.tsfile");
+    TsFileID tsFileID = new TsFileID();
+
+    int deviceCnt = 100;
+    int seriesPerDevice = 100;
+    List<IDeviceID> deviceIDList = new ArrayList<>();
+    for (int i = 0; i < deviceCnt; i++) {
+      deviceIDList.add(Factory.DEFAULT_FACTORY.create("root.d" + i));
+    }
+
+    try (TsFileWriter tsFileWriter = new TsFileWriter(file)) {
+      // 100*100 series in the file
+
+      for (int i = 0; i < deviceCnt; i++) {
+        for (int j = 0; j < seriesPerDevice; j++) {
+          tsFileWriter.registerTimeseries(
+              deviceIDList.get(i), new MeasurementSchema("s" + j, 
TSDataType.INT32));
+        }
+      }
+      for (int i = 0; i < deviceCnt; i++) {
+        TSRecord rec = new TSRecord(deviceIDList.get(i), 0);
+        for (int j = 0; j < seriesPerDevice; j++) {
+          rec.addPoint("s" + j, 0);
+        }
+        tsFileWriter.writeRecord(rec);
+      }
+      tsFileWriter.close();
+
+      // read 100*200 series each 10 times in the file
+      TimeSeriesMetadataCache.getInstance().clear();
+      long start = System.currentTimeMillis();
+      QueryContext queryContext = new QueryContext();
+      // put k in outer loop
+      for (int k = 0; k < 10; k++) {
+        for (int i = 0; i < deviceCnt; i++) {
+          for (int j = 0; j < seriesPerDevice; j++) {
+            TimeSeriesMetadataCacheKey key =
+                new TimeSeriesMetadataCacheKey(tsFileID, deviceIDList.get(i), 
"s" + j);
+            TimeSeriesMetadataCache.getInstance()
+                .get(file.getPath(), key, Collections.EMPTY_SET, true, false, 
queryContext);
+          }
+        }
+      }
+      System.out.println("time cost with outer k: " + 
(System.currentTimeMillis() - start));
+
+      TimeSeriesMetadataCache.getInstance().clear();
+      start = System.currentTimeMillis();
+      queryContext = new QueryContext();
+      // put k in inner loop
+      for (int i = 0; i < deviceCnt; i++) {
+        for (int j = 0; j < seriesPerDevice; j++) {
+          TimeSeriesMetadataCacheKey key =
+              new TimeSeriesMetadataCacheKey(tsFileID, deviceIDList.get(i), 
"s" + j);
+          for (int k = 0; k < 10; k++) {
+            TimeSeriesMetadataCache.getInstance()
+                .get(file.getPath(), key, Collections.EMPTY_SET, true, false, 
queryContext);
+          }
+        }
+      }
+      System.out.println("time cost with inner k: " + 
(System.currentTimeMillis() - start));
+    } finally {
+      file.delete();
+    }
+  }
+
+  @Ignore("Performance")
+  @Test
+  public void testCachePlaceHolder() throws IOException, WriteProcessException 
{
+    boolean mayCacheNonExistSeries =
+        
IoTDBDescriptor.getInstance().getMemoryConfig().isMayCacheNonExistSeries();
+    try {
+      System.out.println("warming up");
+      System.out.println("Do not cache non-exist series");
+      
IoTDBDescriptor.getInstance().getMemoryConfig().setMayCacheNonExistSeries(false);
+      testCachePlaceHolderInternal();
+      System.out.println("Cache non-exist series");
+      
IoTDBDescriptor.getInstance().getMemoryConfig().setMayCacheNonExistSeries(true);
+      testCachePlaceHolderInternal();
+
+      System.out.println("actual test");
+      System.out.println("Do not cache non-exist series");
+      
IoTDBDescriptor.getInstance().getMemoryConfig().setMayCacheNonExistSeries(false);
+      testCachePlaceHolderInternal();
+      System.out.println("Cache non-exist series");
+      
IoTDBDescriptor.getInstance().getMemoryConfig().setMayCacheNonExistSeries(true);
+      testCachePlaceHolderInternal();
+    } finally {
+      IoTDBDescriptor.getInstance()
+          .getMemoryConfig()
+          .setMayCacheNonExistSeries(mayCacheNonExistSeries);
+    }
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
index 207c0507093..78c7701b4f7 100644
--- 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
+++ 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
@@ -1035,6 +1035,11 @@ read_consistency_level=strong
 # Datatype: boolean
 meta_data_cache_enable=true
 
+# Whether to cache a placeholder for non-exist series.
+# effectiveMode: restart
+# Datatype: boolean
+may_cache_nonexist_series=true
+
 # Read memory Allocation Ratio: BloomFilterCache : ChunkCache : 
TimeSeriesMetadataCache : Coordinator : Operators : DataExchange : timeIndex in 
TsFileResourceList : others.
 # The parameter form is a:b:c:d:e:f:g:h, where a, b, c, d, e, f, g and h are 
integers. for example: 1:1:1:1:1:1:1:1 , 1:100:200:50:200:200:200:50
 # effectiveMode: restart

Reply via email to