""" Redis Keys: - marine:catalog:full → lista dei dataset completo (TTL 1h) - marine:catalog:search:{hash} → risultati ricerca (TTL 30min) - marine:job:{session_id} → stato job download (TTL 48h) """ import json import os import logging from typing import Any, Optional import redis logger = logging.getLogger(__name__) # Configurazione Redis da variabili ambiente REDIS_HOST = os.getenv("REDIS_HOST", "meb-redis") REDIS_PORT = int(os.getenv("REDIS_PORT", "6379")) # Pool di connessioni condiviso (thread-safe, riutilizzabile) _pool: Optional[redis.ConnectionPool] = None _client: Optional[redis.Redis] = None def _get_client() -> Optional[redis.Redis]: """Restituisce il client Redis singleton con connection pool. Ritorna None se Redis non è raggiungibile.""" global _pool, _client if _client is not None: return _client try: _pool = redis.ConnectionPool( host=REDIS_HOST, port=REDIS_PORT, # Decodifica automatica delle risposte in stringhe UTF-8 decode_responses=True, # Massimo 5 connessioni nel pool (VPS 1-core, non serve di più) max_connections=5, # Timeout connessione e socket per evitare blocchi socket_connect_timeout=3, socket_timeout=3, # Riprova automaticamente se la connessione viene interrotta retry_on_timeout=True, ) _client = redis.Redis(connection_pool=_pool) # Test connessione _client.ping() logger.info("[Redis] Connessione stabilita per il servizio Marine") return _client except Exception as e: logger.warning(f"[Redis] Non disponibile, la cache è disabilitata: {e}") _client = None return None def cache_get(key: str) -> Optional[Any]: """Legge un valore dalla cache Redis. Args: key: Chiave Redis (es. 'marine:catalog:full') Returns: Il valore deserializzato da JSON, oppure None se non trovato o errore """ try: client = _get_client() if client is None: return None data = client.get(key) if data is None: return None return json.loads(data) except Exception as e: logger.warning(f"[Redis] Errore lettura chiave '{key}': {e}") return None def cache_set(key: str, value: Any, ttl: int = 3600) -> bool: """Scrive un valore nella cache Redis con TTL. Args: key: Chiave Redis value: Valore da serializzare in JSON ttl: Tempo di vita in secondi (default: 1 ora) Returns: True se scritto con successo, False altrimenti """ try: client = _get_client() if client is None: return False serialized = json.dumps(value) client.setex(key, ttl, serialized) return True except Exception as e: logger.warning(f"[Redis] Errore scrittura chiave '{key}': {e}") return False def cache_delete(key: str) -> bool: """Elimina una chiave dalla cache Redis. Args: key: Chiave Redis da eliminare Returns: True se eliminata, False altrimenti """ try: client = _get_client() if client is None: return False client.delete(key) return True except Exception as e: logger.warning(f"[Redis] Errore eliminazione chiave '{key}': {e}") return False