apache/iceberg · error · UncheckedSQLException

Failed to remove lock %s

Error message

Failed to remove lock %s

What it means

JdbcLock.unlock() throws UncheckedSQLException when the SQL used to read/update the lock table fails, so the lock could not be removed. The message includes the lock identity. Because the lock row may still exist, later maintenance triggers can be blocked until the issue is fixed.

Source

Thrown at flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/api/JdbcLockFactory.java:273

                  sql.setString(2, lockId);
                  sql.setString(3, instanceId);
                  long count = sql.executeUpdate();
                  LOG.info(
                      "Deleted {} lock with instanceId {} with row count {}",
                      this,
                      instanceId,
                      count);
                }

                return null;
              });
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new UncheckedInterruptedException(e, "Interrupted during unlock");
      } catch (SQLException e) {
        // SQL exception happened when getting/updating lock information
        throw new UncheckedSQLException(e, "Failed to remove lock %s", this);
      }
    }

    @Override
    public String toString() {
      return MoreObjects.toStringHelper(this).add("type", type).add("lockId", lockId).toString();
    }

    @SuppressWarnings("checkstyle:NestedTryDepth")
    private String instanceId() {
      try {
        return pool.run(
            conn -> {
              try (PreparedStatement sql = conn.prepareStatement(GET_LOCK_SQL)) {
                sql.setString(1, type.key);
                sql.setString(2, lockId);
                try (ResultSet rs = sql.executeQuery()) {
                  if (rs.next()) {

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Inspect the wrapped SQLException cause (deadlock, timeout, connection reset) and fix that root cause first.
  2. Re-run maintenance or manually delete the stale lock row for this lockId from the lock table.
  3. Add connection retry/validation settings to the JDBC URL and pool so transient drops self-heal.
  4. Ensure only one instance per lockId runs; row contention often means a duplicate trigger is deployed.
  5. If the table is missing, re-initialize the JDBC catalog to recreate the lock table.

Example fix

// before
String url = "jdbc:postgresql://db:5432/iceberg"; // stale connections
// after
String url = "jdbc:postgresql://db:5432/iceberg?connectTimeout=10&socketTimeout=60";
// plus: DELETE FROM iceberg_locks WHERE lock_id = 'my-trigger'; // clear orphaned lock
Defensive patterns

Strategy: retry

Validate before calling

try (Connection c = DriverManager.getConnection(jdbcUrl, user, pass)) { c.createStatement().execute("SELECT 1 FROM iceberg_locks LIMIT 1"); } // table reachable and present

Try / catch

try { lock.unlock(); } catch (UncheckedSQLException e) { log.error("Unlock failed: {}", e.getCause(), e); /* delete orphaned lock row manually */ }

Prevention

When it happens

Trigger: unlock() executes its get/update lock SQL and the driver throws SQLException: broken connection, missing lock table, deadlocks/lock timeouts on the row, or SQL dialect incompatibility.

Common situations: Network drop between Flink and the database mid-unlock; row-level lock contention with another trigger instance; lock table dropped after a catalog re-creation; database failover invalidating pooled connections.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/2c471174781f8e9c. Report an issue: GitHub.