#05 — rgz-redis
EN PRODUCTION
Priorité: 🔴 CRITIQUE · Type: TYPE A · Conteneur: rgz-redis · Code: config/redis/
Dépendances: Aucune
Description
rgz-redis est le moteur de cache et de sessions de la plateforme RGZ. Redis 7 remplit trois rôles distincts dans l'architecture : le stockage de sessions actives (avec TTL automatique aligné sur la durée du forfait WiFi), le cache applicatif pour les données fréquemment lues (branding revendeur, configurations portail, métriques temps réel), et le broker de messages pour Celery Beat (outil #10 rgz-beat).
Chaque clé Redis suit une convention de nommage stricte avec le préfixe rgz: (défini dans les conventions de API_Contracts_RGZ.md), un type de ressource, et un identifiant. Les TTL sont définis de manière cohérente avec les contrats métier : 5 minutes pour un OTP, 60 secondes pour un compteur de rate limiting, jusqu'à 24h pour un voucher consommé. Cette approche évite les données obsolètes sans saturer la mémoire.
Le pattern d'écriture suivant LL#26 est appliqué dans toute l'API : écrire d'abord en base de données PostgreSQL, puis mettre à jour le cache Redis, puis retourner la réponse. Ce pattern garantit la cohérence des données même en cas de crash Redis — la source de vérité reste toujours PostgreSQL. Redis n'est jamais utilisé comme stockage primaire.
La sécurité est assurée par la règle SEC-07 : requirepass obligatoire (mot de passe injecté via REDIS_PASSWORD), ACL par service (chaque microservice a un compte Redis avec accès limité aux clés le concernant). Le port 6379 n'est jamais exposé sur l'hôte — accessible uniquement sur le réseau Docker rgz-net.
Architecture Interne
rgz-redis (redis:7-alpine)
│
├── Port: 6379 (interne rgz-net uniquement)
├── Authentification: requirepass $REDIS_PASSWORD
├── Persistence: AOF (Append Only File) + RDB snapshot
├── Volume: rgz-redis-data → /data
│
├── Base 0 (DB 0) — Sessions + OTP + Rate limiting + Portail
│ ├── rgz:session:{subscriber_id} [String, TTL=forfait]
│ ├── rgz:otp:{phone} [String, TTL=300s]
│ ├── rgz:rate:{phone} [String, TTL=60s]
│ ├── rgz:devices:{subscriber_id} [Set, no TTL]
│ └── rgz:portal:config:{nas_id} [Hash, TTL=3600s]
│
├── Base 1 (DB 1) — Cache métier
│ ├── rgz:dba:mir:{reseller_id} [Hash, TTL=600s]
│ ├── rgz:voucher:used:{code} [String, TTL=86400s]
│ └── rgz:metrics:realtime:{id} [Hash, TTL=300s]
│
└── Base 2 (DB 2) — Broker Celery (rgz-beat)
└── Queues: rgz.monitoring, rgz.billing, rgz.maintenance,
rgz.qos, rgz.compliance, rgz.reportsConfiguration
Variables d'environnement
| Variable | Exemple | Description |
|---|---|---|
REDIS_PASSWORD | changeme-redis-strong | Mot de passe Redis (SEC-07) |
Fichiers de configuration
config/redis/
├── redis.conf # Config Redis: requirepass, maxmemory, AOF
└── acl/
└── users.acl # ACL par service: rgz-api, rgz-beat, rgz-radiusredis.conf (extrait)
requirepass ${REDIS_PASSWORD}
maxmemory 512mb
maxmemory-policy allkeys-lru
appendonly yes
appendfsync everysec
save 900 1
save 300 10
save 60 10000
loglevel noticeACL par service (users.acl)
# SEC-07 : ACL séparées par service
# rgz-api : accès toutes les clés rgz: en DB 0 et DB 1
user rgz-api on >#API_PASSWORD ~rgz:* +@read +@write +@string +@hash +@set -DEBUG -FLUSHDB -FLUSHALL
# rgz-beat (Celery) : accès DB 2 broker uniquement
user rgz-beat on >#BEAT_PASSWORD ~celery* +@all -DEBUG -FLUSHDB -FLUSHALL
# Désactiver l'utilisateur par défaut
user default offRedis Keys Critiques
| Clé | Type | TTL | Description |
|---|---|---|---|
rgz:session:{subscriber_id} | String | session_duration (durée du forfait) | Token de session WiFi actif |
rgz:otp:{phone} | String | 300s (5 min) | Code OTP en attente de validation |
rgz:rate:{phone} | String | 60s | Compteur tentatives OTP (max 3/min) |
rgz:devices:{subscriber_id} | Set | — | MACs connues (tracking multi-device) |
rgz:dba:mir:{reseller_id} | Hash | 600s | MIR calculé par DBA daemon (#26) |
rgz:portal:config:{nas_id} | Hash | 3600s | Config branding portail captif (#18) |
rgz:voucher:used:{code} | String | 86400s | Anti-replay code voucher consommé |
rgz:metrics:realtime:{id} | Hash | 300s | Métriques temps réel NOC dashboard |
rgz:jti:blacklist:{jti} | String | 7 jours | JWT révoqués (SEC-10 logout) |
rgz:compliance:check:latest | Hash | 3600s | Dernier résultat compliance check (#49) |
Patterns d'Utilisation
LL#26 — Write DB first, then Redis
# Pattern CORRECT (app/services/billing.py)
async def create_session(db: AsyncSession, redis: Redis, subscriber_id: str, forfait: dict):
# 1. Écrire en DB d'abord (source de vérité)
session = RadiusSession(subscriber_id=subscriber_id, ...)
db.add(session)
await db.commit()
# 2. Puis mettre à jour le cache Redis
ttl = forfait["duration_seconds"]
await redis.setex(f"rgz:session:{subscriber_id}", ttl, session.id)
# 3. Retourner la réponse
return sessionSEC-02 — Race condition vouchers
# Pattern CORRECT avec WATCH/MULTI/EXEC (app/services/voucher_generator.py)
async def consume_voucher(redis: Redis, code: str, subscriber_id: str):
key = f"rgz:voucher:used:{code}"
async with redis.pipeline() as pipe:
try:
await pipe.watch(key)
existing = await pipe.get(key)
if existing:
raise ValueError("ERR_VOUCHER_ALREADY_USED")
pipe.multi()
pipe.setex(key, 86400, subscriber_id)
await pipe.execute()
except redis.WatchError:
raise ValueError("ERR_VOUCHER_RACE_CONDITION")Healthcheck
# Healthcheck Docker (SEC-07 : toujours passer le mot de passe)
redis-cli -a $REDIS_PASSWORD ping
# Attendu: PONG
# Depuis l'hôte
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" ping
# Vérifier les clés actives par pattern
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" KEYS "rgz:session:*" | wc -l
# Mémoire utilisée
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" INFO memory | grep used_memory_human
# Statistiques globales
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" INFO stats | grep -E "hits|misses|calls"
# Lister les clients connectés
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" CLIENT LISTSécurité
| Règle | Implémentation |
|---|---|
| SEC-07 requirepass | requirepass dans redis.conf, mot de passe via REDIS_PASSWORD |
| SEC-07 ACL | Chaque service a un compte ACL avec accès limité aux clés et commandes utiles |
| LL#26 Cohérence | Redis JAMAIS comme source de vérité, toujours écrire DB en premier |
| SEC-02 Race | WATCH/MULTI/EXEC pour toutes les opérations atomiques critiques (vouchers) |
| Réseau | Port 6379 non-exposé sur l'hôte, accessible uniquement via rgz-net |
Commandes Utiles
# Démarrer
docker compose -f /home/claude-dev/RGZ/docker-compose.core.yml up -d rgz-redis
# Logs Redis
docker logs rgz-redis -f --tail=50
# Status + infos
docker inspect rgz-redis --format='{}'
# Accès CLI interactif
docker exec -it rgz-redis redis-cli -a "$REDIS_PASSWORD"
# Purger le cache portail (forcer rechargement branding)
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" DEL "rgz:portal:config:access_kossou"
# Voir toutes les sessions actives
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" KEYS "rgz:session:*"
# TTL d'une session spécifique
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" TTL "rgz:session:<subscriber-uuid>"
# Flusher uniquement la DB 1 (cache métier) sans toucher sessions ni Celery
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" -n 1 FLUSHDB
# Moniteur temps réel des commandes (debug)
docker exec rgz-redis redis-cli -a "$REDIS_PASSWORD" MONITORImplémentation TODO
- [x] Service défini dans
docker-compose.core.yml - [x] Volume persistant
rgz-redis-dataconfiguré - [x] Variables d'env dans
.env.example - [ ]
config/redis/redis.conf— maxmemory, AOF, requirepass, persistence - [ ]
config/redis/acl/users.acl— ACL par service (rgz-api, rgz-beat) - [ ] Intégration dans
app/deps.py(get_redis()dependency injection) - [ ] Tests unitaires des patterns WATCH/MULTI/EXEC (vouchers)
- [ ] Tests TTL cohérence (sessions expirées correctement)
- [ ] Monitoring mémoire Redis dans Grafana (#37)
Dernière mise à jour: 2026-02-21