apache/iceberg · error · UncheckedSQLException

Failed to remove lock %s

Error message

Failed to remove lock %s

What it means

Thrown by JdbcLock.unlock when the SQL statements that read/update lock info and delete the lock row fail with a SQLException. The lock could not be released, leaving it possibly held (stale). The original SQL error is wrapped in UncheckedSQLException.

Source

Thrown at flink/v2.1/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 (getCause) for the exact SQL failure
  2. Grant the maintenance DB user UPDATE/DELETE privileges on the lock table
  3. Check for orphaned/stale lock rows after failure and delete them before the next run
  4. Verify DB connectivity and retry the release; enable connection pool validation to drop dead connections

Example fix

// before
GRANT SELECT, INSERT ON iceberg_lock TO maintenance_user; // unlock DELETE fails
// after
GRANT SELECT, INSERT, UPDATE, DELETE ON iceberg_lock TO maintenance_user;
Defensive patterns

Strategy: retry

Validate before calling

// verify DML rights before running maintenance
try (Connection c = ds.getConnection()) {
  c.setAutoCommit(false);
  try (Statement s = c.createStatement()) {
    s.execute("DELETE FROM iceberg_lock WHERE lock_id = '__probe__'");
  }
  c.rollback();
} catch (SQLException e) { throw new IllegalStateException("No DELETE grant on lock table"); }

Try / catch

try {
  lock.unlock();
} catch (UncheckedSQLException e) {
  log.error("Lock release failed; manual stale-lock cleanup required", e);
  scheduleStaleLockCleanup();
}

Prevention

When it happens

Trigger: unlock's SELECT/UPDATE/DELETE against the lock table fails: lost connection, lock table missing, insufficient DELETE privilege, or a deadlock/timeout on the lock row.

Common situations: DB failover during unlock; maintenance user granted only SELECT/INSERT but not DELETE; network partition; long-running transaction holding the lock row causes timeout.

Related errors


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