Introduction
La planification de la capacité d'une API REST consiste à prévoir et à provisionner suffisamment de ressources (calcul, mémoire, stockage et réseau) pour absorber le trafic attendu tout en respectant les objectifs de latence et de disponibilité. Ce n'est pas une estimation ponctuelle, mais une boucle continue de mesure, de test et d'ajustement. Cet article fournit des exemples pratiques destinés aux développeurs, ingénieurs DevOps et équipes techniques qui exploitent des API REST, en particulier celles construites avec Node.js, Express et MongoDB.
La planification de la capacité est souvent confondue avec l'optimisation des performances. La planification de la capacité répond à la question : « Combien de requêtes par seconde mon infrastructure actuelle peut-elle traiter avant de se dégrader ? » L'optimisation des performances répond à : « Comment rendre une requête plus rapide ? » Les deux sont importantes, mais cet article se concentre sur la première. Nous couvrons des techniques concrètes : tests de charge, surveillance des ressources, mise à l'échelle horizontale, limitation de débit et reprise après incident.
Vous apprendrez à :
- Inventorier votre environnement API actuel et ses versions.
- Modifier la configuration en toute sécurité pour améliorer la capacité.
- Vérifier la capacité à l'aide de tests de charge et de la surveillance.
- Identifier les modes de défaillance et récupérer après des incidents liés à la capacité.
- Utiliser une liste de contrôle opérationnelle reproductible.
Tous les exemples utilisent des espaces réservés pour les données sensibles. Remplacez-les par les valeurs de votre propre environnement. Exécutez toujours les commandes dans un environnement sûr et hors production au préalable.
Inventaire des versions et de l'environnement
Avant de modifier la configuration, vous devez savoir exactement ce que vous exécutez. Commencez par documenter les versions de votre environnement d'exécution, de votre framework et de vos dépendances. Pour une API Node.js typique :
node --version
npm --version
Sortie attendue (exemple) :
v20.11.0
10.2.4
Pour Express, vérifiez package.json :
npm list express mongodb
Exemple de sortie :
[email protected] /home/user/my-api
├── [email protected]
└── [email protected]
Pourquoi est-ce important ? La planification de la capacité repose sur les caractéristiques de performance de versions spécifiques. Une fuite de mémoire corrigée dans Node.js 20.4 peut être présente dans la version 18.x. Le pilote MongoDB 6.x a des valeurs par défaut de pool de connexions différentes de celles de la version 5.x. Épinglez toujours les versions et effectuez les mises à jour de manière délibérée.
Ensuite, cartographiez votre topologie de déploiement. Exécutez-vous une seule instance derrière un proxy inverse (Nginx, HAProxy) ou plusieurs instances derrière un équilibreur de charge ? Comment MongoDB est-il déployé : autonome, ensemble de réplicas ou cluster partitionné ? Documentez cela dans un schéma simple ou un tableau.
Exemple de tableau de topologie :
| Composant | Type d'instance | Nombre | Version |
|---|---|---|---|
| Serveur API | t3.medium (2 vCPU, 4 Go RAM) | 2 | Node.js 20, Express 4.19 |
| Équilibreur de charge | AWS ALB | 1 | N/A |
| Base de données | db.r5.large (2 vCPU, 16 Go RAM) | 3 (ensemble de réplicas) | MongoDB 7.0 |
Enfin, capturez l'état observable actuel. Sous Linux, vérifiez le processeur et la mémoire :
top -bn1 | head -20
Sur la base de données, vérifiez les requêtes lentes et les statistiques du pool de connexions :
// Shell MongoDB
db.serverStatus().connections
db.serverStatus().opcounters
Cette observation en lecture seule crée une référence de base. Enregistrez ces valeurs avec des horodatages. Après tout changement, comparez-les à la référence pour mesurer l'impact.
Parcours de configuration sécurisé
Un changement de configuration sûr est :
- Petit et limité à un seul paramètre.
- Observable (vous pouvez voir l'effet avant/après).
- Réversible (vous savez comment revenir en arrière).
Commencez par le levier de capacité le plus courant : la boucle d'événements Node.js et les limites de mémoire. Par défaut, Node.js utilise une limite de taille de tas basée sur la mémoire système disponible. Pour la planification de la capacité, vous pouvez la définir explicitement afin d'éviter des pauses inattendues de garbage collection.
Rayon d'impact : Définir un --max-old-space-size trop bas peut faire planter le processus. Ne le définissez qu'après avoir mesuré l'utilisation actuelle du tas.
Prérequis : Node.js 12+ (les versions plus anciennes utilisent une syntaxe d'option différente).
Commande pour définir la limite du tas à 4 Go :
node --max-old-space-size=4096 server.js
Sortie attendue : le serveur démarre sans erreur. Vérifiez avec :
node -e "console.log(v8.getHeapStatistics().heap_size_limit / 1024 / 1024 + ' MB')"
Attendu : 4096 MB (environ).
Récupération : Si le processus plante en raison d'une mémoire insuffisante, supprimez l'option ou augmentez la valeur, puis redémarrez.
Ensuite, Express possède des limites de délai de connexion et de taille du corps qui affectent la capacité. Une limite de corps par défaut de 100 Ko peut être trop petite pour les grandes charges utiles, entraînant des erreurs 413. Augmentez-la si nécessaire, mais soyez attentif à la mémoire par requête.
Configuration dans Express (middleware) :
const express = require('express');
const app = express();
app.use(express.json({ limit: '1mb' })); // augmenter la limite de corps
Rayon d'impact : Des limites plus élevées autorisent plus de mémoire par requête, ce qui peut épuiser le tas sous une forte concurrence.
Vérification : Envoyez une requête avec une charge utile juste en dessous de 1 Mo et assurez-vous qu'elle réussit ; envoyez-en une au-dessus et attendez-vous à une erreur 413. Utilisez curl :
curl -X POST -H "Content-Type: application/json" -d @large.json http://localhost:3000/api/data
Récupération : Revenez à la limite par défaut ou à une limite inférieure.
Pour MongoDB, la taille du pool de connexions a un impact direct sur la capacité. La taille de pool par défaut dans le pilote Node.js est de 5 (à partir de la v6). Pour un débit élevé, vous aurez peut-être besoin de plus. Configurez-la dans votre chaîne de connexion :
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017', {
maxPoolSize: 20,
minPoolSize: 5
});
Rayon d'impact : Trop de connexions peuvent submerger la base de données. Surveillez db.serverStatus().connections pour vous assurer de rester dans les limites.
Vérification : Sous charge, vérifiez serverStatus().connections.current ; il ne doit pas dépasser maxPoolSize ni atteindre maxIncomingConnections de la base de données.
Vérification et diagnostics
La vérification consiste à prouver que votre API peut supporter la charge attendue avant de la déployer en production. La référence en la matière est le test de charge avec des modèles de trafic réalistes. Utilisez des outils comme autocannon (Node.js), wrk ou k6.
Prérequis : Disposez d'un environnement hors production qui reflète les spécifications de la production.
Installer autocannon :
npm install -g autocannon
Exécuter un test de charge simple :
autocannon -c 100 -d 30 http://localhost:3000/api/health
Options : -c 100 signifie 100 connexions simultanées, -d 30 signifie 30 secondes.
Exemple de sortie (tronquée) :
Running 30s test @ http://localhost:3000/api/health
100 connections
┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤
│ Latency │ 1 ms │ 3 ms │ 12 ms │ 20 ms │ 4.5 ms │ 3.1 ms │ 45 ms │
└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Req/Sec │ 1500 │ 1500 │ 2800 │ 3200 │ 2750.33 │ 400.2 │ 1200 │
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
Interprétation : Avec 100 connexions simultanées, l'API traite environ 2 750 requêtes/seconde avec une latence p99 de 20 ms. Est-ce suffisant ? Comparez avec votre objectif de capacité. Si votre objectif est de 5 000 req/s avec une p99 < 50 ms, vous avez de la marge sur la latence mais il vous faut plus de débit.
Pour trouver le point de rupture, augmentez progressivement les connexions :
autocannon -c 200 -d 30 http://localhost:3000/api/health
Observez quand la latence augmente ou que le taux d'erreurs grimpe. Cela définit votre capacité actuelle sur une seule instance.
La surveillance est tout aussi importante. En production, collectez des métriques du processus Node.js et de la base de données. Utilisez prom-client pour les métriques Prometheus dans Express :
const client = require('prom-client');
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics({ timeout: 5000 });
app.get('/metrics', async (req, res) => {
res.set('Content-Type', client.register.contentType);
res.end(await client.register.metrics());
});
Métriques clés : nodejs_eventloop_lag_seconds, nodejs_heap_size_used_bytes, http_request_duration_seconds et process_cpu_user_seconds_total. Surveillez-les avec Grafana ou des alertes Prometheus.
Diagnostics de base de données : dans MongoDB, vérifiez le journal des requêtes lentes :
db.setProfilingLevel(1, { slowms: 100 });
Puis examinez :
db.system.profile.find({ millis: { $gt: 100 } }).sort({ ts: -1 }).limit(5).pretty();
Modes de défaillance et récupération
La planification de la capacité doit inclure ce qui se passe lorsque vous dépassez la capacité. Modes de défaillance courants :
- Épuisement de la mémoire – Le processus Node.js plante avec
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory.
- Détection : le processus se termine, PM2 ou systemd le redémarre (si configuré).
- Diagnostic : consultez les journaux, les vidages de tas.
- Récupération : augmentez
--max-old-space-size, corrigez la fuite de mémoire ou ajoutez des instances.
- Latence de la boucle d'événements – Les requêtes s'accumulent parce que la boucle d'événements est bloquée (par exemple, code synchrone gourmand en CPU).
- Détection : la surveillance montre
nodejs_eventloop_lag_seconds> 0,1. - Diagnostic : profilage CPU, recherche d'opérations bloquantes.
- Récupération : déplacez le travail bloquant vers des threads de travail, optimisez le code ou mettez à l'échelle horizontalement.
- Épuisement du pool de connexions de la base de données – L'API ne peut pas obtenir de connexion du pool, les requêtes expirent.
- Détection : les journaux affichent
MongoPoolClearedErrorouMongoWaitQueueFullError. - Diagnostic : vérifiez
db.serverStatus().connectionset la taille de la file d'attente. - Récupération : augmentez
maxPoolSize(si la base de données peut le supporter), réduisez la latence des requêtes ou ajoutez des réplicas de lecture.
- Limite de débit dépassée – Si vous avez une limitation de débit sur votre API, un trafic excessif peut être rejeté. C'est souvent intentionnel, mais la planification de la capacité doit inclure la valeur de la limite.
- Détection : réponses HTTP 429 dans les journaux.
- Diagnostic : vérifiez les compteurs du limiteur de débit.
- Récupération : ajustez la limite en fonction de la capacité ou ajoutez des instances derrière un équilibreur de charge.
Pour chaque mode de défaillance, créez un runbook avec des commandes exactes. Exemple pour l'épuisement de la mémoire :
Prérequis : processus Node.js géré par PM2. Observation en lecture seule :
pm2 status
pm2 describe my-api
Vérifiez les champs memory et restarts.
Étape de récupération : Si les plantages sont fréquents, redémarrez avec un tas plus grand :
pm2 delete my-api
pm2 start server.js --node-args="--max-old-space-size=8192" --name my-api
Vérification :
pm2 logs my-api --lines 20
Assurez-vous qu'aucune erreur de tas n'apparaît et surveillez la mémoire au fil du temps.
Retour en arrière : Si le problème persiste, revenez aux paramètres par défaut et recherchez la cause première.
Liste de contrôle opérationnelle
Utilisez cette liste avant chaque changement ou déploiement lié à la capacité :
- Identifier le composant et la version
node --version,npm list express mongodb- Enregistrez les valeurs.
- Capturer l'état actuel
- CPU, mémoire, latence de la boucle d'événements, connexions à la base de données.
- Sauvegardez les métriques dans un fichier ou un tableau de bord de surveillance.
- Définir le résultat attendu
- Quelle métrique doit s'améliorer ? De combien ? Quelle est la plage acceptable ?
- Estimer le rayon d'impact
- Quels utilisateurs ou fonctionnalités sont affectés ? Pouvez-vous tester en préproduction ?
- Préparer le plan de retour en arrière
- Commande ou configuration exacte pour annuler.
- Effectuer le changement
- Un seul changement à la fois.
- Vérifier immédiatement
- Exécutez un test de charge ou observez la surveillance pendant au moins 10 minutes.
- Comparez avant/après.
- Documenter
- Mettez à jour le runbook avec le résultat et les surprises éventuelles.
- Surveiller pendant 24 à 48 heures
- Surveillez les effets différés (par exemple, fuite de mémoire).
- Communiquer
- Informez l'équipe du changement et des nouvelles limites de capacité.
Exemple d'exécution de la liste pour augmenter maxPoolSize de 5 à 20 :
- Composant : pilote MongoDB Node.js v6.3.0.
- État actuel :
db.serverStatus().connectionsindique current=15, available=50. - Résultat attendu : lors d'un test de charge avec 200 requêtes simultanées, la latence p99 de l'API diminue de 300 ms à 150 ms.
- Rayon d'impact : toutes les requêtes API utilisant MongoDB ; testez d'abord en préproduction.
- Retour en arrière : remettez
maxPoolSizeà 5. - Changement : mettez à jour la chaîne de connexion.
- Vérification : exécutez
autocannon -c 200 -d 60et comparez. - Documenter : mettez à jour le plan de capacité.
Conclusion
La planification de la capacité d'une API REST n'est pas une activité ponctuelle. Elle exige une observation, des tests et des ajustements continus. En suivant les pratiques décrites dans cet article (inventaire des versions, changements de configuration sûrs, vérification par des tests de charge et runbooks de récupération), vous pouvez prévenir la plupart des pannes liées à la capacité.
Commencez modestement : choisissez un point de terminaison, mesurez sa capacité actuelle, fixez un objectif réaliste et mettez en place la surveillance. Puis étendez progressivement à toute la surface de l'API. N'oubliez pas : chaque changement doit être observé, vérifié et réversible. Avec un plan de capacité solide, votre API peut évoluer en douceur de 100 à 1 000 000 d'utilisateurs.