Tests de montée en charge Rapport de campagne — Tests de montée en charge OCPP WeGo Date : 28 mai 2026 Environnement : Préprod ( ocpp.preprod.we-go.pro ) Réalisé par : Brian KAZMIEROWSKI Destinataires : Direction Technique, Direction régional, SpikeeLabs 1. Contexte et objectif Contexte La plateforme WeGo OCPP est le socle de communication temps réel entre les bornes de recharge et le back-office. Chaque borne maintient une connexion WebSocket permanente vers le CSMS (Charging Station Management System) pour échanger les messages OCPP 1.6 : démarrage/arrêt de charge, remontée de consommation, heartbeat, etc. Avec la croissance du parc, il est essentiel de valider la capacité de la plateforme à absorber un nombre élevé de bornes simultanées sans dégradation de service. Objectif Déterminer la capacité maximale de la stack actuelle en nombre de bornes connectées simultanément, identifier les goulots d'étranglement , et les corriger progressivement jusqu'à atteindre 20 000 bornes. Méthodologie Outil d'injection : k6 avec extension OCPP custom (protocole WebSocket natif) Scénario : montée progressive par paliers de 5% toutes les 6 secondes Cycle de vie simulé : connexion → BootNotification → StatusNotification → Authorize → StartTransaction → MeterValues → StopTransaction Critère d'arrêt automatique : taux de succès global < 95% Métrique principale : nombre de bornes connectées simultanément avec latence OCPP < 30s au p95 2. Synthèse des résultats Progression de la capacité Étape Capacité maximale Latence OCPP p95 Facteur limitant État initial ~500 bornes Non mesurable Erreurs de données Après correction du dataset ~2 200 bornes 137 ms ✅ Nginx — connexions WebSocket Après correction Nginx 5 000 bornes ✅ 2 s Backend applicatif Test à 10 000 bornes 4 200 bornes 5,9 s Saturation CPU backend Résultat clé La capacité de la plateforme a été multipliée par 10 (de ~500 à 5 000 bornes simultanées) grâce aux corrections infrastructure et données, sans aucune modification du code applicatif. Bornes connectées simultanément │ 5000 ┤ ████████████████████████████████████████████████ 4 999 ✅ │ 4000 ┤ ███████████████████████████████████████████ 4 172 (test 10K) │ 3000 ┤ │ 2000 ┤ ████████████████████ 2 200 (plafond Nginx) │ 1000 ┤ │ 500 ┤ █████ ~500 (état initial) │ 0 ┼────────────────────────────────────────────────── Initial Dataset Nginx fix Test 10K 3. Problèmes identifiés et corrections apportées 3.1 — Dataset de test incohérent Problème : les bornes de test existantes avaient des structures hétérogènes (connecteurs avec des identifiants variés : 1, 2, 10…), ce qui provoquait des échecs systématiques dans les scénarios k6. Correction : génération d'un jeu de données dédié de 20 000 bornes avec une structure homogène (préfixe LOADTEST.CBID.* , 2 connecteurs par borne, autorisations RFID configurées). Un pool de 5 875 badges RFID a été constitué à partir des autorisations existantes. Impact : élimination de 100% des erreurs liées aux données de test. 3.2 — Épuisement des badges RFID sous charge Problème : le mécanisme de distribution des badges RFID dans l'injecteur était linéaire — une fois le stock épuisé, les VUs restaient bloquées indéfiniment, faussant les métriques. Correction : mise en place d'un recyclage circulaire des badges, permettant à chaque VU de réutiliser ses RFID quand le stock est épuisé. Impact : suppression totale des erreurs rfid_missing et des VUs bloquées. 3.3 — MariaDB sous-dimensionnée Problème : la base de données fonctionnait avec une configuration par défaut inadaptée à la charge visée. Correction : Paramètre Avant Après Buffer pool InnoDB 128 Mo 4 Go Connexions max 151 1 000 Slow query log Désactivé Activé (seuil : 1s) Impact : réduction de la latence DB et capacité de diagnostic des requêtes lentes. Un backup complet de la configuration a été réalisé avant modification. La procédure de rollback est documentée. 3.4 — Nginx — Plafond de connexions WebSocket (cause racine principale) Problème : les connexions WebSocket étaient plafonnées à environ 2 200 bornes . Au-delà, Nginx refusait toute nouvelle connexion avec l'erreur : 4096 worker_connections are not enough while connecting to upstream Explication : chaque WebSocket proxifiée par Nginx consomme 2 descripteurs de fichier (un côté client, un côté backend). Avec worker_connections = 4096 , la capacité réelle était de 4096 / 2 = 2 048 connexions WebSocket par worker. De plus, toutes les connexions de l'injecteur (IP unique) étaient routées vers un seul worker Nginx, concentrant la saturation. Correction : Paramètre Avant Après worker_connections 4 096 65 535 Distribution inter-workers Désactivée Activée ( reuseport ) Capacité théorique ~2 048 WS ~458 000 WS Impact : +127% de capacité immédiate — de 2 200 à 5 000 bornes connectées, avec 0% d'erreur de connexion. Configuration rollbackable. Backups des fichiers originaux conservés sur le serveur. 3.5 — Limites de fichiers ouverts sur l'injecteur Problème : le conteneur d'injection était limité à 10 000 descripteurs de fichier, insuffisant pour simuler plus de 10 000 bornes. Correction : augmentation à 65 535 descripteurs. Impact : permet la simulation jusqu'à 20 000+ bornes depuis un seul injecteur. 3.6 — Scripts d'injection k6 Problème : les scripts ne disposaient pas de gardes contre les débordements de dataset ni de compteurs d'erreurs exploitables. Correction : Garde anti-débordement (au-delà de 20 000 bornes) Compteurs dédiés : rfid_missing , stucked_vu , dataset_out_of_range Alternance automatique des connecteurs 1 et 2 Impact : résultats de test fiables et exploitables. 4. Analyse du test à 5 000 bornes (résultat de référence) Résultats Métrique Valeur Bornes connectées 4 999 / 5 000 (99,98%) Taux de connexion 100% — 0 échec Taux de succès global 99,04% Latence OCPP p95 1,99 s Latence OCPP médiane 139 ms Transactions démarrées 436 Transactions terminées 33 Bilan par composant Composant État sous charge Verdict Nginx (14 workers) Aucune erreur, connexions distribuées ✅ Sain OCPP Gateway 11% CPU, 440 Mo RAM ✅ Confortable Worker NestJS 104% CPU ⚠️ Limite atteinte Backend API 89% CPU ⚠️ Chargé MariaDB Latence stable, slow log actif ✅ Sain Redis Stable ✅ Sain 5. Analyse du test à 10 000 bornes Résultats Métrique 5 000 bornes 10 000 bornes Bornes connectées 4 999 4 172 Taux de connexion 100% 95% Authorize success 89% 15% ❌ Latence p95 2 s 5,9 s Cause d'arrêt Fin normale Seuil qualité franchi Diagnostic Le backend applicatif (NestJS) sature au-delà de ~4 000 bornes : Le worker NestJS consomme 100% d'un cœur CPU dès 5 000 bornes Les requêtes d'autorisation ( Authorize ) expirent en timeout La latence OCPP triple entre 5 000 et 10 000 bornes Conclusion : l'infrastructure réseau et base de données tiennent la charge. Le facteur limitant est désormais exclusivement le backend applicatif . 6. Cartographie des goulots d'étranglement flowchart LR A["Dataset\n(corrigé ✅)"] --> B["Nginx\n(corrigé ✅)"] B --> C["Backend NestJS\n⚠️ PROCHAIN"] C --> D["MariaDB\n(tuné ✅)"] C --> E["Redis\n(OK ✅)"] style A fill:#4ade80,stroke:#166534,color:#000 style B fill:#4ade80,stroke:#166534,color:#000 style C fill:#fbbf24,stroke:#92400e,color:#000 style D fill:#4ade80,stroke:#166534,color:#000 style E fill:#4ade80,stroke:#166534,color:#000 Rang Composant Statut Seuil Action 1 Dataset ✅ Corrigé 500 20 000 bornes prêtes 2 Nginx WebSocket ✅ Corrigé 2 200 Capacité 458K+ 3 MariaDB ✅ Tuné N/A 4 Go buffer, 1 000 conn. 4 Backend NestJS ⚠️ À traiter ~4 200 Voir section 7 5 Scalabilité horizontale Non démarré ~10 000 Voir section 7 7. Recommandations pour atteindre 20 000 bornes Priorité 1 — Optimisation applicative (impact estimé : +50 à 100%) Des points d'optimisation ont été identifiés dans le code NestJS lors de l'audit : Optimisation Description Impact attendu Suppression d'un double appel d'autorisation La vérification checkInternalGroupAuthorization est appelée deux fois lors de chaque Authorize Réduction de 50% de la charge DB sur les autorisations Remplacement save() par insert() Les MeterValues utilisent un save() TypeORM qui effectue un SELECT avant chaque INSERT Réduction significative des requêtes DB Correction d'un crash silencieux checkInternalGroupAuthorization peut crasher sur undefined Stabilité accrue sous forte charge Ces corrections ne nécessitent pas de changement d'architecture et peuvent être déployées rapidement. Priorité 2 — Scalabilité horizontale du backend Si les optimisations applicatives ne suffisent pas pour atteindre 10 000+ bornes : Ajout d'instances OCPP derrière un load balancer WebSocket (sticky sessions) Augmentation des ressources CPU du serveur backend (actuellement 4 cœurs) Séparation des workers : dédier des conteneurs aux traitements asynchrones (MeterValues, facturation) Priorité 3 — Infrastructure d'injection Pour les tests au-delà de 5 000 bornes : L'injecteur actuel (8 Go RAM) est insuffisant pour 10 000+ bornes Solution : utiliser une machine avec 16 Go+ ou distribuer sur plusieurs injecteurs (mécanisme déjà prévu dans l'outil) 8. Synthèse des risques Risque Probabilité Impact Mitigation Panne WebSocket en prod avec croissance du parc Élevée si non corrigé Bornes déconnectées Appliquer le fix Nginx en prod Saturation backend au-delà de 4 000 bornes actives Moyenne Dégradation du service (timeouts) Optimisations NestJS + scaling Requêtes lentes non détectées Faible (slow log activé) Latence progressive Monitoring continu 9. Conclusion Cette campagne a permis de multiplier par 10 la capacité de la plateforme (de ~500 à 5 000 bornes) sans modification du code applicatif, uniquement par corrections infrastructure et données. Le prochain palier (10 000 à 20 000 bornes) nécessitera des optimisations du backend NestJS , déjà identifiées et prêtes à être implémentées. [!IMPORTANT] Action immédiate recommandée : vérifier et appliquer le correctif Nginx ( worker_connections ) en production. Sans cette correction, la prod est exposée au même plafond de ~2 200 connexions WebSocket. Rapport généré le 28 mai 2026 — Mis à jour le 1er juin 2026