Performance & Load Testing
Load test your applications with k6 and Artillery, run stress and soak tests, identify bottlenecks, and set performance budgets
Types of Performance Tests
Performance testing validates that your application meets speed, scalability, and stability requirements. Different test types simulate different real-world scenarios to find different kinds of problems.
Load Testing
Test with expected user volume. Does the system handle normal traffic?
Stress Testing
Push beyond normal limits. Find the breaking point of your system.
Soak Testing
Run at moderate load for hours. Find memory leaks and resource exhaustion.
Spike Testing
Sudden burst of traffic. Does the system recover after a spike?
Load Testing with k6
k6 is a developer-centric load testing tool that uses JavaScript for test scripts.
// load-test.js - k6 script
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
// Ramp up to 100 users over 2 minutes, sustain, then ramp down
stages: [
{ duration: '1m', target: 50 }, // Ramp up
{ duration: '3m', target: 100 }, // Sustain peak
{ duration: '1m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
http_req_failed: ['rate<0.01'], // Less than 1% errors
http_reqs: ['rate>100'], // At least 100 RPS
},
};
export default function () {
// GET request
const listRes = http.get('http://localhost:3000/api/products');
check(listRes, {
'list status is 200': (r) => r.status === 200,
'list response time < 200ms': (r) => r.timings.duration < 200,
'list returns array': (r) => JSON.parse(r.body).length > 0,
});
// POST request
const payload = JSON.stringify({
name: `Product \${Date.now()}`,
price: Math.random() * 100,
});
const createRes = http.post('http://localhost:3000/api/products', payload, {
headers: { 'Content-Type': 'application/json' },
});
check(createRes, {
'create status is 201': (r) => r.status === 201,
});
sleep(1); // Think time between requests
}
// Run: k6 run load-test.js
// Run with cloud output: k6 run --out cloud load-test.js
k6 Advanced Scenarios
// Stress test - find breaking point
import http from 'k6/http';
export const options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '2m', target: 200 },
{ duration: '2m', target: 400 },
{ duration: '2m', target: 800 }, // Keep increasing
{ duration: '2m', target: 0 }, // Recovery
],
};
// Spike test - sudden traffic burst
export const spikeOptions = {
stages: [
{ duration: '30s', target: 10 }, // Normal load
{ duration: '10s', target: 500 }, // Spike!
{ duration: '30s', target: 500 }, // Sustain spike
{ duration: '10s', target: 10 }, // Return to normal
{ duration: '30s', target: 10 }, // Recovery period
],
};
// Soak test - extended duration
export const soakOptions = {
stages: [
{ duration: '5m', target: 50 }, // Ramp up
{ duration: '4h', target: 50 }, // Sustain for hours
{ duration: '5m', target: 0 }, // Ramp down
],
};
Artillery for Load Testing
# artillery-config.yml
config:
target: "http://localhost:3000"
phases:
- duration: 120
arrivalRate: 10
rampTo: 50
name: "Ramp up load"
- duration: 300
arrivalRate: 50
name: "Sustained load"
defaults:
headers:
Content-Type: "application/json"
ensure:
p99: 500 # 99th percentile under 500ms
maxErrorRate: 1 # Less than 1% errors
scenarios:
- name: "Browse and purchase"
flow:
- get:
url: "/api/products"
capture:
- json: "$[0].id"
as: "productId"
- think: 2
- get:
url: "/api/products/{{ productId }}"
- think: 1
- post:
url: "/api/cart"
json:
productId: "{{ productId }}"
quantity: 1
# Run: npx artillery run artillery-config.yml
# Quick test: npx artillery quick --count 100 --num 10 http://localhost:3000/api/products
Identifying Bottlenecks
Common Bottlenecks & Solutions:
Database queries slowing down under load:
Add indexes, use connection pooling, implement query caching, optimize N+1 queries.
Memory usage growing over time (soak test):
Profile with --inspect flag, check for event listener leaks, unbounded caches, or large array accumulation.
Response times increasing with concurrency:
Add horizontal scaling, implement rate limiting, use async processing for heavy operations.
High error rate during spikes:
Implement circuit breakers, add request queuing, configure auto-scaling with appropriate thresholds.
Key Takeaways
- Use different test types (load, stress, spike, soak) to find different problems
- Set clear thresholds (p95 latency, error rate, throughput) before testing
- k6 is great for developers; Artillery is YAML-first and easy to start
- Run performance tests in CI against staging environments
- Profile the system (CPU, memory, DB) during load tests to find bottlenecks