Copilot commented on code in PR #11266:
URL: https://github.com/apache/gravitino/pull/11266#discussion_r3323583453


##########
scripts/postgresql/schema-1.3.0-postgresql.sql:
##########
@@ -1054,3 +1054,21 @@ COMMENT ON COLUMN entity_change_log.entity_type IS 
'METALAKE | CATALOG | SCHEMA
 COMMENT ON COLUMN entity_change_log.entity_full_name IS 'Dot-separated full 
name of the affected entity. For ALTER, stores the old name. For DROP, stores 
the entity name.';
 COMMENT ON COLUMN entity_change_log.operate_type IS 'Operate type code: 
1=ALTER, 2=DROP, 3=INSERT. Codes are stable and never re-used.';
 COMMENT ON COLUMN entity_change_log.created_at IS 'timestamp of the change in 
millis';
+
+CREATE TABLE IF NOT EXISTS iceberg_cleanup_job (
+  id                BIGINT        NOT NULL PRIMARY KEY,
+  catalog_id        BIGINT        NOT NULL,
+  namespace         VARCHAR(512)  NOT NULL,
+  table_name        VARCHAR(256)  NOT NULL,
+  metadata_location TEXT          NOT NULL,
+  file_io_impl      VARCHAR(256)  NOT NULL,
+  file_io_props     TEXT          NOT NULL,
+  state             VARCHAR(16)   NOT NULL,
+  attempts          INT           NOT NULL DEFAULT 0,
+  last_error        VARCHAR(2048),
+  heartbeat_at      BIGINT        NOT NULL DEFAULT 0,
+  created_by        VARCHAR(128)  NOT NULL,
+  updated_at        BIGINT        NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_state_updated ON iceberg_cleanup_job (state, 
updated_at);
+CREATE INDEX IF NOT EXISTS idx_object ON iceberg_cleanup_job (catalog_id, 
namespace, table_name, state);

Review Comment:
   The PostgreSQL schema script adds COMMENTs for most tables/columns, but 
`iceberg_cleanup_job` is missing COMMENT ON TABLE/COLUMN statements. This makes 
the new table harder to inspect and inconsistent with the rest of the 
PostgreSQL schema (and the MySQL script which includes detailed comments).



##########
scripts/postgresql/upgrade-1.2.0-to-1.3.0-postgresql.sql:
##########
@@ -151,3 +151,21 @@ COMMENT ON COLUMN idp_user_group_rel.group_id IS 'idp 
group id';
 COMMENT ON COLUMN idp_user_group_rel.current_version IS 'idp relation current 
version';
 COMMENT ON COLUMN idp_user_group_rel.last_version IS 'idp relation last 
version';
 COMMENT ON COLUMN idp_user_group_rel.deleted_at IS 'idp relation deleted at';
+
+CREATE TABLE IF NOT EXISTS iceberg_cleanup_job (
+  id                BIGINT        NOT NULL PRIMARY KEY,
+  catalog_id        BIGINT        NOT NULL,
+  namespace         VARCHAR(512)  NOT NULL,
+  table_name        VARCHAR(256)  NOT NULL,
+  metadata_location TEXT          NOT NULL,
+  file_io_impl      VARCHAR(256)  NOT NULL,
+  file_io_props     TEXT          NOT NULL,
+  state             VARCHAR(16)   NOT NULL,
+  attempts          INT           NOT NULL DEFAULT 0,
+  last_error        VARCHAR(2048),
+  heartbeat_at      BIGINT        NOT NULL DEFAULT 0,
+  created_by        VARCHAR(128)  NOT NULL,
+  updated_at        BIGINT        NOT NULL
+);
+CREATE INDEX IF NOT EXISTS idx_state_updated ON iceberg_cleanup_job (state, 
updated_at);
+CREATE INDEX IF NOT EXISTS idx_object ON iceberg_cleanup_job (catalog_id, 
namespace, table_name, state);

Review Comment:
   The PostgreSQL upgrade script documents other new tables with COMMENT ON 
TABLE/COLUMN, but the new `iceberg_cleanup_job` table is missing those 
comments. Adding them here keeps the upgrade output self-describing and 
consistent with `schema-1.3.0-postgresql.sql`.



##########
scripts/h2/upgrade-1.2.0-to-1.3.0-h2.sql:
##########
@@ -100,3 +100,22 @@ CREATE TABLE IF NOT EXISTS `idp_user_group_rel` (
     KEY `idx_iuig_uid` (`user_id`),
     KEY `idx_iuig_gid` (`group_id`)
 ) ENGINE=InnoDB;
+
+CREATE TABLE IF NOT EXISTS `iceberg_cleanup_job` (
+  `id`                BIGINT        NOT NULL,
+  `catalog_id`        BIGINT        NOT NULL,
+  `namespace`         VARCHAR(512)  NOT NULL,
+  `table_name`        VARCHAR(256)  NOT NULL,
+  `metadata_location` CLOB          NOT NULL,
+  `file_io_impl`      VARCHAR(256)  NOT NULL,
+  `file_io_props`     CLOB          NOT NULL,
+  `state`             VARCHAR(16)   NOT NULL,
+  `attempts`          INT           NOT NULL DEFAULT 0,
+  `last_error`        VARCHAR(2048) NULL,
+  `heartbeat_at`      BIGINT        NOT NULL DEFAULT 0,
+  `created_by`        VARCHAR(128)  NOT NULL,
+  `updated_at`        BIGINT        NOT NULL,
+  PRIMARY KEY (`id`)
+);

Review Comment:
   In the H2 1.2.0->1.3.0 upgrade script, the new `iceberg_cleanup_job` table 
is created without the inline table/column `COMMENT` metadata that the 
surrounding tables use. Adding comments here keeps the upgrade script 
consistent with the rest of the H2 DDL and makes the schema self-describing.



##########
iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/cleanup/IcebergCleanupJobStore.java:
##########
@@ -0,0 +1,209 @@
+/*
+ * 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.iceberg.service.cleanup;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.List;
+import java.util.Optional;
+import 
org.apache.gravitino.iceberg.service.cleanup.mapper.IcebergCleanupJobMapper;
+import org.apache.gravitino.iceberg.service.cleanup.po.IcebergCleanupJobPO;
+import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.storage.relational.utils.SessionUtils;
+
+/**
+ * Persistence for {@code iceberg_cleanup_job}, layered on the Gravitino 
entity store's shared
+ * relational backend. Async cleanup reuses the entity store's connection 
pool, transaction
+ * management, and per-backend SQL dispatch instead of opening its own JDBC 
connections. Row ids and
+ * timestamps are supplied by the application, keeping the SQL portable across 
H2, MySQL, and
+ * PostgreSQL.
+ */
+public class IcebergCleanupJobStore {
+
+  private static final int MAX_ERROR_LENGTH = 2048;
+
+  private final IdGenerator idGenerator;
+
+  /**
+   * Creates a cleanup job store.
+   *
+   * @param idGenerator generator for new row ids
+   */
+  public IcebergCleanupJobStore(IdGenerator idGenerator) {
+    this.idGenerator = idGenerator;
+  }
+
+  /**
+   * Persists a new PENDING job.
+   *
+   * @param job job to persist
+   * @return generated id
+   */
+  public long addJob(IcebergCleanupJob job) {
+    long id = idGenerator.nextId();
+    long now = System.currentTimeMillis();
+    IcebergCleanupJobPO po = IcebergCleanupJobPO.fromCleanupJob(job, id, now);
+    SessionUtils.doWithCommit(IcebergCleanupJobMapper.class, mapper -> 
mapper.insertCleanupJob(po));
+    return id;
+  }
+
+  /**
+   * Scans a small candidate window and takes the first available row via 
compare-and-swap.
+   *
+   * @param now current epoch millis, written as the initial heartbeat
+   * @param heartbeatTimeoutMs age past which a RUNNING heartbeat is stale
+   * @param window max candidates to consider
+   * @return the taken job, or {@link Optional#empty()} if nothing was 
available
+   */
+  public Optional<IcebergCleanupJob> takePendingJob(long now, long 
heartbeatTimeoutMs, int window) {
+    long heartbeatExpiry = now - heartbeatTimeoutMs;
+    List<IcebergCleanupJobPO> candidates =
+        SessionUtils.getWithoutCommit(
+            IcebergCleanupJobMapper.class,
+            mapper -> mapper.selectCandidateJobs(heartbeatExpiry, window));
+    for (IcebergCleanupJobPO po : candidates) {
+      long id = po.getId();
+      int marked =
+          SessionUtils.doWithCommitAndFetchResult(
+              IcebergCleanupJobMapper.class,
+              mapper -> mapper.markRunning(id, now, heartbeatExpiry));
+      if (marked == 1) {
+        // The claim only flips mutable columns (state, heartbeat_at, 
updated_at); everything
+        // toCleanupJob reads was fixed at enqueue, so the candidate snapshot 
is still accurate.
+        return Optional.of(po.toCleanupJob());
+      }
+    }
+    return Optional.empty();
+  }
+
+  /**
+   * Marks a RUNNING job SUCCEEDED.
+   *
+   * @param id job id
+   * @return {@code true} iff the row was still RUNNING and was updated (i.e. 
the caller still owned
+   *     the job)
+   */
+  public boolean markSucceeded(long id) {
+    long now = System.currentTimeMillis();
+    return SessionUtils.doWithCommitAndFetchResult(
+            IcebergCleanupJobMapper.class,
+            mapper -> mapper.markFinished(id, 
IcebergCleanupJob.State.SUCCEEDED.name(), null, now))
+        > 0;
+  }
+
+  /**
+   * Marks a RUNNING job FAILED immediately.
+   *
+   * @param id job id
+   * @param reason failure text
+   * @return {@code true} iff the row was still RUNNING and was updated (i.e. 
the caller still owned
+   *     the job)
+   */
+  public boolean markFailed(long id, String reason) {
+    long now = System.currentTimeMillis();
+    String err = truncate(reason);
+    return SessionUtils.doWithCommitAndFetchResult(
+            IcebergCleanupJobMapper.class,
+            mapper -> mapper.markFinished(id, 
IcebergCleanupJob.State.FAILED.name(), err, now))
+        > 0;
+  }
+
+  /**
+   * Records a transient failure: {@code attempts++}, then FAILED at the 
ceiling else PENDING.
+   *
+   * @param id job id
+   * @param reason failure text
+   * @param maxAttempts ceiling from config
+   * @return {@code true} iff the row was still RUNNING and was updated (i.e. 
the caller still owned
+   *     the job)
+   */
+  public boolean recordFailure(long id, String reason, int maxAttempts) {
+    long now = System.currentTimeMillis();
+    String err = truncate(reason);
+    return SessionUtils.doWithCommitAndFetchResult(
+            IcebergCleanupJobMapper.class,
+            mapper -> mapper.recordFailure(id, err, maxAttempts, now))
+        > 0;
+  }
+
+  /**
+   * Refreshes a heartbeat with compare-and-swap ownership check.
+   *
+   * @param id job id
+   * @param lastHeartbeat previous heartbeat value
+   * @param now new heartbeat value
+   * @return {@code true} iff the row was still owned by the caller
+   */
+  public boolean heartbeat(long id, long lastHeartbeat, long now) {
+    return SessionUtils.doWithCommitAndFetchResult(
+            IcebergCleanupJobMapper.class, mapper -> mapper.heartbeat(id, 
lastHeartbeat, now))
+        > 0;
+  }
+
+  /**
+   * Finds the id of an unfinished (PENDING or RUNNING) cleanup job for the 
identifier, if any.
+   *
+   * @param catalogId globally unique id of the owning catalog
+   * @param namespace table namespace
+   * @param table table name
+   * @return the unfinished job id, or {@link Optional#empty()} if none exists
+   */
+  public Optional<Long> findUnfinishedJobId(long catalogId, String namespace, 
String table) {
+    return Optional.ofNullable(
+        SessionUtils.getWithoutCommit(
+            IcebergCleanupJobMapper.class,
+            mapper -> mapper.selectUnfinishedJobId(catalogId, namespace, 
table)));
+  }
+
+  /**
+   * Deletes finished (SUCCEEDED or FAILED) jobs whose last update predates 
the timeline.
+   *
+   * @param legacyTimeline cutoff epoch millis; rows updated before this are 
removed
+   * @return rows deleted
+   */
+  public int deleteFinishedJobsByLegacyTimeline(long legacyTimeline) {
+    return SessionUtils.doWithCommitAndFetchResult(
+        IcebergCleanupJobMapper.class,
+        mapper -> mapper.deleteFinishedJobsByLegacyTimeline(legacyTimeline));
+  }
+
+  /**
+   * Reads a job state for tests.
+   *
+   * @param id job id
+   * @return its current state
+   * @throws IllegalStateException if the row is gone
+   */
+  @VisibleForTesting
+  public IcebergCleanupJob.State stateOf(long id) {
+    String state =

Review Comment:
   `stateOf` is annotated `@VisibleForTesting` and only used by tests in the 
same package, but it is declared `public`, which unnecessarily expands the 
production API surface of `IcebergCleanupJobStore`. Make it package-private to 
keep the test-only helper accessible without exporting it as part of the 
store's public API.



##########
scripts/h2/schema-1.3.0-h2.sql:
##########
@@ -612,3 +612,22 @@ CREATE TABLE IF NOT EXISTS `entity_change_log` (
   PRIMARY KEY (`id`),
   KEY `idx_ecl_created_at` (`created_at`)
 ) ENGINE=InnoDB COMMENT='Append-only log of entity structural changes for 
targeted metadataIdCache invalidation';
+
+CREATE TABLE IF NOT EXISTS `iceberg_cleanup_job` (
+  `id`                BIGINT        NOT NULL,
+  `catalog_id`        BIGINT        NOT NULL,
+  `namespace`         VARCHAR(512)  NOT NULL,
+  `table_name`        VARCHAR(256)  NOT NULL,
+  `metadata_location` CLOB          NOT NULL,
+  `file_io_impl`      VARCHAR(256)  NOT NULL,
+  `file_io_props`     CLOB          NOT NULL,
+  `state`             VARCHAR(16)   NOT NULL,
+  `attempts`          INT           NOT NULL DEFAULT 0,
+  `last_error`        VARCHAR(2048) NULL,
+  `heartbeat_at`      BIGINT        NOT NULL DEFAULT 0,
+  `created_by`        VARCHAR(128)  NOT NULL,
+  `updated_at`        BIGINT        NOT NULL,
+  PRIMARY KEY (`id`)
+);

Review Comment:
   In the H2 schema script, most tables/columns include inline `COMMENT` 
metadata (for example `entity_change_log` just above), but the new 
`iceberg_cleanup_job` table is missing both table and column comments. Adding 
comments keeps the H2 schema consistent and makes troubleshooting easier.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to