Stress test pour développeurs

Première fois ? Ce guide t’emmène du vocabulaire de base jusqu’à la lecture d’un rapport de charge — pour que tu saches exactement ce que tu mesures, et ce que tu ne mesures pas. L’exemple fil rouge : une boutique e-commerce fictive, ShopNova, pendant une vente flash.

Niveau : débutant → avancé Outils : k6 · Locust · aiohttp Exemple : ShopNova (vente flash) Objectif jour J : 10k acheteurs simultanés

1. Pourquoi faire un stress test ?

En développement, tu testes souvent avec 1 utilisateur : toi. Le jour d’une vente flash, d’une ouverture de billetterie ou d’un lancement produit, ce n’est plus 1 personne — c’est des milliers qui frappent les mêmes endpoints en même temps.

Un stress test répond à une question simple :

« À partir de combien d’utilisateurs simultanés mon app devient-elle trop lente ou inutilisable ? »

Ce que ça teste

Temps de réponse, erreurs, timeouts, saturation CPU / DB / CDN / serverless / files d’attente.

Ce que ça ne teste pas

Le design UI, les bugs métier rares, ni les services tiers hors périmètre (PSP, CDN média, e-mail…).

2. Vocabulaire indispensable

MotSignificationExemple
VU (Virtual User) Un utilisateur simulé qui suit un scénario (comme un vrai navigateur). --vus 500 = 500 VUs en parallèle
RPS Requests Per Second — combien de requêtes HTTP / seconde. ~800 RPS pendant un pic checkout
Latence Temps entre l’envoi de la requête et la réponse complète. p50 = 180 ms, p95 = 2,4 s…
Timeout La réponse n’est pas arrivée avant la limite (souvent 10–30 s). Compté comme fail capacité
Error rate % de requêtes en échec (timeout, 5xx, réseau…). Objectif SLA courant : < 1 %
Think-time Pause entre deux actions (l’utilisateur “lit” la page). 1,5 – 4,5 s entre catalogue et fiche produit
Spawn rate Combien de VUs on démarre par seconde (la montée en charge). 100/s → 5 000 VUs en ~50 s
Concurrency Nombre max de requêtes HTTP en vol en même temps. Plafond client ≠ nombre de VUs
Piège n°1 : 10 000 VUs ≠ 10 000 requêtes simultanées. Avec du think-time, la plupart des VUs “attendent”. Les requêtes HTTP en vol sont plafonnées par la concurrence du client de test.

3. Modèles de charge (closed vs open)

Closed loop

Tu fixes un nombre de VUs. Chaque VU enchaîne : action → pause → action. C’est le modèle Locust / k6 closed. Il représente bien “N personnes connectées en même temps sur le site”.

Open loop

Tu fixes un débit (ex. 1 000 RPS), peu importe le nombre d’utilisateurs. Utile pour trouver la limite brute d’une API.

Charge réelle ≈ f(VUs, think-time, nb de requêtes par boucle, spawn-rate)

4. SLA et percentiles — comment décider PASS / FAIL

Une moyenne ment. Si 95 personnes ont 200 ms et 5 personnes ont 20 s, la moyenne peut “avoir l’air OK” alors que l’expérience est pourrie.

Percentiles

  • p50 (médiane) : la moitié des requêtes est plus rapide.
  • p95 : 95 % sont plus rapides — seul 5 % est pire.
  • p99 : la queue extrême (souvent le début de la douleur).

SLA d’exemple (vente flash ShopNova)

1) taux d’erreur capacité < 1 %
2) p95 ≤ 2 secondes sur tous les endpoints critiques (catalogue, fiche, panier, checkout)

Astuce : sépare les erreurs “métier attendues” (ex. stock épuisé → 409) des vraies pannes capacité (timeout / 5xx / 429 / réseau). Seules ces dernières jugent le SLA.

5. Le parcours à stresser (exemple ShopNova)

Un bon scénario suit un parcours réel, pas “ouvrir la homepage en boucle”. Pour une vente flash e-commerce :

