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]

Reply via email to