Triage et intégration des propositions backend du buffer 95_a_capitaliser.md (lot local RL799_V2 + app-alexandrie, mai-juin 2026), distinct de la capitalisation remote antérieure (triage 2026-05-02). ~73 entrées intégrées sur knowledge/backend/, dont : - patterns/auth.md : série "membrane d'auth fédérée BFF/OIDC" (9 patterns) + jose algo whitelist - patterns/prisma.md : recette fusionnée "Migration String/Int → enum" (backfill + Cas A/B/C), row réactivable, endpoint replace atomique, updateMany conditionnel, etc. - risques/general.md : 19 risques (epoch s vs ms, keepAliveTimeout=0, upsert+filtre liste, fail-safe catch-all, retrait asymétrique front/back, anti-énumération rate-limit, etc.) - patterns/general, async, nestjs, contracts, tests + risques/auth, contracts, prisma, redis, stripe, tests - compléments d'entrées existantes (authorize-after-fetch, P3014, cursor opaque, DI swc, Stripe v20...) - README patterns/risques mis à jour Doublons internes corrigés en relecture (suppression-champ .map() → general seul ; e2e DB-based → tests.md seul). Doublons hors backend / entrées projet / rejets non intégrés. Source 95_a_capitaliser.md non purgée à ce stade (purge en fin de capitalisation complète). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.8 KiB
Backend — Risques & vigilance : Redis
Extrait de la base de connaissance Lead_tech. Voir
knowledge/backend/risques/README.mdpour 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 INCRBYbest-effort pareventType→ 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
expireAtouTTLde quota journalier doit utiliserDate.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
nulldu 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/EXECou un script Lua (atomicité garantie), au prix d'une complexité plus élevée.
Compenser AUSSI quand la transaction DB échoue (pas seulement au dépassement)
Le même compteur doit être compensé quand l'écriture DB qui suit le quota échoue. Pattern récurrent : consumeDailyQuota (incrément Redis) est appelé avant une transaction DB. Si la transaction throw, le compteur du user est consommé sans avoir produit le side-effect attendu → quota fantôme. Le piège : la compensation incrBy(-1) existante n'est souvent déclenchée que sur dépassement de quota, pas sur exception de la transaction.
// ❌ si $transaction throw, le compteur reste incrémenté → quota fantôme
await consumeDailyQuota({ ..., action: 'dm-message' });
const message = await prisma.$transaction(async (tx) => { /* INSERT, peut échouer */ });
// ✅ compensation systématique sur exception de la transaction
await consumeDailyQuota({ ... });
try {
const message = await prisma.$transaction(...);
} catch (err) {
await redis.incrBy(quotaKey, -1).catch(() => {});
throw err;
}
-
Trade-off : garder l'ordre
quota → tx → compensation(et non « tx puis quota ») garantit qu'on ne dépasse pas la limite sous charge concurrente (deux requêtes simultanées qui passeraient toutes deux le check). La compensation sur catch est donc obligatoire. -
Règle : tout flow
consumeDailyQuotapuis écriture DB doit compenser sur exception. Vérifier aussi les autres actions partageant ce flow (comment,post,support_ticket). -
Contexte technique : Redis / NestJS / Prisma — app-alexandrie 01-04-2026, complété 13-05-2026 (story 10.2)
Rate-limit à compteur partagé entre endpoints jumeaux
Risques
- Un helper de rate-limit dont la clé Redis omet un discriminant d'endpoint (
quota:${action}:${userId}:${jour}) fait partager un unique compteur à plusieurs endpoints réutilisant le mêmeactionet le même discriminant (ex : l'IP). - Les seuils respectifs des endpoints perdent tout sens : un excès sur l'un consomme le quota de l'autre. Ex : un excès de
loginbloque unresetde mot de passe légitime.
Symptômes
- Un endpoint renvoie 429 alors que son propre seuil n'est pas atteint, à cause du trafic sur un endpoint jumeau.
- Le bug est invisible en test : chaque e2e exerce un seul endpoint à la fois, donc le compteur n'est jamais partagé pendant un test.
Bonnes pratiques / mitigations
-
Règle de clé :
clé = quota:<action>:<endpoint>:<identité>:<fenêtre>. La clé DOIT inclure un discriminant d'endpoint, pas seulement l'identité de l'appelant. -
Test de non-régression : exercer DEUX endpoints jumeaux jusqu'au seuil dans le même test — un test mono-endpoint ne révèle jamais ce bug.
-
Contexte technique : Redis / NestJS — app-alexandrie 22-05-2026