perf: reduce webapp and admin cache stampedes
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Awaitable, Callable, Optional
|
||||
|
||||
from bot.infra.redis import cache_delete, redis_key
|
||||
from bot.utils.ttl_cache import AsyncTTLCache
|
||||
from config.settings import Settings
|
||||
|
||||
_WEBAPP_USER_PAYLOAD_CACHES: dict[tuple[int, str, int], AsyncTTLCache] = {}
|
||||
|
||||
|
||||
def _webapp_user_payload_cache(
|
||||
settings: Settings,
|
||||
namespace: str,
|
||||
ttl_seconds: int,
|
||||
) -> Optional[AsyncTTLCache]:
|
||||
ttl = max(0, int(ttl_seconds or 0))
|
||||
if ttl <= 0:
|
||||
return None
|
||||
cache_key = (id(settings), namespace, ttl)
|
||||
cache = _WEBAPP_USER_PAYLOAD_CACHES.get(cache_key)
|
||||
if cache is None:
|
||||
cache = AsyncTTLCache(
|
||||
ttl_seconds=ttl,
|
||||
settings=settings,
|
||||
namespace=f"webapp:{namespace}",
|
||||
)
|
||||
_WEBAPP_USER_PAYLOAD_CACHES[cache_key] = cache
|
||||
return cache
|
||||
|
||||
|
||||
async def webapp_cached_user_payload(
|
||||
settings: Settings,
|
||||
namespace: str,
|
||||
user_id: int,
|
||||
ttl_seconds: int,
|
||||
loader: Callable[[], Awaitable[Any]],
|
||||
) -> Any:
|
||||
cache = _webapp_user_payload_cache(settings, namespace, ttl_seconds)
|
||||
if cache is None:
|
||||
return await loader()
|
||||
return await cache.get_or_load(str(int(user_id)), loader)
|
||||
|
||||
|
||||
def invalidate_local_webapp_user_payload(
|
||||
settings: Settings,
|
||||
namespace: str,
|
||||
user_id: int,
|
||||
) -> None:
|
||||
key = str(int(user_id))
|
||||
for (settings_id, cache_namespace, _ttl), cache in tuple(
|
||||
_WEBAPP_USER_PAYLOAD_CACHES.items()
|
||||
):
|
||||
if settings_id == id(settings) and cache_namespace == namespace:
|
||||
cache.invalidate(key)
|
||||
|
||||
|
||||
async def invalidate_webapp_user_caches(
|
||||
settings: Settings,
|
||||
*user_ids: Optional[int],
|
||||
include_devices: bool = False,
|
||||
) -> None:
|
||||
keys: list[str] = []
|
||||
seen: set[int] = set()
|
||||
for raw_user_id in user_ids:
|
||||
if raw_user_id is None:
|
||||
continue
|
||||
try:
|
||||
user_id = int(raw_user_id)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if user_id in seen:
|
||||
continue
|
||||
seen.add(user_id)
|
||||
keys.append(redis_key(settings, "cache", "webapp", "me", user_id))
|
||||
invalidate_local_webapp_user_payload(settings, "me", user_id)
|
||||
if include_devices:
|
||||
keys.append(redis_key(settings, "cache", "webapp", "devices", user_id))
|
||||
invalidate_local_webapp_user_payload(settings, "devices", user_id)
|
||||
if keys:
|
||||
await cache_delete(settings, *keys)
|
||||
Reference in New Issue
Block a user