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

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new d9bead0d1e [#7472] feat(core): Add JMH Tests for EntityCache (#7501)
d9bead0d1e is described below

commit d9bead0d1e35822f2eca9d35b6c2a55521ecf026
Author: Lord of Abyss <[email protected]>
AuthorDate: Mon Jul 7 13:59:08 2025 +0800

    [#7472] feat(core): Add JMH Tests for EntityCache (#7501)
    
    ### What changes were proposed in this pull request?
    
    The following JMH benchmarks were executed under the environment below:
    
    - CPU: Apple M2 Pro
    - Memory: 16 GB
    - OS: macOS Ventura 15.5
    - JVM: openjdk version "17.0.15"
    - JMH Version: 1.37
    - Benchmark Config:
        - `@BenchmarkMode`: Throughput & AverageTime
        - `@State`: Scope.Thread
        - `@Fork`: 1
        - `@Warmup:` 5 iterations
        - `@Measurement`: 10 iterations
    
    Add JMH Tests for EntityCache, the result as follows:
    
    ```text
    Benchmark                                                   (totalCnt)   
Mode  Cnt         Score         Error  Units
    EntityCacheClearBenchmark.benchmarkClear                            10  
thrpt   10    536704.823 ±   36351.106  ops/s
    EntityCacheClearBenchmark.benchmarkClear                           100  
thrpt   10     89391.338 ±    4692.738  ops/s
    EntityCacheClearBenchmark.benchmarkClear                          1000  
thrpt   10      8406.573 ±     738.173  ops/s
    EntityCacheContainsBenchmark.benchmarkContains                      10  
thrpt   10  19008727.286 ± 1172892.696  ops/s
    EntityCacheContainsBenchmark.benchmarkContains                     100  
thrpt   10  19129605.626 ±  928728.148  ops/s
    EntityCacheContainsBenchmark.benchmarkContains                    1000  
thrpt   10  17954917.808 ±  916939.288  ops/s
    EntityCacheContainsBenchmark.benchmarkContainsWithRelation          10  
thrpt   10   9454829.072 ±  403482.020  ops/s
    EntityCacheContainsBenchmark.benchmarkContainsWithRelation         100  
thrpt   10   5910924.166 ±  234228.471  ops/s
    EntityCacheContainsBenchmark.benchmarkContainsWithRelation        1000  
thrpt   10   1021172.939 ±  188152.980  ops/s
    EntityCacheGetBenchmark.benchmarkGet                                10  
thrpt   10  17646662.661 ± 1850512.796  ops/s
    EntityCacheGetBenchmark.benchmarkGet                               100  
thrpt   10  17906401.139 ±  905521.957  ops/s
    EntityCacheGetBenchmark.benchmarkGet                              1000  
thrpt   10  17882451.612 ± 1013749.411  ops/s
    EntityCacheGetBenchmark.benchmarkGetWithRelations                   10  
thrpt   10   7949607.041 ±  323537.818  ops/s
    EntityCacheGetBenchmark.benchmarkGetWithRelations                  100  
thrpt   10   4939219.694 ±   54283.320  ops/s
    EntityCacheGetBenchmark.benchmarkGetWithRelations                 1000  
thrpt   10   1060788.506 ±   22922.363  ops/s
    EntityCacheInvalidateBenchmark.benchmarkInvalidate                  10  
thrpt   10    184227.751 ±    6303.043  ops/s
    EntityCacheInvalidateBenchmark.benchmarkInvalidate                 100  
thrpt   10     19663.536 ±     684.142  ops/s
    EntityCacheInvalidateBenchmark.benchmarkInvalidate                1000  
thrpt   10      1651.429 ±     213.587  ops/s
    EntityCachePutBenchmark.benchmarkPut                                10  
thrpt   10    222207.294 ±   10992.713  ops/s
    EntityCachePutBenchmark.benchmarkPut                               100  
thrpt   10     20128.455 ±     434.551  ops/s
    EntityCachePutBenchmark.benchmarkPut                              1000  
thrpt   10      1902.510 ±      75.715  ops/s
    EntityCachePutBenchmark.benchmarkPutWithRelation                    10  
thrpt   10    335683.335 ±   34641.231  ops/s
    EntityCachePutBenchmark.benchmarkPutWithRelation                   100  
thrpt   10     26415.590 ±    1511.666  ops/s
    EntityCachePutBenchmark.benchmarkPutWithRelation                  1000  
thrpt   10      2345.904 ±      89.693  ops/s
    EntityCacheSizeBenchmark.entityCacheSize                            10  
thrpt   10   9416189.995 ± 1018247.698  ops/s
    EntityCacheSizeBenchmark.entityCacheSize                           100  
thrpt   10    944383.407 ±   35235.930  ops/s
    EntityCacheSizeBenchmark.entityCacheSize                          1000  
thrpt   10     79938.016 ±    2466.431  ops/s
    EntityCacheClearBenchmark.benchmarkClear                            10   
avgt   10        ≈ 10⁻⁵                 s/op
    EntityCacheClearBenchmark.benchmarkClear                           100   
avgt   10        ≈ 10⁻⁴                 s/op
    EntityCacheClearBenchmark.benchmarkClear                          1000   
avgt   10        ≈ 10⁻³                 s/op
    EntityCacheContainsBenchmark.benchmarkContains                      10   
avgt   10        ≈ 10⁻⁷                 s/op
    EntityCacheContainsBenchmark.benchmarkContains                     100   
avgt   10        ≈ 10⁻⁷                 s/op
    EntityCacheContainsBenchmark.benchmarkContains                    1000   
avgt   10        ≈ 10⁻⁷                 s/op
    EntityCacheContainsBenchmark.benchmarkContainsWithRelation          10   
avgt   10        ≈ 10⁻⁶                 s/op
    EntityCacheContainsBenchmark.benchmarkContainsWithRelation         100   
avgt   10        ≈ 10⁻⁶                 s/op
    EntityCacheContainsBenchmark.benchmarkContainsWithRelation        1000   
avgt   10        ≈ 10⁻⁵                 s/op
    EntityCacheGetBenchmark.benchmarkGet                                10   
avgt   10        ≈ 10⁻⁷                 s/op
    EntityCacheGetBenchmark.benchmarkGet                               100   
avgt   10        ≈ 10⁻⁷                 s/op
    EntityCacheGetBenchmark.benchmarkGet                              1000   
avgt   10        ≈ 10⁻⁷                 s/op
    EntityCacheGetBenchmark.benchmarkGetWithRelations                   10   
avgt   10        ≈ 10⁻⁶                 s/op
    EntityCacheGetBenchmark.benchmarkGetWithRelations                  100   
avgt   10        ≈ 10⁻⁶                 s/op
    EntityCacheGetBenchmark.benchmarkGetWithRelations                 1000   
avgt   10        ≈ 10⁻⁵                 s/op
    EntityCacheInvalidateBenchmark.benchmarkInvalidate                  10   
avgt   10        ≈ 10⁻⁵                 s/op
    EntityCacheInvalidateBenchmark.benchmarkInvalidate                 100   
avgt   10        ≈ 10⁻⁴                 s/op
    EntityCacheInvalidateBenchmark.benchmarkInvalidate                1000   
avgt   10         0.002 ±       0.001   s/op
    EntityCachePutBenchmark.benchmarkPut                                10   
avgt   10        ≈ 10⁻⁵                 s/op
    EntityCachePutBenchmark.benchmarkPut                               100   
avgt   10        ≈ 10⁻⁴                 s/op
    EntityCachePutBenchmark.benchmarkPut                              1000   
avgt   10         0.002 ±       0.001   s/op
    EntityCachePutBenchmark.benchmarkPutWithRelation                    10   
avgt   10        ≈ 10⁻⁵                 s/op
    EntityCachePutBenchmark.benchmarkPutWithRelation                   100   
avgt   10        ≈ 10⁻⁴                 s/op
    EntityCachePutBenchmark.benchmarkPutWithRelation                  1000   
avgt   10         0.002 ±       0.001   s/op
    EntityCacheSizeBenchmark.entityCacheSize                            10   
avgt   10        ≈ 10⁻⁶                 s/op
    EntityCacheSizeBenchmark.entityCacheSize                           100   
avgt   10        ≈ 10⁻⁵                 s/op
    EntityCacheSizeBenchmark.entityCacheSize                          1000   
avgt   10        ≈ 10⁻⁴                 s/op
    ```
    The summary of operation complexity as follows
    
    | Operation Type | Time Complexity | Notes |
    | -------------- | ------------------------------------ |
    ------------------------------------------------------------ |
    | `contains` | $O(1)$ | Constant-time key lookup |
    | `get` | $O(1)$ | Direct key-based retrieval |
    | `getWithRel` | $O(K)$ | Lookup is $O(1)$, but list conversion incurs
    linear cost over $K$ |
    | `put` | $O(1)$ per entry → $O(N)$ overall | No batch insert; iterates
    over $N$ entries |
    | `putWithRel` | $O(1)$ per relation → $O(N)$ overall | Each related
    list is written individually |
    | `invalidate` | $O(N)$ | Performed per entry or group of keys |
    | `clear` | $O(N)$ | Linear sweep over all cache entries |
    | `size()` | $O(N)$ | Scans all entries to compute accurate size |
    
    Although `getWithRel()` performs a key-based lookup in the cache (which
    is O(1)), the overall cost is dominated by the conversion step that
    transforms a cached `List<Entity>` into a typed `List<E>`.
    
    This conversion involves:
    
    - Iterating over all elements in the cached list;
    - Validating and casting each Entity to the expected generic type E.
    
    As a result, the time complexity of the operation becomes $O(K)$, where
    K is the size of the cached list. This cost grows linearly with the
    number of related entities stored under a single cache key.
    
    ### Why are the changes needed?
    
    Fix: #7472
    
    ### Does this PR introduce _any_ user-facing change?
    
    no
    
    ### How was this patch tested?
    
    local test, `./gradlew :core:jmh`
---
 LICENSE.bin                                        |   1 +
 build.gradle.kts                                   |   2 +-
 core/build.gradle.kts                              |  16 ++++
 .../gravitino/cache/AbstractEntityBenchmark.java   |  92 ++++++++++++++++++
 .../apache/gravitino/cache/BenchmarkHelper.java    | 105 +++++++++++++++++++++
 .../gravitino/cache/ClearEntityCacheBenchmark.java |  58 ++++++++++++
 .../cache/ContainsEntityCacheBenchmark.java        |  66 +++++++++++++
 .../gravitino/cache/GetEntityCacheBenchmark.java   |  75 +++++++++++++++
 .../cache/InvalidateEntityCacheBenchmark.java      |  59 ++++++++++++
 .../cache/MeasureSizeEntityCacheBenchmark.java     |  76 +++++++++++++++
 .../gravitino/cache/PutEntityCacheBenchmark.java   |  77 +++++++++++++++
 .../java/org/apache/gravitino/utils/TestUtil.java  |  17 +++-
 gradle/libs.versions.toml                          |   3 +
 13 files changed, 644 insertions(+), 3 deletions(-)

diff --git a/LICENSE.bin b/LICENSE.bin
index 4e075212bc..1120dae1c2 100644
--- a/LICENSE.bin
+++ b/LICENSE.bin
@@ -378,6 +378,7 @@
    Awaitility
    npgall concurrent-trees
    reyerizo jcstress-gradle-plugin
+   melix jmh-gradle-plugin
 
    This product bundles various third-party components also under the
    Apache Software Foundation License 1.1
diff --git a/build.gradle.kts b/build.gradle.kts
index c929de3506..4adda3e60c 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -853,7 +853,7 @@ tasks {
         dependsOn("${it.name}:build")
         from("${it.name}/build/libs") {
           include("*.jar")
-          exclude("*-jcstress.jar")
+          exclude("*-jcstress.jar", "*-jmh.jar")
         }
         into("distribution/package/libs")
         setDuplicatesStrategy(DuplicatesStrategy.INCLUDE)
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 43e4cb5574..65129aab15 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -23,6 +23,7 @@ plugins {
   id("java")
   id("idea")
   alias(libs.plugins.jcstress)
+  alias(libs.plugins.jmh)
 }
 
 dependencies {
@@ -82,6 +83,11 @@ tasks.withType<JavaCompile>().configureEach {
   }
 }
 
+tasks.named<JavaCompile>("jmhCompileGeneratedClasses").configure {
+  options.errorprone?.isEnabled = false
+  options.compilerArgs.removeAll { it.contains("Xplugin:ErrorProne") }
+}
+
 jcstress {
   /*
    Available modes:
@@ -93,3 +99,13 @@ jcstress {
   mode = "default"
   jvmArgsPrepend = "-Djdk.stdout.sync=true"
 }
+
+jmh {
+  jmhVersion.set(libs.versions.jmh.asProvider())
+  warmupIterations = 5
+  iterations = 10
+  fork = 1
+  threads = 4
+  resultFormat = "csv"
+  resultsFile = file("$buildDir/reports/jmh/results.csv")
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/AbstractEntityBenchmark.java 
b/core/src/jmh/java/org/apache/gravitino/cache/AbstractEntityBenchmark.java
new file mode 100644
index 0000000000..a90ce8208c
--- /dev/null
+++ b/core/src/jmh/java/org/apache/gravitino/cache/AbstractEntityBenchmark.java
@@ -0,0 +1,92 @@
+/*
+ * 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.gravitino.cache;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * AbstractEntityBenchmark is a JMH-based benchmark base class designed to 
evaluate the performance
+ * of the {@link EntityCache} implementation in Gravitino.
+ *
+ * <p>This class sets up a controlled environment where a configurable number 
of entities and their
+ * associated relations are preloaded into the cache before each iteration. It 
supports benchmarking
+ * cache operations such as put, get, and relation-based access under varying 
data volumes.
+ *
+ * <p>Features:
+ *
+ * <ul>
+ *   <li>Supports benchmarking with different entity counts (e.g., 10, 100, 
1000)
+ *   <li>Measures both throughput and average execution time using JMH modes
+ *   <li>Preloads both simple entities and relation entities (e.g., user-role 
mappings)
+ * </ul>
+ *
+ * <p>Subclasses should implement benchmark methods to evaluate specific cache 
operations.
+ *
+ * @param <E> the type of entity under test, must implement {@link Entity} and 
{@link HasIdentifier}
+ * @see org.apache.gravitino.cache.CaffeineEntityCache
+ * @see org.openjdk.jmh.annotations.Benchmark
+ * @see org.apache.gravitino.cache.BenchmarkHelper
+ */
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.SECONDS)
+@State(Scope.Thread)
+public abstract class AbstractEntityBenchmark<E extends Entity & 
HasIdentifier> {
+  @Param({"10", "100", "1000"})
+  public int totalCnt;
+
+  protected EntityCache cache;
+  protected List<ModelEntity> entities;
+  protected Map<RoleEntity, List<E>> entitiesWithRelations;
+  protected Random random = new Random();
+
+  @Setup(Level.Iteration)
+  public void setup() {
+    Config config = new Config() {};
+    this.cache = new CaffeineEntityCache(config);
+    this.entities = BenchmarkHelper.getEntities(totalCnt);
+    this.entitiesWithRelations = BenchmarkHelper.getRelationEntities(totalCnt);
+
+    entities.forEach(cache::put);
+    entitiesWithRelations.forEach(
+        (roleEntity, userList) ->
+            cache.put(
+                roleEntity.nameIdentifier(),
+                roleEntity.type(),
+                SupportsRelationOperations.Type.ROLE_USER_REL,
+                userList));
+  }
+}
diff --git a/core/src/jmh/java/org/apache/gravitino/cache/BenchmarkHelper.java 
b/core/src/jmh/java/org/apache/gravitino/cache/BenchmarkHelper.java
new file mode 100644
index 0000000000..b8e3f1815c
--- /dev/null
+++ b/core/src/jmh/java/org/apache/gravitino/cache/BenchmarkHelper.java
@@ -0,0 +1,105 @@
+/*
+ * 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.gravitino.cache;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.utils.TestUtil;
+
+public class BenchmarkHelper {
+  public static final int RELATION_CNT = 5;
+  private static final Random random = new Random();
+
+  private BenchmarkHelper() {
+    // Utility class, no constructor needed
+  }
+
+  /**
+   * Generates a map of entities with their relations.
+   *
+   * @param entityCnt the count of entities to generate.
+   * @return a map of entities with their relations.
+   * @param <E> the type of the entity.
+   */
+  public static <E extends Entity & HasIdentifier> Map<RoleEntity, List<E>> 
getRelationEntities(
+      int entityCnt) {
+    Map<RoleEntity, List<E>> entitiesWithRelations = Maps.newHashMap();
+    for (int i = 0; i < entityCnt; i++) {
+      RoleEntity testRoleEntity = TestUtil.getTestRoleEntity();
+      int userCnt = random.nextInt(RELATION_CNT);
+      List<E> userList = 
BaseEntityCache.convertEntities(getUserList(testRoleEntity, userCnt));
+      entitiesWithRelations.put(testRoleEntity, userList);
+    }
+
+    return entitiesWithRelations;
+  }
+
+  /**
+   * Generates a list of entities with the specified count.
+   *
+   * @param entityCnt the count of entities to generate.
+   * @return a list of model entities.
+   */
+  public static List<ModelEntity> getEntities(int entityCnt) {
+    List<ModelEntity> entities = new ArrayList<>(entityCnt);
+    for (int i = 0; i < entityCnt; i++) {
+      entities.add(TestUtil.getTestModelEntity());
+    }
+
+    return entities;
+  }
+
+  /**
+   * Returns a randomly selected key from the given map.
+   *
+   * @param map the input map from which to select a random key
+   * @param <K> the type of keys in the map
+   * @param <V> the type of values in the map
+   * @return a randomly selected key from the map, or {@code null} if the map 
is null or empty
+   */
+  public static <K, V> K getRandomKey(Map<K, V> map) {
+    if (map == null || map.isEmpty()) {
+      return null;
+    }
+
+    List<K> keys = new ArrayList<>(map.keySet());
+
+    return keys.get(random.nextInt(keys.size()));
+  }
+
+  private static List<Entity> getUserList(RoleEntity roleEntity, int userCnt) {
+    List<Entity> userList = new ArrayList<>(userCnt);
+    List<Long> roleIds = ImmutableList.of(roleEntity.id());
+
+    for (int i = 0; i < userCnt; i++) {
+      userList.add(TestUtil.getTestUserEntity(roleIds));
+    }
+
+    return userList;
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/ClearEntityCacheBenchmark.java 
b/core/src/jmh/java/org/apache/gravitino/cache/ClearEntityCacheBenchmark.java
new file mode 100644
index 0000000000..4ccaea4079
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/ClearEntityCacheBenchmark.java
@@ -0,0 +1,58 @@
+/*
+ * 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.gravitino.cache;
+
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.meta.ModelEntity;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Setup;
+
+/**
+ * EntityCacheClearBenchmark benchmarks the performance of the {@link 
EntityCache#clear()} operation
+ * under varying cache sizes.
+ *
+ * <p>This benchmark repeatedly clears the cache to evaluate the efficiency 
and overhead of the
+ * cache invalidation mechanism, especially under different entity volumes 
(e.g., 10, 100, 1000).
+ *
+ * <p>Before each invocation, the cache is pre-populated with a predefined 
list of entities to
+ * ensure the {@code clear()} method operates on a non-empty cache.
+ *
+ * @param <E> the entity type being tested, must extend {@link Entity} and 
implement {@link
+ *     HasIdentifier}
+ * @see org.apache.gravitino.cache.EntityCache
+ * @see org.openjdk.jmh.annotations.Benchmark
+ */
+public class ClearEntityCacheBenchmark<E extends Entity & HasIdentifier>
+    extends AbstractEntityBenchmark {
+
+  @Setup(Level.Invocation)
+  @SuppressWarnings("unchecked")
+  public void prepareCacheForClear() {
+    cache.clear();
+    entities.forEach(e -> cache.put((ModelEntity) e));
+  }
+
+  @Benchmark
+  public void benchmarkClear() {
+    cache.clear();
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/ContainsEntityCacheBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/ContainsEntityCacheBenchmark.java
new file mode 100644
index 0000000000..905500d1af
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/ContainsEntityCacheBenchmark.java
@@ -0,0 +1,66 @@
+/*
+ * 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.gravitino.cache;
+
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.openjdk.jmh.annotations.Benchmark;
+
+/**
+ * EntityCacheContainsBenchmark benchmarks the performance of the {@link 
EntityCache#contains}
+ * operation for both regular entities and relation-based entries.
+ *
+ * <p>This benchmark evaluates how efficiently the cache can determine whether 
a given entity or a
+ * relationship entry exists, under different data volumes (e.g., 10, 100, 
1000).
+ *
+ * <p>It includes two benchmark methods:
+ *
+ * <ul>
+ *   <li>{@code benchmarkContains}: Checks the presence of a single {@link 
ModelEntity} in the
+ *       cache.
+ *   <li>{@code benchmarkContainsWithRelation}: Checks whether a relation 
entry (e.g., role-to-user
+ *       mapping) exists for a given {@link RoleEntity} and relation type.
+ * </ul>
+ *
+ * @see org.apache.gravitino.cache.EntityCache
+ * @see org.openjdk.jmh.annotations.Benchmark
+ */
+public class ContainsEntityCacheBenchmark extends AbstractEntityBenchmark {
+
+  @Benchmark
+  public boolean benchmarkContains() {
+    int idx = random.nextInt(entities.size());
+    ModelEntity sampleEntity = (ModelEntity) entities.get(idx);
+
+    return cache.contains(sampleEntity.nameIdentifier(), sampleEntity.type());
+  }
+
+  @Benchmark
+  @SuppressWarnings("unchecked")
+  public boolean benchmarkContainsWithRelation() {
+    RoleEntity sampleRoleEntity = (RoleEntity) 
BenchmarkHelper.getRandomKey(entitiesWithRelations);
+
+    return cache.contains(
+        sampleRoleEntity.nameIdentifier(),
+        sampleRoleEntity.type(),
+        SupportsRelationOperations.Type.ROLE_USER_REL);
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/GetEntityCacheBenchmark.java 
b/core/src/jmh/java/org/apache/gravitino/cache/GetEntityCacheBenchmark.java
new file mode 100644
index 0000000000..b19a5e1ae2
--- /dev/null
+++ b/core/src/jmh/java/org/apache/gravitino/cache/GetEntityCacheBenchmark.java
@@ -0,0 +1,75 @@
+/*
+ * 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.gravitino.cache;
+
+import java.util.List;
+import java.util.Optional;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.openjdk.jmh.annotations.Benchmark;
+
+/**
+ * EntityCacheGetBenchmark benchmarks the performance of the {@link 
EntityCache#getIfPresent}
+ * operation for both individual entities and entity relations.
+ *
+ * <p>This benchmark measures the efficiency of cache lookups under varying 
data sizes (e.g., 10,
+ * 100, 1000 entries), helping evaluate the speed and consistency of the 
cache's get operations.
+ *
+ * <p>It includes two benchmark methods:
+ *
+ * <ul>
+ *   <li>{@code benchmarkGet}: Retrieves a {@link ModelEntity} by its 
identifier and type.
+ *   <li>{@code benchmarkGetWithRelations}: Retrieves a list of related 
entities (e.g., users under
+ *       a role) using a relation type and a {@link RoleEntity} as the lookup 
key.
+ * </ul>
+ *
+ * @param <E> the type of related entity, extending {@link Entity} and 
implementing {@link
+ *     HasIdentifier}
+ * @see org.apache.gravitino.cache.EntityCache
+ * @see org.openjdk.jmh.annotations.Benchmark
+ */
+public class GetEntityCacheBenchmark<E extends Entity & HasIdentifier>
+    extends AbstractEntityBenchmark {
+
+  @Benchmark
+  public Entity benchmarkGet() {
+    int idx = random.nextInt(entities.size());
+    ModelEntity sampleEntity = (ModelEntity) entities.get(idx);
+
+    return cache.getIfPresent(sampleEntity.nameIdentifier(), 
sampleEntity.type()).orElse(null);
+  }
+
+  @Benchmark
+  @SuppressWarnings("unchecked")
+  public List<E> benchmarkGetWithRelations() {
+    RoleEntity sampleRoleEntity = (RoleEntity) 
BenchmarkHelper.getRandomKey(entitiesWithRelations);
+
+    Optional<List<E>> relationFromCache =
+        cache.getIfPresent(
+            SupportsRelationOperations.Type.ROLE_USER_REL,
+            sampleRoleEntity.nameIdentifier(),
+            sampleRoleEntity.type());
+
+    return relationFromCache.orElse(null);
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/InvalidateEntityCacheBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/InvalidateEntityCacheBenchmark.java
new file mode 100644
index 0000000000..9eb3f202a3
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/InvalidateEntityCacheBenchmark.java
@@ -0,0 +1,59 @@
+/*
+ * 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.gravitino.cache;
+
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.meta.ModelEntity;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Setup;
+
+/**
+ * EntityCacheInvalidateBenchmark benchmarks the performance of the {@link 
EntityCache#invalidate}
+ * operation for a single entity entry.
+ *
+ * <p>Before each benchmark invocation, the cache is repopulated with a 
predefined list of entities
+ * to ensure that the invalidation targets a present and consistent entry.
+ *
+ * <p>The benchmark focuses on the following scenario:
+ *
+ * <ul>
+ *   <li>{@code benchmarkInvalidate}: Invalidates a single {@link Entity} 
identified by a fixed
+ *       {@link NameIdentifier} and {@link Entity.EntityType}.
+ * </ul>
+ *
+ * @see org.apache.gravitino.cache.EntityCache
+ * @see org.apache.gravitino.cache.CaffeineEntityCache
+ * @see org.openjdk.jmh.annotations.Benchmark
+ */
+public class InvalidateEntityCacheBenchmark extends AbstractEntityBenchmark {
+
+  @Setup(Level.Invocation)
+  @SuppressWarnings("unchecked")
+  public void prepareCacheForClear() {
+    entities.forEach(e -> cache.put((ModelEntity) e));
+  }
+
+  @Benchmark
+  public void benchmarkInvalidate() {
+    cache.invalidate(NameIdentifier.of("m1"), Entity.EntityType.METALAKE);
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/MeasureSizeEntityCacheBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/MeasureSizeEntityCacheBenchmark.java
new file mode 100644
index 0000000000..5643feb10e
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/MeasureSizeEntityCacheBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * 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.gravitino.cache;
+
+import java.util.List;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.openjdk.jmh.annotations.Benchmark;
+
+/**
+ * EntityCacheSizeBenchmark benchmarks the performance and overhead of 
querying the cache size via
+ * {@link EntityCache#size()} under varying data volumes.
+ *
+ * <p>During setup, both standard entities and relation-based entities are 
preloaded into the cache
+ * using the configured entity count. This ensures that the {@code size()} 
method operates on a
+ * fully populated cache with realistic structure and distribution.
+ *
+ * <p>The benchmark includes a single method:
+ *
+ * <ul>
+ *   <li>{@code entityCacheSize}: Measures the execution time of retrieving 
the total number of
+ *       cached entries.
+ * </ul>
+ *
+ * @param <E> the type of related entity, extending {@link Entity} and 
implementing {@link
+ *     HasIdentifier}
+ * @see org.apache.gravitino.cache.EntityCache
+ * @see org.openjdk.jmh.annotations.Benchmark
+ */
+public class MeasureSizeEntityCacheBenchmark<E extends Entity & HasIdentifier>
+    extends AbstractEntityBenchmark {
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public void setup() {
+    Config config = new Config() {};
+    this.cache = new CaffeineEntityCache(config);
+    this.entities = BenchmarkHelper.getEntities(totalCnt);
+    this.entitiesWithRelations = BenchmarkHelper.getRelationEntities(totalCnt);
+
+    entities.forEach(e -> cache.put((ModelEntity) e));
+    entitiesWithRelations.forEach(
+        (roleEntity, userList) ->
+            cache.put(
+                ((RoleEntity) roleEntity).nameIdentifier(),
+                ((RoleEntity) roleEntity).type(),
+                SupportsRelationOperations.Type.ROLE_USER_REL,
+                (List<E>) userList));
+  }
+
+  @Benchmark
+  public long entityCacheSize() {
+    return cache.size();
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/PutEntityCacheBenchmark.java 
b/core/src/jmh/java/org/apache/gravitino/cache/PutEntityCacheBenchmark.java
new file mode 100644
index 0000000000..1a20a08549
--- /dev/null
+++ b/core/src/jmh/java/org/apache/gravitino/cache/PutEntityCacheBenchmark.java
@@ -0,0 +1,77 @@
+/*
+ * 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.gravitino.cache;
+
+import java.util.List;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Setup;
+
+/**
+ * EntityCachePutBenchmark benchmarks the performance of {@link 
EntityCache#put} operations for both
+ * standard entities and relation-based entity groups.
+ *
+ * <p>Each benchmark invocation starts with a cleared cache to ensure that 
measurements reflect
+ * fresh insertions without side effects from previous state.
+ *
+ * <p>This benchmark covers two insertion scenarios:
+ *
+ * <ul>
+ *   <li>{@code benchmarkPut}: Inserts a batch of {@link ModelEntity} 
instances into the cache.
+ *   <li>{@code benchmarkPutWithRelation}: Inserts relation mappings between 
{@link RoleEntity} and
+ *       related entities (e.g., users), using a relation type as part of the 
cache key.
+ * </ul>
+ *
+ * @param <E> the type of related entity, extending {@link Entity} and 
implementing {@link
+ *     HasIdentifier}
+ * @see org.apache.gravitino.cache.EntityCache
+ * @see org.openjdk.jmh.annotations.Benchmark
+ */
+public class PutEntityCacheBenchmark<E extends Entity & HasIdentifier>
+    extends AbstractEntityBenchmark {
+
+  @Setup(Level.Invocation)
+  public void prepareForCachePut() {
+    cache.clear();
+  }
+
+  @Benchmark
+  @SuppressWarnings("unchecked")
+  public void benchmarkPut() {
+    entities.forEach(e -> cache.put((ModelEntity) e));
+  }
+
+  @Benchmark
+  @SuppressWarnings("unchecked")
+  public void benchmarkPutWithRelation() {
+    entitiesWithRelations.forEach(
+        (role, relationEntities) ->
+            cache.put(
+                ((RoleEntity) role).nameIdentifier(),
+                ((RoleEntity) role).type(),
+                SupportsRelationOperations.Type.ROLE_USER_REL,
+                (List<E>) relationEntities));
+  }
+}
diff --git a/core/src/test/java/org/apache/gravitino/utils/TestUtil.java 
b/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
index 1242a342ed..fc909dc6a1 100644
--- a/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
+++ b/core/src/test/java/org/apache/gravitino/utils/TestUtil.java
@@ -171,7 +171,8 @@ public class TestUtil {
    * @return The test {@link TableEntity} entity.
    */
   public static ModelEntity getTestModelEntity() {
-    return getTestModelEntity(generator.nextId(), "test_model", 
Namespace.of("m1", "c1", "s1"));
+    return getTestModelEntity(
+        generator.nextId(), RandomNameUtils.genRandomName("model"), 
Namespace.of("m1", "c1", "s1"));
   }
 
   /**
@@ -365,6 +366,17 @@ public class TestUtil {
         generator.nextId(), "test_user", "test_metalake", 
ImmutableList.of(1L));
   }
 
+  /**
+   * Returns a test user entity with the given ID, name, and roles.
+   *
+   * @param roles The roles of the user entity.
+   * @return The test {@link UserEntity} entity.
+   */
+  public static UserEntity getTestUserEntity(List<Long> roles) {
+    return getTestUserEntity(
+        generator.nextId(), RandomNameUtils.genRandomName("user"), 
"test_metalake", roles);
+  }
+
   /**
    * Returns a test user entity with the given ID, name, and metalake.
    *
@@ -421,7 +433,8 @@ public class TestUtil {
    * @return The test {@link RoleEntity} entity.
    */
   public static RoleEntity getTestRoleEntity() {
-    return getTestRoleEntity(generator.nextId(), "test_role", "test_metalake");
+    return getTestRoleEntity(
+        generator.nextId(), RandomNameUtils.genRandomName("role"), 
"test_metalake");
   }
 
   /**
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 0796f97ca8..8330ca8759 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -121,6 +121,8 @@ aliyun-credentials = "0.3.12"
 openlineage = "1.29.0"
 concurrent-trees = "2.6.0"
 jcstress = "0.8.15"
+jmh-plugin = "0.7.3"
+jmh = "1.37"
 
 [libraries]
 aws-iam = { group = "software.amazon.awssdk", name = "iam", version.ref = 
"awssdk" }
@@ -309,3 +311,4 @@ dependencyLicenseReport = {id = 
"com.github.jk1.dependency-license-report", vers
 bom = {id = "org.cyclonedx.bom", version = "1.5.0"}
 errorprone = {id = "net.ltgt.errorprone", version.ref = "error-prone"}
 jcstress = { id = "io.github.reyerizo.gradle.jcstress", version.ref = 
"jcstress" }
+jmh = { id = "me.champeau.jmh", version.ref = "jmh-plugin" }
\ No newline at end of file


Reply via email to