- Delete data-entry-app/ (1.6GB), reports-app/ (447MB), .auto-build-data/
- Saved ~1.4GB disk space (64% reduction: 2.2GB → 845MB)
Updated references across 38 files:
- .claude/rules/ paths: backend/modules/, src/modules/
- .claude/commands/validate.md: all validation paths
- docs/ (13 files): data-entry, telegram, README, CLAUDE.md
- scripts/ (3 files): backup-secrets, restore-secrets, test-docker
- security/ (2 files): git_cleanup, SECURITY_PROCEDURES
- deployment/ & shared/: updated all stale comments
All paths now reflect ultrathin monolith architecture:
- Backend: backend/modules/{reports,data_entry,telegram}/
- Frontend: src/modules/{reports,data-entry}/
- Shared: shared/{auth,database,routes}/
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
30 KiB
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 initializationshared/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.0backend/.env.example- Adaugă REDIS_* env varsbackend/app/main.py- Initialize Redis at startupbackend/app/services/dashboard_service.py- Apply cachingbackend/app/services/invoice_service.py- Apply cachingbackend/app/routers/dashboard.py- Cache invalidation on mutationsbackend/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:
-
Adaugă dependency în requirements.txt
- Fișier:
backend/requirements.txt - Acțiune: Adaugă linia
redis>=5.0.0dupăhttpx>=0.27.0 - Motivație: redis 5.0+ are async support nativ
- Fișier:
-
Creează package cache în shared/
- Fișiere:
shared/cache/__init__.py - Acțiune: Creează directorul și fișierul de init cu exports:
from .redis_client import redis_cache from .decorators import cached, invalidate_cache __all__ = ['redis_cache', 'cached', 'invalidate_cache']
- Fișiere:
-
Implementează RedisCache singleton client
- Fișier:
shared/cache/redis_client.py - Acțiune: Creează clasa
RedisCachesimilar cuOraclePool:- 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)
- Fișier:
-
Adaugă env variables în .env.example
- Fișier:
backend/.env.example - Acțiune: Adaugă la sfârșitul fișierului:
# Redis Configuration REDIS_URL=redis://roa-redis:6379/0 REDIS_PASSWORD=roa2web_redis_password REDIS_ENABLED=true CACHE_DEFAULT_TTL=300
- Fișier:
-
Initialize Redis la startup în main.py
- Fișier:
backend/app/main.py - Acțiune: În funcția
startup_event():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()
- Fișier:
Output Verificabil:
pip install -r requirements.txtrulează 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:
-
Implementează cache key generator
- Fișier:
shared/cache/utils.py - Acțiune: Funcții helper pentru key generation:
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')
- Fișier:
-
Implementează @cached decorator
- Fișier:
shared/cache/decorators.py - Acțiune: Decorator pentru auto-caching de funcții async:
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
- Fișier:
-
Implementează invalidate_cache helper
- Fișier:
shared/cache/decorators.py(same file) - Acțiune: Helper function pentru manual invalidation:
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}")
- Fișier:
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:
-
Apply @cached în DashboardService.get_complete_summary()
- Fișier:
backend/app/services/dashboard_service.py - Acțiune: Adaugă decorator la metoda
get_complete_summary: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
- Fișier:
-
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
- Fișier:
-
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
- Fișier:
-
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.)
- Fișier:
-
Apply @cached în InvoiceService.get_invoice_summary()
- Fișier:
backend/app/services/invoice_service.py - Acțiune: TTL=180 (3 min pentru summary)
- Fișier:
-
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):
# 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")
- Fișier:
-
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_listinvoices_summarydashboard_summary(afectează dashboard)
- Fișier:
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/MISSmessages
FAZA 4: Testing, Monitoring și Cleanup (45 min)
Obiectiv: Validare funcționare corectă, performance benchmarks, și documentare.
Tasks:
-
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
- Fișier:
-
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
- Fișier:
-
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
- Acțiune: Pornește stack complet cu
-
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%
- Without cache (REDIS_ENABLED=false):
- Salvează results în
shared/docs/REDIS_PERFORMANCE_BENCHMARK.md
-
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
-
Update CLAUDE.md documentation
-
Fișier:
CLAUDE.md -
Acțiune: Adaugă secțiune "Redis Caching":
## 💾 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:
- Modifică
extract_tenant_id()înutils.pysă citească dinrequest.state.tenant_id - JWT token va include
tenant_idfield - Cache keys automat vor fi per-tenant
- 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:
# Î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:
# 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 connectiontest_redis_connection_failure_graceful()- Redis down, no exception throwntest_redis_get_set_delete()- Basic operationstest_redis_delete_pattern()- Pattern matching deletiontest_redis_ttl_expiration()- Verify TTL workstest_redis_connection_retry()- Exponential backoff retry
File: shared/tests/test_cache_decorators.py
test_cached_decorator_hit()- Second call returns cached valuetest_cached_decorator_miss()- First call queries functiontest_cache_key_generation()- Keys format correcttest_cache_key_unique_params()- Different params = different keystest_invalidate_cache_pattern()- Invalidation workstest_cached_disabled()- Works when REDIS_ENABLED=false
Integration Tests
Setup: docker-compose up -d
Test Scenarios:
-
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)
-
Cache Invalidation Test:
- Call endpoint (cache populated)
- Invalidate via
redis-cli KEYS "cache:*"+DEL - Call endpoint again
- Verify: Oracle query executed (cache miss)
-
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):
# 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:
# 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
- Stop Redis:
-
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
# ============================================================================
# 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:
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:
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:
docker-compose exec roa-redis redis-cli -a roa2web_redis_password MONITOR
Check Cache Stats:
docker-compose exec roa-redis redis-cli -a roa2web_redis_password INFO stats
🎯 Code Templates
Template: RedisCache Client (shared/cache/redis_client.py)
"""
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
-
No Cache Warming: Cache is cold on startup (first requests slow)
- Future: Implement background task to pre-populate hot keys
-
Manual Invalidation: Invalidation must be coded manually in routers
- Future: Auto-invalidation via database triggers or event system
-
Single Tenant: All cache keys use
tenant_id="default"- Future: Extract tenant_id from JWT token when multi-tenant implemented
-
No Cache Monitoring: No dashboard/metrics for cache performance
- Future: Integrate Prometheus metrics (hit/miss rate, latency, memory)
-
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:
- Redis nu pornește: Check
docker-compose logs roa-redis - Connection failed: Verify REDIS_URL și REDIS_PASSWORD în .env
- Cache nu funcționează: Verify REDIS_ENABLED=true și logs pentru errors
- 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.