perf!: speedup @site_cache by ~4x (#28802)
* perf(site_cache): reduce access to frappe.local namespace This change also allows calling @site_cache during init, as long as `site` parameter is set. * test: frappe.init patching * perf: use monotonic time instead of realtime for eviction datetime is complex, slow and not really required for this use case. * perf!: Drop support for unhashable arguments Just like LRU cache, no need to support unhashable types in site_cache. Current usage in codebase also shows that it's not required and json.dumps is quite slow.
This commit is contained in:
commit
98a33b4516
2 changed files with 22 additions and 18 deletions
|
|
@ -204,6 +204,16 @@ class TestPerformance(IntegrationTestCase):
|
|||
with self.assertRedisCallCounts(1):
|
||||
frappe.get_doc("User", "Administrator")
|
||||
|
||||
def test_one_time_setup(self):
|
||||
site = frappe.local.site
|
||||
frappe.init(site, force=True)
|
||||
run = frappe.qb._BuilderClasss.run
|
||||
|
||||
frappe.init(site, force=True)
|
||||
patched_run = frappe.qb._BuilderClasss.run
|
||||
|
||||
self.assertIs(run, patched_run, "frappe.init should run one-time patching code just once")
|
||||
|
||||
|
||||
@run_only_if(db_type_is.MARIADB)
|
||||
class TestOverheadCalls(FrappeAPITestCase):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. Check LICENSE
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
|
|
@ -113,33 +112,28 @@ def site_cache(ttl: int | None = None, maxsize: int | None = None) -> Callable:
|
|||
|
||||
if ttl is not None and not callable(ttl):
|
||||
func.ttl = ttl
|
||||
func.expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
||||
seconds=func.ttl
|
||||
)
|
||||
func.expiration = time.monotonic() + func.ttl
|
||||
|
||||
if maxsize is not None and not callable(maxsize):
|
||||
func.maxsize = maxsize
|
||||
|
||||
@wraps(func)
|
||||
def site_cache_wrapper(*args, **kwargs):
|
||||
if getattr(frappe.local, "initialised", None):
|
||||
func_call_key = json.dumps((args, kwargs))
|
||||
if site := getattr(frappe.local, "site", None):
|
||||
func_call_key = __generate_request_cache_key(args, kwargs)
|
||||
|
||||
if hasattr(func, "ttl") and datetime.datetime.now(datetime.timezone.utc) >= func.expiration:
|
||||
if hasattr(func, "ttl") and time.monotonic() >= func.expiration:
|
||||
func.clear_cache()
|
||||
func.expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
||||
seconds=func.ttl
|
||||
)
|
||||
func.expiration = time.monotonic() + func.ttl
|
||||
|
||||
if hasattr(func, "maxsize") and len(_SITE_CACHE[func_key][frappe.local.site]) >= func.maxsize:
|
||||
_SITE_CACHE[func_key][frappe.local.site].pop(
|
||||
next(iter(_SITE_CACHE[func_key][frappe.local.site])), None
|
||||
)
|
||||
if hasattr(func, "maxsize") and len(_SITE_CACHE[func_key][site]) >= func.maxsize:
|
||||
# Note: This implements FIFO eviction policty
|
||||
_SITE_CACHE[func_key][site].pop(next(iter(_SITE_CACHE[func_key][site])), None)
|
||||
|
||||
if func_call_key not in _SITE_CACHE[func_key][frappe.local.site]:
|
||||
_SITE_CACHE[func_key][frappe.local.site][func_call_key] = func(*args, **kwargs)
|
||||
if func_call_key not in _SITE_CACHE[func_key][site]:
|
||||
_SITE_CACHE[func_key][site][func_call_key] = func(*args, **kwargs)
|
||||
|
||||
return _SITE_CACHE[func_key][frappe.local.site][func_call_key]
|
||||
return _SITE_CACHE[func_key][site][func_call_key]
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue