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
- Retry unassignUserRole after a short backoff.
- Enforce single-writer access to basic-security metadata.
- Serialize user offboarding operations.
- Increase numRetries to absorb concurrent update bursts.
- 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
- Serialize offboarding workflows
- Single-writer metadata updates
- Backoff between unassignment retries
- Tune numRetries to expected concurrency
- Check metadata store latency
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
- Could not create group mapping [%s] due to concurrent update
- Could not delete group mapping [%s] due to concurrent update
- Could not create role [%s] due to concurrent update contenti
- Could not delete role [%s] due to concurrent update contenti
- Could not assign role [%s] to user [%s] due to concurrent up
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/2bd4b70772ae8e3e.
Report an issue: GitHub.