fix(awooop): cache heavy operator summaries
Some checks failed
CD Pipeline / tests (push) Successful in 1m28s
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-01 09:20:18 +08:00
parent 0e30171858
commit 159f514f55
9 changed files with 381 additions and 30 deletions

View File

@@ -0,0 +1,122 @@
"""Short TTL cache for read-only AwoooP operator summaries.
This cache intentionally lives in the API pod memory. It reduces repeated heavy
operator-console reads without becoming a new source of truth.
"""
from __future__ import annotations
import hashlib
import json
import time
from copy import deepcopy
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from typing import Any
_CACHE_SCHEMA_VERSION = "operator_summary_cache_v1"
_CACHE_SOURCE = "api_pod_memory"
@dataclass(slots=True)
class _CacheRecord:
value: dict[str, Any]
stored_at: datetime
stored_monotonic: float
ttl_seconds: int
_CACHE: dict[str, _CacheRecord] = {}
def _cache_key(namespace: str, key_parts: dict[str, Any]) -> str:
payload = json.dumps(
{"namespace": namespace, "key_parts": key_parts},
ensure_ascii=False,
sort_keys=True,
default=str,
)
digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()
return f"{namespace}:{digest}"
def _cache_meta(
*,
status: str,
record: _CacheRecord,
age_seconds: float,
) -> dict[str, Any]:
ttl_seconds = max(1, int(record.ttl_seconds))
expires_at = record.stored_at + timedelta(seconds=ttl_seconds)
return {
"schema_version": _CACHE_SCHEMA_VERSION,
"status": status,
"source": _CACHE_SOURCE,
"ttl_seconds": ttl_seconds,
"age_seconds": round(max(0.0, age_seconds), 3),
"stored_at": record.stored_at.isoformat(),
"expires_at": expires_at.isoformat(),
}
def _with_cache_meta(value: dict[str, Any], meta: dict[str, Any]) -> dict[str, Any]:
response = deepcopy(value)
response["cache"] = meta
return response
def get_cached_operator_summary(
namespace: str,
key_parts: dict[str, Any],
*,
ttl_seconds: int,
now_monotonic: float | None = None,
) -> dict[str, Any] | None:
"""Return cached summary with hit metadata, or None if absent/expired."""
cache_key = _cache_key(namespace, key_parts)
record = _CACHE.get(cache_key)
if record is None:
return None
now_value = time.monotonic() if now_monotonic is None else now_monotonic
ttl_value = max(1, int(ttl_seconds))
age_seconds = now_value - record.stored_monotonic
if age_seconds >= ttl_value:
_CACHE.pop(cache_key, None)
return None
return _with_cache_meta(
record.value,
_cache_meta(status="hit", record=record, age_seconds=age_seconds),
)
def store_operator_summary(
namespace: str,
key_parts: dict[str, Any],
value: dict[str, Any],
*,
ttl_seconds: int,
now_monotonic: float | None = None,
now_utc: datetime | None = None,
) -> dict[str, Any]:
"""Store a fresh summary and return it with miss metadata."""
cache_key = _cache_key(namespace, key_parts)
stored_at = now_utc or datetime.now(UTC)
record = _CacheRecord(
value=deepcopy(value),
stored_at=stored_at,
stored_monotonic=time.monotonic() if now_monotonic is None else now_monotonic,
ttl_seconds=max(1, int(ttl_seconds)),
)
_CACHE[cache_key] = record
return _with_cache_meta(
record.value,
_cache_meta(status="miss", record=record, age_seconds=0.0),
)
def clear_operator_summary_cache() -> None:
"""Clear process-local cache for tests and controlled operator refreshes."""
_CACHE.clear()