Load Testing
k6 (recommended)
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 }, // Ramp up to 50 users
{ duration: '3m', target: 50 }, // Stay at 50
{ duration: '1m', target: 100 }, // Ramp to 100
{ duration: '3m', target: 100 }, // Stay at 100
{ duration: '1m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th < 500ms
http_req_failed: ['rate<0.01'], // <1% error rate
},
};
export default function () {
const res = http.get('https://api.example.com/products');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
Authenticated Requests
import http from 'k6/http';
export function setup() {
const res = http.post('https://api.example.com/auth/login', JSON.stringify({
email: 'loadtest@example.com', password: 'test-password',
}), { headers: { 'Content-Type': 'application/json' } });
return { token: res.json('token') };
}
export default function (data) {
const headers = { Authorization: `Bearer ${data.token}`, 'Content-Type': 'application/json' };
// Simulate user journey
http.get('https://api.example.com/products', { headers });
sleep(2);
http.get('https://api.example.com/products/1', { headers });
sleep(1);
http.post('https://api.example.com/orders', JSON.stringify({ productId: '1', qty: 2 }), { headers });
}
Artillery
# artillery.yml
config:
target: "https://api.example.com"
phases:
- duration: 60
arrivalRate: 10
name: "Warm up"
- duration: 120
arrivalRate: 50
name: "Sustained load"
defaults:
headers:
Content-Type: "application/json"
scenarios:
- name: "Browse and purchase"
flow:
- get:
url: "/products"
capture:
- json: "$.data[0].id"
as: "productId"
- think: 2
- get:
url: "/products/{{ productId }}"
- think: 1
- post:
url: "/orders"
json:
productId: "{{ productId }}"
quantity: 1
Locust (Python)
from locust import HttpUser, task, between
class WebUser(HttpUser):
wait_time = between(1, 3)
def on_start(self):
res = self.client.post("/auth/login", json={
"email": "test@example.com", "password": "password"
})
self.token = res.json()["token"]
self.headers = {"Authorization": f"Bearer {self.token}"}
@task(3)
def browse_products(self):
self.client.get("/products", headers=self.headers)
@task(1)
def create_order(self):
self.client.post("/orders", json={"productId": "1", "qty": 1}, headers=self.headers)
Test Types
| Type |
Purpose |
Duration |
| Smoke |
Verify system works under minimal load |
1-2 min |
| Load |
Expected normal/peak traffic |
10-30 min |
| Stress |
Beyond normal capacity, find breaking point |
10-20 min |
| Soak |
Sustained load to find memory leaks |
1-4 hours |
| Spike |
Sudden traffic burst |
5-10 min |
Anti-Patterns
| Anti-Pattern |
Fix |
| Testing from same network as server |
Test from external network or cloud |
| No think time between requests |
Add realistic sleep/think delays |
| Single endpoint only |
Test realistic user journeys |
| No thresholds defined |
Set p95/p99 latency and error rate thresholds |
| Running load tests against production |
Use staging environment |
Production Checklist
1---2name: load-testing3description: Load and performance testing. k6 (Grafana), Artillery, Apache JMeter, Locust (Python). Scenario design, thresholds, ramp-up patterns, and CI/CD integration. USE WHEN: user mentions "load test", "performance test", "stress test", "k6", "Artillery", "JMeter", "Locust", "throughput", "benchmarking" DO NOT USE FOR: unit/integration testing - use testing skills; monitoring in production - use `opentelemetry`4---5# Load Testing67## k6 (recommended)89```javascript10// load-test.js11import http from 'k6/http';12import { check, sleep } from 'k6';1314export const options = {15 stages: [16 { duration: '1m', target: 50 }, // Ramp up to 50 users17 { duration: '3m', target: 50 }, // Stay at 5018 { duration: '1m', target: 100 }, // Ramp to 10019 { duration: '3m', target: 100 }, // Stay at 10020 { duration: '1m', target: 0 }, // Ramp down21 ],22 thresholds: {23 http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th < 500ms24 http_req_failed: ['rate<0.01'], // <1% error rate25 },26};2728export default function () {29 const res = http.get('https://api.example.com/products');30 check(res, {31 'status is 200': (r) => r.status === 200,32 'response time < 500ms': (r) => r.timings.duration < 500,33 });34 sleep(1);35}36```3738### Authenticated Requests3940```javascript41import http from 'k6/http';4243export function setup() {44 const res = http.post('https://api.example.com/auth/login', JSON.stringify({45 email: 'loadtest@example.com', password: 'test-password',46 }), { headers: { 'Content-Type': 'application/json' } });47 return { token: res.json('token') };48}4950export default function (data) {51 const headers = { Authorization: `Bearer ${data.token}`, 'Content-Type': 'application/json' };5253 // Simulate user journey54 http.get('https://api.example.com/products', { headers });55 sleep(2);56 http.get('https://api.example.com/products/1', { headers });57 sleep(1);58 http.post('https://api.example.com/orders', JSON.stringify({ productId: '1', qty: 2 }), { headers });59}60```6162## Artillery6364```yaml65# artillery.yml66config:67 target: "https://api.example.com"68 phases:69 - duration: 6070 arrivalRate: 1071 name: "Warm up"72 - duration: 12073 arrivalRate: 5074 name: "Sustained load"75 defaults:76 headers:77 Content-Type: "application/json"7879scenarios:80 - name: "Browse and purchase"81 flow:82 - get:83 url: "/products"84 capture:85 - json: "$.data[0].id"86 as: "productId"87 - think: 288 - get:89 url: "/products/{{ productId }}"90 - think: 191 - post:92 url: "/orders"93 json:94 productId: "{{ productId }}"95 quantity: 196```9798## Locust (Python)99100```python101from locust import HttpUser, task, between102103class WebUser(HttpUser):104 wait_time = between(1, 3)105106 def on_start(self):107 res = self.client.post("/auth/login", json={108 "email": "test@example.com", "password": "password"109 })110 self.token = res.json()["token"]111 self.headers = {"Authorization": f"Bearer {self.token}"}112113 @task(3)114 def browse_products(self):115 self.client.get("/products", headers=self.headers)116117 @task(1)118 def create_order(self):119 self.client.post("/orders", json={"productId": "1", "qty": 1}, headers=self.headers)120```121122## Test Types123124| Type | Purpose | Duration |125|------|---------|----------|126| Smoke | Verify system works under minimal load | 1-2 min |127| Load | Expected normal/peak traffic | 10-30 min |128| Stress | Beyond normal capacity, find breaking point | 10-20 min |129| Soak | Sustained load to find memory leaks | 1-4 hours |130| Spike | Sudden traffic burst | 5-10 min |131132## Anti-Patterns133134| Anti-Pattern | Fix |135|--------------|-----|136| Testing from same network as server | Test from external network or cloud |137| No think time between requests | Add realistic `sleep`/`think` delays |138| Single endpoint only | Test realistic user journeys |139| No thresholds defined | Set p95/p99 latency and error rate thresholds |140| Running load tests against production | Use staging environment |141142## Production Checklist143144- [ ] Realistic user scenarios (not just single endpoints)145- [ ] Thresholds for latency (p95, p99) and error rate146- [ ] Ramp-up pattern matching expected traffic shape147- [ ] Test data seeded (not reusing same record)148- [ ] CI/CD integration for regression detection149- [ ] Results archived for comparison over time