apache/iceberg · error · UncheckedSQLException

Failed to create %s lock

Error message

Failed to create %s lock

What it means

When JdbcLock.tryLock's INSERT throws SQLException, the code checks whether the lock was created anyway (by comparing the stored instance ID). If the stored instanceId does not match this instance's newInstanceId, the lock genuinely could not be created, and the SQLException is wrapped as UncheckedSQLException with 'Failed to create %s lock'.

Source

Thrown at flink/v1.20/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 the lock table contents: if a stale row from a dead job exists, remove it or let the maintenance recovery procedure reset the lock.
  2. Verify DB connectivity and fix any SQLException surfaced in the cause chain.
  3. Ensure only one trigger job with this lock ID runs concurrently.
  4. Recreate the lock table if its schema drifted from what the current version expects.

Example fix

// stale row cleanup (example for MySQL)
// DELETE FROM iceberg_triggers_lock WHERE trigger_id = 'my-table' AND instance_id != '<current>';
Defensive patterns

Strategy: try-catch

Validate before calling

// before acquiring, check who holds the lock
String holder = fetchInstanceId(lockId);
if (holder != null && !holder.equals(myInstanceId)) {
    throw new IllegalStateException("Lock held by instance: " + holder);
}

Try / catch

try {
    acquired = lock.tryLock();
} catch (UncheckedSQLException e) {
    // inspect cause: constraint violation vs connection loss
    LOG.error("tryLock failed", e.getCause());
    // check for stale rows and clean up per runbook, then retry once
}

Prevention

When it happens

Trigger: tryLock fails when the INSERT errors and the lock row in the maintenance lock table holds a different instance ID — meaning another holder owns the row or the row is stale/corrupt, or the SQL failure is a real error (constraint violation, connection drop).

Common situations: Stale lock rows left by a crashed job with a different instance ID; DB connectivity dropping mid-INSERT; schema/constraint mismatches on the lock table.

Related errors


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