CatalogueGET /products + GET /api/products?sale=flash
Fiche produitGET /products/{slug} + GET /api/products/{id}
Ajout au panierPOST /api/cart/items
CheckoutGET /checkout + POST /api/orders
Paiement (simulé)POST /api/payments/intent (souvent mocké en preprod — ne DDoS pas ton PSP)
ConfirmationGET /orders/{id}
Important : réussir le stress de ton app ne prouve pas que Stripe / PayPal / le CDN images tiendront. Définis clairement le périmètre du test.

6. Outils courants

OutilRôle
k6 Script JS, métriques riches, CI-friendly — très utilisé en entreprise
Locust Scénarios Python, UI web, bon pour itérer vite
Artillery / Gatling Alternatives YAML / Scala selon les stacks d’équipe
Script custom (aiohttp, vegeta…) Utile quand tu contrôles tout le parcours et le format de rapport

Scénarios typiques

  • browse : catalogue + fiche (lecture seule)
  • cart : browse + ajout panier
  • checkout : parcours complet jusqu’à la commande

7. Comment lancer (du plus sûr au plus dur)

# Exemple k6 — smoke
k6 run --vus 5 --duration 30s checkout.js

# Petit run réaliste
k6 run --vus 200 --duration 2m checkout.js

# Autour d’un plafond suspecté
k6 run --vus 500 --duration 3m --ramp-up 30s checkout.js

# Locust équivalent
locust -f checkout.py --users 200 --spawn-rate 20 --run-time 2m --headless
Règle d’or : ne lance pas 10k VUs “pour voir” sur un environnement partagé sans prévenir. Tu impacts tout le monde. Monte en échelle : 50 → 100 → 250 → 500 → 1 000.

8. Comment lire un rapport

Que tu sois en k6, Locust ou script custom, cherche toujours les mêmes blocs :

A. En-tête du run

{
  "target": "https://preprod.shopnova.example",
  "users": 500,
  "vus_started": 500,
  "scenario": "checkout",
  "elapsed_s": 120
}
  • users = objectif
  • vus_started = réellement démarrés (si < users, la montée n’a pas fini)
  • scenario = quel parcours a été joué

B. Summary (le verdict rapide)

"summary": {
  "requests": 18420,
  "ok": 18310,
  "fail": 110,
  "error_rate_pct": 0.6,
  "rps": 153.5,
  "errors": { "timeout": 82, "http_503": 28 }
}
ChampComment le lire
okRéponses “saines” (2xx/3xx ou codes métier attendus)
failVraies pannes capacité
error_rate_pctfail / total — doit rester < 1
errorsex. {"timeout": 82} → le serveur n’arrive plus à temps

C. Endpoints (où ça fait mal)

{
  "name": "POST /api/orders",
  "requests": 2000,
  "fail": 140,
  "error_rate_pct": 7.0,
  "latency_ms": { "p50": 900, "p95": 12000, "p99": 15000 }
}

Lis toujours par endpoint. Dans un run 1 000 VUs, le catalogue peut rester à 0 % d’erreur pendant que POST /api/orders timeout à 7 %. Le goulot = écriture commande / DB, pas le listing HTML.

D. SLA + verdict

"sla": {
  "pass_error_rate_lt_1pct": false,
  "pass_p95_lt_2000ms": false
},
"verdict": {
  "decision": "NO",
  "supports_10k_checkout": false,
  "headline": "Checkout p95 > 2s dès 800 VUs"
}
Astuce : un smoke test (5 VUs) peut passer le SLA et le verdict “10k le jour J” rester NO. Le verdict capacité s’appuie sur les runs d’échelle, pas sur le smoke.

9. Visualiser et comparer

Exporte les rapports (JSON / HTML / Grafana)
Compare plusieurs runs (100 → 250 → 500 → 1000)
Repère le premier seuil où p95 ou error rate casse
Isole l’endpoint coupable avant d’optimiser “au hasard”

L’objectif n’est pas un joli graphe : c’est de trouver à partir de combien l’expérience utilisateur bascule.

