# Story 6.3: Redis Configuration

Status: done

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

## 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.
