{"record":{"id":"6139b76c71e111cb","repo":"apache/druid","slug":"could-not-assign-role-s-to-group-mapping-s-d","errorCode":null,"errorMessage":"Could not assign role [%s] to group mapping [%s] due to concurrent update contention.","messagePattern":"Could not assign role \\[(.+?)\\] to 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":821,"sourceCode":"  }\n\n  private void assignGroupMappingRoleInternal(String prefix, String groupMappingName, String roleName)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (assignGroupMappingRoleOnce(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 assign role [%s] to group mapping [%s] due to concurrent update contention.\",\n                  roleName,\n                  groupMappingName\n    );\n  }\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) {","sourceCodeStart":803,"sourceCodeEnd":839,"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#L803-L839","documentation":"Assigning a role to an authorization group mapping failed after numRetries compare-and-swap attempts, each beaten by a concurrent writer of the group mapping/role metadata. The updater re-reads the map, re-applies the assignment, and retries with randomized delay before throwing this ISE naming the role and group mapping. It indicates metadata-storage write contention, not an invalid role or mapping.","triggerScenarios":"Calling assignGroupMappingRole while other writers repeatedly mutate authorization metadata for the same authorizer prefix, so every CAS fails through all retries.","commonSituations":"Automation provisioning group mappings and roles concurrently from multiple workers; simultaneous group mapping edits via the console and API; multi-writer coordinator misconfiguration.","solutions":["Retry assignGroupMappingRole after a delay.","Ensure only the leader coordinator writes basic-security metadata.","Serialize group mapping and role provisioning workflows.","Increase numRetries if concurrent updates are expected.","Inspect metadata storage latency."],"exampleFix":"// before\ngms.forEach(gm -> client.assignGroupMappingRole(prefix, gm, role));\n// after: retry each assignment\nfor (final String gm : gms) {\n  await.untilAsserted(() -> client.assignGroupMappingRole(prefix, gm, role));\n}","handlingStrategy":"retry","validationCode":"// Verify group mapping and role exist first\nMap<String, BasicAuthorizerGroupMapping> gms =\n    BasicAuthUtils.deserializeAuthorizerGroupMappingMap(mapper, getCurrentGroupMappingMapBytes(prefix));\nMap<String, BasicAuthorizerRole> roles =\n    BasicAuthUtils.deserializeAuthorizerRoleMap(mapper, getCurrentRoleMapBytes(prefix));\nif (gms.get(groupMappingName) == null || roles.get(roleName) == null)\n  throw new IllegalArgumentException(\"group mapping or role missing\");","typeGuard":null,"tryCatchPattern":"try {\n  updater.assignGroupMappingRole(prefix, groupMappingName, roleName);\n} catch (IJSE e) {\n  RetryUtils.retry(() -> updater.assignGroupMappingRole(prefix, groupMappingName, roleName),\n                   ex -> ex instanceof IllegalStateException, MAX_ATTEMPTS);\n}","preventionTips":["Provision group mappings and roles sequentially","Leader-only writes to metadata","Randomized backoff between attempts","Raise numRetries for concurrent setups","Monitor metadata storage"],"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"}