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 372876f02a [#10098] feat(optimizer): support MYSQL and PG for metrics 
storage (#164) (#10105)
372876f02a is described below

commit 372876f02a5995b9060ed082418c0ff47a442161
Author: FANNG <[email protected]>
AuthorDate: Mon Mar 2 15:29:28 2026 +0900

    [#10098] feat(optimizer): support MYSQL and PG for metrics storage (#164) 
(#10105)
    
    ## What changes are added in this PR?
    
    - Added JDBC metrics storage support for MySQL and PostgreSQL (H2
    already
        supported).
    - Updated metrics schema creation/validation flow in
    JdbcMetricsRepository and
        GenericJdbcMetricsRepository.
    - Added metrics DDL to schema-1.2.0-*.sql and
    upgrade-1.1.0-to-1.2.0-*.sql for
        H2/MySQL/PostgreSQL.
      - Removed obsolete scripts/h2/optimizer-metrics-schema-1.1.0-h2.sql.
      - Refactored tests:
          - Introduced shared behavior base test.
    - Added H2/MySQL/PostgreSQL integration tests under
    integration.test.storage. - Kept Docker tag only on MySQL/PG tests. -
    Removed redundant prefix-override tests.
    
      ## Why are the changes needed?
    
      - Before this PR, JDBC metrics repository was effectively H2-centric.
    - Metrics tables were required by runtime validation, but upgrade
    scripts did
        not consistently guarantee table creation for upgraded deployments.
    - Config behavior needed to be explicit (use
    gravitino.optimizer.jdbcMetrics.*
        directly, no EntityStore fallback).
      - Cross-database behavior needed integration coverage.
    
      Fix: #10098
    
      ## Does this PR introduce any user-facing change?
    
    - Yes: JDBC metrics storage now supports MySQL and PostgreSQL in
    addition to
        H2.
      - No new public API.
      - No new property keys added.
    
      ## How was this patch tested?
    
      ./
    gradlew :maintenance:optimizer:spotlessApply
    :maintenance:optimizer:compileTes
      tJava
    
      IT_SPARK_HOME=/Users/fanng/deploy/demo/spark-3.5.3-bin-hadoop3
    IT_SPARK_ARGS='--conf
    spark.jars=/Users/fanng/deploy/demo/jars/iceberg-spark-
    
    
    runtime-3.5_2.12-1.9.2.jar,/Users/fanng/deploy/demo/jars/iceberg-aws-bundle-
      1.9.2.jar --conf
    
    
    
spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionEx
    tensions --conf
    spark.sql.catalog.rest=org.apache.iceberg.spark.SparkCatalog
      --conf spark.sql.catalog.rest.type=rest --conf
      spark.sql.catalog.rest.uri=http://127.0.0.1:9001/iceberg/ --conf
    
    
    
spark.sql.catalog.rest.header.X-Iceberg-Access-Delegation=vended-credentials'
      IT_TABLE_IDENTIFIER=rest.ab.a1 GRAVITINO_ENV_IT=true ./
      gradlew :maintenance:optimizer:test --tests
    
    
    
'org.apache.gravitino.maintenance.optimizer.integration.test.storage.TestH2Gen
      ericJdbcMetricsRepositoryIT' -PskipIT=false
---
 maintenance/optimizer/build.gradle.kts             |   5 +
 .../storage/jdbc/GenericJdbcMetricsRepository.java |  12 +-
 .../storage/jdbc/JdbcMetricsRepository.java        |  10 +-
 .../metrics/storage/jdbc/MySQLMetricsDialect.java  |  29 ++++
 .../storage/jdbc/PostgreSQLMetricsDialect.java     |  29 ++++
 .../test/storage/JdbcMetricsRepositoryITUtils.java |  89 ++++++++++++
 .../TestH2GenericJdbcMetricsRepositoryIT.java      | 151 ++++++++++++++++++++
 ...ySqlGenericJdbcMetricsRepositoryBehaviorIT.java |  61 ++++++++
 ...stPostgreSqlGenericJdbcMetricsRepositoryIT.java |  61 ++++++++
 .../metrics/TestGravitinoMetricsUpdater.java       |   2 +-
 ...a => BaseGenericJdbcMetricsRepositoryTest.java} | 157 ++-------------------
 scripts/h2/optimizer-metrics-schema-1.2.0-h2.sql   |  41 ------
 scripts/h2/schema-1.2.0-h2.sql                     |  27 ++++
 scripts/h2/upgrade-1.1.0-to-1.2.0-h2.sql           |  27 ++++
 scripts/mysql/schema-1.2.0-mysql.sql               |  24 ++++
 scripts/mysql/upgrade-1.1.0-to-1.2.0-mysql.sql     |  24 ++++
 scripts/postgresql/schema-1.2.0-postgresql.sql     |  39 +++++
 .../upgrade-1.1.0-to-1.2.0-postgresql.sql          |  39 +++++
 18 files changed, 636 insertions(+), 191 deletions(-)

diff --git a/maintenance/optimizer/build.gradle.kts 
b/maintenance/optimizer/build.gradle.kts
index f986287674..d539cc894b 100644
--- a/maintenance/optimizer/build.gradle.kts
+++ b/maintenance/optimizer/build.gradle.kts
@@ -84,6 +84,11 @@ dependencies {
     exclude(group = "org.slf4j", module = "slf4j-log4j12")
   }
   testImplementation(libs.testcontainers)
+  testImplementation(libs.testcontainers.junit.jupiter)
+  testImplementation(libs.testcontainers.mysql)
+  testImplementation(libs.testcontainers.postgresql)
+  testRuntimeOnly(libs.mysql.driver)
+  testRuntimeOnly(libs.postgresql.driver)
   testAnnotationProcessor(libs.lombok)
   testCompileOnly(libs.lombok)
 
diff --git 
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/GenericJdbcMetricsRepository.java
 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/GenericJdbcMetricsRepository.java
index 3e7b818d3e..9be98ef24c 100644
--- 
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/GenericJdbcMetricsRepository.java
+++ 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/GenericJdbcMetricsRepository.java
@@ -44,10 +44,7 @@ public class GenericJdbcMetricsRepository extends 
JdbcMetricsRepository {
 
   @Override
   public void initialize(Map<String, String> optimizerProperties) {
-    Map<String, String> jdbcProperties =
-        MapUtils.getPrefixMap(
-            optimizerProperties, OptimizerConfig.OPTIMIZER_PREFIX + 
JDBC_METRICS_PREFIX);
-    Map<String, String> effectiveJdbcProperties = new 
HashMap<>(jdbcProperties);
+    Map<String, String> effectiveJdbcProperties = 
buildEffectiveJdbcProperties(optimizerProperties);
 
     String jdbcUrl = effectiveJdbcProperties.getOrDefault(JDBC_URL, 
DEFAULT_H2_JDBC_URL);
     if (StringUtils.isBlank(jdbcUrl)) {
@@ -63,4 +60,11 @@ public class GenericJdbcMetricsRepository extends 
JdbcMetricsRepository {
         new DataSourceJdbcConnectionProvider(effectiveJdbcProperties);
     initializeStorage(connectionProvider);
   }
+
+  private Map<String, String> buildEffectiveJdbcProperties(
+      Map<String, String> optimizerProperties) {
+    return new HashMap<>(
+        MapUtils.getPrefixMap(
+            optimizerProperties, OptimizerConfig.OPTIMIZER_PREFIX + 
JDBC_METRICS_PREFIX));
+  }
 }
diff --git 
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/JdbcMetricsRepository.java
 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/JdbcMetricsRepository.java
index d16d0b0963..3ff92e246a 100644
--- 
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/JdbcMetricsRepository.java
+++ 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/JdbcMetricsRepository.java
@@ -65,8 +65,10 @@ public abstract class JdbcMetricsRepository implements 
MetricsRepository {
   private static final int MAX_METRIC_NAME_LENGTH = 1024;
   private static final long MAX_REASONABLE_EPOCH_SECONDS = 9_999_999_999L;
   private static final String DATABASE_PRODUCT_H2 = "h2";
+  private static final String DATABASE_PRODUCT_MYSQL = "mysql";
+  private static final String DATABASE_PRODUCT_POSTGRESQL = "postgresql";
   private static final String H2_SCHEMA_FILE_NAME =
-      "optimizer-metrics-schema-" + ConfigConstants.CURRENT_SCRIPT_VERSION + 
"-h2.sql";
+      "schema-" + ConfigConstants.CURRENT_SCRIPT_VERSION + "-h2.sql";
   private static final String TABLE_METRICS_TABLE = "table_metrics";
   private static final String JOB_METRICS_TABLE = "job_metrics";
   private static final Set<String> REQUIRED_TABLE_METRICS_COLUMNS =
@@ -186,6 +188,12 @@ public abstract class JdbcMetricsRepository implements 
MetricsRepository {
     if (databaseProduct.contains(DATABASE_PRODUCT_H2)) {
       return new H2MetricsDialect();
     }
+    if (databaseProduct.contains(DATABASE_PRODUCT_MYSQL)) {
+      return new MySQLMetricsDialect();
+    }
+    if (databaseProduct.contains(DATABASE_PRODUCT_POSTGRESQL)) {
+      return new PostgreSQLMetricsDialect();
+    }
 
     throw new IllegalArgumentException(
         "Unsupported JDBC database product for metrics repository: " + 
databaseProduct);
diff --git 
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/MySQLMetricsDialect.java
 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/MySQLMetricsDialect.java
new file mode 100644
index 0000000000..7bdb502bc3
--- /dev/null
+++ 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/MySQLMetricsDialect.java
@@ -0,0 +1,29 @@
+/*
+ * 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.maintenance.optimizer.updater.metrics.storage.jdbc;
+
+/** MySQL dialect marker for JDBC metrics storage. */
+public class MySQLMetricsDialect implements JdbcMetricsDialect {
+
+  @Override
+  public String name() {
+    return "mysql";
+  }
+}
diff --git 
a/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/PostgreSQLMetricsDialect.java
 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/PostgreSQLMetricsDialect.java
new file mode 100644
index 0000000000..fda39a4a50
--- /dev/null
+++ 
b/maintenance/optimizer/src/main/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/jdbc/PostgreSQLMetricsDialect.java
@@ -0,0 +1,29 @@
+/*
+ * 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.maintenance.optimizer.updater.metrics.storage.jdbc;
+
+/** PostgreSQL dialect marker for JDBC metrics storage. */
+public class PostgreSQLMetricsDialect implements JdbcMetricsDialect {
+
+  @Override
+  public String name() {
+    return "postgresql";
+  }
+}
diff --git 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/JdbcMetricsRepositoryITUtils.java
 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/JdbcMetricsRepositoryITUtils.java
new file mode 100644
index 0000000000..801d03e06d
--- /dev/null
+++ 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/JdbcMetricsRepositoryITUtils.java
@@ -0,0 +1,89 @@
+/*
+ * 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.maintenance.optimizer.integration.test.storage;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.config.ConfigConstants;
+import org.apache.gravitino.maintenance.optimizer.common.conf.OptimizerConfig;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.jdbc.GenericJdbcMetricsRepository;
+import org.apache.gravitino.utils.jdbc.JdbcSqlScriptUtils;
+
+final class JdbcMetricsRepositoryITUtils {
+
+  private JdbcMetricsRepositoryITUtils() {}
+
+  static void initializeSchema(
+      String jdbcUrl, String username, String password, String databaseType) {
+    String schemaSql = loadSchemaSql(databaseType);
+    try (Connection connection = DriverManager.getConnection(jdbcUrl, 
username, password)) {
+      JdbcSqlScriptUtils.executeSqlScript(connection, schemaSql);
+    } catch (SQLException e) {
+      throw new RuntimeException("Failed to initialize " + databaseType + " 
schema for metrics", e);
+    }
+  }
+
+  static String loadSchemaSql(String databaseType) {
+    String gravitinoHome = System.getenv("GRAVITINO_HOME");
+    if (StringUtils.isBlank(gravitinoHome)) {
+      throw new IllegalStateException("GRAVITINO_HOME environment variable 
must be set for ITs");
+    }
+    Path root = Path.of(gravitinoHome);
+    Path scriptPath =
+        root.resolve(
+            Path.of(
+                "scripts",
+                databaseType,
+                "schema-" + ConfigConstants.CURRENT_SCRIPT_VERSION + "-" + 
databaseType + ".sql"));
+    try {
+      return Files.readString(scriptPath, StandardCharsets.UTF_8);
+    } catch (IOException e) {
+      throw new RuntimeException("Failed to load schema script: " + 
scriptPath, e);
+    }
+  }
+
+  static Map<String, String> createJdbcMetricsConfigs(
+      String jdbcUrl, String username, String password, String 
driverClassName) {
+    return Map.of(
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_URL,
+        jdbcUrl,
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_USER,
+        username,
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_PASSWORD,
+        password,
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_DRIVER,
+        driverClassName);
+  }
+}
diff --git 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestH2GenericJdbcMetricsRepositoryIT.java
 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestH2GenericJdbcMetricsRepositoryIT.java
new file mode 100644
index 0000000000..1eabae2134
--- /dev/null
+++ 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestH2GenericJdbcMetricsRepositoryIT.java
@@ -0,0 +1,151 @@
+/*
+ * 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.maintenance.optimizer.integration.test.storage;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.maintenance.optimizer.common.conf.OptimizerConfig;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.BaseGenericJdbcMetricsRepositoryTest;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.MetricRecord;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.MetricRecordImpl;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.TableMetricWriteRequest;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.jdbc.GenericJdbcMetricsRepository;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class TestH2GenericJdbcMetricsRepositoryIT extends 
BaseGenericJdbcMetricsRepositoryTest {
+  private static final String H2_USER = "sa";
+  private static final String H2_PASSWORD = "";
+  private static final String H2_DRIVER = "org.h2.Driver";
+
+  private String jdbcUrl;
+
+  @BeforeAll
+  void setUp() throws IOException {
+    Path testDir = Files.createTempDirectory("optimizer-h2-metrics");
+    jdbcUrl = "jdbc:h2:file:" + testDir.resolve("metrics.db") + ";MODE=MYSQL";
+    initializeSchema(jdbcUrl);
+    storage = new GenericJdbcMetricsRepository();
+    storage.initialize(createJdbcConfigs(jdbcUrl));
+    storage.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    storage.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+  }
+
+  @AfterAll
+  void tearDown() {
+    storage.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    storage.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    storage.close();
+  }
+
+  @Test
+  void testInitializeTwiceFails() {
+    GenericJdbcMetricsRepository repository = new 
GenericJdbcMetricsRepository();
+    String initTwiceJdbcUrl = withSuffix(jdbcUrl, "_init_twice");
+    initializeSchema(initTwiceJdbcUrl);
+    repository.initialize(createJdbcConfigs(initTwiceJdbcUrl));
+    try {
+      IllegalStateException e =
+          Assertions.assertThrows(
+              IllegalStateException.class,
+              () -> 
repository.initialize(createJdbcConfigs(initTwiceJdbcUrl)));
+      Assertions.assertTrue(e.getMessage().contains("already been 
initialized"));
+    } finally {
+      cleanupAndClose(repository);
+    }
+  }
+
+  @Test
+  void testInitializeWithoutSchemaAutoCreatesForH2() {
+    String autoCreateSchemaJdbcUrl = withSuffix(jdbcUrl, 
"_auto_create_schema");
+    GenericJdbcMetricsRepository repository = 
createInitializedRepository(autoCreateSchemaJdbcUrl);
+    try {
+      NameIdentifier tableId = NameIdentifier.of("catalog", "db", 
"auto_create_table");
+      long now = currentEpochSeconds();
+      repository.storeTableMetrics(
+          List.of(
+              new TableMetricWriteRequest(
+                  tableId, "row_count", Optional.empty(), new 
MetricRecordImpl(now, "12"))));
+
+      Map<String, List<MetricRecord>> metrics =
+          repository.getTableMetrics(tableId, now - 1, now + 1);
+      Assertions.assertTrue(metrics.containsKey("row_count"));
+      Assertions.assertEquals(List.of("12"), 
getMetricValues(metrics.get("row_count")));
+    } finally {
+      cleanupAndClose(repository);
+    }
+  }
+
+  private Map<String, String> createJdbcConfigs(String jdbcUrl) {
+    return Map.of(
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_URL,
+        jdbcUrl,
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_USER,
+        H2_USER,
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_PASSWORD,
+        H2_PASSWORD,
+        OptimizerConfig.OPTIMIZER_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
+            + GenericJdbcMetricsRepository.JDBC_DRIVER,
+        H2_DRIVER);
+  }
+
+  private void initializeSchema(String jdbcUrl) {
+    JdbcMetricsRepositoryITUtils.initializeSchema(jdbcUrl, H2_USER, 
H2_PASSWORD, "h2");
+  }
+
+  private String withSuffix(String baseJdbcUrl, String suffix) {
+    int optionsSeparator = baseJdbcUrl.indexOf(';');
+    if (optionsSeparator < 0) {
+      return baseJdbcUrl + suffix;
+    }
+
+    return baseJdbcUrl.substring(0, optionsSeparator)
+        + suffix
+        + baseJdbcUrl.substring(optionsSeparator);
+  }
+
+  private GenericJdbcMetricsRepository createInitializedRepository(String 
jdbcUrl) {
+    GenericJdbcMetricsRepository repository = new 
GenericJdbcMetricsRepository();
+    repository.initialize(createJdbcConfigs(jdbcUrl));
+    return repository;
+  }
+
+  private void cleanupAndClose(GenericJdbcMetricsRepository repository) {
+    repository.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    repository.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    repository.close();
+  }
+}
diff --git 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestMySqlGenericJdbcMetricsRepositoryBehaviorIT.java
 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestMySqlGenericJdbcMetricsRepositoryBehaviorIT.java
new file mode 100644
index 0000000000..da64f064c3
--- /dev/null
+++ 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestMySqlGenericJdbcMetricsRepositoryBehaviorIT.java
@@ -0,0 +1,61 @@
+/*
+ * 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.maintenance.optimizer.integration.test.storage;
+
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.BaseGenericJdbcMetricsRepositoryTest;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.jdbc.GenericJdbcMetricsRepository;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.TestInstance;
+import org.testcontainers.containers.MySQLContainer;
+
+@Tag("gravitino-docker-test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class TestMySqlGenericJdbcMetricsRepositoryBehaviorIT extends 
BaseGenericJdbcMetricsRepositoryTest {
+
+  private MySQLContainer<?> mysql;
+
+  @BeforeAll
+  void setUp() {
+    mysql = new MySQLContainer<>("mysql:8.0.33");
+    mysql.start();
+    JdbcMetricsRepositoryITUtils.initializeSchema(
+        mysql.getJdbcUrl(), mysql.getUsername(), mysql.getPassword(), "mysql");
+
+    storage = new GenericJdbcMetricsRepository();
+    storage.initialize(
+        JdbcMetricsRepositoryITUtils.createJdbcMetricsConfigs(
+            mysql.getJdbcUrl(),
+            mysql.getUsername(),
+            mysql.getPassword(),
+            mysql.getDriverClassName()));
+  }
+
+  @AfterAll
+  void tearDown() {
+    storage.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    storage.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    storage.close();
+    if (mysql != null) {
+      mysql.stop();
+    }
+  }
+}
diff --git 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestPostgreSqlGenericJdbcMetricsRepositoryIT.java
 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestPostgreSqlGenericJdbcMetricsRepositoryIT.java
new file mode 100644
index 0000000000..8f29479be0
--- /dev/null
+++ 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/integration/test/storage/TestPostgreSqlGenericJdbcMetricsRepositoryIT.java
@@ -0,0 +1,61 @@
+/*
+ * 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.maintenance.optimizer.integration.test.storage;
+
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.BaseGenericJdbcMetricsRepositoryTest;
+import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.jdbc.GenericJdbcMetricsRepository;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.TestInstance;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+@Tag("gravitino-docker-test")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class TestPostgreSqlGenericJdbcMetricsRepositoryIT extends 
BaseGenericJdbcMetricsRepositoryTest {
+
+  private PostgreSQLContainer<?> postgres;
+
+  @BeforeAll
+  void setUp() {
+    postgres = new PostgreSQLContainer<>("postgres:13");
+    postgres.start();
+    JdbcMetricsRepositoryITUtils.initializeSchema(
+        postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword(), 
"postgresql");
+
+    storage = new GenericJdbcMetricsRepository();
+    storage.initialize(
+        JdbcMetricsRepositoryITUtils.createJdbcMetricsConfigs(
+            postgres.getJdbcUrl(),
+            postgres.getUsername(),
+            postgres.getPassword(),
+            postgres.getDriverClassName()));
+  }
+
+  @AfterAll
+  void tearDown() {
+    storage.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    storage.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
+    storage.close();
+    if (postgres != null) {
+      postgres.stop();
+    }
+  }
+}
diff --git 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/TestGravitinoMetricsUpdater.java
 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/TestGravitinoMetricsUpdater.java
index 57411eedf8..6fd3ff10ab 100644
--- 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/TestGravitinoMetricsUpdater.java
+++ 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/TestGravitinoMetricsUpdater.java
@@ -162,7 +162,7 @@ class TestGravitinoMetricsUpdater {
   @Test
   void testInitializeWithJdbcConfigStillUsesGenericJdbcRepository() throws 
Exception {
     GravitinoMetricsUpdater updater = new GravitinoMetricsUpdater();
-    String jdbcUrl = 
"jdbc:h2:mem:test_metrics_updater_repo_type;DB_CLOSE_DELAY=-1";
+    String jdbcUrl = 
"jdbc:h2:mem:test_metrics_updater_repo_type;DB_CLOSE_DELAY=-1;MODE=MYSQL";
     OptimizerConfig config =
         new OptimizerConfig(
             Map.of(
diff --git 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/TestGenericJdbcMetricsRepository.java
 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/BaseGenericJdbcMetricsRepositoryTest.java
similarity index 70%
rename from 
maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/TestGenericJdbcMetricsRepository.java
rename to 
maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/BaseGenericJdbcMetricsRepositoryTest.java
index 6c03b4f41c..b1f5f33970 100644
--- 
a/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/TestGenericJdbcMetricsRepository.java
+++ 
b/maintenance/optimizer/src/test/java/org/apache/gravitino/maintenance/optimizer/updater/metrics/storage/BaseGenericJdbcMetricsRepositoryTest.java
@@ -19,55 +19,25 @@
 
 package org.apache.gravitino.maintenance.optimizer.updater.metrics.storage;
 
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.SQLException;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.NameIdentifier;
-import org.apache.gravitino.config.ConfigConstants;
-import org.apache.gravitino.maintenance.optimizer.common.conf.OptimizerConfig;
 import 
org.apache.gravitino.maintenance.optimizer.updater.metrics.storage.jdbc.GenericJdbcMetricsRepository;
-import org.apache.gravitino.utils.jdbc.JdbcSqlScriptUtils;
-import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-class TestGenericJdbcMetricsRepository {
-  private static final long MAX_REASONABLE_EPOCH_SECONDS = 9_999_999_999L;
-  private GenericJdbcMetricsRepository storage;
-  private String jdbcUrl;
-
-  @BeforeAll
-  void setUp() throws IOException {
-    cleanupLegacyDataFiles();
-    Path testDir = Files.createTempDirectory("optimizer-h2-metrics");
-    jdbcUrl = "jdbc:h2:file:" + testDir.resolve("metrics.db");
-    initializeSchema(jdbcUrl);
-    storage = new GenericJdbcMetricsRepository();
-    storage.initialize(createJdbcConfigs(jdbcUrl));
-    storage.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
-    storage.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
-  }
 
-  @AfterAll
-  void tearDown() {
-    storage.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
-    storage.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
-    storage.close();
-  }
+/**
+ * Shared behavior tests for {@link GenericJdbcMetricsRepository}.
+ *
+ * <p>Environment setup (H2/MySQL/PostgreSQL) is provided by subclasses.
+ */
+public abstract class BaseGenericJdbcMetricsRepositoryTest {
+
+  protected static final long MAX_REASONABLE_EPOCH_SECONDS = 9_999_999_999L;
+  protected GenericJdbcMetricsRepository storage;
 
   @Test
   void testStoreAndRetrieveTableMetricsWithNullPartition() {
@@ -100,12 +70,9 @@ class TestGenericJdbcMetricsRepository {
         storage.getTableMetrics(nameIdentifier, 0, Long.MAX_VALUE);
 
     Assertions.assertEquals(2, metrics.size());
-
     Assertions.assertTrue(metrics.containsKey("metric1"));
     Assertions.assertEquals(Arrays.asList("value1"), 
getMetricValues(metrics.get("metric1")));
-
     Assertions.assertTrue(metrics.containsKey("metric2"));
-    Assertions.assertEquals(2, getMetricValues(metrics.get("metric2")).size());
     Assertions.assertEquals(
         Arrays.asList("value1", "value2"), 
getMetricValues(metrics.get("metric2")));
   }
@@ -128,7 +95,6 @@ class TestGenericJdbcMetricsRepository {
 
     Map<String, List<MetricRecord>> metrics =
         storage.getPartitionMetrics(nameIdentifier, partition1, 0, 
Long.MAX_VALUE);
-
     Assertions.assertEquals(1, metrics.size());
     Assertions.assertTrue(metrics.containsKey("metric"));
     Assertions.assertEquals(Arrays.asList("value1"), 
getMetricValues(metrics.get("metric")));
@@ -199,19 +165,6 @@ class TestGenericJdbcMetricsRepository {
     Assertions.assertEquals(List.of("v1"), 
getMetricValues(jobMetrics.get("job_metric")));
   }
 
-  @Test
-  void testInitializeTwiceFails() {
-    GenericJdbcMetricsRepository repository = new 
GenericJdbcMetricsRepository();
-    initializeSchema(jdbcUrl + "_init_twice");
-    repository.initialize(createJdbcConfigs(jdbcUrl + "_init_twice"));
-
-    IllegalStateException e =
-        Assertions.assertThrows(
-            IllegalStateException.class,
-            () -> repository.initialize(createJdbcConfigs(jdbcUrl + 
"_init_twice")));
-    Assertions.assertTrue(e.getMessage().contains("already been initialized"));
-  }
-
   @Test
   void testInvalidTimeWindowFailsFast() {
     NameIdentifier id = NameIdentifier.of("catalog", "db", "table");
@@ -351,32 +304,11 @@ class TestGenericJdbcMetricsRepository {
     Assertions.assertEquals(longValue, 
jobMetrics.get("metric_long").get(0).getValue());
   }
 
-  @Test
-  void testInitializeWithoutSchemaAutoCreatesForH2() {
-    GenericJdbcMetricsRepository repository = new 
GenericJdbcMetricsRepository();
-    String autoCreateSchemaJdbcUrl = jdbcUrl + "_auto_create_schema";
-    repository.initialize(createJdbcConfigs(autoCreateSchemaJdbcUrl));
-
-    NameIdentifier tableId = NameIdentifier.of("catalog", "db", 
"auto_create_table");
-    long now = currentEpochSeconds();
-    repository.storeTableMetrics(
-        List.of(
-            new TableMetricWriteRequest(
-                tableId, "row_count", Optional.empty(), new 
MetricRecordImpl(now, "12"))));
-
-    Map<String, List<MetricRecord>> metrics = 
repository.getTableMetrics(tableId, now - 1, now + 1);
-    Assertions.assertTrue(metrics.containsKey("row_count"));
-    Assertions.assertEquals(List.of("12"), 
getMetricValues(metrics.get("row_count")));
-    repository.cleanupTableMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
-    repository.cleanupJobMetricsBefore(MAX_REASONABLE_EPOCH_SECONDS);
-    repository.close();
-  }
-
-  private List<String> getMetricValues(List<MetricRecord> metrics) {
+  protected List<String> getMetricValues(List<MetricRecord> metrics) {
     return metrics.stream().map(MetricRecord::getValue).toList();
   }
 
-  private void storeTableMetric(
+  protected void storeTableMetric(
       NameIdentifier nameIdentifier,
       String metricName,
       Optional<String> partition,
@@ -385,75 +317,12 @@ class TestGenericJdbcMetricsRepository {
         List.of(new TableMetricWriteRequest(nameIdentifier, metricName, 
partition, metric)));
   }
 
-  private void storeJobMetric(
+  protected void storeJobMetric(
       NameIdentifier nameIdentifier, String metricName, MetricRecord metric) {
     storage.storeJobMetrics(List.of(new JobMetricWriteRequest(nameIdentifier, 
metricName, metric)));
   }
 
-  private long currentEpochSeconds() {
+  protected long currentEpochSeconds() {
     return Instant.now().getEpochSecond();
   }
-
-  private void cleanupLegacyDataFiles() throws IOException {
-    deleteIfExists("data/metrics.db.mv.db");
-    deleteIfExists("data/metrics.db.trace.db");
-    deleteIfExists("maintenance/optimizer/data/metrics.db.mv.db");
-    deleteIfExists("maintenance/optimizer/data/metrics.db.trace.db");
-    deleteIfExists("maintenance/optimizer/metrics_db.mv.db");
-    deleteIfExists("maintenance/optimizer/metrics_db.trace.db");
-    deleteIfExists("metrics_db.mv.db");
-    deleteIfExists("metrics_db.trace.db");
-  }
-
-  private void deleteIfExists(String filePath) throws IOException {
-    Files.deleteIfExists(Path.of(filePath));
-  }
-
-  private Map<String, String> createJdbcConfigs(String jdbcUrl) {
-    return Map.of(
-        OptimizerConfig.OPTIMIZER_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_URL,
-        jdbcUrl,
-        OptimizerConfig.OPTIMIZER_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_USER,
-        "sa",
-        OptimizerConfig.OPTIMIZER_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_PASSWORD,
-        "",
-        OptimizerConfig.OPTIMIZER_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_METRICS_PREFIX
-            + GenericJdbcMetricsRepository.JDBC_DRIVER,
-        "org.h2.Driver");
-  }
-
-  private void initializeSchema(String jdbcUrl) {
-    String schemaSql = loadSchemaSql();
-    try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
-      JdbcSqlScriptUtils.executeSqlScript(conn, schemaSql);
-    } catch (SQLException e) {
-      throw new RuntimeException("Failed to initialize test schema for 
metrics", e);
-    }
-  }
-
-  private String loadSchemaSql() {
-    String gravitinoHome = System.getenv("GRAVITINO_HOME");
-    if (StringUtils.isBlank(gravitinoHome)) {
-      throw new RuntimeException("GRAVITINO_HOME environment variable is not 
set");
-    }
-
-    Path scriptPath =
-        Paths.get(
-            gravitinoHome,
-            "scripts",
-            "h2",
-            "optimizer-metrics-schema-" + 
ConfigConstants.CURRENT_SCRIPT_VERSION + "-h2.sql");
-    try {
-      return Files.readString(scriptPath, StandardCharsets.UTF_8);
-    } catch (IOException e) {
-      throw new RuntimeException("Failed to load schema script: " + 
scriptPath, e);
-    }
-  }
 }
diff --git a/scripts/h2/optimizer-metrics-schema-1.2.0-h2.sql 
b/scripts/h2/optimizer-metrics-schema-1.2.0-h2.sql
deleted file mode 100644
index 1c81c024ec..0000000000
--- a/scripts/h2/optimizer-metrics-schema-1.2.0-h2.sql
+++ /dev/null
@@ -1,41 +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.
-
--- Optimizer metrics schema for H2
-CREATE TABLE IF NOT EXISTS table_metrics (
-  id BIGINT AUTO_INCREMENT PRIMARY KEY,
-  table_identifier VARCHAR(1024) NOT NULL,
-  metric_name VARCHAR(1024) NOT NULL,
-  table_partition VARCHAR(1024),
-  metric_ts BIGINT NOT NULL,
-  metric_value VARCHAR(1024) NOT NULL
-);
-
-CREATE TABLE IF NOT EXISTS job_metrics (
-  id BIGINT AUTO_INCREMENT PRIMARY KEY,
-  job_identifier VARCHAR(1024) NOT NULL,
-  metric_name VARCHAR(1024) NOT NULL,
-  metric_ts BIGINT NOT NULL,
-  metric_value VARCHAR(1024) NOT NULL
-);
-
-CREATE INDEX IF NOT EXISTS idx_table_metrics_metric_ts ON 
table_metrics(metric_ts);
-CREATE INDEX IF NOT EXISTS idx_job_metrics_metric_ts ON job_metrics(metric_ts);
-CREATE INDEX IF NOT EXISTS idx_table_metrics_composite
-  ON table_metrics(table_identifier, table_partition, metric_ts);
-CREATE INDEX IF NOT EXISTS idx_job_metrics_identifier_metric_ts
-  ON job_metrics(job_identifier, metric_ts);
diff --git a/scripts/h2/schema-1.2.0-h2.sql b/scripts/h2/schema-1.2.0-h2.sql
index 9bf2ea2892..1a39474c31 100644
--- a/scripts/h2/schema-1.2.0-h2.sql
+++ b/scripts/h2/schema-1.2.0-h2.sql
@@ -510,3 +510,30 @@ CREATE TABLE IF NOT EXISTS partition_statistic_meta (
 );
 
 CREATE INDEX IF NOT EXISTS idx_table_partition ON 
partition_statistic_meta(table_id, partition_name);
+
+-- Optimizer metrics schema
+CREATE TABLE IF NOT EXISTS `table_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `table_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized table 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `table_partition` VARCHAR(1024) DEFAULT NULL COMMENT 'normalized partition 
identifier',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB COMMENT='optimizer table metrics';
+
+CREATE TABLE IF NOT EXISTS `job_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `job_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized job 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB COMMENT='optimizer job metrics';
+
+CREATE INDEX IF NOT EXISTS `idx_table_metrics_metric_ts` ON 
`table_metrics`(`metric_ts`);
+CREATE INDEX IF NOT EXISTS `idx_job_metrics_metric_ts` ON 
`job_metrics`(`metric_ts`);
+CREATE INDEX IF NOT EXISTS `idx_table_metrics_composite`
+  ON `table_metrics`(`table_identifier`, `table_partition`, `metric_ts`);
+CREATE INDEX IF NOT EXISTS `idx_job_metrics_identifier_metric_ts`
+  ON `job_metrics`(`job_identifier`, `metric_ts`);
diff --git a/scripts/h2/upgrade-1.1.0-to-1.2.0-h2.sql 
b/scripts/h2/upgrade-1.1.0-to-1.2.0-h2.sql
index 65b05958fe..63eb8cec17 100644
--- a/scripts/h2/upgrade-1.1.0-to-1.2.0-h2.sql
+++ b/scripts/h2/upgrade-1.1.0-to-1.2.0-h2.sql
@@ -81,3 +81,30 @@ CREATE TABLE IF NOT EXISTS partition_statistic_meta (
 );
 
 CREATE INDEX IF NOT EXISTS idx_table_partition ON 
partition_statistic_meta(table_id, partition_name);
+
+-- Add optimizer metrics storage tables
+CREATE TABLE IF NOT EXISTS `table_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `table_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized table 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `table_partition` VARCHAR(1024) DEFAULT NULL COMMENT 'normalized partition 
identifier',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB COMMENT='optimizer table metrics';
+
+CREATE TABLE IF NOT EXISTS `job_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `job_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized job 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`)
+) ENGINE=InnoDB COMMENT='optimizer job metrics';
+
+CREATE INDEX IF NOT EXISTS `idx_table_metrics_metric_ts` ON 
`table_metrics`(`metric_ts`);
+CREATE INDEX IF NOT EXISTS `idx_job_metrics_metric_ts` ON 
`job_metrics`(`metric_ts`);
+CREATE INDEX IF NOT EXISTS `idx_table_metrics_composite`
+  ON `table_metrics`(`table_identifier`, `table_partition`, `metric_ts`);
+CREATE INDEX IF NOT EXISTS `idx_job_metrics_identifier_metric_ts`
+  ON `job_metrics`(`job_identifier`, `metric_ts`);
diff --git a/scripts/mysql/schema-1.2.0-mysql.sql 
b/scripts/mysql/schema-1.2.0-mysql.sql
index e0fa45cd39..12eac057c4 100644
--- a/scripts/mysql/schema-1.2.0-mysql.sql
+++ b/scripts/mysql/schema-1.2.0-mysql.sql
@@ -500,3 +500,27 @@ CREATE TABLE IF NOT EXISTS `partition_statistic_meta` (
     PRIMARY KEY (`table_id`, `partition_name`(255), `statistic_name`),
     KEY `idx_table_partition` (`table_id`, `partition_name`(255))
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'partition 
statistics metadata';
+
+-- Optimizer metrics schema
+CREATE TABLE IF NOT EXISTS `table_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `table_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized table 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `table_partition` VARCHAR(1024) DEFAULT NULL COMMENT 'normalized partition 
identifier',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`),
+    KEY `idx_table_metrics_metric_ts` (`metric_ts`),
+    KEY `idx_table_metrics_composite` (`table_identifier`(255), 
`table_partition`(255), `metric_ts`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'optimizer 
table metrics';
+
+CREATE TABLE IF NOT EXISTS `job_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `job_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized job 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`),
+    KEY `idx_job_metrics_metric_ts` (`metric_ts`),
+    KEY `idx_job_metrics_identifier_metric_ts` (`job_identifier`(255), 
`metric_ts`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'optimizer 
job metrics';
diff --git a/scripts/mysql/upgrade-1.1.0-to-1.2.0-mysql.sql 
b/scripts/mysql/upgrade-1.1.0-to-1.2.0-mysql.sql
index 0de595267f..716302e48c 100644
--- a/scripts/mysql/upgrade-1.1.0-to-1.2.0-mysql.sql
+++ b/scripts/mysql/upgrade-1.1.0-to-1.2.0-mysql.sql
@@ -80,3 +80,27 @@ CREATE TABLE IF NOT EXISTS `partition_statistic_meta` (
     PRIMARY KEY (`table_id`, `partition_name`(255), `statistic_name`),
     KEY `idx_table_partition` (`table_id`, `partition_name`(255))
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'partition 
statistics metadata';
+
+-- Add optimizer metrics storage tables
+CREATE TABLE IF NOT EXISTS `table_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `table_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized table 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `table_partition` VARCHAR(1024) DEFAULT NULL COMMENT 'normalized partition 
identifier',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`),
+    KEY `idx_table_metrics_metric_ts` (`metric_ts`),
+    KEY `idx_table_metrics_composite` (`table_identifier`(255), 
`table_partition`(255), `metric_ts`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'optimizer 
table metrics';
+
+CREATE TABLE IF NOT EXISTS `job_metrics` (
+    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment 
id',
+    `job_identifier` VARCHAR(1024) NOT NULL COMMENT 'normalized job 
identifier',
+    `metric_name` VARCHAR(1024) NOT NULL COMMENT 'metric name',
+    `metric_ts` BIGINT(20) NOT NULL COMMENT 'metric timestamp in epoch 
seconds',
+    `metric_value` VARCHAR(1024) NOT NULL COMMENT 'metric value payload',
+    PRIMARY KEY (`id`),
+    KEY `idx_job_metrics_metric_ts` (`metric_ts`),
+    KEY `idx_job_metrics_identifier_metric_ts` (`job_identifier`(255), 
`metric_ts`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'optimizer 
job metrics';
diff --git a/scripts/postgresql/schema-1.2.0-postgresql.sql 
b/scripts/postgresql/schema-1.2.0-postgresql.sql
index 6c2c189504..359aa7ef85 100644
--- a/scripts/postgresql/schema-1.2.0-postgresql.sql
+++ b/scripts/postgresql/schema-1.2.0-postgresql.sql
@@ -884,3 +884,42 @@ COMMENT ON COLUMN partition_statistic_meta.statistic_value 
IS 'statistic value a
 COMMENT ON COLUMN partition_statistic_meta.audit_info IS 'audit information as 
JSON';
 COMMENT ON COLUMN partition_statistic_meta.created_at IS 'creation timestamp 
in milliseconds';
 COMMENT ON COLUMN partition_statistic_meta.updated_at IS 'last update 
timestamp in milliseconds';
+
+-- Optimizer metrics schema
+CREATE TABLE IF NOT EXISTS table_metrics (
+    id BIGSERIAL PRIMARY KEY,
+    table_identifier VARCHAR(1024) NOT NULL,
+    metric_name VARCHAR(1024) NOT NULL,
+    table_partition VARCHAR(1024),
+    metric_ts BIGINT NOT NULL,
+    metric_value VARCHAR(1024) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS job_metrics (
+    id BIGSERIAL PRIMARY KEY,
+    job_identifier VARCHAR(1024) NOT NULL,
+    metric_name VARCHAR(1024) NOT NULL,
+    metric_ts BIGINT NOT NULL,
+    metric_value VARCHAR(1024) NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS idx_table_metrics_metric_ts ON 
table_metrics(metric_ts);
+CREATE INDEX IF NOT EXISTS idx_job_metrics_metric_ts ON job_metrics(metric_ts);
+CREATE INDEX IF NOT EXISTS idx_table_metrics_composite
+  ON table_metrics(table_identifier, table_partition, metric_ts);
+CREATE INDEX IF NOT EXISTS idx_job_metrics_identifier_metric_ts
+  ON job_metrics(job_identifier, metric_ts);
+
+COMMENT ON TABLE table_metrics IS 'optimizer table metrics';
+COMMENT ON TABLE job_metrics IS 'optimizer job metrics';
+COMMENT ON COLUMN table_metrics.id IS 'auto increment id';
+COMMENT ON COLUMN table_metrics.table_identifier IS 'normalized table 
identifier';
+COMMENT ON COLUMN table_metrics.metric_name IS 'metric name';
+COMMENT ON COLUMN table_metrics.table_partition IS 'normalized partition 
identifier';
+COMMENT ON COLUMN table_metrics.metric_ts IS 'metric timestamp in epoch 
seconds';
+COMMENT ON COLUMN table_metrics.metric_value IS 'metric value payload';
+COMMENT ON COLUMN job_metrics.id IS 'auto increment id';
+COMMENT ON COLUMN job_metrics.job_identifier IS 'normalized job identifier';
+COMMENT ON COLUMN job_metrics.metric_name IS 'metric name';
+COMMENT ON COLUMN job_metrics.metric_ts IS 'metric timestamp in epoch seconds';
+COMMENT ON COLUMN job_metrics.metric_value IS 'metric value payload';
diff --git a/scripts/postgresql/upgrade-1.1.0-to-1.2.0-postgresql.sql 
b/scripts/postgresql/upgrade-1.1.0-to-1.2.0-postgresql.sql
index 7aa6bda401..e75d75735a 100644
--- a/scripts/postgresql/upgrade-1.1.0-to-1.2.0-postgresql.sql
+++ b/scripts/postgresql/upgrade-1.1.0-to-1.2.0-postgresql.sql
@@ -125,3 +125,42 @@ COMMENT ON COLUMN partition_statistic_meta.statistic_value 
IS 'statistic value a
 COMMENT ON COLUMN partition_statistic_meta.audit_info IS 'audit information as 
JSON';
 COMMENT ON COLUMN partition_statistic_meta.created_at IS 'creation timestamp 
in milliseconds';
 COMMENT ON COLUMN partition_statistic_meta.updated_at IS 'last update 
timestamp in milliseconds';
+
+-- Add optimizer metrics storage tables
+CREATE TABLE IF NOT EXISTS table_metrics (
+    id BIGSERIAL PRIMARY KEY,
+    table_identifier VARCHAR(1024) NOT NULL,
+    metric_name VARCHAR(1024) NOT NULL,
+    table_partition VARCHAR(1024),
+    metric_ts BIGINT NOT NULL,
+    metric_value VARCHAR(1024) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS job_metrics (
+    id BIGSERIAL PRIMARY KEY,
+    job_identifier VARCHAR(1024) NOT NULL,
+    metric_name VARCHAR(1024) NOT NULL,
+    metric_ts BIGINT NOT NULL,
+    metric_value VARCHAR(1024) NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS idx_table_metrics_metric_ts ON 
table_metrics(metric_ts);
+CREATE INDEX IF NOT EXISTS idx_job_metrics_metric_ts ON job_metrics(metric_ts);
+CREATE INDEX IF NOT EXISTS idx_table_metrics_composite
+  ON table_metrics(table_identifier, table_partition, metric_ts);
+CREATE INDEX IF NOT EXISTS idx_job_metrics_identifier_metric_ts
+  ON job_metrics(job_identifier, metric_ts);
+
+COMMENT ON TABLE table_metrics IS 'optimizer table metrics';
+COMMENT ON TABLE job_metrics IS 'optimizer job metrics';
+COMMENT ON COLUMN table_metrics.id IS 'auto increment id';
+COMMENT ON COLUMN table_metrics.table_identifier IS 'normalized table 
identifier';
+COMMENT ON COLUMN table_metrics.metric_name IS 'metric name';
+COMMENT ON COLUMN table_metrics.table_partition IS 'normalized partition 
identifier';
+COMMENT ON COLUMN table_metrics.metric_ts IS 'metric timestamp in epoch 
seconds';
+COMMENT ON COLUMN table_metrics.metric_value IS 'metric value payload';
+COMMENT ON COLUMN job_metrics.id IS 'auto increment id';
+COMMENT ON COLUMN job_metrics.job_identifier IS 'normalized job identifier';
+COMMENT ON COLUMN job_metrics.metric_name IS 'metric name';
+COMMENT ON COLUMN job_metrics.metric_ts IS 'metric timestamp in epoch seconds';
+COMMENT ON COLUMN job_metrics.metric_value IS 'metric value payload';

Reply via email to