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

liuxun 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 d753e69316 [#7546] feat(cache): Add JMH E2E Performance Test (#7717)
d753e69316 is described below

commit d753e693162ce1453e34653face3b20438a68c25
Author: Lord of Abyss <[email protected]>
AuthorDate: Mon Jul 21 11:07:17 2025 +0800

    [#7546] feat(cache): Add JMH E2E Performance Test (#7717)
    
    ### 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
    - Backend: H2
    
    JMH configuration as follows:
    
    ```gradle
    jmh {
      jmhVersion.set(libs.versions.jmh.asProvider())
      warmupIterations = 5
      iterations = 10
      fork = 1
      threads = 10
      resultFormat = "csv"
      resultsFile = file("$buildDir/reports/jmh/results.csv")
    }
    ```
    > !!!If the CPU number of the machine you tested is less than 10, the
    results may not be very accurate.
    
    Add JMH Tests for `EntityStorage`(**enable** cache system), the result
    as follows:
    
    ```bash
    Benchmark                                                    (totalCnt)   
Mode  Cnt         Score         Error  Units
    ContainsEntityStorageBenchmark.benchmarkContains                     10  
thrpt   10   8638224.459 ±  566802.726  ops/s
    ContainsEntityStorageBenchmark.benchmarkContains                    100  
thrpt    9   8253686.221 ± 1024636.576  ops/s
    ContainsEntityStorageBenchmark.benchmarkContains                   1000  
thrpt    9   7905133.978 ± 1657545.830  ops/s
    GetEntityStorageBenchmark.benchmarkGet                               10  
thrpt   10  10514791.439 ± 1278830.339  ops/s
    GetEntityStorageBenchmark.benchmarkGet                              100  
thrpt    9  10266771.834 ± 3241412.140  ops/s
    GetEntityStorageBenchmark.benchmarkGet                             1000  
thrpt    9   9205199.021 ±   45147.883  ops/s
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation          10  
thrpt   10   9776657.093 ± 2340808.096  ops/s
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation         100  
thrpt    9   6256556.784 ± 1027312.353  ops/s
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation        1000  
thrpt    9   1056512.115 ±   33070.525  ops/s
    ContainsEntityStorageBenchmark.benchmarkContains                     10   
avgt   10        ≈ 10⁻⁶                 s/op
    ContainsEntityStorageBenchmark.benchmarkContains                    100   
avgt    9        ≈ 10⁻⁶                 s/op
    ContainsEntityStorageBenchmark.benchmarkContains                   1000   
avgt    9        ≈ 10⁻⁶                 s/op
    GetEntityStorageBenchmark.benchmarkGet                               10   
avgt   10        ≈ 10⁻⁶                 s/op
    GetEntityStorageBenchmark.benchmarkGet                              100   
avgt    9        ≈ 10⁻⁶                 s/op
    GetEntityStorageBenchmark.benchmarkGet                             1000   
avgt    9        ≈ 10⁻⁶                 s/op
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation          10   
avgt   10        ≈ 10⁻⁶                 s/op
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation         100   
avgt    9        ≈ 10⁻⁶                 s/op
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation        1000   
avgt    9        ≈ 10⁻⁵                 s/op
    ```
    
    Add JMH Tests for `EntityStorage`(**disable** cache system), the result
    as follows:
    
    ```bash
    Benchmark                                                    (totalCnt)   
Mode  Cnt      Score       Error  Units
    ContainsEntityStorageBenchmark.benchmarkContains                     10  
thrpt   10  89990.430 ±  6330.841  ops/s
    ContainsEntityStorageBenchmark.benchmarkContains                    100  
thrpt   10  88707.494 ± 11850.585  ops/s
    ContainsEntityStorageBenchmark.benchmarkContains                   1000  
thrpt   10  86073.612 ± 16018.779  ops/s
    GetEntityStorageBenchmark.benchmarkGet                               10  
thrpt   10  91341.115 ±  4857.600  ops/s
    GetEntityStorageBenchmark.benchmarkGet                              100  
thrpt   10  89763.528 ±  5004.285  ops/s
    GetEntityStorageBenchmark.benchmarkGet                             1000  
thrpt   10  86559.595 ±  8223.997  ops/s
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation          10  
thrpt   10  44403.008 ±  2316.614  ops/s
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation         100  
thrpt   10  45409.150 ±  1077.432  ops/s
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation        1000  
thrpt   10  43654.250 ±  1195.958  ops/s
    ContainsEntityStorageBenchmark.benchmarkContains                     10   
avgt   10     ≈ 10⁻⁴               s/op
    ContainsEntityStorageBenchmark.benchmarkContains                    100   
avgt   10     ≈ 10⁻⁴               s/op
    ContainsEntityStorageBenchmark.benchmarkContains                   1000   
avgt   10     ≈ 10⁻⁴               s/op
    GetEntityStorageBenchmark.benchmarkGet                               10   
avgt   10     ≈ 10⁻⁴               s/op
    GetEntityStorageBenchmark.benchmarkGet                              100   
avgt   10     ≈ 10⁻⁴               s/op
    GetEntityStorageBenchmark.benchmarkGet                             1000   
avgt   10     ≈ 10⁻⁴               s/op
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation          10   
avgt   10     ≈ 10⁻⁴               s/op
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation         100   
avgt   10     ≈ 10⁻⁴               s/op
    ListRelationEntityStorageBenchmark.benchmarkGetWithRelation        1000   
avgt   10     ≈ 10⁻⁴               s/op
    ```
    
    Also, I've added a mixed benchmark to simulate more realistic workloads,
    consisting of:
    
    - 60% get entity operations
    - 30% listRelation operations
    - 10% contains checks
    
    Here are the benchmark results with caching **enabled**:
    
    ```bash
    Benchmark                                               (totalCnt)   Mode  
Cnt         Score         Error  Units
    MixEntityStorageBenchmark.ops                                   10  thrpt   
10  16314701.666 ± 2321546.251  ops/s
    MixEntityStorageBenchmark.ops:benchmarkContains                 10  thrpt   
10  11524726.896 ± 1375673.356  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGet                      10  thrpt   
10   4381654.718 ±  840550.376  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation          10  thrpt   
10    408320.052 ±  111527.772  ops/s
    MixEntityStorageBenchmark.ops                                  100  thrpt   
10  15701411.738 ±  963344.095  ops/s
    MixEntityStorageBenchmark.ops:benchmarkContains                100  thrpt   
10  12149946.906 ±  716587.395  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGet                     100  thrpt   
10   3372738.319 ±  231818.443  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation         100  thrpt   
10    178726.513 ±   22041.810  ops/s
    MixEntityStorageBenchmark.ops                                 1000  thrpt   
10  12649753.420 ±  455084.479  ops/s
    MixEntityStorageBenchmark.ops:benchmarkContains               1000  thrpt   
10   9676336.734 ±  355157.774  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGet                    1000  thrpt   
10   2916187.967 ±  103215.487  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation        1000  thrpt   
10     57228.719 ±    6088.712  ops/s
    MixEntityStorageBenchmark.ops                                   10   avgt   
10        ≈ 10⁻⁶                 s/op
    MixEntityStorageBenchmark.ops:benchmarkContains                 10   avgt   
10        ≈ 10⁻⁷                 s/op
    MixEntityStorageBenchmark.ops:benchmarkGet                      10   avgt   
10        ≈ 10⁻⁶                 s/op
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation          10   avgt   
10        ≈ 10⁻⁵                 s/op
    MixEntityStorageBenchmark.ops                                  100   avgt   
10        ≈ 10⁻⁵                 s/op
    MixEntityStorageBenchmark.ops:benchmarkContains                100   avgt   
10        ≈ 10⁻⁷                 s/op
    MixEntityStorageBenchmark.ops:benchmarkGet                     100   avgt   
10        ≈ 10⁻⁶                 s/op
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation         100   avgt   
10        ≈ 10⁻⁵                 s/op
    MixEntityStorageBenchmark.ops                                 1000   avgt   
10        ≈ 10⁻⁵                 s/op
    MixEntityStorageBenchmark.ops:benchmarkContains               1000   avgt   
10        ≈ 10⁻⁷                 s/op
    MixEntityStorageBenchmark.ops:benchmarkGet                    1000   avgt   
10        ≈ 10⁻⁶                 s/op
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation        1000   avgt   
10        ≈ 10⁻⁴                 s/op
    ```
    
    Here are the benchmark results with caching **disabled**:
    
    ```bash
    Benchmark                                               (totalCnt)   Mode  
Cnt      Score       Error  Units
    MixEntityStorageBenchmark.ops                                   10  thrpt   
10  44856.068 ±  2272.854  ops/s
    MixEntityStorageBenchmark.ops:benchmarkContains                 10  thrpt   
10  23405.339 ±  1167.919  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGet                      10  thrpt   
10  18215.276 ±  2410.224  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation          10  thrpt   
10   3235.453 ±  1927.851  ops/s
    MixEntityStorageBenchmark.ops                                  100  thrpt   
10  34786.730 ± 14113.541  ops/s
    MixEntityStorageBenchmark.ops:benchmarkContains                100  thrpt   
10  17616.938 ±  6843.631  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGet                     100  thrpt   
10  16811.959 ±  7305.477  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation         100  thrpt   
10    357.833 ±   268.326  ops/s
    MixEntityStorageBenchmark.ops                                 1000  thrpt   
10  40777.915 ±  5099.553  ops/s
    MixEntityStorageBenchmark.ops:benchmarkContains               1000  thrpt   
10  20994.004 ±  2557.324  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGet                    1000  thrpt   
10  19771.629 ±  2611.305  ops/s
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation        1000  thrpt   
10     12.282 ±     6.073  ops/s
    MixEntityStorageBenchmark.ops                                   10   avgt   
10      0.001 ±     0.001   s/op
    MixEntityStorageBenchmark.ops:benchmarkContains                 10   avgt   
10     ≈ 10⁻⁴               s/op
    MixEntityStorageBenchmark.ops:benchmarkGet                      10   avgt   
10     ≈ 10⁻³               s/op
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation          10   avgt   
10      0.001 ±     0.001   s/op
    MixEntityStorageBenchmark.ops                                  100   avgt   
10      0.114 ±     0.157   s/op
    MixEntityStorageBenchmark.ops:benchmarkContains                100   avgt   
10     ≈ 10⁻⁴               s/op
    MixEntityStorageBenchmark.ops:benchmarkGet                     100   avgt   
10     ≈ 10⁻³               s/op
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation         100   avgt   
10      0.379 ±     0.524   s/op
    MixEntityStorageBenchmark.ops                                 1000   avgt   
10      0.230 ±     0.143   s/op
    MixEntityStorageBenchmark.ops:benchmarkContains               1000   avgt   
10     ≈ 10⁻⁴               s/op
    MixEntityStorageBenchmark.ops:benchmarkGet                    1000   avgt   
10     ≈ 10⁻⁴               s/op
    MixEntityStorageBenchmark.ops:benchmarkGetWithRelation        1000   avgt   
10      0.765 ±     0.478   s/op
    ```
    
    
    ### Why are the changes needed?
    
    Fix: #7546
    
    ### Does this PR introduce _any_ user-facing change?
    
    no
    
    ### How was this patch tested?
    
    local test.
---
 core/build.gradle.kts                              |   2 +-
 .../apache/gravitino/cache/BenchmarkHelper.java    |  26 ++
 .../cache/it/AbstractEntityStorageBenchmark.java   | 392 +++++++++++++++++++++
 .../cache/it/ContainsEntityStorageBenchmark.java   |  69 ++++
 .../cache/it/GetEntityStorageBenchmark.java        |  79 +++++
 .../it/ListRelationEntityStorageBenchmark.java     |  88 +++++
 .../cache/it/MixEntityStorageBenchmark.java        | 155 ++++++++
 7 files changed, 810 insertions(+), 1 deletion(-)

diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 65129aab15..44b3319964 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -105,7 +105,7 @@ jmh {
   warmupIterations = 5
   iterations = 10
   fork = 1
-  threads = 4
+  threads = 10
   resultFormat = "csv"
   resultsFile = file("$buildDir/reports/jmh/results.csv")
 }
diff --git a/core/src/jmh/java/org/apache/gravitino/cache/BenchmarkHelper.java 
b/core/src/jmh/java/org/apache/gravitino/cache/BenchmarkHelper.java
index b8e3f1815c..7d80a36c47 100644
--- a/core/src/jmh/java/org/apache/gravitino/cache/BenchmarkHelper.java
+++ b/core/src/jmh/java/org/apache/gravitino/cache/BenchmarkHelper.java
@@ -19,6 +19,7 @@
 
 package org.apache.gravitino.cache;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import java.util.ArrayList;
@@ -27,6 +28,7 @@ import java.util.Map;
 import java.util.Random;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.meta.ModelEntity;
 import org.apache.gravitino.meta.RoleEntity;
 import org.apache.gravitino.utils.TestUtil;
@@ -92,6 +94,30 @@ public class BenchmarkHelper {
     return keys.get(random.nextInt(keys.size()));
   }
 
+  /**
+   * Returns the {@link NameIdentifier} of the entity based on its type.
+   *
+   * @param entity The {@link Entity} instance.
+   * @return The {@link NameIdentifier} of the entity
+   */
+  public static NameIdentifier getIdentFromEntity(Entity entity) {
+    validateEntityHasIdentifier(entity);
+    HasIdentifier hasIdentifier = (HasIdentifier) entity;
+
+    return hasIdentifier.nameIdentifier();
+  }
+
+  /**
+   * Checks if the entity is of type {@link HasIdentifier}.
+   *
+   * @param entity The {@link Entity} instance to check.
+   */
+  public static void validateEntityHasIdentifier(Entity entity) {
+    Preconditions.checkArgument(entity != null, "Entity cannot be null");
+    Preconditions.checkArgument(
+        entity instanceof HasIdentifier, "Unsupported EntityType: " + 
entity.type());
+  }
+
   private static List<Entity> getUserList(RoleEntity roleEntity, int userCnt) {
     List<Entity> userList = new ArrayList<>(userCnt);
     List<Long> roleIds = ImmutableList.of(roleEntity.id());
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/it/AbstractEntityStorageBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/it/AbstractEntityStorageBenchmark.java
new file mode 100644
index 0000000000..a7f17709e7
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/it/AbstractEntityStorageBenchmark.java
@@ -0,0 +1,392 @@
+/*
+ * 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.it;
+
+import static org.apache.gravitino.Configs.DEFAULT_ENTITY_RELATIONAL_STORE;
+import static 
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER;
+import static 
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_MAX_CONNECTIONS;
+import static 
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD;
+import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PATH;
+import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL;
+import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_USER;
+import static 
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_WAIT_MILLISECONDS;
+import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_STORE;
+import static org.apache.gravitino.Configs.ENTITY_STORE;
+import static org.apache.gravitino.Configs.RELATIONAL_ENTITY_STORE;
+import static org.apache.gravitino.Configs.STORE_DELETE_AFTER_TIME;
+import static org.apache.gravitino.Configs.VERSION_RETENTION_COUNT;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.EntityStoreFactory;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.meta.BaseMetalake;
+import org.apache.gravitino.meta.CatalogEntity;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.meta.SchemaVersion;
+import org.apache.gravitino.meta.TopicEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.storage.RandomIdGenerator;
+import org.apache.gravitino.storage.relational.RelationalEntityStore;
+import org.apache.gravitino.storage.relational.converters.H2ExceptionConverter;
+import 
org.apache.gravitino.storage.relational.converters.SQLExceptionConverterFactory;
+import org.apache.gravitino.utils.RandomNameUtils;
+import org.apache.gravitino.utils.TestUtil;
+import org.mockito.Mockito;
+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;
+import org.openjdk.jmh.annotations.TearDown;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
+
+/**
+ * Benchmark base class for evaluating {@code EntityStorage} performance using 
JMH.
+ *
+ * <p>This abstract class provides a reusable setup for benchmarking 
relational entity store
+ * implementations. It uses an embedded H2 backend and generates randomized 
metadata entities (e.g.,
+ * models, users, topics) to simulate realistic usage patterns. Subclasses 
should implement specific
+ * {@code @Benchmark} methods to test operations such as {@code put}, {@code 
get}, {@code contains},
+ * and {@code insertRelation}.
+ *
+ * <p>Benchmark operations include:
+ *
+ * <ul>
+ *   <li>{@code benchmarkContains} — test for entity existence
+ *   <li>{@code benchmarkGet} — test for entity retrieval
+ *   <li>{@code benchmarkGetWithRelation} — test for listing entities with 
relations
+ * </ul>
+ *
+ * <p>Environment:
+ *
+ * <ul>
+ *   <li>CPU: Apple M2 Pro
+ *   <li>Memory: 16 GB
+ *   <li>OS: macOS Ventura 15.5
+ *   <li>JVM: OpenJDK 17.0.15
+ *   <li>JMH: 1.37
+ *   <li>Backend: H2
+ * </ul>
+ *
+ * <p>Benchmark configuration:
+ *
+ * <ul>
+ *   <li>{@code @BenchmarkMode}: Throughput and AverageTime
+ *   <li>{@code @State}: Scope.Thread
+ *   <li>{@code @Fork}: 1
+ *   <li>{@code @Warmup}: 5 iterations
+ *   <li>{@code @Measurement}: 10 iterations
+ *   <li>Gradle config:
+ *       <pre>{@code
+ * jmh {
+ *   warmupIterations = 5
+ *   iterations = 10
+ *   fork = 1
+ *   threads = 10
+ *   resultFormat = "csv"
+ *   resultsFile = file("$buildDir/reports/jmh/results.csv")
+ * }
+ *
+ * }</pre>
+ * </ul>
+ *
+ * <p>Two cache modes are benchmarked:
+ *
+ * <ol>
+ *   <li><strong>With cache enabled</strong>: throughput up to ~10⁶ ops/sec
+ *   <li><strong>With cache disabled</strong>: throughput around ~10⁴ ops/sec
+ * </ol>
+ *
+ * <p>A mixed benchmark is also included to simulate real workloads, 
consisting of:
+ *
+ * <ul>
+ *   <li>60% {@code get}
+ *   <li>30% {@code listRelation}
+ *   <li>10% {@code contains}
+ * </ul>
+ *
+ * <p><strong>Note:</strong> If your machine has fewer than 10 CPU cores, 
benchmark results may
+ * vary.
+ *
+ * <p>Related PR: https://github.com/apache/gravitino/issues/7546
+ */
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.SECONDS)
+@State(Scope.Thread)
+public class AbstractEntityStorageBenchmark<E extends Entity & HasIdentifier> {
+  protected static final Random random = ThreadLocalRandom.current();
+  private static final Logger LOG =
+      LoggerFactory.getLogger(AbstractEntityStorageBenchmark.class.getName());
+  private static final String JDBC_STORE_PATH =
+      "/tmp/gravitino_jdbc_entityStore_benchmark_" + 
UUID.randomUUID().toString().replace("-", "");
+  private static final String DB_DIR = JDBC_STORE_PATH + "/testdb";
+  private static final String H2_FILE = DB_DIR + ".mv.db";
+  private static final String BENCHMARK_METALAKE_NAME = "benchmark_metalake";
+  private static final String BENCHMARK_CATALOG_NAME = "benchmark_catalog";
+  private static final String BENCHMARK_SCHEMA_NAME = "benchmark_schema";
+  private static final boolean CACHE_ENABLED = true;
+  private static RandomIdGenerator generator = new RandomIdGenerator();
+  protected List<ModelEntity> entities;
+  protected Map<UserEntity, Entity> entitiesWithRelations;
+  protected EntityStore store;
+  protected BaseMetalake metalake;
+  protected CatalogEntity catalog;
+  protected SchemaEntity schema;
+
+  @Param({"10", "100", "1000"})
+  public int totalCnt;
+
+  @Setup(Level.Trial)
+  public final void init() throws IOException {
+    initStore();
+    initParentEntities();
+    initBenchmarkEntities();
+  }
+
+  @TearDown(Level.Trial)
+  public void destroy() throws IOException {
+    try {
+      if (store != null) {
+        store.close();
+      }
+    } catch (Exception e) {
+      LOG.warn("WARNING: Failed to close store during benchmark teardown: " + 
e.getMessage());
+    }
+    File dir = new File(DB_DIR);
+    if (dir.exists()) {
+      dir.delete();
+    }
+
+    FileUtils.deleteQuietly(new File(H2_FILE));
+  }
+
+  /**
+   * Get test entities.
+   *
+   * @param entityCnt The number of entity to create.
+   * @return The list of test entities.
+   */
+  protected List<ModelEntity> getEntities(int entityCnt) {
+    List<ModelEntity> entities = new ArrayList<>(entityCnt);
+    for (int i = 0; i < entityCnt; i++) {
+      entities.add(
+          TestUtil.getTestModelEntity(
+              generator.nextId(),
+              RandomNameUtils.genRandomName("model"),
+              Namespace.of(
+                  BENCHMARK_METALAKE_NAME, BENCHMARK_CATALOG_NAME, 
BENCHMARK_SCHEMA_NAME)));
+    }
+
+    return entities;
+  }
+
+  /**
+   * Get relation entities.
+   *
+   * @param entityCnt The number of entities to create.
+   * @return The map of relation entities.
+   */
+  protected Map<UserEntity, Entity> getRelationEntities(int entityCnt) {
+    Map<UserEntity, Entity> relationEntities = Maps.newHashMap();
+    for (int i = 0; i < entityCnt; i++) {
+      RoleEntity testRoleEntity =
+          TestUtil.getTestRoleEntity(
+              generator.nextId(), RandomNameUtils.genRandomName("role"), 
BENCHMARK_METALAKE_NAME);
+      List<Long> roleIds = ImmutableList.of(testRoleEntity.id());
+      UserEntity testUserEntity =
+          TestUtil.getTestUserEntity(
+              generator.nextId(),
+              RandomNameUtils.genRandomName("user"),
+              BENCHMARK_METALAKE_NAME,
+              roleIds);
+      TopicEntity testTopicEntity =
+          TestUtil.getTestTopicEntity(
+              generator.nextId(),
+              RandomNameUtils.genRandomName("topic"),
+              Namespace.of(BENCHMARK_METALAKE_NAME, BENCHMARK_CATALOG_NAME, 
BENCHMARK_SCHEMA_NAME),
+              "");
+
+      relationEntities.put(testUserEntity, testTopicEntity);
+    }
+
+    return relationEntities;
+  }
+
+  /**
+   * Validate entity from store.
+   *
+   * @param entity The target entity to validate.
+   * @param entityFromStore The entity from store.
+   * @return The validated entity. if the entity is not equal, it will throw 
an exception.
+   */
+  protected Entity validateEntity(E entity, E entityFromStore) {
+    if (entity.equals(entityFromStore)) {
+      return entityFromStore;
+    }
+
+    throw new RuntimeException("Entity not equal");
+  }
+
+  /**
+   * Init entity store.
+   *
+   * @throws IOException if an I/O error occurs.
+   */
+  protected void initStore() throws IOException {
+    File dir = new File(DB_DIR);
+    if (dir.exists()) {
+      FileUtils.deleteQuietly(dir);
+    }
+    dir.mkdirs();
+
+    Config config = mock(Config.class);
+    Mockito.when(config.get(ENTITY_STORE)).thenReturn(RELATIONAL_ENTITY_STORE);
+    
Mockito.when(config.get(ENTITY_RELATIONAL_STORE)).thenReturn(DEFAULT_ENTITY_RELATIONAL_STORE);
+    
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PATH)).thenReturn(DB_DIR);
+    
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_MAX_CONNECTIONS)).thenReturn(100);
+    
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_WAIT_MILLISECONDS)).thenReturn(1000L);
+    Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 
1000L);
+    Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
+    // Fix cache config for test
+    Mockito.when(config.get(Configs.CACHE_ENABLED)).thenReturn(CACHE_ENABLED);
+    Mockito.when(config.get(Configs.CACHE_MAX_ENTRIES)).thenReturn(10_000);
+    
Mockito.when(config.get(Configs.CACHE_EXPIRATION_TIME)).thenReturn(3_600_000L);
+    Mockito.when(config.get(Configs.CACHE_WEIGHER_ENABLED)).thenReturn(true);
+    Mockito.when(config.get(Configs.CACHE_STATS_ENABLED)).thenReturn(false);
+    
Mockito.when(config.get(Configs.CACHE_IMPLEMENTATION)).thenReturn("caffeine");
+
+    try {
+      Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_URL))
+          .thenReturn(
+              String.format(
+                  
"jdbc:h2:%s;DB_CLOSE_DELAY=-1;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE", 
DB_DIR));
+      
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_USER)).thenReturn("gravitino");
+      
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD)).thenReturn("gravitino");
+      
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER)).thenReturn("org.h2.Driver");
+      
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_MAX_CONNECTIONS)).thenReturn(100);
+      
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_WAIT_MILLISECONDS)).thenReturn(1000L);
+
+      FieldUtils.writeStaticField(
+          SQLExceptionConverterFactory.class, "converter", new 
H2ExceptionConverter(), true);
+
+    } catch (Exception e) {
+      LOG.error("Failed to init entity store", e);
+      throw new RuntimeException(e);
+    }
+
+    store = EntityStoreFactory.createEntityStore(config);
+    store.initialize(config);
+  }
+
+  /**
+   * Init benchmark entities with relations.
+   *
+   * @throws IOException if an I/O error occurs.
+   */
+  protected void initBenchmarkEntities() throws IOException {
+    this.entities = getEntities(totalCnt);
+    this.entitiesWithRelations = getRelationEntities(totalCnt);
+
+    for (ModelEntity entity : entities) {
+      store.put(entity);
+    }
+
+    for (Map.Entry<UserEntity, Entity> entry : 
entitiesWithRelations.entrySet()) {
+      UserEntity user = entry.getKey();
+      TopicEntity topic = (TopicEntity) entry.getValue();
+
+      store.put(user, true);
+      store.put(topic, true);
+
+      ((RelationalEntityStore) store)
+          .insertRelation(
+              SupportsRelationOperations.Type.OWNER_REL,
+              topic.nameIdentifier(),
+              Entity.EntityType.TOPIC,
+              user.nameIdentifier(),
+              Entity.EntityType.USER,
+              true);
+    }
+  }
+
+  private void initParentEntities() throws IOException {
+    metalake =
+        BaseMetalake.builder()
+            .withId(generator.nextId())
+            .withName(BENCHMARK_METALAKE_NAME)
+            .withComment("benchmark metalake")
+            .withProperties(ImmutableMap.of())
+            .withVersion(SchemaVersion.V_0_1)
+            .withAuditInfo(TestUtil.getTestAuditInfo())
+            .build();
+    store.put(metalake, true);
+
+    catalog =
+        CatalogEntity.builder()
+            .withId(generator.nextId())
+            .withName(BENCHMARK_CATALOG_NAME)
+            .withComment("benchmark catalog")
+            .withProperties(ImmutableMap.of())
+            .withNamespace(Namespace.of(BENCHMARK_METALAKE_NAME))
+            .withAuditInfo(TestUtil.getTestAuditInfo())
+            .withType(Catalog.Type.RELATIONAL)
+            .withProvider("hive")
+            .build();
+    store.put(catalog, true);
+
+    schema =
+        SchemaEntity.builder()
+            .withId(generator.nextId())
+            .withName(BENCHMARK_SCHEMA_NAME)
+            .withComment("benchmark schema")
+            .withProperties(ImmutableMap.of())
+            .withNamespace(Namespace.of(BENCHMARK_METALAKE_NAME, 
BENCHMARK_CATALOG_NAME))
+            .withAuditInfo(TestUtil.getTestAuditInfo())
+            .build();
+    store.put(schema, true);
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/it/ContainsEntityStorageBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/it/ContainsEntityStorageBenchmark.java
new file mode 100644
index 0000000000..e32f096c38
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/it/ContainsEntityStorageBenchmark.java
@@ -0,0 +1,69 @@
+/*
+ * 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.it;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.cache.BenchmarkHelper;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Benchmark for testing the performance of the {@code EntityStore.exists()} 
method.
+ *
+ * <p>This benchmark randomly selects an entity from a preloaded list and 
checks whether it exists
+ * in the {@link EntityStore}. It ensures correctness by throwing an exception 
if the entity is not
+ * found.
+ *
+ * <p>Benchmark results will reflect the performance of metadata existence 
checks under different
+ * entity counts, as controlled by the {@code @Param totalCnt} field from 
{@link
+ * AbstractEntityStorageBenchmark}.
+ *
+ * <p>This class extends {@link AbstractEntityStorageBenchmark}, which handles 
store setup,
+ * teardown, and test entity generation.
+ *
+ * @see 
org.apache.gravitino.EntityStore#exists(org.apache.gravitino.NameIdentifier,
+ *     Entity.EntityType)
+ */
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.SECONDS)
+@State(Scope.Thread)
+public class ContainsEntityStorageBenchmark extends 
AbstractEntityStorageBenchmark {
+
+  @Benchmark
+  public boolean benchmarkContains() throws IOException {
+    int idx = random.nextInt(entities.size());
+    Entity sampleEntity = (Entity) entities.get(idx);
+
+    boolean exists =
+        store.exists(BenchmarkHelper.getIdentFromEntity(sampleEntity), 
sampleEntity.type());
+    if (!exists) {
+      throw new RuntimeException("Entity not found in store");
+    }
+
+    return exists;
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/it/GetEntityStorageBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/it/GetEntityStorageBenchmark.java
new file mode 100644
index 0000000000..65d13dcb3c
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/it/GetEntityStorageBenchmark.java
@@ -0,0 +1,79 @@
+/*
+ * 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.it;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.cache.BenchmarkHelper;
+import org.apache.gravitino.meta.ModelEntity;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Benchmark for testing the performance of the {@code EntityStore.get()} 
method.
+ *
+ * <p>This benchmark randomly selects a preloaded entity and retrieves it from 
the {@link
+ * EntityStore} using its name identifier and type. It then validates the 
retrieved entity against
+ * the expected one.
+ *
+ * <p>The test dataset is initialized by {@link 
AbstractEntityStorageBenchmark}, and the benchmark
+ * is designed to simulate typical read-access patterns in metadata-intensive 
workloads.
+ *
+ * <p>Benchmark results reflect the efficiency and correctness of metadata 
retrieval under varying
+ * entity counts.
+ *
+ * @see 
org.apache.gravitino.EntityStore#get(org.apache.gravitino.NameIdentifier, 
Entity.EntityType,
+ *     Class)
+ */
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.SECONDS)
+@State(Scope.Thread)
+public class GetEntityStorageBenchmark<E extends Entity & HasIdentifier>
+    extends AbstractEntityStorageBenchmark {
+
+  /**
+   * Benchmark for getting an entity from the store.
+   *
+   * @return the entity from store.
+   */
+  @Benchmark
+  public Entity benchmarkGet() {
+    int idx = random.nextInt(entities.size());
+    Entity sampleEntity = (Entity) entities.get(idx);
+
+    try {
+      ModelEntity entityFromStore =
+          store.get(
+              BenchmarkHelper.getIdentFromEntity(sampleEntity),
+              sampleEntity.type(),
+              ModelEntity.class);
+      return validateEntity((Entity) entities.get(idx), entityFromStore);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/it/ListRelationEntityStorageBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/it/ListRelationEntityStorageBenchmark.java
new file mode 100644
index 0000000000..6f9f9e73ec
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/it/ListRelationEntityStorageBenchmark.java
@@ -0,0 +1,88 @@
+/*
+ * 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.it;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.cache.BenchmarkHelper;
+import org.apache.gravitino.meta.TopicEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.storage.relational.RelationalEntityStore;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Benchmark for testing the performance of listing entities via relation 
operations in a {@link
+ * RelationalEntityStore}.
+ *
+ * <p>This benchmark simulates a common metadata query pattern: retrieving all 
related entities
+ * (e.g., users) associated with a given entity (e.g., topic) by a specific 
relation type (such as
+ * OWNER_REL).
+ *
+ * <p>The test randomly selects a preloaded {@link UserEntity}–{@link 
TopicEntity} pair and invokes
+ * {@code listEntitiesByRelation} to fetch related entities from the store. It 
validates that the
+ * result is non-empty to ensure correctness.
+ *
+ * <p>This benchmark reflects the cost and scalability of relation-based 
metadata queries under
+ * load.
+ *
+ * @see 
RelationalEntityStore#listEntitiesByRelation(SupportsRelationOperations.Type,
+ *     org.apache.gravitino.NameIdentifier, Entity.EntityType, boolean)
+ */
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.SECONDS)
+@State(Scope.Thread)
+public class ListRelationEntityStorageBenchmark<E extends Entity & 
HasIdentifier>
+    extends AbstractEntityStorageBenchmark {
+
+  /**
+   * Benchmark for list entities by relation operation.
+   *
+   * @return the entity with relation from store.
+   * @throws IOException if there is an error getting the entity from the 
store.
+   */
+  @Benchmark
+  public List<E> benchmarkGetWithRelation() throws IOException {
+    UserEntity user = (UserEntity) 
BenchmarkHelper.getRandomKey(entitiesWithRelations);
+    TopicEntity topic = (TopicEntity) entitiesWithRelations.get(user);
+
+    List<E> entitiesFromStore =
+        ((RelationalEntityStore) store)
+            .listEntitiesByRelation(
+                SupportsRelationOperations.Type.OWNER_REL,
+                topic.nameIdentifier(),
+                Entity.EntityType.TOPIC,
+                true);
+
+    if (entitiesFromStore.isEmpty()) {
+      throw new RuntimeException("User list size does not match");
+    }
+
+    return entitiesFromStore;
+  }
+}
diff --git 
a/core/src/jmh/java/org/apache/gravitino/cache/it/MixEntityStorageBenchmark.java
 
b/core/src/jmh/java/org/apache/gravitino/cache/it/MixEntityStorageBenchmark.java
new file mode 100644
index 0000000000..1c2e07498b
--- /dev/null
+++ 
b/core/src/jmh/java/org/apache/gravitino/cache/it/MixEntityStorageBenchmark.java
@@ -0,0 +1,155 @@
+/*
+ * 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.it;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+import org.apache.gravitino.cache.BenchmarkHelper;
+import org.apache.gravitino.meta.ModelEntity;
+import org.apache.gravitino.meta.TopicEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.storage.relational.RelationalEntityStore;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.GroupThreads;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * A JMH benchmark class that simulates a mixed workload over the EntityStore 
system, combining
+ * multiple core operations in a realistic access ratio.
+ *
+ * <p>This benchmark is designed to reflect a read-heavy, concurrent usage 
pattern, which helps
+ * evaluate the performance impact of typical usage scenarios, including:
+ *
+ * <ul>
+ *   <li>60% {@code get} operations to retrieve entities by identifier
+ *   <li>30% {@code listRelation} operations to query relation-based entities
+ *   <li>10% {@code contains} operations to check entity existence
+ * </ul>
+ *
+ * <p>Each benchmark method is annotated with {@code @Group} and {@code 
@GroupThreads} to simulate
+ * mixed operation concurrency under shared state.
+ *
+ * <p>The benchmark runs under both {@code Throughput} and {@code AverageTime} 
modes, providing a
+ * comprehensive view of system performance in terms of latency and throughput.
+ *
+ * <p>This benchmark can be used to identify performance bottlenecks, cache 
efficiency, and
+ * concurrency behavior of the EntityStore implementation, especially when 
caching is enabled or
+ * disabled.
+ *
+ * @param <E> the type of entity under test, which must implement both {@link 
Entity} and {@link
+ *     HasIdentifier}
+ */
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.SECONDS)
+@State(Scope.Group)
+public class MixEntityStorageBenchmark<E extends Entity & HasIdentifier>
+    extends AbstractEntityStorageBenchmark {
+  public static final int GET_ENTITY_RATIO = 6;
+  public static final int CONTAINS_RATIO = 1;
+  public static final int LIST_RELATION_RATIO = 3;
+
+  private final Random random = ThreadLocalRandom.current();
+
+  /**
+   * Test get from store.
+   *
+   * @return The entity.
+   * @throws IOException if an I/O error occurs.
+   */
+  @Benchmark
+  @Group("ops")
+  @GroupThreads(GET_ENTITY_RATIO)
+  public Entity benchmarkGet() {
+    int idx = random.nextInt(entities.size());
+    Entity sampleEntity = (Entity) entities.get(idx);
+
+    try {
+      ModelEntity entityFromStore =
+          store.get(
+              BenchmarkHelper.getIdentFromEntity(sampleEntity),
+              sampleEntity.type(),
+              ModelEntity.class);
+      return validateEntity((Entity) entities.get(idx), entityFromStore);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Test list entities with relation.
+   *
+   * @return The list of entities.
+   * @throws IOException if an I/O error occurs.
+   */
+  @Benchmark
+  @Group("ops")
+  @GroupThreads(LIST_RELATION_RATIO)
+  public List<E> benchmarkGetWithRelation() throws IOException {
+    UserEntity user = (UserEntity) 
BenchmarkHelper.getRandomKey(entitiesWithRelations);
+    TopicEntity topic = (TopicEntity) entitiesWithRelations.get(user);
+
+    List<E> entitiesFromStore =
+        ((RelationalEntityStore) store)
+            .listEntitiesByRelation(
+                SupportsRelationOperations.Type.OWNER_REL,
+                topic.nameIdentifier(),
+                Entity.EntityType.TOPIC,
+                true);
+
+    if (entitiesFromStore.isEmpty()) {
+      throw new RuntimeException("User list size does not match");
+    }
+
+    return entitiesFromStore;
+  }
+
+  /**
+   * Test exists in store.
+   *
+   * @return True if the entity exists in the store.
+   * @throws IOException if an I/O error occurs.
+   */
+  @Benchmark
+  @Group("ops")
+  @GroupThreads(CONTAINS_RATIO)
+  public boolean benchmarkContains() throws IOException {
+    int idx = random.nextInt(entities.size());
+    Entity sampleEntity = (Entity) entities.get(idx);
+
+    boolean exists =
+        store.exists(BenchmarkHelper.getIdentFromEntity(sampleEntity), 
sampleEntity.type());
+    if (!exists) {
+      throw new RuntimeException("Entity not found in store");
+    }
+
+    return exists;
+  }
+}


Reply via email to