simplex-chat-0004: introduceToRemaining notElem O(N×M) member dedup; fix: Set.notMember O(log N)
This commit is contained in:
parent
eb534f0944
commit
d9ca5b236f
12 changed files with 1006 additions and 0 deletions
|
|
@ -0,0 +1,68 @@
|
|||
# cockroachdb-0002: EnsureUserOnlyBelongsToRoles O(R²) slices.Contains in role sync loop
|
||||
|
||||
## Severity
|
||||
MEDIUM
|
||||
|
||||
## Location
|
||||
`pkg/sql/authorization.go:570-572` — `EnsureUserOnlyBelongsToRoles` role membership diff loop
|
||||
|
||||
## Pattern
|
||||
SLOW: `slices.Contains(roles, role)` inside `for role := range currentRoles` loop — O(R) per iteration
|
||||
FAST: `rolesSet := make(map[username.SQLUsername]struct{}, len(roles))` before loop — O(1) per iteration
|
||||
|
||||
## Context
|
||||
|
||||
`EnsureUserOnlyBelongsToRoles` synchronizes a user's role memberships with an external source of
|
||||
truth (e.g. LDAP). It computes roles to revoke by iterating every current role and checking if it
|
||||
appears in the desired `roles` slice:
|
||||
|
||||
```go
|
||||
for role := range currentRoles { // O(R_current) — map iteration
|
||||
if !slices.Contains(roles, role) { // O(R_desired) — linear scan
|
||||
rolesToRevoke = append(rolesToRevoke, role)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`slices.Contains` performs linear search over the `roles` slice. `SQLUsername` comparison is string
|
||||
equality on the internal `.u` field. With R_current=100 current roles and R_desired=100 desired roles
|
||||
(typical LDAP groups-per-user in large enterprises), this is O(10,000) string comparisons per sync
|
||||
operation. LDAP sync can run on every authentication or on a background schedule affecting all users.
|
||||
|
||||
The asymmetry is stark: the second loop (roles to grant) correctly uses `currentRoles[role]` map
|
||||
lookup at O(1). The first loop does not.
|
||||
|
||||
## Speedup
|
||||
100× at R=100 roles (enterprise LDAP with 100 group memberships per user)
|
||||
|
||||
## Patch
|
||||
|
||||
```diff
|
||||
--- a/pkg/sql/authorization.go
|
||||
+++ b/pkg/sql/authorization.go
|
||||
func EnsureUserOnlyBelongsToRoles(...) error {
|
||||
return execCfg.InternalDB.DescsTxn(ctx, func(...) error {
|
||||
currentRoles, err := MemberOfWithAdminOption(ctx, execCfg, txn, user)
|
||||
if err != nil { return err }
|
||||
|
||||
rolesToRevoke := make([]username.SQLUsername, 0, len(currentRoles))
|
||||
rolesToGrant := make([]username.SQLUsername, 0, len(roles))
|
||||
+ // Build O(1)-lookup set from the desired roles slice.
|
||||
+ desiredRolesSet := make(map[username.SQLUsername]struct{}, len(roles))
|
||||
+ for _, r := range roles {
|
||||
+ desiredRolesSet[r] = struct{}{}
|
||||
+ }
|
||||
for role := range currentRoles {
|
||||
- if !slices.Contains(roles, role) {
|
||||
+ if _, ok := desiredRolesSet[role]; !ok {
|
||||
rolesToRevoke = append(rolesToRevoke, role)
|
||||
}
|
||||
}
|
||||
for _, role := range roles {
|
||||
if _, ok := currentRoles[role]; !ok {
|
||||
rolesToGrant = append(rolesToGrant, role)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `slices` import can be removed from this file if this was the only usage (verify first).
|
||||
Loading…
Add table
Add a link
Reference in a new issue