# Story 6.3: Redis Configuration Status: done ## Story As a **system**, I want **Redis configured for rate limiting and caching**, so that **the application scales**. ## Acceptance Criteria 1. **Given** Redis container is running **When** the application starts **Then** Redis connection is established **And** rate limiting middleware uses Redis **And** translation progress is cached in Redis **And** Redis data is persisted to volume ## Tasks / Subtasks - [x] Task 1 (AC: #1) — Connexion Redis au démarrage - [x] 1.1 Vérifier que REDIS_URL est lu depuis l'environnement et qu'un client Redis (sync ou async) est initialisé au démarrage de l'app. - [x] 1.2 S'assurer que l'échec de connexion Redis est géré proprement (démarrage sans Redis en dev optionnel, ou fail-fast en prod selon convention projet). - [x] Task 2 (AC: #1) — Rate limiting via Redis - [x] 2.1 Le middleware de rate limiting (par IP et/ou par utilisateur) utilise Redis pour les compteurs (sliding window ou équivalent) afin que la limite soit partagée entre instances. - [x] 2.2 Documenter ou adapter le middleware existant (rate_limiting.py et tier_quota) pour qu'au moins le quota par tier (daily) et idéalement les limites par fenêtre utilisent Redis. - [x] Task 3 (AC: #1) — Cache de progression traduction - [x] 3.1 La progression des traductions (status, progress_percent, current_step) est lue/écrite via Redis pour permettre le polling multi-instance et la persistance courte durée. - [x] 3.2 Vérifier que le storage_tracker / métadonnées jobs utilisent bien Redis (déjà le cas) et que l'endpoint GET /api/v1/translations/{id} s'appuie sur ce stockage. - [x] Task 4 (AC: #1) — Persistance Redis - [x] 4.1 En environnement Docker (dev et prod), Redis est lancé avec persistance (appendonly yes) et volume dédié (déjà en place dans docker-compose.dev.yml et docker-compose.yml). - [x] 4.2 Documenter dans .env.example ou README que REDIS_URL est requis pour le rate limiting et le cache de progression en production. ## Dev Notes - **Contexte** : Les stories 6-1 et 6-2 ont livré Docker Compose dev et prod avec un service Redis (redis:7-alpine, volume, healthcheck). Le backend utilise déjà REDIS_URL à plusieurs endroits : tier_quota (quota quotidien par user), storage_tracker (métadonnées fichiers/jobs), auth blocklist (JWT révoqués), admin sessions, health check. Le middleware `rate_limiting.py` (limites par IP) est actuellement **en mémoire** (SlidingWindowCounter, TokenBucket) ; les AC exigent que le rate limiting middleware utilise Redis pour la scalabilité. - **Architecture** : [Source: _bmad-output/planning-artifacts/architecture.md] — Rate Limiting Redis + sliding window ; Translation → Redis (rate limiting, progress). Health check format : `redis: connected` dans GET /health. - **NFR10** : REDIS_URL via variables d'environnement uniquement. - **Fichiers à toucher** : configuration Redis centralisée (core/config ou équivalent), `middleware/rate_limiting.py` (option Redis backend), `middleware/tier_quota.py` (déjà Redis), `services/storage_tracker.py` (déjà Redis), `main.py` (health check Redis), `.env.example` / README. ### Project Structure Notes - Racine : `office_translator/` avec `main.py`, `middleware/`, `services/`, `routes/`. Redis utilisé depuis plusieurs modules ; viser un client partagé (singleton) pour éviter trop de connexions. - Docker : `docker-compose.yml` et `docker-compose.dev.yml` définissent déjà le service `redis` avec volume et `redis-server --appendonly yes`. ### References - [Source: _bmad-output/planning-artifacts/architecture.md] — Infrastructure & Deployment, Rate Limiting, Health Check Format. - [Source: _bmad-output/planning-artifacts/epics.md] — Epic 6, Story 6.3, AC BDD. - [Source: _bmad-output/implementation-artifacts/6-2-docker-compose-production.md] — Redis service prod, REDIS_URL, healthcheck. ### Technical Requirements (Architecture Compliance) - **Connexion** : Un seul client Redis async partagé (ou sync selon usage) recommandé pour tier_quota, storage_tracker, et si possible rate_limiting ; initialisation au démarrage ou lazy avec gestion d'erreur claire. - **Rate limiting** : Utiliser Redis pour stocker les fenêtres (sliding window) par clé (ex. `rate_limit:ip:{ip}:minute`). Si le middleware actuel est en mémoire, ajouter un backend Redis optionnel et l'activer quand REDIS_URL est défini (comportement existant dans tier_quota et storage_tracker). - **Progress** : Les métadonnées de job (id, status, progress_percent, current_step) doivent être lisibles via Redis pour GET /api/v1/translations/{id} (déjà le cas si storage_tracker est utilisé pour ces infos). - **Persistance** : Redis avec `appendonly yes` et volume monté (déjà en place dans Compose) ; pas de changement nécessaire si déjà configuré. - **Health check** : GET /health doit inclure un champ `redis` avec status "healthy" / "unhealthy" / "not_configured" (déjà présent dans main.py). ### Architecture Compliance - Alignement avec architecture.md : Redis pour rate limiting distribué et cache (progress). Pas de nouveau service ; configuration et usage cohérents. - Respect de la structure projet : middleware/, services/, core/ si config centralisée. ### Library / Framework Requirements - **redis** (Python) : utiliser `redis` (sync) et `redis.asyncio` (async) selon le module. Version compatible Python 3.11+ (redis >= 4.5 pour asyncio). Déjà présent dans le projet (tier_quota, storage_tracker, auth_service, admin_routes, main.py). - Pas de nouvelle dépendance si redis est déjà dans requirements.txt ou pyproject.toml ; sinon l’ajouter. ### File Structure Requirements - Ne pas dupliquer la logique de connexion Redis : centraliser dans un module (ex. `core/redis.py` ou réutiliser le pattern `_get_async_redis()` depuis un seul module importé par les autres). - `.env.example` : REDIS_URL documenté (ex. `redis://redis:6379/0` pour Docker, `redis://localhost:6379/0` pour dev local). ### Testing Requirements - Vérifier que lorsque REDIS_URL est défini, l’application se connecte à Redis et que GET /health renvoie `redis: healthy`. - Vérifier que le rate limiting (par user/tier ou par IP si migré) utilise Redis : deux requêtes depuis deux processus/instances différentes partagent la même limite. - Vérifier que la progression d’une traduction (GET /api/v1/translations/{id}) est bien lue depuis Redis après un enregistrement par le worker. - Vérifier qu’en Docker, le volume Redis persiste les données après redémarrage du conteneur redis. ### Previous Story Intelligence (6-2 Docker Compose Production) - **Fichiers créés/modifiés** : `docker-compose.yml`, `.env.example`, `scripts/verify-prod-compose.sh`. Redis service déjà défini avec healthcheck, volume `redis_data`, command `redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru`. - **Patterns établis** : backend dépend de redis avec `condition: service_healthy` ; REDIS_URL=redis://redis:6379/0 dans environment backend. - **Différence clé pour 6-3** : pas de changement Compose nécessaire pour Redis ; focus sur l’application (connexion, middleware, cache progress, documentation). ### Git Intelligence Summary - Codebase existante utilise déjà Redis dans plusieurs services (tier_quota, storage_tracker, auth blocklist, admin sessions, health). La story 6-3 formalise et complète la configuration : s’assurer que le rate limiting (middleware IP) utilise Redis si pas déjà le cas, et que toute la doc/config est cohérente. ### Latest Tech Information - redis-py 5.x : API stable, redis.asyncio recommandé pour FastAPI. Compatible Redis 6/7. Pas de breaking change majeur pour from_url, ping(), setex, get, etc. - Redis 7 : appendonly yes et volume suffisent pour la persistance ; allkeys-lru avec maxmemory pour éviter saturation. ### Project Context Reference - Aucun fichier `project-context.md` trouvé. Contexte métier et stack : architecture.md et epics.md. ## Dev Agent Record ### Agent Model Used (dev-story workflow) ### Debug Log References - ### Completion Notes List - **Task 1**: Added `core/redis.py` with get_redis_url(), get_async_redis(), get_sync_redis(), ping_sync(). Refactored storage_tracker, tier_quota, main.py health/ready, admin_routes, auth_service to use shared clients. Connection failure handled gracefully (None when REDIS_URL missing or connection fails). - **Task 2**: Rate limiting middleware uses Redis when REDIS_URL is set: sliding-window counters (minute/hour/day, trans_minute/trans_hour) stored in Redis (key pattern rate_limit:ip:{client_id}:{window}). Fallback to in-memory when Redis unavailable. tier_quota already used Redis. - **Task 3**: Translation job status cached in Redis: core.redis set_job_status_async/get_job_status_async (key translation:job:{job_id}). Job created/updated in translate_routes triggers Redis write; background task syncs job to Redis every 0.5s until completed/failed. GET /api/v1/translations/{id} reads from Redis first, then falls back to _translation_jobs. - **Task 4**: Docker Compose dev/prod already use Redis with appendonly yes and volume. .env.example updated with Redis section stating REDIS_URL required for rate limiting and translation progress cache in production. - **Code Review (AI)** : GET /download/{id} résout le job via Redis (get_job_status_async) puis _translation_jobs pour cohérence multi-instance. Rate limiting Redis : application de max_total_size_per_hour_mb (clé rate_limit:size_hour:{client_id}:{hour_ts}). core/redis : socket_connect_timeout=5 pour le client sync, logs sur set_job_status_async/get_job_status_async. Test ping_sync succès ajouté. File List complétée avec fichiers modifiés non listés initialement. ### File List - core/__init__.py (créé) - core/redis.py (créé — timeout sync, logs job status) - services/storage_tracker.py (modifié — utilise core.redis.get_async_redis) - middleware/tier_quota.py (modifié — utilise core.redis.get_async_redis) - main.py (modifié — health/ready utilisent core.redis.ping_sync) - routes/admin_routes.py (modifié — get_redis_client utilise core.redis.get_sync_redis) - services/auth_service.py (modifié — _get_blocklist_redis utilise core.redis.get_sync_redis) - middleware/rate_limiting.py (modifié — backend Redis sliding window + size_hour; quand REDIS_URL défini) - routes/translate_routes.py (modifié — set_job_status_async/get_job_status_async, GET status from Redis, GET download résout job via Redis) - .env.example (modifié — section Redis) - tests/test_core_redis.py (créé — tests unitaires core.redis + ping_sync success) - _bmad-output/implementation-artifacts/6-3-redis-configuration.md (modifié — statut, tâches, Dev Agent Record) - _bmad-output/implementation-artifacts/sprint-status.yaml (modifié — 6-3 → done) - alembic/env.py (modifié — non listé initialement, ajouté en revue) - docker-compose.yml (modifié — non listé initialement, ajouté en revue) - docker-compose.dev.yml (modifié — non listé initialement, ajouté en revue) - .env.production (modifié — non listé initialement, ajouté en revue) - scripts/verify-dev-compose.sh (créé — non listé initialement, ajouté en revue) ### Senior Developer Review (AI) - **Date** : 2026-03-10. **Issues trouvés** : 2 critiques, 3 haute, 3 moyenne, 2 basse. **Écarts Git vs File List** : 5. - **Correctifs appliqués** : GET /download/{id} résout le job via Redis puis mémoire ; rate limiting Redis applique max_total_size_per_hour_mb ; core/redis timeout + logs ; test ping_sync success ; File List complétée. - **Statut** : done (tous les correctifs HIGH/MEDIUM appliqués). ### Change Log - 2026-03-10 — Code review (AI) : correctifs appliqués, story passée en done, sprint-status synced.