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

jackietien 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 c60a3b83320 Optimized the dual key cache memory computation
c60a3b83320 is described below

commit c60a3b833201080fe1a0377dec79d4197e3f9e42
Author: Caideyipi <[email protected]>
AuthorDate: Tue Apr 15 16:48:59 2025 +0800

    Optimized the dual key cache memory computation
---
 .../cache/schema/dualkeycache/IDualKeyCache.java   |  28 +-
 .../dualkeycache/impl/CacheEntryGroupImpl.java     |  57 ++-
 .../cache/schema/dualkeycache/impl/CacheStats.java |  47 +-
 .../schema/dualkeycache/impl/DualKeyCacheImpl.java | 526 +++++++--------------
 .../schema/dualkeycache/impl/ICacheEntryGroup.java |  13 +-
 .../planner/plan/node/write/InsertTabletNode.java  |   6 +-
 .../node/write/RelationalInsertTabletNode.java     |  14 +-
 .../db/storageengine/dataregion/DataRegion.java    |   4 +-
 .../cache/dualkeycache/DualKeyCacheTest.java       | 171 -------
 9 files changed, 263 insertions(+), 603 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/IDualKeyCache.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/IDualKeyCache.java
index f6e20ad58c7..9552a0f6ea6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/IDualKeyCache.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/IDualKeyCache.java
@@ -19,8 +19,6 @@
 
 package org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache;
 
-import org.apache.iotdb.commons.utils.TestOnly;
-
 import javax.annotation.concurrent.GuardedBy;
 
 import java.util.function.Predicate;
