apache/druid · error · IllegalStateException

Could not unassign role [%s] from user [%s] due to concurren

Error message

Could not unassign role [%s] from user [%s] due to concurrent update contention.

What it means

Removing a role from a user failed because every compare-and-swap attempt on the user map lost to concurrent writers, exhausting numRetries. The updater re-reads the user map, removes the role, and retries the CAS with randomized delays before throwing this ISE with the role and user names. It signals persistent contention on metadata storage.

Source

Thrown at extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java:802

  }

  private void unassignUserRoleInternal(String prefix, String userName, String roleName)
  {
    int attempts = 0;
    while (attempts < numRetries) {
      if (unassignUserRoleOnce(prefix, userName, roleName)) {
        return;
      } else {
        attempts++;
      }
      try {
        Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
      }
      catch (InterruptedException ie) {
        throw new RuntimeException(ie);
      }
    }
    throw new ISE("Could not unassign role [%s] from user [%s] due to concurrent update contention.", roleName, userName);
  }

  private void assignGroupMappingRoleInternal(String prefix, String groupMappingName, String roleName)
  {
    int attempts = 0;
    while (attempts < numRetries) {
      if (assignGroupMappingRoleOnce(prefix, groupMappingName, roleName)) {
        return;
      } else {
        attempts++;
      }
      try {
        Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));
      }
      catch (InterruptedException ie) {
        throw new RuntimeException(ie);
      }
    }

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Retry unassignUserRole after a short backoff.
  2. Enforce single-writer access to basic-security metadata.
  3. Serialize user offboarding operations.
  4. Increase numRetries to absorb concurrent update bursts.
  5. Check metadata storage performance.

Example fix

// before
client.unassignUserRole(prefix, userName, roleName);
// after
for (int i = 0; i < 5; i++) {
  try { client.unassignUserRole(prefix, userName, roleName); return; }
  catch (IJSE e) { Thread.sleep(1000); }
}
Defensive patterns

Strategy: retry

Validate before calling

// Skip if user doesn't have the role
BasicAuthorizerUser u = BasicAuthUtils.deserializeAuthorizerUserMap(
    mapper, getCurrentUserMapBytes(prefix)).get(userName);
if (u == null || !u.getRoles().contains(roleName)) return;

Try / catch

try {
  updater.unassignUserRole(prefix, userName, roleName);
} catch (IJSE e) {
  await.atMost(Duration.ofSeconds(10)).untilAsserted(
      () -> updater.unassignUserRole(prefix, userName, roleName));
}

Prevention

When it happens

Trigger: Calling unassignUserRole while other clients continuously update the same authorizer prefix's user or role maps so the CAS never succeeds within the retry budget.

Common situations: Parallel offboarding/cleanup scripts; simultaneous unassignment and permission edits; write contention between admin UI sessions and automation.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/2bd4b70772ae8e3e. Report an issue: GitHub.