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
