E-NO
Express API capacity plan... 7 min de lecture

Planification de capacité d’une API Express avec des exemples pratiques

calendar_today Publié : 2026-08-19
update Dernière mise à jour : 2026-08-19
analytics Efficacité SEO : 100%
Illustration du guide technique pour « Planification de capacité d’une API Express avec des exemples pratiques ».

Intro

La planification de capacité d’une API Express consiste à transformer un comportement observé en performance prévisible sous charge. Ce guide montre comment dresser l’inventaire de votre environnement, choisir la plus petite modification sûre, vérifier les résultats avec des commandes concrètes et revenir en arrière si les attentes ne sont pas atteintes. Il s’adresse aux développeurs, aux ingénieurs DevOps et aux équipes de startups exploitant Express en production.

Nous relierons des choix de mise à l’échelle (nombre de processus, CPU/mémoire, pools de connexions) à des signaux mesurables (RPS, percentiles de latence, taux d’erreur, lag de la boucle d’événements) avec des exemples pratiques. Le principe opératoire est simple : observer d’abord, changer un seul élément bien circonscrit, vérifier, et conserver un chemin de reprise clair.

Inventaire de la version et de l’environnement

Objectif : nommer précisément le composant testé, sa version, où il s’exécute et quelles ressources il possède avant tout changement. Rester en lecture seule.

  • Composant : API HTTP Express (runtime Node.js)
  • Dépendances typiques : OS/conteneur/Kubernetes, équilibreur de charge, base de données/cache, courtier de messages
  • Données à capturer : version, topologie, CPU/mémoire, nombre d’instances, RPS/latence de base, taux d’erreurs

Prérequis

  • Accès shell à l’hôte/conteneur ou au namespace Kubernetes
  • Capacité à exécuter des commandes en lecture seule ; ne pas exposer de secrets

Commandes d’inventaire en lecture seule (exécutez ce qui s’applique à votre contexte)

# Versions
node -v
npm -v
npm ls express --depth=0  # version d’Express installée

# Ressources hôte (Linux)
uname -a
lscpu | egrep 'Model name|CPU\(s\)'
free -h

# Topologie conteneur/Kubernetes
docker ps --filter "name=<SERVICE_NAME>"
docker stats <CONTAINER_ID_OR_NAME>

kubectl -n <NAMESPACE> get deploy <DEPLOYMENT_NAME> -o wide
kubectl -n <NAMESPACE> get pods -l app=<APP_LABEL> -o wide
kubectl -n <NAMESPACE> top pods -l app=<APP_LABEL>

# Accessibilité externe et santé basique
curl -sS -o /dev/null -w "%{http_code}\\n" http://<HOST_OR_LB>/healthz

Instantané de performances de base (non intrusif)

  • Si vous exposez déjà des métriques (ex. Prometheus), capturez : requests_total, request_duration_seconds p50/p95/p99, error_ratio, CPU %, mémoire RSS, pauses GC, lag de la boucle d’événements.
  • Sans métriques, relevez la latence moyenne de réponse depuis les logs de l’équilibreur de charge et le nombre d’instances actuel.

Définir des attentes avant intervention

  • Exemple d’objectif SLO (Service Level Objective) : p95 latence ≤ 200 ms à 500 RPS, taux d’erreur < 1 %, CPU < 75 % soutenu, lag p90 de la boucle d’événements < 30 ms.
  • Exemples de signaux d’échec : p95 au‑delà de la cible pendant > 5 minutes, pics d’erreurs (429/500/503), OOM kills, saturation du pool BD.

Hygiène des données

  • Ne jamais imprimer ni coller de secrets. Masquez les chaînes de connexion et identifiants avec des espaces réservés comme <PG_URI>, <SERVICE_NAME>, <NAMESPACE>.

Chemin de configuration sûr

Objectif : changer une chose à la fois, avec un rayon d’impact connu et un retour arrière testé. Voici des changements courants et peu risqués liés à la capacité d’Express.

  1. Utiliser plusieurs processus worker (un par cœur CPU)
  • Quand : un seul processus Node.js est limité par le CPU ou le lag de la boucle d’événements augmente avec la charge.
  • Prérequis : gestionnaires sans état, ou sessions collantes côté équilibreur si vous utilisez un état en mémoire.
  • Changement minimal avec PM2 :
pm2 start app.js -i max --name <SERVICE_NAME>  # un worker par cœur CPU
pm2 list
  • Vérification : le RPS augmente ~linéairement avec les cœurs pour les routes CPU‑bound ; CPU par worker ~60–75 % sous la charge cible ; percentiles de latence stables.
  • Reprise : pm2 delete <SERVICE_NAME> puis redémarrer l’ancien modèle de processus, ou pm2 scale <SERVICE_NAME> <PREVIOUS_COUNT>.

Alternative avec le module cluster de Node.js (changement de code requis)

const cluster = require('node:cluster');
const os = require('node:os');

