From c14d416d19e1fa0ecf7eff913bc740de2c6ff031 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 6 Jan 2025 19:24:05 +0530 Subject: [PATCH] docs: add usage / notes in code --- frappe/tests/test_client_cache.py | 6 ++++++ frappe/utils/redis_wrapper.py | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/frappe/tests/test_client_cache.py b/frappe/tests/test_client_cache.py index 2a09c82fb0..d8052a556b 100644 --- a/frappe/tests/test_client_cache.py +++ b/frappe/tests/test_client_cache.py @@ -56,3 +56,9 @@ class TestClientCache(IntegrationTestCase): c.set_value(frappe.generate_hash(), 42) self.assertEqual(len(c.cache), 2) + + def test_shared_keyspace(self): + val = frappe.generate_hash() + frappe.client_cache.set_value(TEST_KEY, val) + + self.assertEqual(frappe.client_cache.get_value(TEST_KEY), frappe.cache.get_value(TEST_KEY)) diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index 28cb82637e..5b006779de 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -413,6 +413,33 @@ CachedValue = namedtuple("CachedValue", ["value", "expiry"]) class ClientCache: + """A subset of RedisWrapper that keeps "local" cache across requests. + + Main reason for doing this is improving performance while reading things like hooks, schema. + This feature is internal to Frappe Framework and is subjected to change without any notice. + There aren't many use cases for such aggressive caching outside of core Framework. + + This is an implementation of Redis' "client side caching" concept: + - https://redis.io/docs/latest/develop/reference/client-side-caching/ + + Usage/Notes: + - Cache keys that do not change often: Think hours-days, not minutes. + - Cache keys that are read frequently, e.g. every request or at least >10% of the requests. + - Cache values are not huge, consider avg size of ~4kb per value. You can deviate here and + there but not go crazy with caching large values in this cache. + - We have hardcoded 10 minutes "local" ttl and max 1024 keys. + You're not supposed to work with these numbers, not change them. + - Same keys can be accessed with `frappe.cache` too, but that won't implement invalidation. + - Invalidate things as usual using `delete_value`. Local invalidation should be instant. + Do not expect sub-second invalidation guarantees across processes. + If you need that kind of guarantees, don't use this cache. + - When redis connection isn't available or any unknown exceptions are encountered, this + cache automatically turns itself off and falls back to behaviour that is equivalent to + default Redis cache behaviour. + - Never use `frappe.cache`'s request local cache along with client-side cache. Two + different copies of same key are a big source of data races. + """ + def __init__(self, maxsize: int = 1024, ttl=10 * 60, monitor: RedisWrapper | None = None) -> None: self.maxsize = maxsize or 1024 # Expect 1024 * 4kb objects ~ 4MB self.local_ttl = ttl