10. Résultats d’exemple (ShopNova, preprod)

Parcours checkout authentifié, SLA strict (erreur < 1 % et p95 ≤ 2 s) :

VUsErr %Checkout p95Lecture
100 – 4000 %≤ 800 msPASS — zone confortable
500 – 700< 1 %2,5 – 6 sEncore “up”, mais UX dégradée
1 0002,1 %12 sFAIL — commandes en timeout
10 00028 %30 sCassé (pool DB + files)

400 VUs — SLA OK

~600 VUs — lent

≥ 1000 VUs — cassé

Plafond fiable dans cet exemple : environ 400 acheteurs simultanés sur le parcours checkout. Au-delà, POST /api/orders (écriture Postgres) est le premier mur.

11. Trouver le goulot (méthode)

  1. Regarde quel endpoint a le pire p95 / le plus de timeouts.
  2. Si c’est du HTML → edge / SSR / cold start / poids de page.
  3. Si c’est une API JSON → DB, cache Redis, auth, logique métier, locks stock.
  4. Si tu as des 429 → rate-limit (parfois voulu).
  5. Si tu as des 5xx → exception serveur / pool DB épuisé / worker saturé.
  6. Si tu as des erreurs côté client de test → bug du script, pas du serveur.

Sur ShopNova, le mur observé est l’écriture commande. Le catalogue HTML et l’API produits tiennent mieux grâce au cache. Le prochain mur, si tu ajoutes un suivi de panier temps réel, sera souvent Redis / WebSocket / Postgres sous écriture soutenue.

12. Pièges classiques (avancé)

1 compte ≠ N users

Un cookie / token partagé entre tous les VUs ne simule pas N sessions distinctes ni N paniers isolés.

Client vs serveur

Des erreurs dans le rapport peuvent venir du script (session fermée trop tôt, fuite de sockets) — pas de ton API.

Ne pas DDoS les tiers

Mocke le PSP, le SMS, l’e-mail, les webhooks externes. Tu testes ton app, pas Stripe.

Limites OS du runner

Fichiers ouverts, ports éphémères, CPU du laptop : le “plafond” peut être ton poste de test.

Environnement ≠ prod

Une preprod sous-dimensionnée donne un plafond pessimiste ; une preprod surdimensionnée ment dans l’autre sens.

p95 global

Si un seul endpoint critique dépasse 2 s, le SLA global FAIL — même si le reste est parfait. C’est voulu.

13. Pour monter au-delà du plafond (pistes)

  • Cache / CDN agressif sur catalogue et fiches produit
  • Pages statiques / ISR pour les contenus peu dynamiques
  • Réduire le poids HTML/JS/images
  • Pool DB + PgBouncer / ProxySQL, index, éviter les locks stock naïfs
  • File d’attente pour les commandes (accepter puis confirmer)
  • Tester avec N vrais comptes / paniers distincts avant le jour J
  • Valider les quotas des services tiers (PSP, e-mail, SMS)

14. Aide-mémoire (imprime cette section)

QUESTION        → MÉTRIQUE              → BON SIGNE
------------------------------------------------------
Ça répond ?     → error_rate_pct        → < 1 %
C’est rapide ?  → latency p95           → ≤ 2000 ms
Qui souffre ?   → endpoint le + lent    → isoler HTML vs API
Assez de VUs ?  → vus_started / users   → ≈ 100 %
Auth OK ?       → login / session 200
Métier OK ?     → codes attendus (pas 5xx)
Tiers ?         → hors scope (mockés)

COMMANDES UTILES
  k6 run --vus 5 --duration 30s checkout.js
  k6 run --vus 500 --duration 3m checkout.js
  comparer les rapports 100 → 250 → 500 → 1000

PLAFOND EXEMPLE (ShopNova, checkout, SLA strict)
  ~400 acheteurs simultanés
En une phrase : tu simules des utilisateurs qui suivent un parcours réel ; tu regardes si l’app répond vite et sans erreur ; tu montes en charge jusqu’au premier mur — puis tu optimises cet endpoint, pas tout le système au hasard.