Fait partie de notre série Performance & Scalability
Lire le guide completTest de charge k6 : testez sous contrainte vos API avant le lancement
Envoyer un produit sans test de charge est un pari. Vous ne connaissez pas votre point de rupture jusqu'à ce que les utilisateurs le trouvent pour vous – généralement lors du lancement d'un produit, d'un moment viral ou d'un pic de ventes. k6 est l'outil de test de charge moderne qui vous permet d'écrire des tests en JavaScript, de les exécuter à partir de CI et de découvrir votre plafond de performances avant les utilisateurs. Il est convivial pour les développeurs, économe en ressources (k6 utilise des goroutines, pas des threads) et s'intègre parfaitement à Grafana et Prometheus pour des métriques en temps réel.
Ce guide couvre k6 depuis le premier script jusqu'aux tests de charge multi-scénarios complexes, aux métriques personnalisées, aux seuils, à l'intégration CI et aux modèles de test sécurisés en production pour les API Node.js/NestJS.
Points clés à retenir
- Les scripts k6 sont JavaScript mais s'exécutent dans un runtime Go — pas d'API Node.js (pas de
require, pas defs, pas desetTimeout)- Les utilisateurs virtuels (VU) sont des utilisateurs simulés simultanés ; les itérations sont des exécutions de script individuelles - Toujours définir des seuils avant l'exécution : les seuils d'échec arrêtent le test et font échouer le CI.
- Utiliser des scénarios (vus constant, ramping-vus, taux d'arrivée constant) pour modéliser les modèles de trafic réels
- Ne chargez jamais la production de test sans coordination avec les opérations - testez par rapport à une préparation ou à un clone de production
- La métrique
http_req_durationest votre principale métrique SLA – p95 et p99 comptent plus que les moyennes.- Limitez le débit de vos coureurs K6 - le trafic de test de charge ne devrait pas épuiser votre budget limite de débit
- Utilisez k6 Cloud ou Grafana k6 pour les tests de charge distribués dans les régions géographiques
Installation et premier script
# macOS
brew install k6
# Ubuntu/Debian
sudo gpg -k && sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6
# Windows (via Chocolatey)
choco install k6
Votre premier script k6 :
// k6/homepage-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
// Custom metric: error rate
const errorRate = new Rate('error_rate');
export const options = {
// Ramp up to 50 VUs over 1 minute, hold 2 minutes, ramp down
stages: [
{ duration: '1m', target: 50 },
{ duration: '2m', target: 50 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th pct < 500ms
http_req_failed: ['rate<0.01'], // Less than 1% errors
error_rate: ['rate<0.01'],
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
export default function () {
const res = http.get(`${BASE_URL}/`);
const ok = check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'body contains ECOSIRE': (r) => r.body.includes('ECOSIRE'),
});
errorRate.add(!ok);
sleep(1); // 1 second think time between iterations
}
Exécutez-le :
k6 run k6/homepage-load.js
k6 run --env BASE_URL=https://staging.ecosire.com k6/homepage-load.js
k6 run --vus 100 --duration 30s k6/homepage-load.js # Quick smoke test
Test de charge API avec authentification
// k6/api-load.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Counter, Trend } from 'k6/metrics';
const apiCalls = new Counter('api_calls');
const authDuration = new Trend('auth_duration');
export const options = {
scenarios: {
// Constant arrival rate: 100 requests/second regardless of VU count
constant_load: {
executor: 'constant-arrival-rate',
rate: 100,
timeUnit: '1s',
duration: '3m',
preAllocatedVUs: 50,
maxVUs: 200,
},
},
thresholds: {
http_req_duration: ['p(95)<300', 'p(99)<500'],
'http_req_duration{type:api}': ['p(95)<200'],
http_req_failed: ['rate<0.005'], // 0.5% error budget
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3001';
// setup() runs once before VUs start
export function setup() {
// Authenticate and return tokens for VUs to use
const loginRes = http.post(`${BASE_URL}/auth/login`, JSON.stringify({
email: __ENV.TEST_EMAIL,
password: __ENV.TEST_PASSWORD,
}), {
headers: { 'Content-Type': 'application/json' },
});
check(loginRes, { 'login successful': (r) => r.status === 200 });
const { accessToken } = loginRes.json();
return { accessToken };
}
export default function (data) {
const { accessToken } = data;
const headers = {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
group('Contacts API', () => {
// List contacts
const listRes = http.get(`${BASE_URL}/contacts?page=1&limit=20`, {
headers,
tags: { type: 'api', endpoint: 'contacts-list' },
});
check(listRes, {
'list contacts: 200': (r) => r.status === 200,
'list contacts: has items': (r) => r.json('data') !== null,
});
apiCalls.add(1);
sleep(0.5);
// Create a contact
const createRes = http.post(`${BASE_URL}/contacts`, JSON.stringify({
name: `Load Test User ${Date.now()}`,
email: `load-test-${Date.now()}@example.com`,
}), {
headers,
tags: { type: 'api', endpoint: 'contacts-create' },
});
check(createRes, {
'create contact: 201': (r) => r.status === 201,
});
apiCalls.add(1);
});
sleep(1);
}
// teardown() runs once after all VUs finish
export function teardown(data) {
// Clean up test data if needed
console.log(`Test complete. Data: ${JSON.stringify(data)}`);
}
Scénarios de rampe VU
Modélisez des modèles de trafic réels avec plusieurs étapes et scénarios :
// k6/stress-test.js
export const options = {
scenarios: {
// Scenario 1: Normal traffic baseline
normal_traffic: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 20 },
{ duration: '5m', target: 20 },
],
gracefulRampDown: '30s',
},
// Scenario 2: Sudden spike (marketing campaign goes live)
traffic_spike: {
executor: 'ramping-vus',
startVUs: 0,
startTime: '7m', // Starts after baseline is stable
stages: [
{ duration: '30s', target: 200 }, // Rapid spike
{ duration: '2m', target: 200 }, // Hold spike
{ duration: '30s', target: 20 }, // Fall back
],
},
// Scenario 3: Soak test for memory leaks
soak_test: {
executor: 'constant-vus',
vus: 10,
duration: '30m', // Run for 30 minutes
startTime: '10m',
},
},
thresholds: {
'http_req_duration{scenario:normal_traffic}': ['p(95)<300'],
'http_req_duration{scenario:traffic_spike}': ['p(95)<800'],
http_req_failed: ['rate<0.02'],
},
};
Test d'exploration du blog/du plan du site
// k6/sitemap-crawl.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
// Load URLs from sitemap (pre-fetched and saved as JSON)
const urls = new SharedArray('sitemap urls', function () {
// In real usage, fetch sitemap XML and parse URLs beforehand
// SharedArray is loaded once and shared across all VUs (memory efficient)
return JSON.parse(open('./sitemap-urls.json'));
});
export const options = {
vus: 20,
duration: '5m',
thresholds: {
http_req_duration: ['p(95)<2000'], // Public pages can be slower
http_req_failed: ['rate<0.01'],
},
};
export default function () {
// Each VU picks a random URL from the sitemap
const url = urls[Math.floor(Math.random() * urls.length)];
const res = http.get(url, {
headers: {
// Identify as load tester in logs
'User-Agent': 'k6-load-tester/1.0',
},
timeout: '10s',
});
check(res, {
'status is 200': (r) => r.status === 200,
'no error page': (r) => !r.body.includes('Error'),
'has canonical tag': (r) => r.body.includes('rel="canonical"'),
'response time < 2000ms': (r) => r.timings.duration < 2000,
});
sleep(Math.random() * 2 + 1); // Random 1-3 second think time
}
Métriques et seuils personnalisés
// k6/checkout-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate, Trend, Gauge } from 'k6/metrics';
// Custom metrics for business-specific tracking
const checkoutSuccesses = new Counter('checkout_successes');
const checkoutFailures = new Counter('checkout_failures');
const checkoutDuration = new Trend('checkout_duration');
const activeCheckouts = new Gauge('active_checkouts');
export const options = {
stages: [
{ duration: '1m', target: 10 },
{ duration: '3m', target: 30 },
{ duration: '1m', target: 0 },
],
thresholds: {
checkout_duration: ['p(95)<3000'], // Checkout < 3s at p95
checkout_failures: ['count<10'], // Max 10 absolute failures
http_req_duration: ['p(99)<2000'],
},
};
export default function (data) {
activeCheckouts.add(1);
const start = Date.now();
// Step 1: Add to cart
const cartRes = http.post('/api/cart/add', JSON.stringify({
productId: 'prod_odoo_customization',
quantity: 1,
}), { headers: { 'Content-Type': 'application/json' } });
if (!check(cartRes, { 'add to cart: 200': (r) => r.status === 200 })) {
checkoutFailures.add(1);
activeCheckouts.add(-1);
return;
}
sleep(2);
// Step 2: Create checkout session
const checkoutRes = http.post('/api/billing/checkout', JSON.stringify({
cartId: cartRes.json('cartId'),
}), { headers: { 'Content-Type': 'application/json' } });
const success = check(checkoutRes, {
'checkout session created': (r) => r.status === 200,
'has session URL': (r) => r.json('url') !== null,
});
if (success) {
checkoutSuccesses.add(1);
checkoutDuration.add(Date.now() - start);
} else {
checkoutFailures.add(1);
}
activeCheckouts.add(-1);
sleep(1);
}
Intégration de la sortie Grafana
Diffusez les métriques k6 vers InfluxDB et visualisez-les dans Grafana pendant le test :
# Run with InfluxDB output
k6 run --out influxdb=http://localhost:8086/k6 k6/api-load.js
# Or use the k6 Grafana integration (k6 v0.45+)
k6 run --out experimental-prometheus-rw k6/api-load.js
# docker-compose.monitoring.yml
services:
influxdb:
image: influxdb:2.7
ports: ["8086:8086"]
environment:
INFLUXDB_DB: k6
INFLUXDB_ADMIN_USER: admin
INFLUXDB_ADMIN_PASSWORD: password
grafana:
image: grafana/grafana:latest
ports: ["3030:3000"]
environment:
GF_AUTH_ANONYMOUS_ENABLED: "true"
volumes:
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards
- ./grafana/datasources:/etc/grafana/provisioning/datasources
Intégration CI
# .github/workflows/ci.yml (load test section)
load-tests:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' # Only on main branch
needs: [deploy-staging]
steps:
- uses: actions/checkout@v4
- name: Install k6
run: |
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
--keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
| sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6
- name: Run smoke test (30 VUs, 60s)
run: |
k6 run --vus 30 --duration 60s \
--env BASE_URL=${{ secrets.STAGING_URL }} \
--env TEST_EMAIL=${{ secrets.TEST_EMAIL }} \
--env TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} \
apps/api/k6/api-load.js
- name: Upload k6 results
uses: actions/upload-artifact@v4
if: always()
with:
name: k6-results
path: k6-results/
Références de performances
Établir des références et alerter sur les régressions :
| Point de terminaison | p50 | p95 | p99 | Budget d'erreur |
|---|---|---|---|---|
GET / (page d'accueil) | 120 ms | 400 ms | 800 ms | 0,1% |
GET /api/contacts | 50 ms | 150 ms | 300 ms | 0,5% |
POST /api/contacts | 80 ms | 200 ms | 400 ms | 0,5% |
POST /auth/login | 200 ms | 500 ms | 1000 ms | 1% |
POST /billing/checkout | 500 ms | 1500ms | 3000ms | 1% |
GET /blog/[slug] | 100 ms | 300 ms | 600 ms | 0,1% |
Questions fréquemment posées
Quelle est la différence entre les VU et les requêtes par seconde ?
Les VU (Virtual Users) sont des utilisateurs simulés simultanés. Chaque VU exécute votre fonction par défaut en boucle. Le nombre de requêtes par seconde dépend de la vitesse d'exécution de chaque itération. Si chaque itération de VU prend 2 secondes (temps de veille compris) et que vous disposez de 100 VU, vous obtenez environ 50 RPS. Utilisez l'exécuteur constant-arrival-rate lorsque vous devez cibler un RPS spécifique, quelle que soit la durée de l'itération.
Dois-je charger des tests par rapport à la production ou à la préparation ?
Préférez toujours la mise en scène. Les tests de charge de production risquent d'avoir un impact sur les utilisateurs réels, d'épuiser les limites de débit, de créer de véritables commandes à partir des données de test et de déclencher de véritables événements de webhook. Si vous devez tester la production, faites-le pendant les fenêtres à faible trafic, utilisez des jetons de paiement de test et disposez d'un plan de restauration. k6 Cloud prend en charge la génération de charge géo-distribuée afin que vous puissiez tester les performances de votre CDN Edge sans toucher à l'origine.
Comment gérer l'expiration du jeton d'authentification lors de tests longs ?
Pour les tests courts (moins de 15 minutes), obtenez un jeton en setup() et transmettez-le à tous les VU via data. Pour les tests longs (plus de 15 minutes), implémentez l'actualisation du jeton dans le script de test : vérifiez l'âge du jeton dans la fonction par défaut et actualisez-le lorsqu'il approche de son expiration. Stockez le jeton dans une variable JavaScript locale à chaque VU.
Quelle est la différence entre stages et scenarios ?
stages est un raccourci pour un seul scénario ramping-vus — idéal pour les modèles simples de montée/maintien/décélération. scenarios vous donne un contrôle total : plusieurs modèles de trafic simultanés, différents exécuteurs par scénario, seuils par scénario et marquage des scénarios dans les métriques. Utilisez scenarios pour des tests multi-modèles réalistes (ligne de base + pic + trempage simultanément).
Combien de VU un seul processus k6 peut-il gérer ?
Un seul processus k6 sur du matériel moderne peut prendre en charge 5 000 à 10 000 VU et générer 50 000 à 100 000 RPS, en fonction de la complexité du script et de la taille de la réponse. Pour des charges plus élevées, utilisez k6 cloud ou exécutez plusieurs instances k6 derrière un répartiteur de charge. Chaque VU est extrêmement léger (une goroutine) — k6 utilise beaucoup moins de ressources que JMeter ou Gatling pour un nombre de VU équivalent.
Prochaines étapes
Les tests de charge révèlent le véritable profil de performances de votre application avant que les utilisateurs ne le découvrent sous pression. Les modèles présentés dans ce guide (scénarios de montée en puissance, seuils personnalisés, intégration CI et tableaux de bord Grafana) vous fournissent l'infrastructure nécessaire pour rechercher et résoudre en permanence les goulots d'étranglement des performances.
ECOSIRE crée des API NestJS aux performances validées avec des tests de charge k6 couvrant la page d'accueil, les points de terminaison de l'API, les flux de paiement et les analyses complètes du plan du site. Découvrez nos services d'ingénierie backend pour découvrir comment nous construisons des performances dès le premier jour.
Rédigé par
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Développez votre entreprise avec ECOSIRE
Solutions d'entreprise pour l'ERP, le commerce électronique, l'IA, l'analyse et l'automatisation.
Articles connexes
Intégration de l'API Hepsiburada avec Odoo : guide de configuration complet
Guide complet d'intégration de Hepsiburada avec Odoo ERP via API. Automatisez les commandes, l'inventaire et l'exécution sur le marché de confiance de Turquie.
Shopify Integration Hub : Comment connecter Shopify à n'importe quel système en 2026
Guide complet des intégrations Shopify : API, webhooks, middleware, méthodes iPaaS. Connectez Shopify aux systèmes ERP, comptables, CRM, places de marché et points de vente.
Limitation du débit des API : modèles et meilleures pratiques
Limitation du débit de l'API maître avec un compartiment de jetons, une fenêtre coulissante et des modèles de compteurs fixes. Protégez votre backend avec le régulateur NestJS, Redis et des exemples de configuration réels.
Plus de Performance & Scalability
Débogage et surveillance des webhooks : le guide de dépannage complet
Maîtrisez le débogage des webhooks avec ce guide complet couvrant les modèles de défaillance, les outils de débogage, les stratégies de nouvelle tentative, les tableaux de bord de surveillance et les meilleures pratiques de sécurité.
Configuration de production Nginx : SSL, mise en cache et sécurité
Guide de configuration de production Nginx : terminaison SSL, HTTP/2, en-têtes de mise en cache, en-têtes de sécurité, limitation de débit, configuration du proxy inverse et modèles d'intégration Cloudflare.
Odoo Performance Tuning : PostgreSQL et optimisation du serveur
Guide expert sur le réglage des performances d’Odoo 19. Couvre la configuration PostgreSQL, l'indexation, l'optimisation des requêtes, la mise en cache Nginx et le dimensionnement du serveur pour les déploiements d'entreprise.
Odoo vs Acumatica : ERP cloud pour les entreprises en croissance
Odoo vs Acumatica comparés pour 2026 : modèles de tarification uniques, évolutivité, profondeur de fabrication et quel ERP cloud correspond à votre trajectoire de croissance.
Test et surveillance des agents IA en production
Un guide complet pour tester et surveiller les agents IA dans les environnements de production. Couvre les cadres d'évaluation, l'observabilité, la détection des dérives et la réponse aux incidents pour les déploiements OpenClaw.
Agents de surveillance de la conformité avec OpenClaw
Déployez des agents OpenClaw AI pour une surveillance continue de la conformité. Automatisez les contrôles réglementaires, l’application des politiques, la génération de pistes d’audit et les rapports de conformité.