You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ji...@apache.org on 2025/10/17 04:01:47 UTC

(iotdb) branch cache_non_exist_timeseries_metadata created (now 1cd27fa2c17)

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

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


      at 1cd27fa2c17 Cache non-exist timeseries metadata to reduce IO

This branch includes the following new commits:

     new 1cd27fa2c17 Cache non-exist timeseries metadata to reduce IO

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(iotdb) 01/01: Cache non-exist timeseries metadata to reduce IO

Posted by ji...@apache.org.
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 <jt...@163.com>
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