apache/iceberg · error · UncheckedSQLException

Failed to create %s lock

Error message

Failed to create %s lock

What it means

Thrown by JdbcLock.tryLock when the INSERT that creates the lock row fails with a SQLException AND the subsequent instanceId() check shows another instance owns the lock (i.e. the lock was not successfully created behind the scenes). It wraps the original SQL error; the lock remains held by a different instance.

Source

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

                int count = sql.executeUpdate();
                LOG.info(
                    "Created {} lock with instanceId {} with row count {}",
                    this,
                    newInstanceId,
                    count);
                return count == 1;
              }
            });
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new UncheckedInterruptedException(e, "Interrupted during tryLock");
      } catch (SQLException e) {
        // SQL exception happened when creating the lock. Check if the lock creation was
        // successful behind the scenes.
        if (newInstanceId.equals(instanceId())) {
          return true;
        } else {
          throw new UncheckedSQLException(e, "Failed to create %s lock", this);
        }
      }
    }

    @SuppressWarnings("checkstyle:NestedTryDepth")
    @Override
    public boolean isHeld() {
      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()) {
                  return rs.next();
                }
              }
            });

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Check who currently holds the lock (query the lock table's instance id / timestamp) and wait or clear stale locks
  2. Ensure only one maintenance instance is configured per table — a second concurrent job will lose tryLock
  3. If the holder is a crashed job, delete the stale lock row so tryLock can succeed
  4. Inspect the wrapped SQLException (getCause) for the underlying constraint or connectivity issue

Example fix

// before: two jobs trigger maintenance on the same table concurrently
job1: TableMaintenance...lockFactory(jdbcLockFactory).schedule()
job2: TableMaintenance...lockFactory(jdbcLockFactory).schedule() // tryLock fails
// after: single maintenance trigger, or remove stale lock row first
DELETE FROM iceberg_lock WHERE installed_on < NOW() - INTERVAL '1 hour';
Defensive patterns

Strategy: retry

Validate before calling

// before triggering maintenance, check no other instance holds the lock
SELECT lock_id, instance_id, created_at FROM iceberg_lock WHERE lock_id = ?;
// if instance_id differs and created_at is recent, another job owns it

Try / catch

try {
  lock.tryLock();
} catch (UncheckedSQLException e) {
  if (isStaleLock(e)) { deleteStaleLockRow(); retryLater(); }
  else throw e;
}

Prevention

When it happens

Trigger: tryLock's INSERT hits a constraint/violation or connection error, and the recovery check `newInstanceId.equals(instanceId())` returns false because another maintenance instance already inserted its lock row.

Common situations: Two Flink jobs or restarted taskmanagers both configured to run maintenance on the same table; duplicate lock rows from a previous crashed run with a different instance id; database failover mid-insert.

Related errors


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