This is an automated email from the ASF dual-hosted git repository. hsluoyz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/casbin.git
commit 65cb98506a474e2dddd5f24ad534999306c14ec6 Author: Nhan (Nolan) Huynh <[email protected]> AuthorDate: Tue Aug 4 14:56:39 2026 +0700 perf: optimize batch policy removal (#1729) --- management_api_b_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ model/policy.go | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/management_api_b_test.go b/management_api_b_test.go index 1bc534c..b4e7e88 100644 --- a/management_api_b_test.go +++ b/management_api_b_test.go @@ -176,3 +176,50 @@ func BenchmarkRemovePolicyLarge(b *testing.B) { _, _ = e.RemovePolicy(fmt.Sprintf("user%d", rand.Intn(10000)), fmt.Sprintf("data%d", rand.Intn(10000)/10), "read") } } + +func BenchmarkRemovePoliciesLarge(b *testing.B) { + e, _ := NewEnforcer("examples/basic_model.conf") + + // 10000 roles, 1000 resources. + pPolicies := make([][]string, 0) + for i := 0; i < 10000; i++ { + pPolicies = append(pPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read"}) + } + _, err := e.AddPolicies(pPolicies) + if err != nil { + b.Fatal(err) + } + + // Remove 100 rules per batch, restoring them afterwards so that every + // iteration runs against the same 10000-rule policy set. Batches are taken + // in order, and restored rules go to the end of the policy list, so the + // next batch is always at the front - the worst case for index rebuilding. + const batchSize = 100 + batches := make([][][]string, 0, len(pPolicies)/batchSize) + for i := 0; i < len(pPolicies); i += batchSize { + batches = append(batches, pPolicies[i:i+batchSize]) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rules := batches[i%len(batches)] + + removed, err := e.RemovePolicies(rules) + if err != nil { + b.Fatal(err) + } + if !removed { + b.Fatal("expected RemovePolicies to remove rules") + } + + b.StopTimer() + added, err := e.AddPolicies(rules) + if err != nil { + b.Fatal(err) + } + if !added { + b.Fatal("expected AddPolicies to add rules") + } + b.StartTimer() + } +} diff --git a/model/policy.go b/model/policy.go index 1d153b7..d4e4899 100644 --- a/model/policy.go +++ b/model/policy.go @@ -346,24 +346,44 @@ func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) (b // RemovePoliciesWithAffected removes policy rules from the model, and returns affected rules. func (model Model) RemovePoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) { - _, err := model.GetAssertion(sec, ptype) + assertion, err := model.GetAssertion(sec, ptype) if err != nil { return nil, err } + var affected [][]string + removeSet := make(map[string]struct{}, len(rules)) + for _, rule := range rules { - index, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)] - if !ok { + key := strings.Join(rule, DefaultSep) + if _, ok := assertion.PolicyMap[key]; !ok { + continue + } + if _, exists := removeSet[key]; exists { continue } - affected = append(affected, rule) - model[sec][ptype].Policy = append(model[sec][ptype].Policy[:index], model[sec][ptype].Policy[index+1:]...) - delete(model[sec][ptype].PolicyMap, strings.Join(rule, DefaultSep)) - for i := index; i < len(model[sec][ptype].Policy); i++ { - model[sec][ptype].PolicyMap[strings.Join(model[sec][ptype].Policy[i], DefaultSep)] = i + removeSet[key] = struct{}{} + } + + if len(removeSet) == 0 { + return affected, nil + } + + compactPolicy := assertion.Policy[:0] + compactPolicyMap := make(map[string]int, len(assertion.Policy)-len(removeSet)) + for _, policyRule := range assertion.Policy { + key := strings.Join(policyRule, DefaultSep) + if _, ok := removeSet[key]; ok { + continue } + compactPolicyMap[key] = len(compactPolicy) + compactPolicy = append(compactPolicy, policyRule) } + + assertion.Policy = compactPolicy + assertion.PolicyMap = compactPolicyMap + return affected, nil } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
