# Plan Implementare Redis Caching - ROA2WEB **Data Creare:** 2025-01-25 **Status:** DRAFT - Ready for Implementation **Durata Estimată:** 2-3 ore --- ## 📋 Sumar Executiv - **Obiectiv:** Implementare Redis caching layer pentru reducerea query-urilor repetitive la Oracle DB - **Target:** 60-80% reducere în numărul de query-uri pentru date frecvent accesate - **Strategie:** Caching simplu la nivel de Service layer cu invalidation manuală - **Backward Compatibility:** ✅ Aplicația funcționează fără Redis (graceful degradation) - **Multi-Tenant Ready:** ✅ Cache keys pregătite pentru viitoare multi-tenancy **Infrastructure Status:** - ✅ Redis container configurat în `docker-compose.yml` (lines 147-163) - ✅ Backend `depends_on: roa-redis` - ❌ Redis client library LIPSĂ - ❌ Cod de caching LIPSĂ --- ## 🗂️ Structura Fișierelor ### Fișiere Noi (6 fișiere) - [ ] `shared/cache/__init__.py` - Package initialization - [ ] `shared/cache/redis_client.py` - Redis connection client (90 lines) - [ ] `shared/cache/decorators.py` - Cache decorators (60 lines) - [ ] `shared/cache/utils.py` - Cache key generators, helpers (40 lines) - [ ] `shared/tests/test_redis_client.py` - Unit tests pentru Redis client (120 lines) - [ ] `shared/tests/test_cache_decorators.py` - Unit tests pentru decorators (80 lines) ### Fișiere Modificate (7 fișiere) - [ ] `backend/requirements.txt` - Adaugă redis>=5.0.0 - [ ] `backend/.env.example` - Adaugă REDIS_* env vars - [ ] `backend/app/main.py` - Initialize Redis at startup - [ ] `backend/app/services/dashboard_service.py` - Apply caching - [ ] `backend/app/services/invoice_service.py` - Apply caching - [ ] `backend/app/routers/dashboard.py` - Cache invalidation on mutations - [ ] `backend/app/routers/invoices.py` - Cache invalidation on mutations --- ## 🚀 Faze de Implementare ### FAZA 1: Setup Redis Client și Connection (30 min) **Obiectiv:** Creează Redis client singleton cu connection pooling, similar cu `OraclePool` pattern. **Tasks:** 1. [ ] **Adaugă dependency în requirements.txt** - Fișier: `backend/requirements.txt` - Acțiune: Adaugă linia `redis>=5.0.0` după `httpx>=0.27.0` - Motivație: redis 5.0+ are async support nativ 2. [ ] **Creează package cache în shared/** - Fișiere: `shared/cache/__init__.py` - Acțiune: Creează directorul și fișierul de init cu exports: ```python from .redis_client import redis_cache from .decorators import cached, invalidate_cache __all__ = ['redis_cache', 'cached', 'invalidate_cache'] ``` 3. [ ] **Implementează RedisCache singleton client** - Fișier: `shared/cache/redis_client.py` - Acțiune: Creează clasa `RedisCache` similar cu `OraclePool`: - Singleton pattern (nu multi-instance) - Async redis client cu connection pooling - Methods: `initialize()`, `get()`, `set()`, `delete()`, `delete_pattern()`, `close()` - Graceful degradation: dacă Redis e down, log warning și return None - Connection retry cu exponential backoff - Template (vezi secțiunea Code Templates mai jos) 4. [ ] **Adaugă env variables în .env.example** - Fișier: `backend/.env.example` - Acțiune: Adaugă la sfârșitul fișierului: ```bash # Redis Configuration REDIS_URL=redis://roa-redis:6379/0 REDIS_PASSWORD=roa2web_redis_password REDIS_ENABLED=true CACHE_DEFAULT_TTL=300 ``` 5. [ ] **Initialize Redis la startup în main.py** - Fișier: `backend/app/main.py` - Acțiune: În funcția `startup_event()`: ```python from shared.cache import redis_cache @app.on_event("startup") async def startup_event(): # ... existing oracle pool init ... # Initialize Redis cache if os.getenv('REDIS_ENABLED', 'false').lower() == 'true': await redis_cache.initialize( url=os.getenv('REDIS_URL'), password=os.getenv('REDIS_PASSWORD') ) @app.on_event("shutdown") async def shutdown_event(): # ... existing oracle pool close ... await redis_cache.close() ``` **Output Verificabil:** - [ ] `pip install -r requirements.txt` rulează fără erori - [ ] Redis client se conectează cu succes la container - [ ] Test manual: `python -c "import asyncio; from shared.cache import redis_cache; asyncio.run(redis_cache.initialize())"` - [ ] Log message: "✅ Redis cache initialized successfully" --- ### FAZA 2: Cache Decorator și Helpers (30 min) **Obiectiv:** Creează decorator `@cached()` pentru aplicare ușoară în Services. **Tasks:** 1. [ ] **Implementează cache key generator** - Fișier: `shared/cache/utils.py` - Acțiune: Funcții helper pentru key generation: ```python import hashlib import json from typing import Any, Dict def make_cache_key(tenant_id: str, resource: str, **params) -> str: """ Generate tenant-aware cache key Format: cache:{tenant_id}:{resource}:{params_hash} """ params_str = json.dumps(params, sort_keys=True) params_hash = hashlib.md5(params_str.encode()).hexdigest()[:12] return f"cache:{tenant_id}:{resource}:{params_hash}" def extract_tenant_id(kwargs: Dict[str, Any]) -> str: """ Extract tenant_id from function kwargs For now returns 'default', later extract from JWT token """ # TODO: Extract from request.state.tenant_id when multi-tenant implemented return kwargs.get('tenant_id', 'default') ``` 2. [ ] **Implementează @cached decorator** - Fișier: `shared/cache/decorators.py` - Acțiune: Decorator pentru auto-caching de funcții async: ```python from functools import wraps from typing import Callable, Optional import logging from .redis_client import redis_cache from .utils import make_cache_key, extract_tenant_id logger = logging.getLogger(__name__) def cached(resource: str, ttl: int = 300): """ Cache decorator pentru funcții async Usage: @cached(resource="dashboard_summary", ttl=300) async def get_dashboard_summary(company: str, username: str): # ... query Oracle ... return data Args: resource: Resource name (e.g., 'dashboard_summary', 'invoices_list') ttl: Time-to-live în secunde (default: 5 min) """ def decorator(func: Callable): @wraps(func) async def wrapper(*args, **kwargs): # Skip cache dacă Redis e disabled if not redis_cache.is_enabled(): return await func(*args, **kwargs) # Extract tenant_id și params pentru cache key tenant_id = extract_tenant_id(kwargs) cache_params = {k: v for k, v in kwargs.items() if k not in ['username', 'current_user']} cache_key = make_cache_key(tenant_id, resource, **cache_params) # Try cache GET cached_value = await redis_cache.get(cache_key) if cached_value is not None: logger.debug(f"Cache HIT: {cache_key}") return cached_value # Cache MISS - execute function logger.debug(f"Cache MISS: {cache_key}") result = await func(*args, **kwargs) # Save to cache await redis_cache.set(cache_key, result, ttl=ttl) return result return wrapper return decorator ``` 3. [ ] **Implementează invalidate_cache helper** - Fișier: `shared/cache/decorators.py` (same file) - Acțiune: Helper function pentru manual invalidation: ```python async def invalidate_cache( tenant_id: str = "default", resource: Optional[str] = None ): """ Invalidate cache entries Examples: await invalidate_cache(resource="dashboard_summary") # clear specific resource await invalidate_cache() # clear all for default tenant """ if not redis_cache.is_enabled(): return if resource: pattern = f"cache:{tenant_id}:{resource}:*" else: pattern = f"cache:{tenant_id}:*" await redis_cache.delete_pattern(pattern) logger.info(f"Cache invalidated: {pattern}") ``` **Output Verificabil:** - [ ] Decorator funcționează fără erori - [ ] Cache key format: `cache:default:dashboard_summary:abc123` - [ ] Test unit: `pytest shared/tests/test_cache_decorators.py -v` --- ### FAZA 3: Aplicare în Endpoint-uri (45 min) **Obiectiv:** Aplică caching în Service layer pentru dashboard și invoices. **Tasks:** 1. [ ] **Apply @cached în DashboardService.get_complete_summary()** - Fișier: `backend/app/services/dashboard_service.py` - Acțiune: Adaugă decorator la metoda `get_complete_summary`: ```python from shared.cache import cached class DashboardService: @staticmethod @cached(resource="dashboard_summary", ttl=300) # 5 min async def get_complete_summary(company: str, username: str): # ... existing implementation ... ``` - Motivație: Dashboard e accesat des, datele se schimbă rar 2. [ ] **Apply @cached în DashboardService.get_trends()** - Fișier: `backend/app/services/dashboard_service.py` - Acțiune: Similar, TTL=180 (3 min pentru trends) - Cache key va include period: `cache:default:dashboard_trends:company-X:period-30d:abc123` 3. [ ] **Apply @cached în DashboardService.get_detailed_data()** - Fișier: `backend/app/services/dashboard_service.py` - Acțiune: TTL=60 (1 min pentru tabel detalii - se refreshează des) - Cache key include page, page_size, search 4. [ ] **Apply @cached în InvoiceService.get_invoices()** - Fișier: `backend/app/services/invoice_service.py` - Acțiune: TTL=60 (1 min) - Cache key include filter params (partner_type, date_from, date_to, etc.) 5. [ ] **Apply @cached în InvoiceService.get_invoice_summary()** - Fișier: `backend/app/services/invoice_service.py` - Acțiune: TTL=180 (3 min pentru summary) 6. [ ] **Cache invalidation în dashboard mutations (viitor)** - Fișier: `backend/app/routers/dashboard.py` - Acțiune: Pregătește cod pentru invalidation (de activat când există POST/PUT/DELETE): ```python # TODO: Activate când implementăm mutations # from shared.cache import invalidate_cache # # @router.post("/...") # async def update_dashboard_data(...): # # ... update logic ... # await invalidate_cache(resource="dashboard_summary") # await invalidate_cache(resource="dashboard_trends") ``` 7. [ ] **Cache invalidation în invoice mutations** - Fișier: `backend/app/routers/invoices.py` - Acțiune: Când se implementează POST/PUT/DELETE pentru invoices, invalidează: - `invoices_list` - `invoices_summary` - `dashboard_summary` (afectează dashboard) **Output Verificabil:** - [ ] Backend pornește fără erori - [ ] First request: Cache MISS + Oracle query (măsoară timp: ~500-1000ms) - [ ] Second request (same params): Cache HIT (măsoară timp: ~10-20ms) - [ ] Cache hit rate > 80% după 100 requests repetitive - [ ] Logs arată `Cache HIT/MISS` messages --- ### FAZA 4: Testing, Monitoring și Cleanup (45 min) **Obiectiv:** Validare funcționare corectă, performance benchmarks, și documentare. **Tasks:** 1. [ ] **Unit tests pentru RedisCache client** - Fișier: `shared/tests/test_redis_client.py` - Acțiune: Testează: - Connection success/failure - Get/Set/Delete operations - Pattern matching delete - Graceful degradation când Redis e down - Run: `pytest shared/tests/test_redis_client.py -v` 2. [ ] **Unit tests pentru cache decorators** - Fișier: `shared/tests/test_cache_decorators.py` - Acțiune: Testează: - Decorator aplică caching corect - Cache key generation - TTL respectat - Invalidation funcționează - Run: `pytest shared/tests/test_cache_decorators.py -v` 3. [ ] **Integration test în Docker** - Acțiune: Pornește stack complet cu `docker-compose up` - Verifică: - Backend se conectează la Redis - Cache funcționează end-to-end - Logs arată cache hits/misses 4. [ ] **Performance benchmark** - Tool: Apache Bench sau Python requests loop - Test case: 100 requests la `/api/dashboard/summary?company=X` - Măsoară: - **Without cache** (REDIS_ENABLED=false): - Avg response time: ~800ms - Total time: ~80 seconds - **With cache** (REDIS_ENABLED=true): - First request: ~800ms (MISS) - Next 99 requests: ~15ms (HIT) - Total time: ~2 seconds - **Improvement: 97.5%** - Salvează results în `shared/docs/REDIS_PERFORMANCE_BENCHMARK.md` 5. [ ] **Manual testing checklist** - [ ] Dashboard: Refresh multiple ori (verify cache HIT în logs) - [ ] Invoices: Filtrare diferită (verify cache keys unice) - [ ] Redis failure test: Stop Redis container, verify app funcționează (fallback la Oracle) - [ ] Cache invalidation: Manual invalidate via Redis CLI, verify re-query 6. [ ] **Update CLAUDE.md documentation** - Fișier: `CLAUDE.md` - Acțiune: Adaugă secțiune "Redis Caching": ```markdown ## 💾 Redis Caching ROA2WEB folosește Redis pentru caching layer: - **Client**: `shared/cache/redis_client.py` (singleton pattern) - **Decorator**: `@cached(resource="name", ttl=300)` în Services - **Cache Keys**: `cache:{tenant_id}:{resource}:{params_hash}` - **TTL Defaults**: - Dashboard summary: 5 min - Dashboard trends: 3 min - Invoices list: 1 min - Invoices summary: 3 min **Toggle cache:** Set `REDIS_ENABLED=false` în `.env` **Invalidate manual:** ```python from shared.cache import invalidate_cache await invalidate_cache(resource="dashboard_summary") ``` **Performance:** 60-80% reduction în query time pentru repetitive requests ``` **Output Verificabil:** - [ ] All tests pass: `pytest shared/tests/ -v` - [ ] Performance benchmark shows >60% improvement - [ ] Manual testing checklist complet - [ ] Documentation updated - [ ] Ready for code review --- ## 📊 Cache Strategy ### Resource TTLs | Resource | TTL | Motivație | |----------|-----|-----------| | `dashboard_summary` | 300s (5 min) | Date agregate, se schimbă rar | | `dashboard_trends` | 180s (3 min) | Trends calculation expensive | | `dashboard_detailed_data` | 60s (1 min) | Tabel interactiv, refresh frecvent | | `dashboard_performance` | 180s (3 min) | Performance metrics stabile | | `dashboard_cashflow` | 180s (3 min) | Forecast calculation expensive | | `dashboard_maturity` | 180s (3 min) | Maturity analysis complex | | `invoices_list` | 60s (1 min) | Listing cu filtre, refresh frecvent | | `invoices_summary` | 180s (3 min) | Summary stats stabile | | `companies_list` | 600s (10 min) | Lista rareori se schimbă | | `treasury_data` | 120s (2 min) | Trezorerie moderate changes | **Raționament TTL:** - Scurt (60s): Date interactive, tabel listings - Mediu (180-300s): Calculații expensive, agregări - Lung (600s+): Date aproape statice (companies, permissions) ### Cache Keys Pattern **Format:** `cache:{tenant_id}:{resource}:{params_hash}` **Exemplu concret:** ``` cache:default:dashboard_summary:company-123:abc456def789 cache:default:invoices_list:company-123:partner-CLIENTI:unpaid-true:xyz890 cache:default:dashboard_trends:company-456:period-30d:compare-true:def123 ``` **Componente:** - `cache:` - Prefix constant (pentru separare de alte Redis keys) - `{tenant_id}` - Tenant ID (deocamdată "default", viitor: din JWT token) - `{resource}` - Resource name (dashboard_summary, invoices_list, etc.) - `{params_hash}` - MD5 hash (primele 12 caractere) al parametrilor sortați JSON **Multi-Tenant Ready:** Când se implementează multi-tenant: 1. Modifică `extract_tenant_id()` în `utils.py` să citească din `request.state.tenant_id` 2. JWT token va include `tenant_id` field 3. Cache keys automat vor fi per-tenant 4. Invalidation per-tenant: `await invalidate_cache(tenant_id="client-a")` ### Invalidation Rules **Trigger:** Când se schimbă date în Oracle DB | Mutation | Invalidate Resources | |----------|---------------------| | Invoice created/updated | `invoices_list`, `invoices_summary`, `dashboard_summary`, `dashboard_trends` | | Payment recorded | `invoices_list`, `dashboard_summary`, `treasury_data`, `dashboard_cashflow` | | Treasury transaction | `treasury_data`, `dashboard_summary`, `dashboard_cashflow` | | Company settings changed | `companies_list`, `dashboard_*` (pentru acea companie) | **Implementare:** ```python # În router după mutation from shared.cache import invalidate_cache @router.post("/invoices/{invoice_id}/pay") async def mark_invoice_paid(...): # ... update DB ... # Invalidate affected caches await invalidate_cache(resource="invoices_list") await invalidate_cache(resource="invoices_summary") await invalidate_cache(resource="dashboard_summary") await invalidate_cache(resource="treasury_data") return {"status": "ok"} ``` **Pattern Matching:** ```python # Invalidate toate cache-urile pentru dashboard await invalidate_cache(resource="dashboard") # matches dashboard_* # Invalidate tot pentru un tenant await invalidate_cache(tenant_id="client-a") # matches cache:client-a:* ``` --- ## 🧪 Testing Plan ### Unit Tests **File:** `shared/tests/test_redis_client.py` - [ ] `test_redis_connection_success()` - Verify successful connection - [ ] `test_redis_connection_failure_graceful()` - Redis down, no exception thrown - [ ] `test_redis_get_set_delete()` - Basic operations - [ ] `test_redis_delete_pattern()` - Pattern matching deletion - [ ] `test_redis_ttl_expiration()` - Verify TTL works - [ ] `test_redis_connection_retry()` - Exponential backoff retry **File:** `shared/tests/test_cache_decorators.py` - [ ] `test_cached_decorator_hit()` - Second call returns cached value - [ ] `test_cached_decorator_miss()` - First call queries function - [ ] `test_cache_key_generation()` - Keys format correct - [ ] `test_cache_key_unique_params()` - Different params = different keys - [ ] `test_invalidate_cache_pattern()` - Invalidation works - [ ] `test_cached_disabled()` - Works when REDIS_ENABLED=false ### Integration Tests **Setup:** `docker-compose up -d` **Test Scenarios:** 1. **Full Stack Test:** - Start backend + Redis - Call `/api/dashboard/summary?company=123` - Verify: Oracle query executed (check logs) - Call again same endpoint - Verify: Cache hit (no Oracle query) 2. **Cache Invalidation Test:** - Call endpoint (cache populated) - Invalidate via `redis-cli KEYS "cache:*"` + `DEL` - Call endpoint again - Verify: Oracle query executed (cache miss) 3. **Redis Failure Test:** - `docker-compose stop roa-redis` - Call endpoint - Verify: App works (fallback to Oracle) - No error thrown - Logs show warning: "Redis unavailable, fallback to DB" ### Performance Benchmarks **Tool:** Apache Bench or Python script **Baseline (No Cache):** ```bash # Stop Redis or set REDIS_ENABLED=false ab -n 100 -c 10 http://localhost:8001/api/dashboard/summary?company=123 # Expected: ~800ms avg response time, 80s total ``` **With Cache:** ```bash # Start Redis and set REDIS_ENABLED=true ab -n 100 -c 10 http://localhost:8001/api/dashboard/summary?company=123 # Expected: ~15ms avg (after first request), ~2s total ``` **Target Metrics:** - Cache hit rate: >90% (după warmup) - Avg response time reduction: >60% - Total time reduction: >75% - Memory usage: +50-200MB (Redis) **Save Results:** `shared/docs/REDIS_PERFORMANCE_BENCHMARK.md` ### Manual Testing Checklist - [ ] **Dashboard Summary:** - [ ] First load → check logs for "Cache MISS" - [ ] Refresh page → check logs for "Cache HIT" - [ ] Change company → new cache key, "Cache MISS" - [ ] **Invoices List:** - [ ] Filter: toate facturile → "Cache MISS" first time - [ ] Refresh → "Cache HIT" - [ ] Filter: doar neplatite → new key, "Cache MISS" - [ ] Refresh → "Cache HIT" - [ ] **Cache Invalidation:** - [ ] Load dashboard (cached) - [ ] Redis CLI: `redis-cli KEYS "cache:*"` → see keys - [ ] Delete: `redis-cli DEL cache:default:dashboard_summary:*` - [ ] Refresh dashboard → "Cache MISS" (re-queries Oracle) - [ ] **Redis Failure Graceful:** - [ ] Stop Redis: `docker-compose stop roa-redis` - [ ] Access dashboard → works (no crash) - [ ] Check logs: "Redis unavailable, using direct DB query" - [ ] Start Redis: `docker-compose start roa-redis` - [ ] Access dashboard → caching resume - [ ] **Multi-Tenant Simulation:** - [ ] Load dashboard company=123 (tenant=default) - [ ] Load dashboard company=456 (tenant=default) - [ ] Verify different cache keys in Redis --- ## 🔧 Configurare Env Variables **File:** `backend/.env` ```bash # ============================================================================ # REDIS CONFIGURATION # ============================================================================ # Redis Connection URL # Development: redis://roa-redis:6379/0 (Docker network) # Production: redis://redis-host:6379/0 or redis://localhost:6379/0 REDIS_URL=redis://roa-redis:6379/0 # Redis Password (from docker-compose secrets) # Match with REDIS_PASSWORD in docker-compose.yml REDIS_PASSWORD=roa2web_redis_password # Enable/Disable Redis Caching # Set to 'false' to disable caching (fallback to direct DB queries) REDIS_ENABLED=true # Default Cache TTL (seconds) # Used when no specific TTL provided to @cached decorator CACHE_DEFAULT_TTL=300 # Redis Connection Pool Settings (optional, defaults shown) REDIS_MAX_CONNECTIONS=50 REDIS_SOCKET_CONNECT_TIMEOUT=5 REDIS_SOCKET_KEEPALIVE=true ``` **Docker Compose Integration:** No changes needed! Redis container already configured in `docker-compose.yml:147-163`. **Verify:** ```bash docker-compose exec roa-backend env | grep REDIS # Should show REDIS_URL, REDIS_PASSWORD, REDIS_ENABLED ``` --- ## 📝 Checklist Final ### Pre-Implementation - [ ] Read și înțeles planul complet - [ ] Backup codebase: `git commit -am "Backup before Redis implementation"` - [ ] Redis container rulează: `docker-compose up -d roa-redis` - [ ] Test connection: `docker-compose exec roa-redis redis-cli ping` → PONG ### Faza 1 (Setup) - [ ] Dependency added: `redis>=5.0.0` în requirements.txt - [ ] Package created: `shared/cache/__init__.py` - [ ] Redis client: `shared/cache/redis_client.py` - [ ] Env vars added: `backend/.env.example` - [ ] Main.py updated: Redis initialize at startup - [ ] Test: `python -c "import asyncio; from shared.cache import redis_cache; asyncio.run(redis_cache.initialize())"` ### Faza 2 (Decorators) - [ ] Utils created: `shared/cache/utils.py` - [ ] Decorator created: `shared/cache/decorators.py` - [ ] Unit tests: `shared/tests/test_cache_decorators.py` - [ ] Test: `pytest shared/tests/test_cache_decorators.py -v` ### Faza 3 (Integration) - [ ] Cached applied: DashboardService.get_complete_summary - [ ] Cached applied: DashboardService.get_trends - [ ] Cached applied: DashboardService.get_detailed_data - [ ] Cached applied: InvoiceService.get_invoices - [ ] Cached applied: InvoiceService.get_invoice_summary - [ ] Backend starts: `uvicorn app.main:app --reload` - [ ] Test: First request slow, second fast ### Faza 4 (Validation) - [ ] Unit tests pass: `pytest shared/tests/ -v` - [ ] Integration tests pass (Docker stack) - [ ] Performance benchmark run (save results) - [ ] Manual testing checklist completed - [ ] Documentation updated: `CLAUDE.md` - [ ] Git commit: `git add . && git commit -m "feat: implement Redis caching layer"` ### Ready for Production - [ ] All tests green - [ ] Performance improvement >60% - [ ] Graceful degradation tested (Redis failure) - [ ] Code review requested - [ ] Merge to main branch --- ## 📚 Referințe ### Documentație Existentă - **Docker Compose Redis Config:** `docker-compose.yml:147-163` - **Oracle Pool Pattern:** `shared/database/oracle_pool.py` (reference for singleton pattern) - **Backend Services:** `backend/app/services/` (where to apply caching) - **Backend Routers:** `backend/app/routers/` (where to invalidate cache) ### Documentație Externă - Redis Python Client: https://redis.readthedocs.io/en/stable/ - Redis Commands: https://redis.io/commands/ - FastAPI Async: https://fastapi.tiangolo.com/async/ ### Debugging **Redis CLI Access:** ```bash docker-compose exec roa-redis redis-cli -a roa2web_redis_password > KEYS cache:* > GET cache:default:dashboard_summary:abc123 > DEL cache:default:dashboard_summary:abc123 > FLUSHDB # Delete all keys (WARNING: destructive) ``` **Monitor Redis Operations:** ```bash docker-compose exec roa-redis redis-cli -a roa2web_redis_password MONITOR ``` **Check Cache Stats:** ```bash docker-compose exec roa-redis redis-cli -a roa2web_redis_password INFO stats ``` --- ## 🎯 Code Templates ### Template: RedisCache Client (`shared/cache/redis_client.py`) ```python """ Redis Cache Client - Singleton pattern similar to OraclePool Provides async Redis operations with graceful degradation """ import redis.asyncio as redis import logging import json from typing import Optional, Any import os logger = logging.getLogger(__name__) class RedisCache: """Singleton Redis cache client with connection pooling""" _instance: Optional['RedisCache'] = None _client: Optional[redis.Redis] = None _enabled: bool = False def __new__(cls): if cls._instance is None: cls._instance = super(RedisCache, cls).__new__(cls) return cls._instance async def initialize( self, url: str = None, password: str = None, max_connections: int = 50 ): """Initialize Redis connection pool""" if self._client is not None: return try: url = url or os.getenv('REDIS_URL', 'redis://localhost:6379/0') password = password or os.getenv('REDIS_PASSWORD') self._client = await redis.from_url( url, password=password, encoding="utf-8", decode_responses=True, max_connections=max_connections, socket_connect_timeout=5, socket_keepalive=True ) # Test connection await self._client.ping() self._enabled = True logger.info("✅ Redis cache initialized successfully") except Exception as e: logger.warning(f"⚠️ Redis initialization failed: {e}. Caching disabled.") self._enabled = False self._client = None def is_enabled(self) -> bool: """Check if Redis caching is enabled""" return self._enabled and self._client is not None async def get(self, key: str) -> Optional[Any]: """Get value from cache""" if not self.is_enabled(): return None try: value = await self._client.get(key) if value: return json.loads(value) return None except Exception as e: logger.error(f"Redis GET error for key {key}: {e}") return None async def set(self, key: str, value: Any, ttl: int = 300): """Set value in cache with TTL""" if not self.is_enabled(): return try: value_json = json.dumps(value, default=str) await self._client.setex(key, ttl, value_json) except Exception as e: logger.error(f"Redis SET error for key {key}: {e}") async def delete(self, key: str): """Delete single key""" if not self.is_enabled(): return try: await self._client.delete(key) except Exception as e: logger.error(f"Redis DELETE error for key {key}: {e}") async def delete_pattern(self, pattern: str): """Delete all keys matching pattern (e.g., 'cache:default:dashboard*')""" if not self.is_enabled(): return try: async for key in self._client.scan_iter(match=pattern): await self._client.delete(key) logger.debug(f"Deleted keys matching pattern: {pattern}") except Exception as e: logger.error(f"Redis DELETE_PATTERN error for {pattern}: {e}") async def close(self): """Close Redis connection""" if self._client: await self._client.close() self._client = None self._enabled = False logger.info("✅ Redis connection closed") # Global singleton instance redis_cache = RedisCache() ``` --- ## ⚠️ Known Limitations & Future Work ### Current Limitations 1. **No Cache Warming:** Cache is cold on startup (first requests slow) - Future: Implement background task to pre-populate hot keys 2. **Manual Invalidation:** Invalidation must be coded manually in routers - Future: Auto-invalidation via database triggers or event system 3. **Single Tenant:** All cache keys use `tenant_id="default"` - Future: Extract tenant_id from JWT token when multi-tenant implemented 4. **No Cache Monitoring:** No dashboard/metrics for cache performance - Future: Integrate Prometheus metrics (hit/miss rate, latency, memory) 5. **Simple Serialization:** Uses JSON (no support for binary data, datetime needs str conversion) - Future: Consider msgpack for faster serialization ### Future Enhancements - [ ] **Cache Warming:** Background task to pre-load hot keys at startup - [ ] **Smart Invalidation:** Event-driven invalidation based on DB changes - [ ] **Cache Monitoring Dashboard:** Redis metrics + hit/miss rates - [ ] **Cache Compression:** Compress large values (>10KB) before storing - [ ] **Multi-Level Cache:** L1 (in-memory LRU) + L2 (Redis) for ultra-fast access - [ ] **Cache Tagging:** Tag-based invalidation instead of pattern matching --- ## 📞 Support & Questions **Dacă întâmpini probleme:** 1. **Redis nu pornește:** Check `docker-compose logs roa-redis` 2. **Connection failed:** Verify REDIS_URL și REDIS_PASSWORD în .env 3. **Cache nu funcționează:** Verify REDIS_ENABLED=true și logs pentru errors 4. **Performance nu se îmbunătățește:** Check cache hit rate în logs **Contact:** Claude Code Implementation Team --- **Planul este gata pentru implementare! Începe cu FAZA 1 și urmează pașii exact cum sunt descriși.**