Files
_Assistant_Lead_Tech/knowledge/backend/risques/redis.md
T
MaksTinyWorkshop ef24d85d57 capitalisation: triage 95_a_capitaliser + création domaine infra
Triage des 27 propositions du buffer de capitalisation (skill
capitalisation-triage), avec vérification des doublons contre la base.

Intégré dans knowledge/ (23 entrées):
- backend: redis (compensation incrBy non-atomique), nestjs (injection
  cassée sous tsx watch; guard write mode dégradé), async (test rollback
  pipeline multi-fichiers), contracts (idempotence POST), auth (disclosure
  comptes soft-deleted), prisma (index partial soft-delete), llm-providers
  (nouveau: OAuth vs API key, prompt caching).
- frontend: tests (garde-fous parking Later), navigation (fichiers
  non-route sous src/app Expo Router), general (type client vs payload
  backend), state (fallback catch-all mapping DB→UI).
- workflow: story-tracking (statut BMAD vs narratif obsolète).
- product: general (nouveau: doc feature store sans UI).
- infra: NOUVEAU DOMAINE (traefik, tailscale, docker, docker-networking,
  reverse-proxy-paths, sidecar tailscale) + 00_INDEX.md.

Autres:
- 90_debug_et_postmortem.md: post-mortem réseau Docker partagé hors compose.
- Rejeté 3 doublons (types enum contracts, getter PrismaService, $transaction).
- Buffer 95_a_capitaliser.md purgé et restauré à son état initial.
- _projects.conf: MAJ statuts epics + ajout app-rl799.
2026-06-25 10:31:22 +02:00

5.1 KiB

Backend — Risques & vigilance : Redis

Extrait de la base de connaissance Lead_tech. Voir knowledge/backend/risques/README.md pour l'index complet.


Redis — thrash de connexion sous charge

Risques

  • Connexions concurrentes multiples si connect() est appelé "à la demande" sans lock
  • Spam logs + saturation connexions quand Redis est down ou lent

Symptômes

  • N appels simultanés → N tentatives de connexion en parallèle
  • Logs "Redis connection failed" en rafale au démarrage ou lors d'un restart Redis

Bonnes pratiques / mitigations

// Pattern single-flight + cooldown + fallback DB best-effort
if (!this.connectPromise) {
  this.connectPromise = this.client.connect().finally(() => { this.connectPromise = null; });
}
await this.connectPromise;
// Si échec → nextConnectRetryAtMs = now + 1000 → return false → fallback DB
  • Contexte technique : Redis / NestJS — 09-03-2026

Entitlements — TTL cache supérieur au SLA de propagation

Risques

  • TTL cache > SLA propagation → un webhook raté viole mécaniquement le SLA (accès stale plus long que garanti)
  • Utilisateur avec accès périmé ou sans accès dû, pendant toute la durée du TTL résiduel

Symptômes

  • Accès premium encore actif après annulation (ou inversement)
  • NFR "propagation ≤ 60s" non respecté en cas de webhook manqué

Bonnes pratiques / mitigations

  • TTL cache ≤ SLA cible (ex : NFR "≤ 60s" → TTL = 60s max)
  • Toujours coupler TTL + invalidation explicite via webhook (les deux, pas l'un ou l'autre)
  • Contexte technique : Redis / entitlements / NestJS — 09-03-2026

Compteurs in-memory ≠ métriques persistées

Risques

  • Compteurs in-memory remis à zéro au restart (perte de données)
  • Non agrégables sur plusieurs instances (données partielles par pod)

Symptômes

  • Métriques qui "repartent de 0" à chaque déploiement
  • Dashboards incorrects en environnement multi-instance

Bonnes pratiques / mitigations

  • V1 low-cost : Redis INCRBY best-effort par eventType → persisté et agrégé multi-instances
  • Évolutif vers Prometheus/OTel sans changer l'interface (abstraction dès le départ)
  • Contexte technique : Redis / NestJS — 09-03-2026

TTL Redis quota calculé en heure locale (dérive jusqu'à ±12h)

Risques

  • Le reset du quota journalier dérive selon le timezone du serveur, pouvant aller jusqu'à ±12h d'écart par rapport à minuit UTC

Symptômes

  • Quota qui se remet à zéro à des heures inattendues selon l'environnement de déploiement
  • Comportement différent en dev local (TZ machine) et en prod (TZ container)

Bonnes pratiques / mitigations

// ✅ CORRECT — UTC midnight garanti
const midnight = new Date(
  Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1),
);
const ttlMs = midnight.getTime() - now.getTime();

// ❌ RISQUÉ — heure locale du serveur
const endOfDay = new Date();
endOfDay.setHours(23, 59, 59, 999); // dérive selon TZ serveur
  • Règle : tout expireAt ou TTL de quota journalier doit utiliser Date.UTC() — vérifier systématiquement en review

  • Contexte technique : Redis / NestJS — app-alexandrie 20-03-2026


Compensation incrBy(-1) non-atomique après dépassement de quota

Risques

  • Pattern courant de quota Redis : INCR → vérifier > limit → si oui, décrémenter (incrBy(-1)) et lever 429. La compensation n'est PAS atomique avec le check.
  • Si Redis devient indisponible (flap) entre l'incrément et la compensation, l'appel de décrément échoue silencieusement et retourne null. Le compteur reste incrémenté → quota fantôme jusqu'à l'expiration de la clé.

Symptômes

  • Utilisateur bloqué par le quota alors qu'il n'a pas atteint la limite réelle
  • Aucune erreur visible : la valeur de retour null du décrément n'est pas vérifiée
  • Comportement intermittent corrélé aux instabilités Redis

Bonnes pratiques / mitigations

// ❌ Compensation non vérifiée — échec silencieux possible
await redis.incr(key);
if (current > limit) {
  await redis.incrBy(key, -1); // retourne null si Redis flap → compensation perdue
  throw quotaExceeded();
}

// ✅ Vérifier le retour de la compensation et loguer
const compensated = await redis.incrBy(key, -1);
if (compensated === null) {
  logger.error({ type: 'quota', event: 'compensation_failed', key });
}
  • Règle : toujours vérifier le retour du décrément de compensation et loguer explicitement si null. Documenter ce choix dans les Dev Notes de la story (comportement intentionnel vs bug).

  • Solution robuste : encapsuler incrément + compensation conditionnelle dans le même pipeline MULTI/EXEC ou un script Lua (atomicité garantie), au prix d'une complexité plus élevée.

  • Contexte technique : Redis / NestJS — app-alexandrie 01-04-2026