All parts · Scaling
Scaling to 1M Requests — Part 09Testing

Load Testing Before Launch with k6

Find your breaking point before your users do: smoke, load, stress, spike and soak tests with k6, arrival-rate scenarios and percentile thresholds.

Syed Wajeeh Haider
Syed Wajeeh Haider
Full Stack Software Engineer
Sep 15, 20263 min read
#k6#Load Testing#Performance#Capacity Planning

Every architecture looks scalable on a whiteboard. Load testing is how you find out which component actually breaks first, at what traffic level, and what it looks like when it does. It's far cheaper to learn that on a Tuesday afternoon in staging than during launch week.

5
Test types worth running
p95
The latency number to gate on
< 1%
A typical error-rate threshold

Five tests, five questions

  • —Smoke: a few users for a minute. Does the script work, and does the system respond at all?
  • —Load: your expected peak for 15 to 30 minutes. Do you meet your latency and error targets?
  • —Stress: keep ramping past the peak. Where is the breaking point, and does it fail gracefully?
  • —Spike: jump from normal to 5 to 10 times normal in seconds. Does auto-scaling react in time?
  • —Soak: moderate load for hours. Do memory leaks, connection leaks or growing queues appear?

Model arrival rate, not users

Real traffic arrives at a rate whether or not your server is slow. Many tools simulate a fixed number of virtual users that each wait for a response before sending the next request, so when the system slows down, the load politely slows down too and hides the problem. k6's ramping-arrival-rate executor starts iterations at the rate you specify, regardless of response times.

js
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  scenarios: {
    ramp_to_peak: {
      executor: 'ramping-arrival-rate',
      startRate: 50,
      timeUnit: '1s', // rates below are requests per second
      preAllocatedVUs: 200,
      maxVUs: 2000,
      stages: [
        { target: 500, duration: '5m' }, // warm up
        { target: 2000, duration: '10m' }, // ramp to peak
        { target: 2000, duration: '15m' }, // hold the peak
        { target: 0, duration: '2m' }, // ramp down
      ],
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const res = http.get(`${__ENV.BASE_URL}/api/products?page=1`);
  check(res, { 'status is 200': (r) => r.status === 200 });
}
load-test.js
bash
k6 run -e BASE_URL=https://staging.example.com load-test.js

Thresholds turn the test into a pass or fail gate: if p95 latency goes over 500 ms or more than 1% of requests fail, k6 exits with a non-zero code. Run a smaller version in CI on every release so regressions never reach production quietly.

Test somewhere that tells the truth

  • —Prod-like infrastructure: the same instance types, auto-scaling rules, database size and cache configuration. A test against a laptop-sized staging database tells you about the laptop.
  • —Realistic data: enough rows that indexes and query plans behave as they will in production, and realistic cache hit rates.
  • —Realistic traffic mix: weight endpoints the way users do. If 70% of production traffic is reads, your test should be too.
  • —Generators with headroom: make sure the load generator is not the bottleneck. Run k6 from several machines or regions for large tests.

Reading the results

Ignore the average. An average of 120 ms can hide a p99 of 4 seconds, and at a million requests that p99 is ten thousand unhappy users. Look at p95 and p99 latency, the error rate, and throughput achieved versus throughput requested. If k6 asked for 2,000 requests per second and only got 1,400, the system is saturated even if latency looks acceptable.

Find the knee of the curve

Plot latency against request rate during a stress test. Latency stays flat, then bends sharply upwards: that bend is the knee, where some resource saturates and queues start building. Your safe operating capacity sits comfortably below it. Then find out what caused it by lining the timeline up with your metrics: CPU, event loop delay, database connections, cache hit rate, or a downstream API.

  1. 01Run the stress test and note the rate where p99 starts climbing.
  2. 02Find the first resource that saturated at that moment.
  3. 03Fix or scale that one thing.
  4. 04Run the test again. A new, higher knee will appear somewhere else. Repeat until it sits well above your target peak.

Key takeaways

  • —Run smoke, load, stress, spike and soak tests. Each one answers a different question.
  • —Use arrival-rate executors so slow responses do not hide saturation.
  • —Gate releases on p95, p99 and error-rate thresholds, never on averages.
  • —Find the knee, fix the bottleneck, and test again.

Load tests show you problems before launch. In production you need to see them as they happen. The series finale: Observability for High-Traffic Systems.

The series — Scaling to 1M Requests
(10)Let's talk

Ready to scaleyour application?

Stop worrying about technical debt. I can help you build a reliable, high-performance product from the ground up, tailored exactly to your business needs.

Actively open for new opportunities
Get in touch
Local time
--:-- PKT
© 2026 Syed Wajeeh Haider. All rights reserved.