feat(web): cache dashboard aggregations with version invalidation

This commit is contained in:
kairee committed 2026-11-01 03:27:49 +08:00
1 parent 91aa3e1f95
commit 9fd277c05f
15 files changed
+399 -30

No files matched your search

+69
View File
@@ -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)