Skip to content

Performance Testing

Performance testing measures how a system behaves under load: how fast it responds, how much throughput it sustains, and where it breaks. It is not one test but a family of profiles.

VUs
50 │ ┌──────────────┐ load / soak
│ / \
0 └─────┘ └──────► time
stress: ramp until it breaks spike: sudden jump soak: long steady run

The numbers that matter are latency percentiles (p50, p95, p99 — never just the average), throughput (requests per second), and error rate at each load step.

Use it for

  • Before a launch, sale or migration where traffic will step up sharply
  • Verifying an SLA or service-level objective (e.g. p95 < 300 ms at 1k rps)
  • Finding the saturation point and the first bottleneck (CPU, pool, lock, GC)
  • Checking memory or connection leaks with a sustained soak run

Avoid or reduce it when

  • The feature is still changing shape — optimise after the design settles
  • The environment is not representative (shared CI runner, laptop, cold cache)
  • You only read the average latency; percentiles are where the pain hides
  • You run a full soak on every commit — keep a fast smoke load in CI instead
Tool Ecosystem Notes
k6 JS/TS Scriptable in JavaScript, CLI-first, great CI story
Apache JMeter Java / XML GUI + CLI, huge protocol coverage, mature
Gatling Java / Scala / Kotlin DSL, excellent HTML reports
Locust Python Plain Python load scripts, distributed workers
vegeta Go Tiny CLI, constant-rate attack
NBomber C# / .NET Fluent .NET load tests
Artillery Node.js YAML scenarios, serverless-friendly
Grafana Cloud k6 SaaS Hosted runs, dashboards
Terminal window
brew install k6 # or: docker pull grafana/k6
k6 run script.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 }, // ramp up
{ duration: '1m', target: 50 }, // steady
{ duration: '30s', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p(95)<300'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('http://localhost:8080/health');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
  • Report percentiles, not averages. Averages hide the slow tail that real users feel.
  • Match the environment. Test where the shape of the data, network and instance sizes resemble production, or the numbers mean nothing.
  • Fix one variable at a time. Change load, not also the build, so you can attribute the result.
  • Set thresholds in code. Fail the pipeline on p95 or error-rate regressions, not on a human eyeballing a graph.
  • Model think time. No pauses measures raw throughput, not user behaviour.
  • Watch the system, not only the client. Correlate load with CPU, memory, GC, pool saturation and downstream latency.

Self-assessment quiz

Pick one answer per question, then press Check answers.

  1. Which metric best represents the experience of slow users?
  2. What does a *soak* test primarily look for?
  3. Which tool runs load scripts written in JavaScript?
  4. What is a *spike* test?
  5. Why should performance thresholds be committed in code?