if (cluster.isPrimary) {
  const num = Number(process.env.WORKERS || os.cpus().length);
  for (let i = 0; i < num; i++) cluster.fork();
} else {
  // démarrez votre app Express ici
}
  1. Définir des timeouts HTTP sûrs et activer les keep‑alives
  • Quand : beaucoup de requêtes courtes ou de churn de connexions ; ou des clients lents monopolisent des sockets.
  • Changement minimal (dans le bootstrap du serveur) :
const server = app.listen(process.env.PORT || 3000, () => {
  console.log('listening');
});
server.keepAliveTimeout = 60000;      // connexions persistantes 60s
server.headersTimeout = 65000;        // doit être > keepAliveTimeout
server.requestTimeout = 30000;        // 30s max par requête (à ajuster par endpoint)
  • Rayon d’impact : affecte la réutilisation des connexions et les clients lents ; coordonner avec les timeouts de l’équilibreur amont.
  • Vérification : le nombre de sockets se stabilise, moins de pics TIME_WAIT/ESTABLISHED ; latence inchangée ou améliorée.
  • Reprise : restaurer les valeurs précédentes et recharger.
  1. Dimensionner correctement les pools de connexions base de données
  • Quand : la BD est le goulot d’étranglement ou vous observez des timeouts d’épuisement du pool.
  • Changement minimal (exemple PostgreSQL avec pg) :
const { Pool } = require('pg');
const pool = new Pool({
  connectionString: process.env.PG_URI,      // <PG_URI>
  max: Number(process.env.PG_POOL_MAX || 20),
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000
});
  • Recommandation : commencer avec pool_size = min(2 x cœurs_CPU, pool_autorisé_par_la_BD / nombre_d’instances). Des pools trop grands dégradent le débit de la BD.
  • Vérification : moins de ETIMEDOUT/attente de pool ; CPU BD < 75 % ; latence des requêtes en baisse.
  • Reprise : revenir à PG_POOL_MAX antérieur et recharger l’app.
  1. Ajouter un limiteur de concurrence simple pour protéger les backends
  • Quand : un trafic en rafale submerge la BD ou un service amont.
  • Changement minimal côté code (faible rayon d’impact) :
let inFlight = 0;
const MAX_IN_FLIGHT = Number(process.env.MAX_IN_FLIGHT || 100);
app.use((req, res, next) => {
  if (inFlight >= MAX_IN_FLIGHT) return res.status(503).send('busy');
  inFlight++;
  res.on('finish', () => { inFlight--; });
  next();
});
  • Vérification : le taux d’erreur peut augmenter temporairement (503), mais la p95 de latence et la saturation BD s’améliorent ; la mise en file se fait en périphérie/au LB plutôt qu’à l’intérieur de l’app.
  • Reprise : retirer le middleware ou abaisser MAX_IN_FLIGHT.
  1. Fixer explicitement les limites mémoire du processus
  • Quand : pauses GC ou OOM kills sous charge.
  • Changement minimal (flag au démarrage) :
node --max-old-space-size=1024 app.js   # ~1 Go de heap, à ajuster selon la mémoire du conteneur
  • Vérification : la RSS reste sous la limite du conteneur ; moins de blocages GC.
  • Reprise : revenir à la valeur précédente et redémarrer.

Vérification et diagnostics

Objectif : confirmer qu’un changement améliore la capacité sans régresser la stabilité. Mesurer avant/après avec la même méthode.

Vérifications non perturbatrices

  • CPU/mémoire : top, htop, docker stats, kubectl top pods
  • Pression sur les sockets (Linux) : ss -s et ss -tan state established | wc -l
  • Latence et taux d’erreur dérivés des logs de votre LB ou passerelle API

Tests de charge (pré‑prod ou fenêtre contrôlée uniquement)

  • Exemple Autocannon :
npx autocannon -c 50 -d 60 -p 10 http://<HOST_OR_LB>/api/<ENDPOINT>
  • Exemple wrk :
wrk -t4 -c200 -d60s http://<HOST_OR_LB>/api/<ENDPOINT>
  • À comparer : RPS, p50/p95/p99 de latence, taux de non‑2xx/5xx. Maintenir tout le reste constant entre les runs.

Lag de la boucle d’événements (instrumentation minimale)

const { monitorEventLoopDelay } = require('node:perf_hooks');
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
setInterval(() => {
  console.log(`eventLoopLag_p90_ms=${(h.percentile(90) / 1e6).toFixed(1)}`);
}, 10000);
  • Attente : p90 < 30 ms pour des APIs saines ; des pics suggèrent du travail CPU synchrone ou une pression GC.

Endpoint de stats de debug simple (protégez‑le en production)

app.get('/debug/stats', (req, res) => {
  const mu = process.memoryUsage();
  res.json({
    pid: process.pid,
    rss: mu.rss,
    heapUsed: mu.heapUsed,
    eventLoopLag_p90_ms: Number((h.percentile(90) / 1e6).toFixed(1))
  });
});
  • Vérification : confirme l’empreinte au niveau processus sous charge et aide à corréler avec la latence.

