PD-172 — Rate limiting distribué Redis multi-dimensions (contractuel V2)
1. Objectif
La User Story PD-172 définit un contrat de limitation de débit distribué, cohérent multi-instances, pour protéger les endpoints critiques du backend ProbatioVault contre les abus automatisés, tout en préservant la disponibilité globale.
Objectifs contractuels :
- protéger l’authentification contre le bruteforce ;
- plafonner les endpoints coûteux ;
- garantir l’équité entre tenants ;
- rendre chaque blocage observable côté exploitation ;
- fournir des signaux client standard (
X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After) ; - appliquer une stratégie Redis-down différenciée par famille de routes, avec code HTTP normatif ;
- rester conforme RGPD (minimisation des données dans Redis, diagnostic opérationnel conservé en logs).
2. Périmètre / Hors périmètre
Inclus
- Évaluation rate-limit avant exécution logique métier.
- Dimensions V1 obligatoires :
ip, user_id, tenant, route. - Politique multi-fenêtres V1 :
burst + sustained. - Garde-fou global tenant :
500 req/min (sustained). - Réponse
429 en cas de dépassement quota. - Stratégie Redis indisponible :
auth : fail-closed 503 + Retry-After read : fail-open contrôlé costly (upload/export/proof) : fail-closed 503 + Retry-After - Routes santé
/health/* et /ready/* : profil dédié très élevé (pas de whitelist). - Évaluation atomique multi-dimensions/multi-fenêtres en une seule commande Redis Lua.
- Observabilité : logs structurés + métriques exploitables.
- Configuration statique au boot (V1).
Exclu
- Anti-DDoS réseau/CDN/WAF.
- CAPTCHA.
- Device fingerprinting.
- Quotas billing/pricing.
- Limitation BullMQ/jobs.
- Whitelist/bypass ops V1.
- API key en dimension V1 (prévu V2).
- Reload dynamique de configuration sans redéploiement (V2).
3. Définitions
| Terme | Définition |
Rate-limit check | Décision d’autorisation/refus avant métier. |
burst | Fenêtre courte anti-rafale. |
sustained | Fenêtre longue anti-abus continu. |
fail-open | Requête autorisée si backend de quota indisponible. |
fail-closed | Requête refusée si backend de quota indisponible. |
Redis indisponible | Vrai si au moins une condition est vraie : timeout > 50 ms sur check quota, ou connection refused/reset, ou circuit-breaker local en état OPEN. |
identifier_raw | Identifiant canonique avant pseudonymisation (utilisé en mémoire applicative et en logs sécurité). |
identifier_hmac | hex(lower(HMAC-SHA256(secret, identifier_raw))), utilisé dans les clés Redis. |
route_key | base64url(route normalisée, sans padding) pour composition de clé Redis sûre. |
Machine d’état requête | État de décision pour une requête unique (RECEIVED -> terminal). |
Machine d’état système | État du sous-système rate-limit inter-requêtes (HEALTHY/DEGRADED/RECOVERING). |
Endpoint sensible | Endpoint avec protection plus restrictive que le profil générique. |
Endpoint coûteux | Endpoint à coût CPU/IO élevé (ex: upload/export/proof). |
4. Invariants (non négociables)
| ID | Règle | Justification |
| INV-172-01 | Pour une même entrée normalisée (route, dimension, identifier, fenêtres actives), la décision rate-limit DOIT être identique quelle que soit l’instance backend. | Cohérence inter-instance. |
| INV-172-02 | Si la décision est refus, la logique métier cible NE DOIT PAS s’exécuter. | Atomicité quota-métier. |
| INV-172-03 | Tout refus (429 ou 503 fail-closed) DOIT produire un événement observable (log structuré + métrique). | Exploitabilité opérationnelle. |
| INV-172-04 | Les endpoints sensibles DOIVENT avoir un profil plus restrictif que le profil générique sur au moins une fenêtre ou une dimension. | Hiérarchie de protection. |
| INV-172-05 | La stratégie Redis-down DOIT respecter : auth=fail-closed, read=fail-open contrôlé, costly=fail-closed. | Arbitrage sécurité/disponibilité. |
| INV-172-06 | Le modèle d’états requête (§5.4) est normatif ; toute transition non listée est interdite. | Évite ambiguïté de comportement. |
| INV-172-07 | Aucun mécanisme de whitelist/bypass n’est autorisé en V1. | Réduction du risque d’exemption abusive. |
| INV-172-08 | La configuration des seuils est figée au boot en V1. | Stabilité opérationnelle et reproductibilité. |
| INV-172-09 | Les formats de données sont définis une seule fois en §5.1 ; toute autre section y fait référence. | Source de vérité unique. |
| INV-172-10 | L’évaluation multi-dimensions/multi-fenêtres DOIT être faite en une commande Redis Lua atomique. | Évite divergences et races multi-clés. |
| INV-172-11 | Redis NE DOIT PAS contenir d’IP brute ni d’identifiants bruts ; uniquement identifier_hmac. | Minimisation RGPD. |
| INV-172-12 | Les logs sécurité PEUVENT contenir client_ip_raw pour diagnostic, avec rétention maximale 7 jours. | Besoin de diagnostic + proportionnalité RGPD. |
| INV-172-13 | /health/* et /ready/* DOIVENT utiliser un profil dédié haut (10000 req/min) et NE SONT PAS une whitelist. | Disponibilité monitoring sans bypass. |
| INV-172-14 | Les clés rate-limit ont un TTL plafonné et un budget mémoire dédié (256 MB sur db=2). | Maîtrise du risque DoS par cardinalité. |
5. Flux nominaux
5.1 Modèle de données contractuel (source de vérité unique)
| Donnée | Format / encodage | Taille / longueur | Jeu de caractères | Casse | Validation | Comportement si invalide |
route | chemin HTTP absolu normalisé (template route) | 1..256 chars | ASCII URL path | case-sensitive | ^/[A-Za-z0-9._~!$&'()*+,;=:@%/-]{1,255}$ | rejet 400 ERR-172-03 |
route_key | base64url(route UTF-8, sans '=') | 1..344 chars | [A-Za-z0-9_-] | case-sensitive | ^[A-Za-z0-9_-]{1,344}$ | rejet 500 ERR-172-04 |
dimension | enum | 1 valeur | ip\|user_id\|tenant\|route | lowercase | ^(ip|user_id|tenant|route)$ | rejet 400 ERR-172-03 |
identifier_raw | valeur canonique pré-hash | 1..256 chars | selon dimension | case-sensitive | ip: IPv4/IPv6 canonique ; user_id/tenant/route: ^[A-Za-z0-9._:@/-]{1,256}$ | rejet 400 ERR-172-03 |
identifier_hmac | hex(lower(HMAC-SHA256(secret, identifier_raw))) | 64 chars | [a-f0-9] | lowercase | ^[a-f0-9]{64}$ | rejet 500 ERR-172-04 |
redis_key | rate_limit:v1:{route_key}:{dimension}:{identifier_hmac}:{window_kind} | 32..512 chars | [A-Za-z0-9:_-] | case-sensitive | ^rate_limit:v1:[A-Za-z0-9_-]{1,344}:(ip|user_id|tenant|route):[a-f0-9]{64}:(burst|sustained)$ | rejet 500 ERR-172-04 |
window_kind | enum | 1 valeur | burst\|sustained | lowercase | ^(burst|sustained)$ | rejet 500 ERR-172-05 |
route_family | enum | 1 valeur | auth\|read\|costly | lowercase | ^(auth|read|costly)$ | rejet 500 ERR-172-05 |
route_profile | enum | 1 valeur | auth\|read_standard\|costly\|health_monitoring | lowercase | regex enum stricte | rejet 500 ERR-172-05 |
decision_state | enum | 1 valeur | ALLOWED\|THROTTLED\|BYPASS_DEGRADED\|DENIED_DEGRADED\|REJECTED_INVALID_CONTEXT | UPPERCASE | regex enum stricte | rejet 500 ERR-172-05 |
system_state | enum | 1 valeur | HEALTHY\|DEGRADED\|RECOVERING | UPPERCASE | regex enum stricte | rejet 500 ERR-172-05 |
X-RateLimit-Limit | entier décimal | 1..10 chars | [0-9] | n/a | ^[0-9]{1,10}$ | réponse non conforme |
X-RateLimit-Remaining | entier décimal | 1..10 chars | [0-9] | n/a | ^[0-9]{1,10}$ | réponse non conforme |
Retry-After | entier décimal (secondes) | 1..6 chars | [0-9] | n/a | ^[0-9]{1,6}$ | réponse non conforme |
Règles de normalisation complémentaires :
- Si
user_id est absent : valeur canonique anonymous. - Si
tenant est absent : valeur canonique public. - Si
ip ou route est absent/non fiable : ERR-172-03.
Note C-01 (résolue) :
- Exemple route valide :
/api/v1/:id. - Exemple
route_key correspondant : L2FwaS92MS86aWQ. - La clé Redis reste valide car
route n’est jamais injectée brute dans un segment séparé par :.
Note RGPD C-03 (tranchée) :
- Clé Redis : uniquement
identifier_hmac. - Logs sécurité :
client_ip_raw autorisé pour diagnostic anti-abus. - Rétention
client_ip_raw : 7 jours max, accès restreint SOC/SRE. - Base légale : intérêt légitime sécurité (RGPD art. 6(1)(f)), minimisation appliquée.
5.2 Bornes numériques obligatoires
| Paramètre | Défaut | Min | Max | Unité | Contexte | Comportement hors bornes |
redis_db_index | 2 | 0 | 15 | index DB | runtime | rejet configuration au boot |
redis_timeout_unavailable_ms | 50 | 10 | 200 | ms | définition Redis indisponible | rejet configuration au boot |
latency_rate_limit_check_p99 | 5 | 0 | 5 | ms | backend API P99 | non-conformité perf + alerte |
redis_rtt_per_check_max | 1 | 1 | 1 | RTT | décision unitaire | non-conformité perf |
dimensions_count_v1 | 4 | 4 | 4 | dimensions | V1 | rejet config si différent |
windows_per_route_v1 | 2 | 2 | 2 | fenêtres | burst+sustained | rejet config si différent |
retry_after_fail_closed_seconds | 1 | 1 | 60 | s | réponses 503 fail-closed | rejet config si hors bornes |
key_ttl_max_seconds | 3600 | 60 | 3600 | s | garde-fou anti-cardinalité | rejet config si hors bornes |
redis_memory_budget_mb_db2 | 256 | 64 | 512 | MB | budget logique rate-limit | ouverture circuit-breaker + alerte |
raw_ip_log_retention_days | 7 | 1 | 30 | jours | conformité RGPD | non-conformité sécurité/compliance |
degraded_clearing_cycles | 3 | 1 | 10 | cycles | retour HEALTHY | rejet config si hors bornes |
Profils contractuels par défaut :
| Profil | Routes cibles | Burst | Sustained | Dimensions évaluées |
auth | /auth/login, /auth/otp, /auth/refresh | 5 req / 10s | 20 req / 60s | ip,user_id,tenant,route |
costly | /documents/upload, /exports, /proof/generate | 3 req / 10s | 10 req / 60s | ip,user_id,tenant,route |
read_standard | routes lecture métier | 30 req / 10s | 120 req / 60s | ip,user_id,tenant,route |
health_monitoring | /health/*, /ready/* | 2000 req / 10s | 10000 req / 60s | ip,user_id,tenant,route |
Garde-fou global tenant (contractuel) :
| Paramètre | Valeur |
tenant_global_sustained.max_requests | 500 |
tenant_global_sustained.window_seconds | 60 |
| Scope | dimension tenant, clé route_key="__global__", fenêtre sustained |
5.3 SLA temporels (fenêtres et expiration)
| Paramètre SLA | Défaut | Min | Max | Configurabilité | Comportement à expiration |
ttl_padding_seconds | 5 | 1 | 30 | statique au boot (V1) | TTL effectif recalculé à chaque incrément |
burst_window_ttl | 15 | 11 | 3600 | dérivé (10 + padding) | compteur burst expiré puis recréé au prochain hit |
sustained_window_ttl | 65 | 61 | 3600 | dérivé (60 + padding) | compteur sustained expiré puis recréé |
tenant_global_ttl | 65 | 61 | 3600 | dérivé | guardrail tenant réévalué au prochain hit |
degraded_probe_interval_seconds | 10 | 1 | 60 | statique au boot | sonde de sortie de mode dégradé |
degraded_clearing_cycles | 3 | 1 | 10 | statique au boot | retour HEALTHY après N sondes successives |
Règle absolue : aucune clé rate_limit:v1:* ne doit survivre au-delà de key_ttl_max_seconds=3600.
5.4 Machine d’états contractuelle (requête)
États :
RECEIVED ALLOWED (terminal) THROTTLED (terminal) BYPASS_DEGRADED (terminal) DENIED_DEGRADED (terminal) REJECTED_INVALID_CONTEXT (terminal)
Transitions autorisées :
| État source | État cible | Statut | Condition | HTTP |
RECEIVED | ALLOWED | AUTORISÉE | quotas respectés | réponse métier |
RECEIVED | THROTTLED | AUTORISÉE | quota dépassé | 429 + headers RL |
RECEIVED | BYPASS_DEGRADED | AUTORISÉE | Redis indisponible + route_family=read | réponse métier |
RECEIVED | DENIED_DEGRADED | AUTORISÉE | Redis indisponible + route_family=auth|costly | 503 + Retry-After |
RECEIVED | REJECTED_INVALID_CONTEXT | AUTORISÉE | contexte invalide (ip/route invalides) | 400 |
Transitions interdites :
| État source | Transition sortante | Statut | Justification |
ALLOWED | -> * | INTERDITE | terminal |
THROTTLED | -> * | INTERDITE | terminal |
BYPASS_DEGRADED | -> * | INTERDITE | terminal |
DENIED_DEGRADED | -> * | INTERDITE | terminal |
REJECTED_INVALID_CONTEXT | -> * | INTERDITE | terminal |
5.4 bis Machine d’états système (inter-requêtes)
États :
HEALTHY DEGRADED RECOVERING
Transitions autorisées :
| État source | État cible | Condition |
HEALTHY | DEGRADED | détection Redis indisponible |
DEGRADED | RECOVERING | première sonde Redis réussie |
RECOVERING | HEALTHY | degraded_clearing_cycles sondes successives réussies |
RECOVERING | DEGRADED | échec sonde |
Clarification C-02 :
- La machine requête décide la réponse HTTP d’une requête.
- La machine système décrit la santé du sous-système quota entre requêtes.
- Elles sont distinctes et non substituables.
5.5 Atomicité multi-composant
| Scope | Synchrone/Async | Garantie |
Évaluation quotas (4 dimensions x 2 fenêtres + guardrail tenant) | Synchrone | Une seule commande Redis EVALSHA atomique |
| Émission log structuré | Async immédiat | Traçabilité refus exigée (INV-172-03) |
| Émission métrique | Async immédiat | Observabilité SRE |
| Crash pré-décision | n/a | aucune exécution métier |
| Crash post-décision / pré-observabilité | n/a | incident d’exploitation (non-conformité INV-172-03) |
Contrat Lua normatif :
- L’API calcule toutes les clés Redis nécessaires.
- L’API appelle
EVALSHA rate_limit_v1.lua une seule fois. - Le script lit toutes les fenêtres/dimensions, détecte tout dépassement, calcule
retry_after. - Si dépassement : retour
THROTTLED, aucune décision partielle multi-clé. - Sinon : incrément + TTL de toutes les clés concernées dans la même exécution atomique.
5.6 Stratégie de migration DDL
Aucune modification de schéma SQL requise pour PD-172.
Aucune migration DDL applicable.
5.7 Mécanismes de protection distribuée
| Mécanisme | Applicabilité | Contrat |
| Lock distribué | Non applicable | L’atomicité Redis Lua couvre la section critique quota. |
| Idempotence | Non applicable (requête API) | Chaque requête compte comme tentative distincte. |
| Réconciliation | Applicable | Sonde toutes les 10s; sortie de dégradé après 3 succès consécutifs. |
| Rate-limiting | Applicable | Granularité V1 obligatoire : ip + user_id + tenant + route; dépassement => 429. |
| Clearing conditionnel | Applicable | Circuit-breaker OPEN si indisponibilité répétée ou budget mémoire dépassé; fermeture selon cycles conformes. |
Checklist protection distribuée :
5.8 Contraintes inter-modules
| Élément | Contrat PD-172 |
| Routes à protéger | /auth/login, /auth/otp, /auth/refresh, /documents/upload, /exports, /proof/generate, routes lecture, /health/*, /ready/* |
| Mécanisme de protection | Contrôle rate-limit en pré-traitement route |
| Données cross-module nécessaires | request.path, request.ip, request.user.sub, request.user.tenant |
| Résolution FK cross-module | Non applicable |
| Scope d’enregistrement | Ciblé par profils de routes |
| Exceptions d’accès | Aucune exception whitelist/bypass en V1 |
5bis. Diagrammes (si applicable)
Diagramme d’état requête (stateDiagram-v2)
stateDiagram-v2
[*] --> RECEIVED
RECEIVED --> ALLOWED : quotas OK
RECEIVED --> THROTTLED : quota depasse
RECEIVED --> BYPASS_DEGRADED : redis down + route_family=read
RECEIVED --> DENIED_DEGRADED : redis down + route_family=auth|costly
RECEIVED --> REJECTED_INVALID_CONTEXT : contexte invalide
ALLOWED --> [*]
THROTTLED --> [*]
BYPASS_DEGRADED --> [*]
DENIED_DEGRADED --> [*]
REJECTED_INVALID_CONTEXT --> [*]
Diagramme d’état système (stateDiagram-v2)
stateDiagram-v2
[*] --> HEALTHY
HEALTHY --> DEGRADED : redis indisponible
DEGRADED --> RECOVERING : sonde succes
RECOVERING --> HEALTHY : N succes consecutifs
RECOVERING --> DEGRADED : sonde echec
Diagramme de séquence (sequenceDiagram)
sequenceDiagram
participant C as Client
participant API as Backend API (NestJS)
participant RL as Redis RateLimit (db=2)
participant OBS as Logs/Metrics
C->>API: HTTP request(route, ip, auth context)
API->>API: normalize route + identifier_raw
API->>API: route_key=base64url(route)
API->>API: identifier_hmac=HMAC-SHA256(secret, identifier_raw)
API->>API: compose all redis keys
API->>RL: EVALSHA rate_limit_v1.lua (single atomic command)
RL-->>API: {decision, remaining, retry_after, system_state}
alt decision=ALLOWED
API->>OBS: metric decision=ALLOWED
API-->>C: response metier + X-RateLimit-Limit + X-RateLimit-Remaining
else decision=THROTTLED
API->>OBS: log+metric decision=THROTTLED
API-->>C: 429 + X-RateLimit-* + Retry-After
else decision=BYPASS_DEGRADED
API->>OBS: log+metric decision=BYPASS_DEGRADED
API-->>C: response metier (fail-open controle)
else decision=DENIED_DEGRADED
API->>OBS: log+metric decision=DENIED_DEGRADED
API-->>C: 503 + Retry-After
end
6. Cas d’erreur
| Code | Cas | Réponse contractuelle |
ERR-172-01 | Quota dépassé (burst ou sustained) | 429 + X-RateLimit-Limit + X-RateLimit-Remaining + Retry-After |
ERR-172-02 | Redis indisponible sur route fail-closed (auth|costly) | 503 Service Unavailable + Retry-After |
ERR-172-03 | Contexte invalide (ip/route absents ou invalides) | 400 |
ERR-172-04 | Clé Redis construite hors format contractuel | 500 |
ERR-172-05 | Profil de limite manquant/invalide au boot | démarrage refusé |
ERR-172-06 | Timeout check quota (>50ms) | traité comme Redis indisponible (ERR-172-02 ou fail-open read) |
ERR-172-07 | IP client non résolue de manière fiable via chaîne proxy | 400 |
ERR-172-08 | Saturation cardinalité/mémoire (>256MB sur budget RL) | ouverture circuit-breaker + alerte + matrice fail-open/fail-closed |
7. Critères d’acceptation (testables)
| ID | Critère | Observable |
| CA-172-01 | Le quota est partagé globalement entre instances backend. | Même client atteint la limite indépendamment de l’instance. |
| CA-172-02 | Tout dépassement renvoie 429 avec headers standard. | X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After présents. |
| CA-172-03 | Les profils auth/costly/read appliquent les seuils contractuels chiffrés. | Seuils mesurés = 5/10s & 20/60s, 3/10s & 10/60s, 30/10s & 120/60s. |
| CA-172-04 | Les dimensions V1 ip,user_id,tenant,route sont exploitées. | Variation d’une dimension change la clé et la décision. |
| CA-172-05 | Deux fenêtres sont actives par route (burst + sustained). | Dépassement d’une fenêtre suffit à refuser. |
| CA-172-06 | Tout refus est observable en logs/métriques. | Événement structuré + métrique par refus. |
| CA-172-07 | Redis down applique la matrice fail-open/fail-closed. | read bypass, auth|costly en 503 + Retry-After. |
| CA-172-08 | Le check rate-limit respecte P99 <= 5 ms. | Mesure perf instrumentée. |
| CA-172-09 | L’évaluation multi-dimensions est atomique en une commande Redis. | Un seul EVALSHA par requête, pas de divergence multi-clé. |
| CA-172-10 | route avec : produit une clé Redis valide. | Ex. /api/v1/:id accepté et clé conforme regex. |
| CA-172-11 | Redis ne stocke que des identifiants pseudonymisés HMAC. | Aucune IP brute / user_id brut dans rate_limit:v1:*. |
| CA-172-12 | Les logs sécurité conservent IP brute avec rétention limitée. | client_ip_raw présent, purge <= 7 jours. |
| CA-172-13 | /health/* et /ready/* ont un profil dédié haut sans whitelist. | 10000 req/min effectif, aucun bypass dédié. |
| CA-172-14 | Budget cardinalité mémoire et TTL max sont respectés. | TTL <= 3600s, budget logique <=256MB, alerte sinon. |
| CA-172-15 | Garde-fou global tenant 500 req/min actif. | Dépassement tenant global bloque même si route individuelle sous seuil. |
8. Scénarios de test (Given / When / Then)
| ID | Given | When | Then |
| ST-172-01 | 2 instances backend actives | un même client dépasse un quota partagé | refus cohérent sur les 2 instances |
| ST-172-02 | profil route défini et quota atteint | requête supplémentaire même fenêtre | 429 + headers contractuels |
| ST-172-03 | route sensible + route générique | campagnes identiques | seuil sensible atteint plus tôt |
| ST-172-04 | dimensions V1 renseignées | variation d’une seule dimension | décision recalculée selon nouvelle clé |
| ST-172-05 | fenêtres burst et sustained actives | dépassement de burst uniquement | refus immédiat |
| ST-172-06 | refus de quota généré | consultation logs/métriques | trace exploitable présente |
| ST-172-07 | Redis indisponible + /auth/login | appel route | 503 + Retry-After |
| ST-172-08 | Redis indisponible + route lecture | appel route | réponse métier (fail-open contrôlé) |
| ST-172-09 | charge représentative | mesure latence check | P99 <= 5 ms |
| ST-172-10 | redémarrage sans changement config | trafic identique | comportement stable |
| ST-172-11 | route /api/v1/:id | génération de clé Redis | clé conforme regex redis_key |
| ST-172-12 | trafic normal | inspection clés Redis | identifiants uniquement en HMAC |
| ST-172-13 | trafic monitoring élevé /health/live | 9000 req/min | pas de throttling avant seuil contractuel |
| ST-172-14 | tenant en trafic multi-routes | total > 500 req/min | throttling tenant global |
| ST-172-15 | budget mémoire RL > 256MB | nouvelle vague de requêtes | circuit-breaker OPEN + alerte + matrice dégradée |
| ST-172-16 | état système DEGRADED | 3 sondes consécutives réussies | transition RECOVERING -> HEALTHY |
9. Hypothèses explicites
| ID | Hypothèse | Impact si faux |
| H-172-01 | La normalisation des templates de route NestJS est déterministe. | Risque de clés divergentes entre instances. |
| H-172-02 | La chaîne proxy de confiance est correctement configurée. | Risque d’erreurs IP (ERR-172-07). |
| H-172-03 | L’infrastructure permet la purge stricte des logs IP bruts à 7 jours. | Risque de non-conformité RGPD. |
| H-172-04 | Le budget mémoire rate-limit est monitorable sur le périmètre dédié. | Détection tardive de saturation cardinalité. |
10. Points clarifiés
10.1 Contraintes techniques (stack réelle)
| Élément | Valeur contractuelle |
| Projet cible | ProbatioVault-backend |
| Langage principal | TypeScript |
| Framework backend | NestJS |
| ORM | TypeORM |
| Base de données métier | PostgreSQL |
| Backend rate limiting distribué | Redis |
| Files/jobs (hors périmètre PD-172) | BullMQ |
10.2 Décisions tranchées dans cette version
| ID historique | Décision normative V2 |
| Q-172-01 / Q-172-02 | Seuils burst/sustained fournis par profil (auth, costly, read, health) |
| Q-172-03 | Fail-closed fixé à 503 Service Unavailable + Retry-After |
| Q-172-05 | identifier_hmac en Redis, IP brute uniquement en logs sécurité (7 jours max) |
| Q-172-06 | /health/* et /ready/* : profil dédié 10000 req/min, pas de whitelist |
| C-05 / H-04 | Atomicité multi-clé contractualisée via script Lua unique |
| A-01 | Redis indisponible défini formellement (timeout>50ms ou connection refused/reset ou breaker OPEN) |
| R-02 | TTL max des clés + budget mémoire 256MB sur db=2 contractualisés |
| DIV-01 | Référentiel Epic harmonisé vers PD-186 |
10.3 Questions résiduelles (non bloquantes)
| ID | Point résiduel | Impact |
| Q-172-04 | Liste exhaustive des routes read métier | couverture fonctionnelle à finaliser côté PO |
| Q-172-09 | Cardinalité maximale des labels métriques | tuning observabilité / coût |
| Q-172-11 | Détail fin de stratégie proxy trust multi-environnements | robustesse IP canonique |
10.4 Learnings contextuels intégrés
| Source | Learning intégré | Effet contractuel |
| PD-180 | Le rate-limit ne doit pas perdre d’événements en charge ; observabilité obligatoire. | INV-172-03 + CA-172-06/07 renforcés. |
| PD-81 | Toute logique temporelle doit formaliser SLA min/max/default. | §5.3 entièrement borné (plus de NON FOURNI). |
Références
- Epic :
PD-186 — Backend Core sécurisé et modulaire - JIRA :
PD-172 - Repos concernés :
ProbatioVault-backend - Documents associés :
docs/epics/backend-core/PD-172-rate-limiting-redis/PD-172-besoin.md docs/epics/backend-core/PD-172-rate-limiting-redis/PD-172-clarifications.md src/config/rate-limit.config.ts src/config/config.schema.ts src/common/middleware/global-rate-limit.middleware.ts