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
- Check who currently holds the lock (query the lock table's instance id / timestamp) and wait or clear stale locks
- Ensure only one maintenance instance is configured per table — a second concurrent job will lose tryLock
- If the holder is a crashed job, delete the stale lock row so tryLock can succeed
- 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
- Guarantee single-runner semantics: one Flink job triggers maintenance per table
- Add a cleanup job that removes lock rows older than a safe threshold (crashed holders)
- Use distinct lock ids per table so jobs do not fight over the same lock row
- Inspect the cause chain — a constraint violation usually means someone else inserted first
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
- Failed to check the state of the lock %s
- Failed to remove lock %s
- Failed to get lock information for %s
- Cannot initialize JDBC table maintenance lock
- Failed to check the state of the lock %s
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/6ca3108692721143.
Report an issue: GitHub.