ifixkart-backend/app/core/database/cache_manager.py

121 lines
4.1 KiB
Python

import time
import logging
import threading
from typing import Any, Dict, Optional
logger = logging.getLogger("ifixkart.cache")
class MemoryCache:
"""Thread-safe in-process memory cache with TTL (Time To Live)."""
def __init__(self):
self._cache: Dict[str, Dict[str, Any]] = {}
self._lock = threading.Lock()
def get(self, key: str) -> Optional[Any]:
with self._lock:
item = self._cache.get(key)
if not item:
return None
if item["expire_at"] is not None and time.time() > item["expire_at"]:
del self._cache[key]
return None
return item["value"]
def set(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
expire_at = (time.time() + ttl_seconds) if ttl_seconds is not None else None
with self._lock:
self._cache[key] = {
"value": value,
"expire_at": expire_at
}
def delete(self, key: str) -> None:
with self._lock:
if key in self._cache:
del self._cache[key]
def invalidate_prefix(self, prefix: str) -> None:
with self._lock:
keys_to_del = [k for k in self._cache.keys() if k.startswith(prefix)]
for k in keys_to_del:
del self._cache[k]
def clear(self) -> None:
with self._lock:
self._cache.clear()
class CacheManager:
"""Consolidated cache interface with Redis connection fallback."""
def __init__(self):
self.memory_cache = MemoryCache()
self.redis_client = None
self._try_init_redis()
def _try_init_redis(self):
try:
import redis
import os
redis_host = os.getenv("REDIS_HOST", "127.0.0.1")
redis_port = int(os.getenv("REDIS_PORT", 6379))
self.redis_client = redis.Redis(
host=redis_host,
port=redis_port,
db=0,
socket_connect_timeout=1,
decode_responses=False
)
self.redis_client.ping()
logger.info("CacheManager: Redis cache connected successfully.")
except Exception:
self.redis_client = None
logger.warning("CacheManager: Redis is offline or not installed. Falling back to In-Memory cache.")
def get(self, key: str) -> Optional[Any]:
if self.redis_client:
try:
import json
data = self.redis_client.get(key)
if data:
return json.loads(data.decode("utf-8"))
except Exception as e:
logger.error(f"Redis get failed: {e}")
return self.memory_cache.get(key)
def set(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None:
if self.redis_client:
try:
import json
serialized = json.dumps(value)
self.redis_client.set(key, serialized, ex=ttl_seconds)
return
except Exception as e:
logger.error(f"Redis set failed: {e}")
self.memory_cache.set(key, value, ttl_seconds)
def delete(self, key: str) -> None:
if self.redis_client:
try:
self.redis_client.delete(key)
except Exception as e:
logger.error(f"Redis delete failed: {e}")
self.memory_cache.delete(key)
def invalidate_prefix(self, prefix: str) -> None:
if self.redis_client:
try:
keys = self.redis_client.keys(f"{prefix}*")
if keys:
self.redis_client.delete(*keys)
except Exception as e:
logger.error(f"Redis prefix invalidation failed: {e}")
self.memory_cache.invalidate_prefix(prefix)
def clear(self) -> None:
if self.redis_client:
try:
self.redis_client.flushdb()
except Exception as e:
logger.error(f"Redis flushdb failed: {e}")
self.memory_cache.clear()
cache = CacheManager()