Skip to content

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

Configuration

Variables d'environnement

VariableExempleDescription
REDIS_PASSWORDchangeme-redis-strongMot 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-radius

redis.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 notice

ACL 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 off

Redis Keys Critiques

CléTypeTTLDescription
rgz:session:{subscriber_id}Stringsession_duration (durée du forfait)Token de session WiFi actif
rgz:otp:{phone}String300s (5 min)Code OTP en attente de validation
rgz:rate:{phone}String60sCompteur tentatives OTP (max 3/min)
rgz:devices:{subscriber_id}SetMACs connues (tracking multi-device)
rgz:dba:mir:{reseller_id}Hash600sMIR calculé par DBA daemon (#26)
rgz:portal:config:{nas_id}Hash3600sConfig branding portail captif (#18)
rgz:voucher:used:{code}String86400sAnti-replay code voucher consommé
rgz:metrics:realtime:{id}Hash300sMétriques temps réel NOC dashboard
rgz:jti:blacklist:{jti}String7 joursJWT révoqués (SEC-10 logout)
rgz:compliance:check:latestHash3600sDernier résultat compliance check (#49)

Patterns d'Utilisation

LL#26 — Write DB first, then Redis

python
# 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 session

SEC-02 — Race condition vouchers

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

bash
# 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 LIST

Sécurité

RègleImplémentation
SEC-07 requirepassrequirepass dans redis.conf, mot de passe via REDIS_PASSWORD
SEC-07 ACLChaque service a un compte ACL avec accès limité aux clés et commandes utiles
LL#26 CohérenceRedis JAMAIS comme source de vérité, toujours écrire DB en premier
SEC-02 RaceWATCH/MULTI/EXEC pour toutes les opérations atomiques critiques (vouchers)
RéseauPort 6379 non-exposé sur l'hôte, accessible uniquement via rgz-net

Commandes Utiles

bash
# 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" MONITOR

Implémentation TODO

  • [x] Service défini dans docker-compose.core.yml
  • [x] Volume persistant rgz-redis-data configuré
  • [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

PROJET MOSAÏQUE — 81 outils, 22 conteneurs, 500+ revendeurs WiFi Zone