Calcul rapide de capacité

  • Estimer la concurrence avec la loi de Little : Concurrents = RPS × LatenceMoyenneSecondes. Exemple : 500 RPS × 0,1 s = 50 en vol.
  • Si chaque requête consomme ~1 connexion BD et que votre pool max est 20, attendez‑vous à de la file d’attente sauf à ajouter des workers ou augmenter le pool (uniquement si la BD le supporte).

Modes de défaillance et reprise

Symptômes fréquents et quoi faire :

  • Rafales 503 Service Unavailable sous charge
  • Cause : pics de concurrence au‑delà de la capacité des workers ou de la BD
  • Actions : augmenter les workers (pm2 scale <SERVICE_NAME> <N>), ajouter un limiteur de concurrence (voir ci‑dessus), confirmer les retries et backoff du LB
  • Reprise : revenir au dernier N stable ; vérifier que la p95 revient à la ligne de base
  • Erreurs 500 avec ECONNRESET ou ETIMEDOUT
  • Cause : timeouts de dépendances amont ou timeouts serveur trop agressifs
  • Actions : aligner server.requestTimeout sur les SLA des dépendances ; s’assurer que le timeout de connexion BD < timeout HTTP ; ajuster la politique de retry côté client/LB
  • Reprise : restaurer les timeouts précédents et retester
  • Pics mémoire et OOM kills
  • Cause : gros payloads, caches non bornés, fuites mémoire
  • Actions : définir --max-old-space-size, plafonner les payloads (express.json({ limit: '1mb' })), profiler les allocations hors pic
  • Reprise : réduire le trafic (diminuer les réplicas derrière le LB), redémarrer les workers proprement (pm2 reload <SERVICE_NAME>), puis avancer avec des limites
  • Trop de fichiers/sockets ouverts (EMFILE)
  • Cause : explosion de connexions ou ulimit trop bas
  • Actions : augmenter la limite soft pour l’utilisateur du service et le conteneur ; activer keep‑alive et réutiliser les connexions
  • Reprise : réduire temporairement les connexions (baisser la charge), puis augmenter les limites ; exemple pour une session shell : ulimit -n 65535 (rendre persistant via la config du service)
  • Effet « thundering herd » induit par le déploiement
  • Cause : redémarrage simultané de tous les pods/processus
  • Actions : utiliser des redémarrages roulants (pm2 reload ou mise à jour rolling Kubernetes avec limites surge/unavailable)
  • Reprise : annuler le déploiement
kubectl -n <NAMESPACE> rollout status deploy/<DEPLOYMENT_NAME>
kubectl -n <NAMESPACE> rollout undo deploy/<DEPLOYMENT_NAME>

Liste de contrôle des opérations

À utiliser avant chaque changement de capacité :

  • Identifier
  • Noter l’horodatage, les versions de Node et d’Express, le nombre d’instances, CPU/mémoire par instance
  • Noter les SLOs et la base actuelle : RPS, latence p95/p99, taux d’erreur, CPU %, mémoire RSS, lag de la boucle d’événements
  • Observer (lecture seule)
  • Confirmer que /healthz répond 200
  • Capturer docker stats ou kubectl top pods
  • Prendre un instantané des sockets avec ss -s
  • Planifier un changement
  • Choisir le changement minimal (workers, taille de pool, timeouts, plafond mémoire, limiteur de concurrence)
  • Définir le rayon d’impact et la commande de rollback exacte
  • Définir à l’avance l’amélioration attendue et le signal d’échec
  • Appliquer et vérifier
  • Appliquer le changement (ex. pm2 scale <SERVICE_NAME> <N>, mise à jour d’ENV + redémarrage)
  • Exécuter la même commande de test de charge ou observer le trafic réel pendant une fenêtre fixe
  • Comparer les métriques côte à côte ; tenir un journal de notes
  • Décider et documenter
  • Si amélioration, conserver et documenter la nouvelle norme avec le périmètre de version
  • Si dégradation, exécuter immédiatement le rollback et consigner le mode de défaillance

Conclusion

La planification de capacité pour une API Express devient fiable quand chaque étape est bornée par la version, observable et réversible. Commencez par un inventaire : confirmez les versions de Node/Express, la topologie et la performance actuelle. Appliquez le plus petit changement sûr (workers, timeouts, tailles de pool, limiteurs de concurrence, plafonds mémoire) et vérifiez avec des mesures répétables : RPS, percentiles de latence, taux d’erreur, CPU/mémoire, lag de la boucle d’événements. Si le signal attendu n’apparaît pas, revenez en arrière, enregistrez ce qui s’est passé et essayez le prochain plus petit changement.

Choisissez aujourd’hui une vérification à faible risque : capturez votre base, lancez un court test de charge sur un endpoint à fort trafic et confirmez que vous pouvez augmenter et diminuer les workers en toute sécurité. Une boucle opérationnelle fiable rend l’échec visible, protège les valeurs sensibles, limite le rayon d’impact et répète la reprise avant qu’un incident ne l’exige.

Recherches connexes

Score de qualité de l’article

Utilité pour le lecteur 100%
  • check_circle Guide prêt à lire
  • check_circle Exemples pratiques inclus
  • check_circle URL d’article optimisée pour le SEO