{"record":{"id":"bc51e027aebf6573","repo":"apache/druid","slug":"could-not-delete-group-mapping-s-due-to-concurr","errorCode":null,"errorMessage":"Could not delete group mapping [%s] due to concurrent update contention.","messagePattern":"Could not delete group mapping \\[(.+?)\\] due to concurrent update contention\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":500,"severity":"error","filePath":"extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java","lineNumber":726,"sourceCode":"  }\n\n  private void deleteGroupMappingInternal(String prefix, String groupMappingName)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (deleteGroupMappingOnce(prefix, groupMappingName)) {\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 delete group mapping [%s] due to concurrent update contention.\", groupMappingName);\n  }\n\n  private void createRoleInternal(String prefix, String roleName)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (createRoleOnce(prefix, 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    }","sourceCodeStart":708,"sourceCodeEnd":744,"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#L708-L744","documentation":"The updater failed to delete an authorization group mapping because every compare-and-swap attempt on the group mapping map lost to a concurrent writer, exhausting numRetries. Each retry re-reads the current map and re-applies the removal; persistent losers mean another client is continuously mutating the same map. The ISE is thrown after the retry loop with the contested group mapping name.","triggerScenarios":"Calling deleteGroupMapping while other writers repeatedly update the same authorizer prefix's group mapping map so tryUpdateGroupMappingMap CAS fails for all numRetries attempts.","commonSituations":"Two admin sessions or automation jobs deleting/updating group mappings at once; a coordinator leader failover leaving a second active writer; bursty API traffic against basic-security endpoints backed by a laggy metadata store.","solutions":["Retry the deleteGroupMapping call after a short delay.","Ensure a single leader/writer performs authorization metadata updates for this authorizer.","Rate-limit or serialize group mapping mutations from automation tooling.","Raise numRetries for the updater if concurrent updates are routine.","Check metadata storage latency; slow writes increase CAS failure rates."],"exampleFix":"// before\ngroupMappings.forEach(gm -> client.deleteGroupMapping(prefix, gm));\n// after: retry on contention\nfor (final String gm : groupMappings) {\n  await.untilAsserted(() -> client.deleteGroupMapping(prefix, gm));\n}","handlingStrategy":"retry","validationCode":"// Check the mapping still exists before deleting\nbyte[] map = curator.getData().forPath(groupMappingPath(prefix));\nif (BasicAuthUtils.deserializeAuthorizerGroupMappingMap(mapper, map).get(name) == null) return;","typeGuard":null,"tryCatchPattern":"try {\n  updater.deleteGroupMapping(prefix, name);\n} catch (IJSE e) {\n  await.atMost(Duration.ofSeconds(10)).untilAsserted(\n      () -> updater.deleteGroupMapping(prefix, name));\n}","preventionTips":["Ensure single-writer (leader-only) metadata updates","Avoid concurrent delete+update on the same group mapping","Apply exponential/randomized backoff on retries","Raise numRetries when concurrent admins are expected","Watch metadata storage health"],"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-14T05:17:10.506Z"}