Files
roa2web-service-auto/shared/docs/REDIS_IMPLEMENTATION_PLAN.md
Marius Mutu 9008876b16 chore: Remove obsolete microservices directories and update all references
- 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>
2025-12-30 12:08:20 +02:00

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 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:
      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:
      # 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():
      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:
      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:
      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:
      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:
      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):
      # 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":

      ## 💾 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:

# Î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 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):

# 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
  • 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ă

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

  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.