@@ -37,22 +35,7 @@ import java.util.function.ToIntFunction;
 public interface IDualKeyCache<FK, SK, V> {
 
   /** Get the cache value with given first key and second key. */
-  V get(FK firstKey, SK secondKey);
-
-  /**
-   * Traverse target cache values via given first key and second keys provided 
in computation and
-   * execute the defined computation logic. The computation is read only.
-   */
-  void compute(IDualKeyCacheComputation<FK, SK, V> computation);
-
-  /**
-   * Traverse target cache values via given first key and second keys provided 
in computation and
-   * execute the defined computation logic. Value can be updated in this 
computation.
-   */
-  void updateWithLock(final IDualKeyCacheUpdating<FK, SK, V> updating);
-
-  /** put the cache value into cache */
-  void put(final FK firstKey, final SK secondKey, final V value);
+  V get(final FK firstKey, final SK secondKey);
 
   /**
    * Update the existing value. The updater shall return the difference caused 
by the update,
@@ -102,18 +85,9 @@ public interface IDualKeyCache<FK, SK, V> {
   @GuardedBy("DataNodeSchemaCache#writeLock")
   void invalidateAll();
 
-  /**
-   * Clean up all data and info of this cache, including cache keys, cache 
values and cache stats.
-   */
-  @GuardedBy("DataNodeSchemaCache#writeLock")
-  void cleanUp();
-
   /** Return all the current cache status and statistics. */
   IDualKeyCacheStats stats();
 
-  @TestOnly
-  void evictOneEntry();
-
   /** remove all entries for firstKey */
   @GuardedBy("DataNodeSchemaCache#writeLock")
   void invalidate(final FK firstKey);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheEntryGroupImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheEntryGroupImpl.java
index 2847a3d1ccc..54c53a15d50 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheEntryGroupImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheEntryGroupImpl.java
@@ -19,22 +19,36 @@
 
 package 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.impl;
 
+import org.apache.tsfile.utils.RamUsageEstimator;
+
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public class CacheEntryGroupImpl<FK, SK, V, T extends ICacheEntry<SK, V>>
     implements ICacheEntryGroup<FK, SK, V, T> {
 
+  private static final long INSTANCE_SIZE =
+      RamUsageEstimator.shallowSizeOfInstance(CacheEntryGroupImpl.class)
+          + RamUsageEstimator.shallowSizeOfInstance(AtomicLong.class)
+          + RamUsageEstimator.shallowSizeOfInstance(ConcurrentHashMap.class)
+          // Calculate the outer entry of the "firstKeyMap" here
+          + RamUsageEstimator.HASHTABLE_RAM_BYTES_PER_ENTRY;
+
   private final FK firstKey;
 
   private final Map<SK, T> cacheEntryMap = new ConcurrentHashMap<>();
+  private final ICacheSizeComputer<FK, SK, V> sizeComputer;
+  private final AtomicLong memory;
 
-  CacheEntryGroupImpl(final FK firstKey) {
+  CacheEntryGroupImpl(final FK firstKey, final ICacheSizeComputer<FK, SK, V> 
sizeComputer) {
     this.firstKey = firstKey;
+    this.sizeComputer = sizeComputer;
+    this.memory = new AtomicLong(INSTANCE_SIZE + 
sizeComputer.computeFirstKeySize(firstKey));
   }
 
   @Override
@@ -53,30 +67,49 @@ public class CacheEntryGroupImpl<FK, SK, V, T extends 
ICacheEntry<SK, V>>
   }
 
   @Override
-  public T computeCacheEntry(final SK secondKey, final BiFunction<SK, T, T> 
computation) {
-    return cacheEntryMap.compute(secondKey, computation);
+  public T computeCacheEntry(
+      final SK secondKey, final Function<AtomicLong, BiFunction<SK, T, T>> 
computation) {
+    return cacheEntryMap.compute(secondKey, computation.apply(memory));
   }
 
   @Override
-  public T computeCacheEntryIfAbsent(final SK secondKey, final Function<SK, T> 
computation) {
-    return cacheEntryMap.computeIfAbsent(secondKey, computation);
+  public long removeCacheEntry(final SK secondKey) {
+    final T result = cacheEntryMap.remove(secondKey);
+    if (Objects.nonNull(result)) {
+      final long delta =
+          sizeComputer.computeSecondKeySize(result.getSecondKey())
+              + sizeComputer.computeValueSize(result.getValue())
+              + RamUsageEstimator.HASHTABLE_RAM_BYTES_PER_ENTRY;
+      memory.addAndGet(-delta);
+      return delta;
+    }
+    return 0;
   }
 
   @Override
-  public T removeCacheEntry(final SK secondKey) {
-    return cacheEntryMap.remove(secondKey);
+  public boolean isEmpty() {
+    return cacheEntryMap.isEmpty();
   }
 
   @Override
-  public boolean isEmpty() {
-    return cacheEntryMap.isEmpty();
+  public long getMemory() {
+    return memory.get();
+  }
+
+  @Override
+  public int getEntriesCount() {
+    return cacheEntryMap.size();
   }
 
   @Override
   public boolean equals(final Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    CacheEntryGroupImpl<?, ?, ?, ?> that = (CacheEntryGroupImpl<?, ?, ?, ?>) o;
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    final CacheEntryGroupImpl<?, ?, ?, ?> that = (CacheEntryGroupImpl<?, ?, ?, 
?>) o;
     return Objects.equals(firstKey, that.firstKey);
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheStats.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheStats.java
index 99c331962a2..0628d35db16 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheStats.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/CacheStats.java
@@ -22,6 +22,7 @@ package 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.i
 import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCacheStats;
 
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
 
 class CacheStats implements IDualKeyCacheStats {
 
@@ -30,26 +31,23 @@ class CacheStats implements IDualKeyCacheStats {
 
   private final long memoryThreshold;
 
-  private final AtomicLong memoryUsage = new AtomicLong(0);
-  private final AtomicLong entriesCount = new AtomicLong(0);
+  private final Supplier<Long> memoryComputation;
+  private final Supplier<Long> entriesComputation;
 
   private final AtomicLong requestCount = new AtomicLong(0);
   private final AtomicLong hitCount = new AtomicLong(0);
 
-  CacheStats(long memoryCapacity) {
+  CacheStats(
+      long memoryCapacity,
+      final Supplier<Long> memoryComputation,
+      final Supplier<Long> entriesComputation) {
     this.memoryThreshold = (long) (memoryCapacity * MEMORY_THRESHOLD_RATIO);
+    this.memoryComputation = memoryComputation;
+    this.entriesComputation = entriesComputation;
   }
 
-  void increaseMemoryUsage(int size) {
-    memoryUsage.getAndAdd(size);
-  }
-
-  void decreaseMemoryUsage(int size) {
-    memoryUsage.getAndAdd(-size);
-  }
-
-  boolean isExceedMemoryCapacity() {
-    return memoryUsage.get() > memoryThreshold;
+  long getExceedMemory() {
+    return memoryUsage() - memoryThreshold;
   }
 
   void recordHit(int num) {
@@ -69,14 +67,6 @@ class CacheStats implements IDualKeyCacheStats {
     requestCount.getAndAdd(num);
   }
 
-  void increaseEntryCount() {
-    entriesCount.incrementAndGet();
-  }
-
-  void decreaseEntryCount() {
-    entriesCount.decrementAndGet();
-  }
-
   @Override
   public long requestCount() {
     return requestCount.get();
@@ -102,7 +92,7 @@ class CacheStats implements IDualKeyCacheStats {
 
   @Override
   public long memoryUsage() {
-    return memoryUsage.get();
+    return memoryComputation.get();
   }
 
   @Override
@@ -112,17 +102,6 @@ class CacheStats implements IDualKeyCacheStats {
 
   @Override
   public long entriesCount() {
-    return entriesCount.get();
-  }
-
-  void reset() {
-    resetMemoryUsageAndEntriesCount();
-    hitCount.set(0);
-    requestCount.set(0);
-  }
-
-  void resetMemoryUsageAndEntriesCount() {
-    memoryUsage.set(0);
-    entriesCount.set(0);
+    return entriesComputation.get();
   }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/DualKeyCacheImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/DualKeyCacheImpl.java
index d165abbf210..f88be9c3366 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/DualKeyCacheImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/DualKeyCacheImpl.java
@@ -19,11 +19,10 @@
 
 package 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.impl;
 
-import org.apache.iotdb.commons.utils.TestOnly;
 import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCache;
-import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCacheComputation;
 import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCacheStats;
-import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCacheUpdating;
+
+import org.apache.tsfile.utils.RamUsageEstimator;
 
 import javax.annotation.Nonnull;
 
@@ -34,8 +33,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.BiFunction;
 import java.util.function.Predicate;
 import java.util.function.ToIntFunction;
 
@@ -57,7 +54,7 @@ class DualKeyCacheImpl<FK, SK, V, T extends ICacheEntry<SK, 
V>>
       final long memoryCapacity) {
     this.cacheEntryManager = cacheEntryManager;
     this.sizeComputer = sizeComputer;
-    this.cacheStats = new CacheStats(memoryCapacity);
+    this.cacheStats = new CacheStats(memoryCapacity, this::getMemory, 
this::getEntriesCount);
   }
 
   @Override
@@ -79,106 +76,6 @@ class DualKeyCacheImpl<FK, SK, V, T extends ICacheEntry<SK, 
V>>
     }
   }
 
-  @Override
-  public void compute(final IDualKeyCacheComputation<FK, SK, V> computation) {
-    final FK firstKey = computation.getFirstKey();
-    final ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup = 
firstKeyMap.get(firstKey);
-    final SK[] secondKeyList = computation.getSecondKeyList();
-    if (cacheEntryGroup == null) {
-      for (int i = 0; i < secondKeyList.length; i++) {
-        computation.computeValue(i, null);
-      }
-      cacheStats.recordMiss(secondKeyList.length);
-    } else {
-      T cacheEntry;
-      int hitCount = 0;
-      for (int i = 0; i < secondKeyList.length; i++) {
-        cacheEntry = cacheEntryGroup.getCacheEntry(secondKeyList[i]);
-        if (cacheEntry == null) {
-          computation.computeValue(i, null);
-        } else {
-          computation.computeValue(i, cacheEntry.getValue());
-          cacheEntryManager.access(cacheEntry);
-          hitCount++;
-        }
-      }
-      cacheStats.recordHit(hitCount);
-      cacheStats.recordMiss(secondKeyList.length - hitCount);
-    }
-  }
-
-  @Override
-  public void updateWithLock(final IDualKeyCacheUpdating<FK, SK, V> updating) {
-    final FK firstKey = updating.getFirstKey();
-    final ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup = 
firstKeyMap.get(firstKey);
-    final SK[] secondKeyList = updating.getSecondKeyList();
-    if (cacheEntryGroup == null) {
-      for (int i = 0; i < secondKeyList.length; i++) {
-        updating.updateValue(i, null);
-      }
-      cacheStats.recordMiss(secondKeyList.length);
-    } else {
-      T cacheEntry;
-      for (int i = 0; i < secondKeyList.length; i++) {
-        cacheEntry = cacheEntryGroup.getCacheEntry(secondKeyList[i]);
-        if (cacheEntry == null) {
-          updating.updateValue(i, null);
-        } else {
-          int changeSize = 0;
-          synchronized (cacheEntry) {
-            if (cacheEntry.getBelongedGroup() != null) {
-              // Only update the value when the cache entry is not evicted.
-              // If the cache entry is evicted, getBelongedGroup is null.
-              // Synchronized is to guarantee the cache entry is not evicted 
during the update.
-              changeSize = updating.updateValue(i, cacheEntry.getValue());
-              cacheEntryManager.access(cacheEntry);
-            }
-          }
-          if (changeSize > 0) {
-            increaseMemoryUsageAndMayEvict(changeSize);
-          }
-        }
-      }
-    }
-  }
-
-  @Override
-  public void put(final FK firstKey, final SK secondKey, final V value) {
-    final AtomicInteger usedMemorySize = new AtomicInteger(0);
-    firstKeyMap.compute(
-        firstKey,
-        (k, cacheEntryGroup) -> {
-          if (cacheEntryGroup == null) {
-            cacheEntryGroup = new CacheEntryGroupImpl<>(firstKey);
-            
usedMemorySize.getAndAdd(sizeComputer.computeFirstKeySize(firstKey));
-          }
-          final ICacheEntryGroup<FK, SK, V, T> finalCacheEntryGroup = 
cacheEntryGroup;
-          cacheEntryGroup.computeCacheEntry(
-              secondKey,
-              (sk, cacheEntry) -> {
-                if (cacheEntry == null) {
-                  cacheEntry =
-                      cacheEntryManager.createCacheEntry(secondKey, value, 
finalCacheEntryGroup);
-                  cacheEntryManager.put(cacheEntry);
-                  cacheStats.increaseEntryCount();
-                  
usedMemorySize.getAndAdd(sizeComputer.computeSecondKeySize(sk));
-                } else {
-                  final V existingValue = cacheEntry.getValue();
-                  if (existingValue != value && !existingValue.equals(value)) {
-                    cacheEntry.replaceValue(value);
-                    
usedMemorySize.getAndAdd(-sizeComputer.computeValueSize(existingValue));
-                  }
-                  // update the cache status
-                  cacheEntryManager.access(cacheEntry);
-                }
-                usedMemorySize.getAndAdd(sizeComputer.computeValueSize(value));
-                return cacheEntry;
-              });
-          return cacheEntryGroup;
-        });
-    increaseMemoryUsageAndMayEvict(usedMemorySize.get());
-  }
-
   @Override
   public void update(
       final FK firstKey,
@@ -186,76 +83,65 @@ class DualKeyCacheImpl<FK, SK, V, T extends 
ICacheEntry<SK, V>>
       final V value,
       final ToIntFunction<V> updater,
       final boolean createIfNotExists) {
-    final AtomicInteger usedMemorySize = new AtomicInteger(0);
-
-    firstKeyMap.compute(
-        firstKey,
-        (k, cacheEntryGroup) -> {
-          if (cacheEntryGroup == null) {
-            if (!createIfNotExists) {
-              return null;
-            }
-            cacheEntryGroup = new CacheEntryGroupImpl<>(firstKey);
-            
usedMemorySize.getAndAdd(sizeComputer.computeFirstKeySize(firstKey));
-          }
-          final ICacheEntryGroup<FK, SK, V, T> finalCacheEntryGroup = 
cacheEntryGroup;
-
-          final T cacheEntry =
-              createIfNotExists
-                  ? cacheEntryGroup.computeCacheEntryIfAbsent(
-                      secondKey,
-                      sk -> {
-                        final T entry =
-                            cacheEntryManager.createCacheEntry(
-                                secondKey, value, finalCacheEntryGroup);
-                        cacheEntryManager.put(entry);
-                        cacheStats.increaseEntryCount();
-                        usedMemorySize.getAndAdd(
-                            sizeComputer.computeSecondKeySize(sk)
-                                + 
sizeComputer.computeValueSize(entry.getValue()));
-                        return entry;
-                      })
-                  : cacheEntryGroup.getCacheEntry(secondKey);
-
-          if (Objects.nonNull(cacheEntry)) {
-            final int result = updater.applyAsInt(cacheEntry.getValue());
-            if (Objects.nonNull(cacheEntryGroup.getCacheEntry(secondKey))) {
-              usedMemorySize.getAndAdd(result);
-            }
-          }
-          return cacheEntryGroup;
-        });
-    increaseMemoryUsageAndMayEvict(usedMemorySize.get());
+
+    ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup = firstKeyMap.get(firstKey);
+    if (Objects.isNull(cacheEntryGroup)) {
+      if (createIfNotExists) {
+        cacheEntryGroup = new CacheEntryGroupImpl<>(firstKey, sizeComputer);
+        firstKeyMap.put(firstKey, cacheEntryGroup);
+      } else {
+        return;
+      }
+    }
+
+    final ICacheEntryGroup<FK, SK, V, T> finalCacheEntryGroup = 
cacheEntryGroup;
+    cacheEntryGroup.computeCacheEntry(
+        secondKey,
+        memory ->
+            (sk, cacheEntry) -> {
+              if (Objects.isNull(cacheEntry)) {
+                if (!createIfNotExists) {
+                  return null;
+                }
+                cacheEntry =
+                    cacheEntryManager.createCacheEntry(secondKey, value, 
finalCacheEntryGroup);
+                cacheEntryManager.put(cacheEntry);
+                memory.getAndAdd(
+                    sizeComputer.computeSecondKeySize(sk)
+                        + sizeComputer.computeValueSize(cacheEntry.getValue())
+                        + RamUsageEstimator.HASHTABLE_RAM_BYTES_PER_ENTRY);
+              }
+              memory.getAndAdd(updater.applyAsInt(cacheEntry.getValue()));
+              return cacheEntry;
+            });
+
+    mayEvict();
   }
 
   @Override
   public void update(
       final FK firstKey, final Predicate<SK> secondKeyChecker, final 
ToIntFunction<V> updater) {
-    final AtomicInteger usedMemorySize = new AtomicInteger(0);
-
-    firstKeyMap.compute(
-        firstKey,
-        (k, cacheEntryGroup) -> {
-          if (cacheEntryGroup == null) {
-            return null;
-          }
-          final ICacheEntryGroup<FK, SK, V, T> finalCacheEntryGroup = 
cacheEntryGroup;
-
-          cacheEntryGroup
-              .getAllCacheEntries()
-              .forEachRemaining(
-                  entry -> {
-                    if (!secondKeyChecker.test(entry.getKey())) {
-                      return;
-                    }
-                    final int result = 
updater.applyAsInt(entry.getValue().getValue());
-                    if 
(Objects.nonNull(finalCacheEntryGroup.getCacheEntry(entry.getKey()))) {
-                      usedMemorySize.getAndAdd(result);
-                    }
-                  });
-          return cacheEntryGroup;
-        });
-    increaseMemoryUsageAndMayEvict(usedMemorySize.get());
+    final ICacheEntryGroup<FK, SK, V, T> entryGroup = 
firstKeyMap.get(firstKey);
+    if (Objects.nonNull(entryGroup)) {
+      entryGroup
+          .getAllCacheEntries()
+          .forEachRemaining(
+              entry -> {
+                if (!secondKeyChecker.test(entry.getKey())) {
+                  return;
+                }
+                entryGroup.computeCacheEntry(
+                    entry.getKey(),
+                    memory ->
+                        (secondKey, cacheEntry) -> {
+                          if (Objects.nonNull(cacheEntry)) {
+                            
memory.getAndAdd(updater.applyAsInt(cacheEntry.getValue()));
+                          }
+                          return cacheEntry;
+                        });
+              });
+    }
+    mayEvict();
   }
 
   @Override
@@ -263,93 +149,72 @@ class DualKeyCacheImpl<FK, SK, V, T extends 
ICacheEntry<SK, V>>
       final Predicate<FK> firstKeyChecker,
       final Predicate<SK> secondKeyChecker,
       final ToIntFunction<V> updater) {
-    final AtomicInteger usedMemorySize = new AtomicInteger(0);
     for (final FK firstKey : firstKeyMap.getAllKeys()) {
       if (!firstKeyChecker.test(firstKey)) {
         continue;
       }
-      firstKeyMap.compute(
-          firstKey,
-          (fk, entryGroup) -> {
-            if (Objects.nonNull(entryGroup)) {
-              entryGroup
-                  .getAllCacheEntries()
-                  .forEachRemaining(
-                      entry -> {
-                        if (!secondKeyChecker.test(entry.getKey())) {
-                          return;
-                        }
-                        final int result = 
updater.applyAsInt(entry.getValue().getValue());
-                        if 
(Objects.nonNull(entryGroup.getCacheEntry(entry.getKey()))) {
-                          usedMemorySize.getAndAdd(result);
-                        }
-                      });
-            }
-            return entryGroup;
-          });
+      final ICacheEntryGroup<FK, SK, V, T> entryGroup = 
firstKeyMap.get(firstKey);
+      if (Objects.nonNull(entryGroup)) {
+        entryGroup
+            .getAllCacheEntries()
+            .forEachRemaining(
+                entry -> {
+                  if (!secondKeyChecker.test(entry.getKey())) {
+                    return;
+                  }
+                  entryGroup.computeCacheEntry(
+                      entry.getKey(),
+                      memory ->
+                          (secondKey, cacheEntry) -> {
+                            
memory.getAndAdd(updater.applyAsInt(cacheEntry.getValue()));
+                            return cacheEntry;
+                          });
+                });
+      }
+      mayEvict();
     }
-    increaseMemoryUsageAndMayEvict(usedMemorySize.get());
   }
 
-  private void increaseMemoryUsageAndMayEvict(final int memorySize) {
-    cacheStats.increaseMemoryUsage(memorySize);
-    while (cacheStats.isExceedMemoryCapacity()) {
-      cacheStats.decreaseMemoryUsage(evictOneCacheEntry());
+  private void mayEvict() {
+    long exceedMemory;
+    while ((exceedMemory = cacheStats.getExceedMemory()) > 0) {
+      // Not compute each time to save time when FK is too many
+      // The hard-coded size is 100
+      do {
+        exceedMemory -= evictOneCacheEntry();
+      } while (exceedMemory > 0 && firstKeyMap.size() > 100);
     }
   }
 
-  private int evictOneCacheEntry() {
+  // The returned delta may have some error, but it's OK
+  // Because the delta is only for loop round estimation
+  private long evictOneCacheEntry() {
     final ICacheEntry<SK, V> evictCacheEntry = cacheEntryManager.evict();
     if (evictCacheEntry == null) {
       return 0;
     }
 
-    final AtomicInteger evictedSize = new AtomicInteger(0);
-
     final ICacheEntryGroup<FK, SK, V, T> belongedGroup = 
evictCacheEntry.getBelongedGroup();
     evictCacheEntry.setBelongedGroup(null);
 
-    firstKeyMap.compute(
-        belongedGroup.getFirstKey(),
-        (firstKey, cacheEntryGroup) -> {
-          belongedGroup.removeCacheEntry(evictCacheEntry.getSecondKey());
-          cacheStats.decreaseEntryCount();
-          evictedSize.getAndAdd(
-              sizeComputer.computeValueSize(evictCacheEntry.getValue())
-                  + 
sizeComputer.computeSecondKeySize(evictCacheEntry.getSecondKey()));
-
-          if (cacheEntryGroup == null) {
-            // has been removed by other threads
-            return null;
-          }
-
-          if (cacheEntryGroup.isEmpty()) {
-            evictedSize.getAndAdd(sizeComputer.computeFirstKeySize(firstKey));
-            return null;
-          }
+    long memory = 
belongedGroup.removeCacheEntry(evictCacheEntry.getSecondKey());
 
-          // some other thread has put value to it
-          return cacheEntryGroup;
-        });
-
-    return evictedSize.get();
+    final ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup =
+        firstKeyMap.get(belongedGroup.getFirstKey());
+    if (Objects.nonNull(cacheEntryGroup) && cacheEntryGroup.isEmpty()) {
+      if (Objects.nonNull(firstKeyMap.remove(belongedGroup.getFirstKey()))) {
+        memory +=
+            sizeComputer.computeFirstKeySize(belongedGroup.getFirstKey())
+                + RamUsageEstimator.HASHTABLE_RAM_BYTES_PER_ENTRY;
+      }
+    }
+    return memory;
   }
 
   @Override
   public void invalidateAll() {
-    executeInvalidateAll();
-  }
-
-  private void executeInvalidateAll() {
     firstKeyMap.clear();
     cacheEntryManager.cleanUp();
-    cacheStats.resetMemoryUsageAndEntriesCount();
-  }
-
-  @Override
-  public void cleanUp() {
-    executeInvalidateAll();
-    cacheStats.reset();
   }
 
   @Override
@@ -357,137 +222,104 @@ class DualKeyCacheImpl<FK, SK, V, T extends 
ICacheEntry<SK, V>>
     return cacheStats;
   }
 
-  @Override
-  @TestOnly
-  public void evictOneEntry() {
-    cacheStats.decreaseMemoryUsage(evictOneCacheEntry());
-  }
-
   @Override
   public void invalidate(final FK firstKey) {
-    int estimateSize = 0;
     final ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup = 
firstKeyMap.remove(firstKey);
     if (cacheEntryGroup != null) {
-      estimateSize += sizeComputer.computeFirstKeySize(firstKey);
       for (final Iterator<Map.Entry<SK, T>> it = 
cacheEntryGroup.getAllCacheEntries();
           it.hasNext(); ) {
-        final Map.Entry<SK, T> entry = it.next();
-        if (cacheEntryManager.invalidate(entry.getValue())) {
-          cacheStats.decreaseEntryCount();
-          estimateSize +=
-              sizeComputer.computeSecondKeySize(entry.getKey())
-                  + sizeComputer.computeValueSize(entry.getValue().getValue());
-        }
+        cacheEntryManager.invalidate(it.next().getValue());
       }
-      cacheStats.decreaseMemoryUsage(estimateSize);
     }
   }
 
   @Override
   public void invalidate(final FK firstKey, final SK secondKey) {
-    final AtomicInteger usedMemorySize = new AtomicInteger(0);
-
-    firstKeyMap.compute(
-        firstKey,
-        (key, cacheEntryGroup) -> {
-          if (cacheEntryGroup == null) {
-            // has been removed by other threads
-            return null;
-          }
-
-          final T entry = cacheEntryGroup.getCacheEntry(secondKey);
-          if (Objects.nonNull(entry) && cacheEntryManager.invalidate(entry)) {
-            cacheStats.decreaseEntryCount();
-            usedMemorySize.getAndAdd(
-                sizeComputer.computeSecondKeySize(entry.getSecondKey())
-                    + sizeComputer.computeValueSize(entry.getValue()));
-            cacheEntryGroup.removeCacheEntry(entry.getSecondKey());
-          }
+    final ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup = 
firstKeyMap.get(firstKey);
+    if (Objects.isNull(cacheEntryGroup)) {
+      return;
+    }
 
-          if (cacheEntryGroup.isEmpty()) {
-            
usedMemorySize.getAndAdd(sizeComputer.computeFirstKeySize(firstKey));
-            return null;
-          }
+    final T entry = cacheEntryGroup.getCacheEntry(secondKey);
+    if (Objects.nonNull(entry) && cacheEntryManager.invalidate(entry)) {
+      cacheEntryGroup.removeCacheEntry(entry.getSecondKey());
+    }
 
-          return cacheEntryGroup;
-        });
-    cacheStats.decreaseMemoryUsage(usedMemorySize.get());
+    if (cacheEntryGroup.isEmpty()) {
+      firstKeyMap.remove(firstKey);
+    }
   }
 
   @Override
   public void invalidate(final FK firstKey, final Predicate<SK> 
secondKeyChecker) {
-    final AtomicInteger estimateSize = new AtomicInteger(0);
-    firstKeyMap.compute(
-        firstKey,
-        (key, cacheEntryGroup) -> {
-          if (cacheEntryGroup == null) {
-            // has been removed by other threads
-            return null;
-          }
-
-          for (final Iterator<Map.Entry<SK, T>> it = 
cacheEntryGroup.getAllCacheEntries();
-              it.hasNext(); ) {
-            final Map.Entry<SK, T> entry = it.next();
-            if (secondKeyChecker.test(entry.getKey())
-                && cacheEntryManager.invalidate(entry.getValue())) {
-              cacheStats.decreaseEntryCount();
-              cacheEntryGroup.removeCacheEntry(entry.getKey());
-              estimateSize.addAndGet(
-                  sizeComputer.computeSecondKeySize(entry.getKey())
-                      + 
sizeComputer.computeValueSize(entry.getValue().getValue()));
-            }
-          }
-
-          if (cacheEntryGroup.isEmpty()) {
-            estimateSize.getAndAdd(sizeComputer.computeFirstKeySize(firstKey));
-            return null;
-          }
-
-          return cacheEntryGroup;
-        });
+    final ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup = 
firstKeyMap.get(firstKey);
+    if (Objects.isNull(cacheEntryGroup)) {
+      return;
+    }
+    for (final Iterator<Map.Entry<SK, T>> it = 
cacheEntryGroup.getAllCacheEntries();
+        it.hasNext(); ) {
+      final Map.Entry<SK, T> entry = it.next();
+      if (secondKeyChecker.test(entry.getKey()) && 
cacheEntryManager.invalidate(entry.getValue())) {
+        cacheEntryGroup.removeCacheEntry(entry.getKey());
+      }
+    }
 
-    cacheStats.decreaseMemoryUsage(estimateSize.get());
+    if (cacheEntryGroup.isEmpty()) {
+      firstKeyMap.remove(firstKey);
+    }
   }
 
   @Override
   public void invalidate(
       final Predicate<FK> firstKeyChecker, final Predicate<SK> 
secondKeyChecker) {
-    final AtomicInteger estimateSize = new AtomicInteger(0);
     for (final FK firstKey : firstKeyMap.getAllKeys()) {
       if (!firstKeyChecker.test(firstKey)) {
         continue;
       }
 
-      firstKeyMap.compute(
-          firstKey,
-          (fk, cacheEntryGroup) -> {
-            if (cacheEntryGroup == null) {
-              // has been removed by other threads
-              return null;
-            }
-
-            for (final Iterator<Map.Entry<SK, T>> it = 
cacheEntryGroup.getAllCacheEntries();
-                it.hasNext(); ) {
-              final Map.Entry<SK, T> entry = it.next();
-
-              if (secondKeyChecker.test(entry.getKey())
-                  && cacheEntryManager.invalidate(entry.getValue())) {
-                cacheStats.decreaseEntryCount();
-                cacheEntryGroup.removeCacheEntry(entry.getKey());
-                estimateSize.addAndGet(
-                    sizeComputer.computeSecondKeySize(entry.getKey())
-                        + 
sizeComputer.computeValueSize(entry.getValue().getValue()));
-              }
-            }
-
-            if (cacheEntryGroup.isEmpty()) {
-              
estimateSize.getAndAdd(sizeComputer.computeFirstKeySize(firstKey));
-              return null;
-            }
-            return cacheEntryGroup;
-          });
+      final ICacheEntryGroup<FK, SK, V, T> cacheEntryGroup = 
firstKeyMap.get(firstKey);
+      if (Objects.isNull(cacheEntryGroup)) {
+        return;
+      }
+
+      for (final Iterator<Map.Entry<SK, T>> it = 
cacheEntryGroup.getAllCacheEntries();
+          it.hasNext(); ) {
+        final Map.Entry<SK, T> entry = it.next();
+
+        if (secondKeyChecker.test(entry.getKey())
+            && cacheEntryManager.invalidate(entry.getValue())) {
+          cacheEntryGroup.removeCacheEntry(entry.getKey());
+        }
+      }
+
+      if (cacheEntryGroup.isEmpty()) {
+        firstKeyMap.remove(firstKey);
+      }
     }
-    cacheStats.decreaseMemoryUsage(estimateSize.get());
+  }
+
+  private long getMemory() {
+    long memory = 0;
+    for (final Map<FK, ICacheEntryGroup<FK, SK, V, T>> map : firstKeyMap.maps) 
{
+      if (Objects.nonNull(map)) {
+        for (final ICacheEntryGroup<FK, SK, V, T> group : map.values()) {
+          memory += group.getMemory();
+        }
+      }
+    }
+    return memory;
+  }
+
+  private long getEntriesCount() {
+    long count = 0;
+    for (final Map<FK, ICacheEntryGroup<FK, SK, V, T>> map : firstKeyMap.maps) 
{
+      if (Objects.nonNull(map)) {
+        for (final ICacheEntryGroup<FK, SK, V, T> group : map.values()) {
+          count += group.getEntriesCount();
+        }
+      }
+    }
+    return count;
   }
 
   /**
@@ -500,27 +332,25 @@ class DualKeyCacheImpl<FK, SK, V, T extends 
ICacheEntry<SK, V>>
 
     private final Map<K, V>[] maps = new ConcurrentHashMap[SLOT_NUM];
 
-    V get(K key) {
+    V get(final K key) {
       return getBelongedMap(key).get(key);
     }
 
-    V remove(K key) {
+    V remove(final K key) {
       return getBelongedMap(key).remove(key);
     }
 
-    V compute(K key, BiFunction<? super K, ? super V, ? extends V> 
remappingFunction) {
-      return getBelongedMap(key).compute(key, remappingFunction);
+    V put(final K key, final V value) {
+      return getBelongedMap(key).put(key, value);
     }
 
     void clear() {
       synchronized (maps) {
-        for (int i = 0; i < SLOT_NUM; i++) {
-          maps[i] = null;
-        }
+        Arrays.fill(maps, null);
       }
     }
 
-    Map<K, V> getBelongedMap(K key) {
+    Map<K, V> getBelongedMap(final K key) {
       int slotIndex = key.hashCode() % SLOT_NUM;
       slotIndex = slotIndex < 0 ? slotIndex + SLOT_NUM : slotIndex;
       Map<K, V> map = maps[slotIndex];
@@ -538,7 +368,7 @@ class DualKeyCacheImpl<FK, SK, V, T extends ICacheEntry<SK, 
V>>
 
     // Copied list, deletion-safe
     List<K> getAllKeys() {
-      List<K> res = new ArrayList<>();
+      final List<K> res = new ArrayList<>();
       Arrays.stream(maps)
           .iterator()
           .forEachRemaining(
@@ -549,5 +379,15 @@ class DualKeyCacheImpl<FK, SK, V, T extends 
ICacheEntry<SK, V>>
               });
       return res;
     }
+
+    int size() {
+      int size = 0;
+      for (final Map<K, V> map : maps) {
+        if (Objects.nonNull(map)) {
+          size += map.size();
+        }
+      }
+      return size;
+    }
   }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/ICacheEntryGroup.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/ICacheEntryGroup.java
index 6dde21e1518..d1f8ab9923e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/ICacheEntryGroup.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/cache/schema/dualkeycache/impl/ICacheEntryGroup.java
@@ -21,6 +21,7 @@ package 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.i
 
 import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
@@ -41,11 +42,15 @@ interface ICacheEntryGroup<FK, SK, V, T extends 
ICacheEntry<SK, V>> {
 
   Iterator<Map.Entry<SK, T>> getAllCacheEntries();
 
-  T computeCacheEntry(final SK secondKey, final BiFunction<SK, T, T> 
computation);
+  T computeCacheEntry(
+      final SK secondKey, final Function<AtomicLong, BiFunction<SK, T, T>> 
computation);
 
-  T computeCacheEntryIfAbsent(final SK secondKey, final Function<SK, T> 
computation);
-
-  T removeCacheEntry(final SK secondKey);
+  long removeCacheEntry(final SK secondKey);
 
   boolean isEmpty();
+
+  // Metric
+  long getMemory();
+
+  int getEntriesCount();
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java
index 08557346c39..a9695475a18 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/InsertTabletNode.java
@@ -1285,9 +1285,9 @@ public class InsertTabletNode extends InsertNode 
implements WALEntryValue {
     return firstAliveLoc;
   }
 
-  public void updateLastCache(String databaseName) {
-    String[] rawMeasurements = getRawMeasurements();
-    TimeValuePair[] timeValuePairs = new TimeValuePair[rawMeasurements.length];
+  public void updateLastCache(final String databaseName) {
+    final String[] rawMeasurements = getRawMeasurements();
+    final TimeValuePair[] timeValuePairs = new 
TimeValuePair[rawMeasurements.length];
     for (int i = 0; i < rawMeasurements.length; i++) {
       timeValuePairs[i] = composeLastTimeValuePair(i);
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
index 70321743453..0d4b698108e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/write/RelationalInsertTabletNode.java
@@ -353,16 +353,16 @@ public class RelationalInsertTabletNode extends 
InsertTabletNode {
   }
 
   @Override
-  public void updateLastCache(String databaseName) {
-    String[] rawMeasurements = getRawMeasurements();
+  public void updateLastCache(final String databaseName) {
+    final String[] rawMeasurements = getRawMeasurements();
 
-    List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs = splitByDevice(0, 
rowCount);
+    final List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs = 
splitByDevice(0, rowCount);
     int startOffset = 0;
-    for (Pair<IDeviceID, Integer> deviceEndOffsetPair : deviceEndOffsetPairs) {
-      IDeviceID deviceID = deviceEndOffsetPair.getLeft();
-      int endOffset = deviceEndOffsetPair.getRight();
+    for (final Pair<IDeviceID, Integer> deviceEndOffsetPair : 
deviceEndOffsetPairs) {
+      final IDeviceID deviceID = deviceEndOffsetPair.getLeft();
+      final int endOffset = deviceEndOffsetPair.getRight();
 
-      TimeValuePair[] timeValuePairs = new 
TimeValuePair[rawMeasurements.length];
+      final TimeValuePair[] timeValuePairs = new 
TimeValuePair[rawMeasurements.length];
       for (int i = 0; i < rawMeasurements.length; i++) {
         timeValuePairs[i] = composeLastTimeValuePair(i, startOffset, 
endOffset);
       }
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 59e179fcff1..70d1c944237 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
@@ -1400,7 +1400,7 @@ public class DataRegion implements IDataRegionForQuery {
     }
   }
 
-  private void tryToUpdateInsertTabletLastCache(InsertTabletNode node) {
+  private void tryToUpdateInsertTabletLastCache(final InsertTabletNode node) {
     node.updateLastCache(getDatabaseName());
   }
 
@@ -1424,7 +1424,7 @@ public class DataRegion implements IDataRegionForQuery {
     return tsFileProcessor;
   }
 
-  private void tryToUpdateInsertRowLastCache(InsertRowNode node) {
+  private void tryToUpdateInsertRowLastCache(final InsertRowNode node) {
     node.updateLastCache(databaseName);
   }
 
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/cache/dualkeycache/DualKeyCacheTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/cache/dualkeycache/DualKeyCacheTest.java
deleted file mode 100644
index 305717a3db6..00000000000
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/metadata/cache/dualkeycache/DualKeyCacheTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.metadata.cache.dualkeycache;
-
-import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCache;
-import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCacheComputation;
-import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.IDualKeyCacheUpdating;
-import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.impl.DualKeyCacheBuilder;
-import 
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.dualkeycache.impl.DualKeyCachePolicy;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(Parameterized.class)
-public class DualKeyCacheTest {
-
-  private final String policy;
-
-  public DualKeyCacheTest(String policy) {
-    this.policy = policy;
-  }
-
-  @Parameterized.Parameters
-  public static List<String> getTestModes() {
-    return Arrays.asList("FIFO", "LRU");
-  }
-
-  @Test
-  public void testBasicReadPut() {
-    DualKeyCacheBuilder<String, String, String> dualKeyCacheBuilder = new 
DualKeyCacheBuilder<>();
-    IDualKeyCache<String, String, String> dualKeyCache =
-        dualKeyCacheBuilder
-            .cacheEvictionPolicy(DualKeyCachePolicy.valueOf(policy))
-            .memoryCapacity(300)
-            .firstKeySizeComputer(this::computeStringSize)
-            .secondKeySizeComputer(this::computeStringSize)
-            .valueSizeComputer(this::computeStringSize)
-            .build();
-
-    String[] firstKeyList = new String[] {"root.db.d1", "root.db.d2"};
-    String[] secondKeyList = new String[] {"s1", "s2"};
-    String[][] valueTable =
-        new String[][] {new String[] {"1-1", "1-2"}, new String[] {"2-1", 
"2-2"}};
-
-    for (int i = 0; i < firstKeyList.length; i++) {
-      for (int j = 0; j < secondKeyList.length; j++) {
-        dualKeyCache.put(firstKeyList[i], secondKeyList[j], valueTable[i][j]);
-      }
-    }
-
-    int firstKeyOfMissingEntry = -1, secondKeyOfMissingEntry = -1;
-    for (int i = 0; i < firstKeyList.length; i++) {
-      for (int j = 0; j < secondKeyList.length; j++) {
-        String value = dualKeyCache.get(firstKeyList[i], secondKeyList[j]);
-        if (value == null) {
-          if (firstKeyOfMissingEntry == -1) {
-            firstKeyOfMissingEntry = i;
-            secondKeyOfMissingEntry = j;
-          } else {
-            Assert.fail();
-          }
-        } else {
-          Assert.assertEquals(valueTable[i][j], value);
-        }
-      }
-    }
-
-    Assert.assertEquals(230, dualKeyCache.stats().memoryUsage());
-    Assert.assertEquals(4, dualKeyCache.stats().requestCount());
-    Assert.assertEquals(3, dualKeyCache.stats().hitCount());
-
-    dualKeyCache.put(
-        firstKeyList[firstKeyOfMissingEntry],
-        secondKeyList[secondKeyOfMissingEntry],
-        valueTable[firstKeyOfMissingEntry][secondKeyOfMissingEntry]);
-    Assert.assertEquals(230, dualKeyCache.stats().memoryUsage());
-
-    for (int i = 0; i < firstKeyList.length; i++) {
-      int finalI = i;
-      dualKeyCache.compute(
-          new IDualKeyCacheComputation<String, String, String>() {
-            @Override
-            public String getFirstKey() {
-              return firstKeyList[finalI];
-            }
-
-            @Override
-            public String[] getSecondKeyList() {
-              return secondKeyList;
-            }
-
-            @Override
-            public void computeValue(int index, String value) {
-              if (value != null) {
-                Assert.assertEquals(valueTable[finalI][index], value);
-              }
-            }
-          });
-    }
-
-    Assert.assertEquals(8, dualKeyCache.stats().requestCount());
-    Assert.assertEquals(6, dualKeyCache.stats().hitCount());
-  }
-
-  private int computeStringSize(String string) {
-    return 8 + 8 + 4 + 2 * string.length();
-  }
-
-  @Test
-  public void testComputeAndUpdateSize() {
-    final DualKeyCacheBuilder<String, String, String> dualKeyCacheBuilder =
-        new DualKeyCacheBuilder<>();
-    final IDualKeyCache<String, String, String> dualKeyCache =
-        dualKeyCacheBuilder
-            .cacheEvictionPolicy(DualKeyCachePolicy.valueOf(policy))
-            .memoryCapacity(500)
-            .firstKeySizeComputer(this::computeStringSize)
-            .secondKeySizeComputer(this::computeStringSize)
-            .valueSizeComputer(this::computeStringSize)
-            .build();
-    final String firstKey = "db";
-    final String[] secondKeyList = new String[] {"root.db.d1", "root.db.d2"};
-    for (final String s : secondKeyList) {
-      dualKeyCache.put(firstKey, s, "");
-    }
-    int expectedSize =
-        computeStringSize("db") + computeStringSize("root.db.d1") * 2 + 
computeStringSize("") * 2;
-    Assert.assertEquals(expectedSize, dualKeyCache.stats().memoryUsage());
-    dualKeyCache.updateWithLock(
-        new IDualKeyCacheUpdating<String, String, String>() {
-          @Override
-          public String getFirstKey() {
-            return firstKey;
-          }
-
-          @Override
-          public String[] getSecondKeyList() {
-            return secondKeyList;
-          }
-
-          @Override
-          public int updateValue(final int index, final String value) {
-            return computeStringSize("b") - computeStringSize("");
-          }
-        });
-    expectedSize += (computeStringSize("b") - computeStringSize("")) * 2;
-    Assert.assertEquals(expectedSize, dualKeyCache.stats().memoryUsage());
-  }
-}


Reply via email to