Stage 3 · Build
Performance & Caching at Scale
Load Testing
Run k6, vegeta, or wrk scenarios with realistic traffic, SLO thresholds, and saturation analysis.
Why Load Test
Load testing reveals how your service behaves under realistic traffic. It finds bottlenecks before production does. You discover that your API handles 100 req/s in development but only 50 req/s under sustained load.
k6
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const errorRate = new Rate('errors');
const latency = new Trend('api_latency');
export const options = {
stages: [
{ duration: '30s', target: 20 }, // ramp up
{ duration: '1m', target: 20 }, // sustained load
{ duration: '30s', target: 50 }, // spike
{ duration: '1m', target: 50 }, // sustained spike
{ duration: '30s', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
errors: ['rate<0.01'],
},
};
export default function () {
const res = http.get('http://localhost:8080/api/users');
check(res, {
'status is 200': (r) => r.status === 200,
'latency < 500ms': (r) => r.timings.duration < 500,
});
errorRate.add(res.status !== 200);
latency.add(res.timings.duration);
sleep(1);
}k6 uses JavaScript for test scripts. Define load stages (ramp up, sustained, spike). Set thresholds for pass/fail. k6 reports latency percentiles, error rates, and throughput.
vegeta
# Constant rate attack
echo "GET http://localhost:8080/api/users" | \
vegeta attack -duration=60s -rate=100/s | \
vegeta report
# With headers
echo "GET http://localhost:8080/api/users" | \
vegeta attack -duration=60s -rate=50/s -header="Authorization: Bearer token" | \
vegeta report -every=5s
# Plot results
echo "GET http://localhost:8080/api/users" | \
vegeta attack -duration=60s -rate=100/s | \
vegeta plot > report.htmlvegeta is a simple, powerful load testing tool. Pipe requests to it and get detailed reports. vegeta report shows latency percentiles, throughput, and error rates.
Realistic Scenarios
export default function () {
// Login
const loginRes = http.post('http://localhost:8080/login', JSON.stringify({
email: 'test@example.com',
password: 'password',
}), { headers: { 'Content-Type': 'application/json' } });
const token = loginRes.json('token');
const params = {
headers: { 'Authorization': 'Bearer ' + token },
};
// Browse products
http.get('http://localhost:8080/api/products?limit=20', params);
sleep(0.5);
// View product detail
http.get('http://localhost:8080/api/products/prod_123', params);
sleep(1);
// Create order
http.post('http://localhost:8080/api/orders', JSON.stringify({
product_id: 'prod_123',
quantity: 1,
}), params);
}Test realistic user journeys, not just single endpoints. Login, browse, view, purchase. This reveals bottlenecks in the full request chain.
SLO Thresholds
| Metric | Good | Acceptable | Bad |
|---|---|---|---|
| p50 latency | < 50ms | < 100ms | > 200ms |
| p95 latency | < 200ms | < 500ms | > 1s |
| p99 latency | < 500ms | < 1s | > 2s |
| Error rate | < 0.1% | < 1% | > 5% |
| Throughput | > 1000 req/s | > 500 req/s | < 100 req/s |
Saturation Analysis
Load test at twice your expected peak traffic. This reveals breaking points and gives you headroom. If your API handles 500 req/s at 2x peak, you are safe at 250 req/s in production.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.