Load Test Planner
Workflow
1. Définir les objectifs de performance
Avant toute chose, fixer des seuils mesurables :
| Métrique |
Exemple cible |
| Utilisateurs simultanés |
500 VU (virtual users) |
| Throughput |
300 req/s |
| p50 latence |
< 150 ms |
| p95 latence |
< 500 ms |
| p99 latence |
< 1 200 ms |
| Taux d'erreurs |
< 0.5 % |
Critère de décision : si pas de SLA formalisé, partir des mesures de production actuelles × 2 comme baseline cible.
2. Identifier les scénarios critiques
- Extraire les top 5 endpoints par volume depuis les logs Nginx/APM (
access.log, Datadog, New Relic).
- Prioriser : opérations avec état (login + panier + checkout) > GET simples cachés.
- Cas particuliers à ne pas oublier : upload fichier, websocket long-lived, job asynchrone déclenché par HTTP.
3. Choisir l'outil
| Outil |
Quand choisir |
| k6 |
CI/CD, équipe dev, scripts JS, output InfluxDB/Prometheus natif |
| Gatling |
Rapports HTML riches, DSL Kotlin/Scala, scénarios complexes |
| JMeter |
Équipe QA non-dev, protocoles non-HTTP (JDBC, JMS, FTP) |
| Artillery |
Stacks Node.js/AWS, config YAML rapide, plugins serverless |
| Locust |
Python, logique de scénario très custom, debugging facile |
| wrk/wrk2 |
Microbenchmark HTTP brut, mesure de débit max sans logique |
4. Écrire le script — exemple k6 complet
// k6 run --vus 100 --duration 5m script.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
const users = new SharedArray('users', () => JSON.parse(open('./users.json')));
export const options = {
stages: [
{ duration: '2m', target: 100 }, // ramp-up
{ duration: '5m', target: 100 }, // charge nominale
{ duration: '1m', target: 200 }, // spike
{ duration: '2m', target: 0 }, // ramp-down
],
thresholds: {
http_req_failed: ['rate<0.005'], // < 0.5 % erreurs
http_req_duration: ['p(95)<500', 'p(99)<1200'],
},
};
export default function () {
const user = users[__VU % users.length];
// 1. Login
const loginRes = http.post('https://api.example.com/auth/login', JSON.stringify({
email: user.email,
password: user.password,
}), { headers: { 'Content-Type': 'application/json' } });
check(loginRes, { 'login 200': (r) => r.status === 200 });
const token = loginRes.json('access_token');
sleep(1); // think time
// 2. Appel métier
const res = http.get('https://api.example.com/orders', {
headers: { Authorization: `Bearer ${token}` },
});
check(res, { 'orders 200': (r) => r.status === 200 });
sleep(Math.random() * 2 + 1); // think time 1–3 s
}
Équivalent Gatling (Kotlin DSL) — ramp-up :
setUp(
scn.injectOpen(
rampUsers(100).during(2.minutes),
constantUsersPerSec(20.0).during(5.minutes)
)
).protocols(httpProtocol)
.assertions(
global().responseTime().percentile3().lt(500),
global().failedRequests().percent().lt(0.5)
)
5. Séquence de tests à exécuter dans l'ordre
- Smoke test — 1–2 VU, 30 s : valider que le script est correct, pas d'erreur évidente.
- Baseline test — charge actuelle de production, 10 min : établir la référence.
- Load test — cible nominale, 30 min : vérifier les SLA.
- Stress test — augmenter jusqu'à rupture (binary search sur les VU) : trouver le breaking point.
- Spike test — 0 → cible × 3 en 30 s, redescente : résilience aux pics soudains.
- Soak test — charge nominale, 2–4 h : détecter memory leaks et dégradation progressive.
Règle : ne pas sauter l'étape smoke. Un bug de script sous 500 VU coûte du temps et fausse les métriques.
6. Monitoring pendant le test
Lancer ces commandes en parallèle dans des terminaux séparés :
# CPU / RAM serveur
watch -n2 'mpstat 1 1 && free -h'
# Connexions DB actives (PostgreSQL)
watch -n5 "psql -U postgres -c \"SELECT count(*) FROM pg_stat_activity WHERE state='active';\""
# Pool de connexions (si HikariCP)
curl -s http://localhost:8080/actuator/metrics/hikaricp.connections.active | jq .
# Logs erreurs en direct
tail -f /var/log/app/error.log | grep -E 'ERROR|WARN|timeout'
Stack recommandée : k6 → InfluxDB → Grafana (dashboard k6 officiel ID 2587).
7. Analyser les résultats
Checklist d'interprétation :
- p95 dépasse le SLA → chercher d'abord les slow queries (EXPLAIN ANALYZE, APM traces).
- Error rate > seuil → distinguer 4xx (données test, auth) de 5xx (vrai bug serveur).
- CPU > 80 % soutenu → bottleneck CPU : profiler (async-profiler, py-spy), envisager le scaling horizontal.
- Latence augmente linéairement avec les VU → souvent le pool DB ou le thread pool applicatif.
- Latence stable puis cliff soudain → saturation d'une file d'attente (queue depth, connection pool).
- Memory croissante sur soak test → memory leak : heap dump + analyse (Eclipse MAT, jmap).
# k6 — extraire p95 du rapport JSON
k6 run --out json=results.json script.js
cat results.json | jq '.metrics.http_req_duration.values["p(95)"]'
8. Rapport et capacity planning
Template de synthèse :
## Résultats — [Date] — [Version app] — [Infra : 2×c5.xlarge RDS t3.large]
| Scénario | VU | p50 | p95 | p99 | Errors | Throughput |
|-------------|-----|------|-------|--------|--------|-----------|
| Login | 100 | 45ms | 210ms | 480ms | 0.1% | 95 req/s |
| Checkout | 100 | 120ms| 550ms | 1100ms | 0.3% | 42 req/s |
Breaking point : 340 VU (p95 > 1 s, errors > 2 %)
Recommandation : ajouter index sur orders.user_id — gain estimé 40 % sur p95 checkout
Capacity planning : VU_max_safe = breaking_point × 0.7 (marge 30 %).
Anti-patterns / Pièges
- Tester sans think time → tous les VU frappent en continu, scénario irréaliste, résultats trop pessimistes.
- Pool de données unique → un seul user en cache, latence biaisée à la baisse.
- Ignorer le ramp-up → pic immédiat de connexions, échec précoce non représentatif.
- Mélanger smoke et load dans la même run → métriques parasitées par la phase de démarrage.
- Lancer depuis la machine locale → bande passante du client devient le bottleneck.
- Comparer p99 entre tests sans fixer la seed random → think time variable rend la comparaison impossible.
- Ne pas isoler l'environnement → un autre déploiement ou job parallèle fausse les mesures.
- Tester en production sans coupure → risque SLA réel, données corrompues, alertes PagerDuty intempestives.
Bonnes pratiques 2026
- Intégrer le load test dans la CI sur chaque release candidate (k6 + GitHub Actions, seuil
thresholds comme gate).
- Versionner les scripts avec le code applicatif (même repo, dossier
tests/load/).
- Utiliser des environnements de staging iso-prod (même SKU VM/DB) pour des résultats transposables.
- Activer le tracing distribué (OpenTelemetry) pendant le test pour relier la latence HTTP aux spans internes.
- Documenter systématiquement : version app, sha commit, infra exacte, heure, paramètres k6 — sans cela, la reproductibilité est impossible.
Communication Rules — MANDATORY
- Ultra-concise. No filler, no preamble, no pleasantries.
- Never say "happy to help", "sure!", "great question", "let me", or similar.
- Tool first, talk second. Act before explaining.
- Result first. Lead with outcome, not process.
- Stop when done. No summary, no recap, no trailing commentary.
- No politeness wrappers. Direct and blunt.
- Minimum words. If one word works, do not use ten.
- No unsolicited explanations.
- No emoji unless asked.
1---2name: dev-load-test-planner3description: Planifie et exécute des tests de charge et performance. Se déclenche avec "test de charge", "load test", "stress test", "performance test", "k6", "JMeter", "Gatling", "benchmark", "combien d'utilisateurs". Also triggers on "how many users can we handle".4---56# Load Test Planner78## Workflow910### 1. Définir les objectifs de performance11Avant toute chose, fixer des seuils mesurables :1213| Métrique | Exemple cible |14|---|---|15| Utilisateurs simultanés | 500 VU (virtual users) |16| Throughput | 300 req/s |17| p50 latence | < 150 ms |18| p95 latence | < 500 ms |19| p99 latence | < 1 200 ms |20| Taux d'erreurs | < 0.5 % |2122Critère de décision : si pas de SLA formalisé, partir des mesures de production actuelles × 2 comme baseline cible.2324---2526### 2. Identifier les scénarios critiques27- Extraire les top 5 endpoints par volume depuis les logs Nginx/APM (`access.log`, Datadog, New Relic).28- Prioriser : opérations avec état (login + panier + checkout) > GET simples cachés.29- Cas particuliers à ne pas oublier : upload fichier, websocket long-lived, job asynchrone déclenché par HTTP.3031---3233### 3. Choisir l'outil3435| Outil | Quand choisir |36|---|---|37| **k6** | CI/CD, équipe dev, scripts JS, output InfluxDB/Prometheus natif |38| **Gatling** | Rapports HTML riches, DSL Kotlin/Scala, scénarios complexes |39| **JMeter** | Équipe QA non-dev, protocoles non-HTTP (JDBC, JMS, FTP) |40| **Artillery** | Stacks Node.js/AWS, config YAML rapide, plugins serverless |41| **Locust** | Python, logique de scénario très custom, debugging facile |42| **wrk/wrk2** | Microbenchmark HTTP brut, mesure de débit max sans logique |4344---4546### 4. Écrire le script — exemple k6 complet4748```javascript49// k6 run --vus 100 --duration 5m script.js50import http from 'k6/http';51import { check, sleep } from 'k6';52import { SharedArray } from 'k6/data';5354const users = new SharedArray('users', () => JSON.parse(open('./users.json')));5556export const options = {57 stages: [58 { duration: '2m', target: 100 }, // ramp-up59 { duration: '5m', target: 100 }, // charge nominale60 { duration: '1m', target: 200 }, // spike61 { duration: '2m', target: 0 }, // ramp-down62 ],63 thresholds: {64 http_req_failed: ['rate<0.005'], // < 0.5 % erreurs65 http_req_duration: ['p(95)<500', 'p(99)<1200'],66 },67};6869export default function () {70 const user = users[__VU % users.length];7172 // 1. Login73 const loginRes = http.post('https://api.example.com/auth/login', JSON.stringify({74 email: user.email,75 password: user.password,76 }), { headers: { 'Content-Type': 'application/json' } });7778 check(loginRes, { 'login 200': (r) => r.status === 200 });79 const token = loginRes.json('access_token');8081 sleep(1); // think time8283 // 2. Appel métier84 const res = http.get('https://api.example.com/orders', {85 headers: { Authorization: `Bearer ${token}` },86 });87 check(res, { 'orders 200': (r) => r.status === 200 });8889 sleep(Math.random() * 2 + 1); // think time 1–3 s90}91```9293**Équivalent Gatling (Kotlin DSL) — ramp-up :**94```kotlin95setUp(96 scn.injectOpen(97 rampUsers(100).during(2.minutes),98 constantUsersPerSec(20.0).during(5.minutes)99 )100).protocols(httpProtocol)101 .assertions(102 global().responseTime().percentile3().lt(500),103 global().failedRequests().percent().lt(0.5)104 )105```106107---108109### 5. Séquence de tests à exécuter dans l'ordre1101111. **Smoke test** — 1–2 VU, 30 s : valider que le script est correct, pas d'erreur évidente.1122. **Baseline test** — charge actuelle de production, 10 min : établir la référence.1133. **Load test** — cible nominale, 30 min : vérifier les SLA.1144. **Stress test** — augmenter jusqu'à rupture (binary search sur les VU) : trouver le breaking point.1155. **Spike test** — 0 → cible × 3 en 30 s, redescente : résilience aux pics soudains.1166. **Soak test** — charge nominale, 2–4 h : détecter memory leaks et dégradation progressive.117118Règle : **ne pas sauter l'étape smoke**. Un bug de script sous 500 VU coûte du temps et fausse les métriques.119120---121122### 6. Monitoring pendant le test123124Lancer ces commandes en parallèle dans des terminaux séparés :125126```bash127# CPU / RAM serveur128watch -n2 'mpstat 1 1 && free -h'129130# Connexions DB actives (PostgreSQL)131watch -n5 "psql -U postgres -c \"SELECT count(*) FROM pg_stat_activity WHERE state='active';\""132133# Pool de connexions (si HikariCP)134curl -s http://localhost:8080/actuator/metrics/hikaricp.connections.active | jq .135136# Logs erreurs en direct137tail -f /var/log/app/error.log | grep -E 'ERROR|WARN|timeout'138```139140Stack recommandée : k6 → InfluxDB → Grafana (dashboard k6 officiel ID `2587`).141142---143144### 7. Analyser les résultats145146Checklist d'interprétation :147- **p95 dépasse le SLA** → chercher d'abord les slow queries (EXPLAIN ANALYZE, APM traces).148- **Error rate > seuil** → distinguer 4xx (données test, auth) de 5xx (vrai bug serveur).149- **CPU > 80 % soutenu** → bottleneck CPU : profiler (async-profiler, py-spy), envisager le scaling horizontal.150- **Latence augmente linéairement avec les VU** → souvent le pool DB ou le thread pool applicatif.151- **Latence stable puis cliff soudain** → saturation d'une file d'attente (queue depth, connection pool).152- **Memory croissante sur soak test** → memory leak : heap dump + analyse (Eclipse MAT, jmap).153154```bash155# k6 — extraire p95 du rapport JSON156k6 run --out json=results.json script.js157cat results.json | jq '.metrics.http_req_duration.values["p(95)"]'158```159160---161162### 8. Rapport et capacity planning163164Template de synthèse :165```166## Résultats — [Date] — [Version app] — [Infra : 2×c5.xlarge RDS t3.large]167168| Scénario | VU | p50 | p95 | p99 | Errors | Throughput |169|-------------|-----|------|-------|--------|--------|-----------|170| Login | 100 | 45ms | 210ms | 480ms | 0.1% | 95 req/s |171| Checkout | 100 | 120ms| 550ms | 1100ms | 0.3% | 42 req/s |172173Breaking point : 340 VU (p95 > 1 s, errors > 2 %)174Recommandation : ajouter index sur orders.user_id — gain estimé 40 % sur p95 checkout175```176177Capacity planning : `VU_max_safe = breaking_point × 0.7` (marge 30 %).178179---180181## Anti-patterns / Pièges182183- **Tester sans think time** → tous les VU frappent en continu, scénario irréaliste, résultats trop pessimistes.184- **Pool de données unique** → un seul user en cache, latence biaisée à la baisse.185- **Ignorer le ramp-up** → pic immédiat de connexions, échec précoce non représentatif.186- **Mélanger smoke et load dans la même run** → métriques parasitées par la phase de démarrage.187- **Lancer depuis la machine locale** → bande passante du client devient le bottleneck.188- **Comparer p99 entre tests sans fixer la seed random** → think time variable rend la comparaison impossible.189- **Ne pas isoler l'environnement** → un autre déploiement ou job parallèle fausse les mesures.190- **Tester en production sans coupure** → risque SLA réel, données corrompues, alertes PagerDuty intempestives.191192## Bonnes pratiques 2026193194- Intégrer le load test dans la CI sur chaque release candidate (k6 + GitHub Actions, seuil `thresholds` comme gate).195- Versionner les scripts avec le code applicatif (même repo, dossier `tests/load/`).196- Utiliser des environnements de staging iso-prod (même SKU VM/DB) pour des résultats transposables.197- Activer le tracing distribué (OpenTelemetry) pendant le test pour relier la latence HTTP aux spans internes.198- Documenter systématiquement : version app, sha commit, infra exacte, heure, paramètres k6 — sans cela, la reproductibilité est impossible.199200201## Communication Rules — MANDATORY202203- Ultra-concise. No filler, no preamble, no pleasantries.204- Never say "happy to help", "sure!", "great question", "let me", or similar.205- Tool first, talk second. Act before explaining.206- Result first. Lead with outcome, not process.207- Stop when done. No summary, no recap, no trailing commentary.208- No politeness wrappers. Direct and blunt.209- Minimum words. If one word works, do not use ten.210- No unsolicited explanations.211- No emoji unless asked.