From 392846e92f591410fe8602d2f414cc0cdedcc959 Mon Sep 17 00:00:00 2001 From: joe <1196001399@qq.com> Date: Tue, 6 Oct 2026 11:06:06 +0200 Subject: [PATCH] feat: load policy with a single LRANGE instead of one LINDEX per rule load_policy issued LLEN and then one LINDEX per rule, so loading N rules took N+1 client/Redis round trips. With a few thousand rules over a ~1 ms network that is several seconds per load, almost all of it spent waiting on round trips. A single LRANGE fetches the whole list in one round trip and returns an atomic snapshot, so a concurrent LREM can no longer shrink the list mid-load and make LINDEX return None. This matches the Go redis-adapter, which already loads with LRANGE. --- casbin_redis_adapter/adapter.py | 6 +++--- tests/test_adapter.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/casbin_redis_adapter/adapter.py b/casbin_redis_adapter/adapter.py index fbbabd8..8ec342d 100644 --- a/casbin_redis_adapter/adapter.py +++ b/casbin_redis_adapter/adapter.py @@ -73,9 +73,9 @@ def load_policy(self, model): model (CasbinRule): CasbinRule object """ - length = self.client.llen(self.key) - for i in range(length): - line = self.client.lindex(self.key, i) + # Fetch the whole list with a single LRANGE: one round trip instead of + # one LINDEX per rule, and an atomic snapshot of the list. + for line in self.client.lrange(self.key, 0, -1): line = json.loads(line) rule = CasbinRule(**line) persist.load_policy_line(str(rule), model) diff --git a/tests/test_adapter.py b/tests/test_adapter.py index 858a7de..9d6569b 100644 --- a/tests/test_adapter.py +++ b/tests/test_adapter.py @@ -2,6 +2,7 @@ from casbin_redis_adapter.adapter import CasbinRule from unittest import TestCase +from unittest import mock import redis import casbin import os @@ -71,6 +72,24 @@ def test_enforcer_basic(self): self.assertTrue(e.enforce("alice", "data2", "read")) self.assertTrue(e.enforce("alice", "data2", "write")) + def test_load_policy_single_round_trip(self): + """ + test load_policy reads all rules with one LRANGE, not one LINDEX per rule + """ + e = get_enforcer() + adapter = e.get_adapter() + with mock.patch.object( + adapter.client, "lrange", wraps=adapter.client.lrange + ) as lrange, mock.patch.object( + adapter.client, "lindex", side_effect=AssertionError("LINDEX used") + ): + e.load_policy() + lrange.assert_called_once_with("casbin_rules", 0, -1) + self.assertEqual(len(e.get_policy()), 4) + self.assertEqual(len(e.get_grouping_policy()), 1) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data2", "write")) + def test_add_policy(self): """ test add_policy