feat(web): cache dashboard aggregations with version invalidation
This commit is contained in:
1 parent
91aa3e1f95
commit
9fd277c05f
15 files changed
+399
-30
No files matched your search
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""看板聚合结果的内存 TTL 缓存。
|
||||
|
||||
缓存键包含数据库路径和数据版本;版本存放在 ``kv``(``cache:v:<group>``),
|
||||
因此同一个进程内的调度任务和 Web 线程都会看到一致的失效,跨进程的 CLI
|
||||
导入也可以通过提升版本让旧缓存失效。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Callable, Hashable
|
||||
|
||||
TTL_VIDEOS = 3600
|
||||
TTL_FANS = 86400
|
||||
|
||||
|
||||
class TTLCache:
|
||||
def __init__(self, *, clock: Callable[[], float] = time.time, max_items: int = 256) -> None:
|
||||
self._clock = clock
|
||||
self._max_items = max(16, int(max_items))
|
||||
self._lock = threading.RLock()
|
||||
self._items: dict[Hashable, tuple[float, Any]] = {}
|
||||
|
||||
def get(self, key: Hashable) -> Any | None:
|
||||
with self._lock:
|
||||
entry = self._items.get(key)
|
||||
if entry is None:
|
||||
return None
|
||||
expires_at, value = entry
|
||||
if expires_at <= self._clock():
|
||||
self._items.pop(key, None)
|
||||
return None
|
||||
return value
|
||||
|
||||
def set(self, key: Hashable, ttl: float, value: Any) -> None:
|
||||
with self._lock:
|
||||
now = self._clock()
|
||||
self._items[key] = (now + float(ttl), value)
|
||||
if len(self._items) > self._max_items:
|
||||
expired = [k for k, (exp, _) in self._items.items() if exp <= now]
|
||||
for item in expired:
|
||||
self._items.pop(item, None)
|
||||
if len(self._items) > self._max_items:
|
||||
oldest = sorted(self._items.items(), key=lambda kv: kv[1][0])
|
||||
for item, _ in oldest[: len(self._items) - self._max_items]:
|
||||
self._items.pop(item, None)
|
||||
|
||||
def clear(self) -> None:
|
||||
with self._lock:
|
||||
self._items.clear()
|
||||
|
||||
def __len__(self) -> int:
|
||||
with self._lock:
|
||||
return len(self._items)
|
||||
|
||||
|
||||
DASHBOARD_CACHE = TTLCache()
|
||||
|
||||
|
||||
def build_cache_key(
|
||||
scope: str,
|
||||
group: str,
|
||||
version: int,
|
||||
params: dict[str, Any] | None = None,
|
||||
) -> tuple[Any, ...]:
|
||||
frozen = tuple(sorted((str(key), str(value)) for key, value in (params or {}).items()))
|
||||
return (scope, group, int(version), frozen)
|
||||
Reference in new issue
Block a user