Load & Performance Testing

Unit and integration tests prove that your code works for one user. Performance testing proves that your code works for ten thousand users simultaneously.

Without performance testing, a minor inefficiency (like an N+1 database query or a slow Regex) might go unnoticed in development but will completely crash the server in production.

1. Types of Performance Testing

[!TIP] ELI5: Testing the New Bridge

  • Load Testing: You expect 500 cars per hour on a normal day. So, you drive 500 cars across it to make sure traffic flows smoothly without the bridge swaying.
  • Stress Testing: You want to know exactly when the bridge breaks. You drive 1,000 cars, then 5,000 cars, then 10,000 cars onto it until the concrete starts cracking. Now you know its absolute physical limit.
  • Spike Testing: The bridge is empty. Suddenly, a Godzilla attack happens, and 5,000 cars slam onto the bridge in exactly 10 seconds. You test if the bridge can handle extreme, instantaneous shock.

Load Testing

Validates that the system meets its performance goals under the expected peak concurrent load.

Stress Testing

Pushes the system far beyond its expected capacity until it breaks.

Spike Testing

A specialized form of stress testing that simulates a massive, instantaneous surge in traffic (e.g., tickets going on sale for a Taylor Swift concert, or a viral Black Friday tweet).

2. Key Performance Metrics

When running these tests, you don't just look at "Average Response Time". Averages are deceiving.

3. Modern Tooling: k6

While older tools like Apache JMeter are powerful, they rely on clunky XML configurations. Modern engineering teams prefer developer-friendly, code-based tools like k6 (built by Grafana).

With k6, you write your load tests in JavaScript, making it incredibly easy to integrate into CI/CD pipelines.

Code Example: A k6 Load Test

import http from 'k6/http';
import { check, sleep } from 'k6';

// Define the "Shape" of the load
export const options = {
  stages: [
    { duration: '30s', target: 50 }, // Ramp up to 50 virtual users over 30s
    { duration: '1m', target: 50 },  // Hold at 50 users for 1 minute
    { duration: '10s', target: 0 },  // Ramp down to 0 users
  ],
  thresholds: {
    // We fail the test if 99% of requests take longer than 500ms
    http_req_duration: ['p(99)<500'], 
    // We fail if more than 1% of requests error out
    http_req_failed: ['rate<0.01'],   
  },
};

// The actual test logic executed by every virtual user
export default function () {
  const res = http.get('https://api.example.com/checkout');
  
  // Verify the response
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
  
  // Wait 1 second before making the next request
  sleep(1); 
}