{"record":{"id":"2864cc4f8072284e","repo":"apache/druid","slug":"could-not-unassign-role-s-from-group-mapping","errorCode":null,"errorMessage":"Could not unassign role [%s] from group mapping [%s] due to concurrent update contention.","messagePattern":"Could not unassign role \\[(.+?)\\] from group mapping \\[(.+?)\\] due to concurrent update contention\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java","lineNumber":843,"sourceCode":"  }\n\n  private void unassignGroupMappingRoleInternal(String prefix, String groupMappingName, String roleName)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (unassignGroupMappingRoleOnce(prefix, groupMappingName, roleName)) {\n        return;\n      } else {\n        attempts++;\n      }\n      try {\n        Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));\n      }\n      catch (InterruptedException ie) {\n        throw new RuntimeException(ie);\n      }\n    }\n    throw new ISE(\"Could not unassign role [%s] from group mapping [%s] due to concurrent update contention.\", roleName,\n                  groupMappingName\n    );\n  }\n\n  private void setPermissionsInternal(String prefix, String roleName, List<ResourceAction> permissions)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (setPermissionsOnce(prefix, roleName, permissions)) {\n        return;\n      } else {\n        attempts++;\n      }\n      try {\n        Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));\n      }\n      catch (InterruptedException ie) {\n        throw new RuntimeException(ie);","sourceCodeStart":825,"sourceCodeEnd":861,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java#L825-L861","documentation":"Unassigning a role from an authorization group mapping failed after exhausting numRetries compare-and-swap attempts because concurrent writers kept updating the metadata maps first. Each retry re-reads the current map and re-applies the removal; persistent contention throws this ISE with the role and group mapping names. It reflects metadata-storage contention rather than a data problem.","triggerScenarios":"Calling unassignGroupMappingRole while other clients continuously mutate the same authorizer prefix's metadata so the CAS never succeeds within the retry budget.","commonSituations":"Parallel cleanup jobs editing group mappings; simultaneous unassignment and role/permission changes; contention between admin UI and API calls.","solutions":["Retry unassignGroupMappingRole after a short backoff.","Enforce single-writer semantics for basic-security metadata.","Serialize group mapping cleanup operations.","Raise numRetries to tolerate concurrent update bursts.","Verify metadata storage health/latency."],"exampleFix":"// before\nclient.unassignGroupMappingRole(prefix, groupMappingName, roleName);\n// after\nfor (int i = 0; i < 5; i++) {\n  try { client.unassignGroupMappingRole(prefix, groupMappingName, roleName); return; }\n  catch (IJSE e) { Thread.sleep(1000); }\n}","handlingStrategy":"retry","validationCode":"// Skip if the mapping lacks the role\nBasicAuthorizerGroupMapping gm = BasicAuthUtils.deserializeAuthorizerGroupMappingMap(\n    mapper, getCurrentGroupMappingMapBytes(prefix)).get(groupMappingName);\nif (gm == null || !gm.getRoles().contains(roleName)) return;","typeGuard":null,"tryCatchPattern":"try {\n  updater.unassignGroupMappingRole(prefix, groupMappingName, roleName);\n} catch (IJSE e) {\n  await.atMost(Duration.ofSeconds(10)).untilAsserted(\n      () -> updater.unassignGroupMappingRole(prefix, groupMappingName, roleName));\n}","preventionTips":["Avoid concurrent edits to the same group mapping","Single-writer metadata discipline","Use retry-with-backoff wrappers","Increase numRetries when needed","Keep metadata store responsive"],"tags":["druid","basic-security","zookeeper","concurrency"],"backgroundTag":"concurrent-update-contention","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}