Replace in-memory TTL cache with Redis
This commit is contained in:
@@ -1,29 +1,62 @@
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import redis.asyncio as redis
|
||||
|
||||
class TTLCache:
|
||||
"""Simple in-memory TTL cache."""
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def __init__(self, ttl_seconds: int = 60):
|
||||
self._ttl = ttl_seconds
|
||||
self._store: dict[str, tuple[float, Any]] = {}
|
||||
_redis: redis.Redis | None = None
|
||||
|
||||
def get(self, key: str) -> Any | None:
|
||||
entry = self._store.get(key)
|
||||
if entry is None:
|
||||
|
||||
async def init_cache() -> None:
|
||||
"""Create Redis connection from environment variables."""
|
||||
global _redis
|
||||
host = os.environ.get("REDIS_HOST", "localhost")
|
||||
port = int(os.environ.get("REDIS_PORT", "6379"))
|
||||
db = int(os.environ.get("REDIS_DB", "0"))
|
||||
try:
|
||||
_redis = redis.Redis(host=host, port=port, db=db, decode_responses=True)
|
||||
await _redis.ping()
|
||||
logger.info("Redis connected at %s:%d/%d", host, port, db)
|
||||
except Exception as e:
|
||||
logger.warning("Redis connection failed: %s — running without cache", e)
|
||||
_redis = None
|
||||
|
||||
|
||||
async def close_cache() -> None:
|
||||
"""Close Redis connection."""
|
||||
global _redis
|
||||
if _redis is not None:
|
||||
await _redis.aclose()
|
||||
_redis = None
|
||||
|
||||
|
||||
def redis_available() -> bool:
|
||||
"""Return whether Redis connection is live."""
|
||||
return _redis is not None
|
||||
|
||||
|
||||
async def cache_get(key: str) -> Any | None:
|
||||
"""GET key from Redis, return deserialized value or None on miss/error."""
|
||||
if _redis is None:
|
||||
return None
|
||||
try:
|
||||
raw = await _redis.get(key)
|
||||
if raw is None:
|
||||
return None
|
||||
ts, value = entry
|
||||
if time.monotonic() - ts > self._ttl:
|
||||
del self._store[key]
|
||||
return None
|
||||
return value
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
self._store[key] = (time.monotonic(), value)
|
||||
|
||||
def clear(self) -> None:
|
||||
self._store.clear()
|
||||
return json.loads(raw)
|
||||
except Exception as e:
|
||||
logger.warning("Redis GET %s failed: %s", key, e)
|
||||
return None
|
||||
|
||||
|
||||
smart_cache = TTLCache(ttl_seconds=60)
|
||||
async def cache_set(key: str, value: Any, ttl: int = 120) -> None:
|
||||
"""SET key in Redis with expiry, silently catches errors."""
|
||||
if _redis is None:
|
||||
return
|
||||
try:
|
||||
await _redis.set(key, json.dumps(value), ex=ttl)
|
||||
except Exception as e:
|
||||
logger.warning("Redis SET %s failed: %s", key, e)
|
||||
|
||||
Reference in New Issue
Block a user