Rate Limiting
Le rate limiting est le mecanisme le plus critique d'Opcovia. Les APIs OPCO ont des limites strictes (ex : 60 req/min), et Opcovia peut avoir plusieurs instances qui partagent ce budget.
Vue d'ensemble
┌─────────────────────────────────────────────────────┐
│ Hub Go (centralise) │
│ │
│ Token Bucket adaptatif par OPCO │
│ ┌───────────────────────────────┐ │
│ │ tokens: 45.2 / max: 60 │ │
│ │ refillRate: 0.85 tokens/sec │ │
│ │ slots actifs: 3 / max: 5 │ │
│ │ queue: 2 en attente │ │
│ └───────────────────────────────┘ │
│ │
│ Broadcast rateConfig a toutes les instances : │
│ { refillRate/N, maxTokens/N, ceiling/N } │
│ │
└───────┬───────────────┬───────────────┬──────────────┘
│ WebSocket │ WebSocket │ WebSocket
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Instance 1│ │Instance 2│ │Instance 3│
│ │ │ │ │ │
│ Local │ │ Local │ │ Local │
│ Bucket │ │ Bucket │ │ Bucket │
│ (backup) │ │ (backup) │ │ (backup) │
└──────────┘ └──────────┘ └──────────┘Token Bucket adaptatif (Hub)
Le hub maintient un token bucket par OPCO avec ajustement dynamique du budget :
adjustBudget : reaction aux status HTTP
| Status recu | Multiplicateur rate | Multiplicateur max | Raison |
|---|---|---|---|
| 429 | x 0.7 | x 0.7 | OPCO surcharge, reduction forte |
| timeout (status=0) | x 0.8 | x 0.8 | OPCO lent, reduction moyenne |
| 5xx | x 0.9 | x 0.9 | Erreur serveur, reduction legere |
| 2xx | x 1.05 | x 1.05 | Succes, recuperation progressive |
Planchers : refillRate >= 0.01, maxTokens >= 1. Les ajustements ne peuvent pas descendre en dessous. Plafonds : ne depassent jamais la config d'origine (maxPerMin / 60 pour le rate, maxPerMin pour les tokens).
Recuperation temporelle (+0.5%/sec)
Independamment des reports, le hub augmente passivement le rate de +0.5% par seconde :
// Toutes les secondes dans processQueuesLoop
opco.RefillRate = min(maxRate, opco.RefillRate * 1.005)
opco.MaxTokens = min(maxConfig, opco.MaxTokens * 1.005)Cela permet une recuperation naturelle meme sans trafic. Apres un 429, il faut environ 90 secondes pour revenir a 100% du budget.
Flux connecte (hub disponible)
Broadcast rateConfig
Quand le rate change de plus de 1%, le hub broadcast un rateConfig a toutes les instances connectees pour cet OPCO :
┌─────────────────────────────────────────────────────────┐
│ Hub : OPCO opco-ep (3 instances) │
│ │
│ Rate global : 0.85 tokens/sec, max 51 tokens │
│ │
│ Broadcast a chaque instance : │
│ { │
│ refillRate: 0.85/3 = 0.283 tokens/sec (actuel) │
│ maxTokens: 51/3 = 17 tokens (actuel) │
│ maxRefillRate: 1.0/3 = 0.333 tokens/sec (plafond) │
│ maxMaxTokens: 60/3 = 20 tokens (plafond) │
│ } │
└─────────────────────────────────────────────────────────┘La division par N (nombre d'instances) garantit que meme si toutes les instances consomment au max, le budget global est respecte.
Flux deconnecte (hub indisponible)
Quand le hub est indisponible, chaque instance bascule sur son local rate limiter :
Instance Opcovia (hub deconnecte)
┌──────────────────────────────────────────────┐
│ │
│ pipeline.acquire() │
│ │ │
│ ├─ hubClient.connected? → NON │
│ │ │
│ └─ localRateLimiter.acquire(opcoId) │
│ │ │
│ ├─ tokens >= 1 ? → consomme, passe │
│ │ │
│ └─ tokens < 1 ? → ATTEND │
│ waitMs = deficit / refillRate │
│ (ne rejette JAMAIS) │
│ │
│ Header envoye : Opcovia-Hub-Token: DISCONNECTED │
│ │
└──────────────────────────────────────────────┘Le local rate limiter :
- Utilise la derniere config recue du hub (
refillRate,maxTokens) - Applique les memes coefficients d'ajustement (429 → x0.7, etc.)
- Applique la meme recuperation temporelle (+0.5%/sec)
- Ne rejette jamais : si pas de tokens, il calcule le temps d'attente et fait un
setTimeout
Scenario de recuperation : hub deconnecte 1h
t=0 Hub envoie rateConfig: { refillRate: 0.28, maxTokens: 17, ceiling: 0.33, ceilMax: 20 }
t=5s Hub se deconnecte
Instance continue avec le local rate limiter
t=60s Instance recoit des 429 de l'OPCO
localRateLimiter.reportStatus(429) → rate x0.7 = 0.196
t=120s Recuperation temporelle : rate monte a ~0.24
Les 2xx arrivent → rate x1.05 progressivement
t=3600s (1h) Rate a naturellement converge vers le ceiling (0.33)
grace a la recuperation +0.5%/sec
t=3601s Hub se reconnecte
Nouveau broadcast rateConfig avec les valeurs actuelles
Local bucket se resynchroniseLe ceiling (maxRefillRate, maxMaxTokens) est la valeur maximale que le local rate limiter peut atteindre. Il empeche une instance isolee de consommer plus que sa part (configMax / N).
Interaction avec le pipeline
// Dans pipeline.ts, etape 4 :
if (hubClient?.connected) {
// Mode connecte : le HUB decide quand on peut appeler
grant = await hubClient.acquire(opcoId, method, path, reason)
// → attend le grant (pas de timeout, le hub controle le rythme)
} else if (hubClient) {
// Mode deconnecte : fallback local
await localRateLimiter.acquire(opcoId)
// → attend si pas de tokens (ne rejette jamais)
} else {
// Pas de hub du tout : rate limiter Redis classique
acquired = await rateLimiter.acquire(opcoId)
// → rejette si budget epuise (429)
}Priorite des requetes dans le hub
Le hub utilise une priority queue (min-heap) :
| Priorite | Type | Exemples |
|---|---|---|
| 1 | POST | Transmission dossier, facture |
| 2 | GET dossier | Lecture dossier individuel |
| 3 | GET batch/sync/poll | etats, liste, sync, polling |
A priorite egale, FIFO (premier arrive, premier servi).
Broadcast sur deconnexion d'une instance
Quand une instance se deconnecte, le hub redistribue le budget entre les instances restantes :
Slot TTL et validFor
Quand le hub accorde un grant, il alloue un slot avec un TTL :
validFor = avg_duration_route * 2
min 5s, max 60s
default 30s (premiere requete, pas d'historique)Si l'instance ne renvoie pas de report avant l'expiration du TTL, le slot est automatiquement libere. Cela empeche les slots fantomes de bloquer le budget.
La duree moyenne par route est calculee avec un exponential moving average :
avg = avg * 0.9 + durationMs * 0.1