face-intel / storage /cache.py
Marwan
Restructure + add reverse face search (PimEyes-style)
f5eeb1c
Raw
History Blame Contribute Delete
2.72 kB
"""
In-memory TTL cache.
Keys are strings (typically image hashes + provider name + capability).
Values are arbitrary JSON-serializable objects.
The cache tracks hit/miss counters which the metrics subsystem reads.
"""
from __future__ import annotations
import threading
import time
from collections import OrderedDict
from typing import Any, Optional
class Cache:
"""LRU + TTL cache. Thread-safe."""
def __init__(self, ttl_seconds: int = 3600, max_entries: int = 1000) -> None:
self._ttl = ttl_seconds
self._max = max_entries
self._data: OrderedDict[str, tuple[float, Any]] = OrderedDict()
self._lock = threading.RLock()
self._hits = 0
self._misses = 0
self._evictions = 0
# ------------------------------------------------------------------ #
# Core ops
# ------------------------------------------------------------------ #
def get(self, key: str) -> Optional[Any]:
with self._lock:
entry = self._data.get(key)
if entry is None:
self._misses += 1
return None
expires_at, value = entry
if time.time() > expires_at:
self._data.pop(key, None)
self._misses += 1
return None
self._data.move_to_end(key)
self._hits += 1
return value
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
with self._lock:
expires_at = time.time() + (ttl if ttl is not None else self._ttl)
self._data[key] = (expires_at, value)
self._data.move_to_end(key)
while len(self._data) > self._max:
self._data.popitem(last=False)
self._evictions += 1
def invalidate(self, key: str) -> bool:
with self._lock:
return self._data.pop(key, None) is not None
def clear(self) -> int:
with self._lock:
n = len(self._data)
self._data.clear()
return n
# ------------------------------------------------------------------ #
# Stats (consumed by metrics + /cache endpoint)
# ------------------------------------------------------------------ #
def stats(self) -> dict:
with self._lock:
total = self._hits + self._misses
return {
"entries": len(self._data),
"max_entries": self._max,
"ttl_seconds": self._ttl,
"hits": self._hits,
"misses": self._misses,
"hit_ratio": round(self._hits / total, 4) if total else 0.0,
"evictions": self._evictions,
}