What is Performance Testing?
Performance testing checks how a system behaves under a particular workload: how fast it responds, how many requests it can handle, whether it stays stable over time and what happens when it is pushed past its limits. Functional tests answer "does it work?"; performance tests answer "does it still work at 9 am on launch day with ten thousand users?".
This guide explains the types of performance test and when each is used, the metrics to measure and how to read them, a practical plan for a first test of an API or web application, and the tools that run the load.
Types of performance test
- Load testing — the expected, realistic traffic (average and peak) to confirm the system meets its response-time and error targets under normal conditions.
- Stress testing — traffic pushed beyond the expected peak until something breaks, to find the limit and see whether the failure is graceful (slow, 503s) or catastrophic (crashes, data corruption).
- Spike testing — a sudden jump from low to very high traffic, as in a marketing campaign or a flash sale, to check autoscaling and queueing.
- Soak (endurance) testing — moderate load for hours or days, to catch memory leaks, connection pool exhaustion and disk growth that short tests never show.
- Scalability testing — increasing load in steps to learn how throughput and latency change as resources are added.
- Benchmarking — a quick, repeatable measurement of one endpoint to compare two versions or two implementations.
- Load testingAverage, then peak traffic: are the response-time and error targets met?
Reference line:expected peak
- Stress testingBeyond the expected peak until something breaks: graceful (slow, 503s) or catastrophic?
Reference line:expected peak
something breaks
- Spike testingA sudden jump from low to very high traffic: checks autoscaling and queueing.
Reference line:expected peak
- Soak (endurance) testingModerate load for hours or days: memory leaks, pool exhaustion, disk growth.time · hours or days
Reference line:expected peak
- Scalability testingLoad raised in steps: how do throughput and latency change as resources are added?
Schematic shapes on one shared load scale: the heights and time spans are illustrative. Benchmarking measures one endpoint and has no load shape of its own.
Try it: HTTP Request Benchmark Try it: API Performance Comparator
The metrics that matter
- Response time — measured per request. Report percentiles, not the average: p50 (median), p95 and p99 tell you what most users and the unluckiest users experience. An average hides the slow tail.
- Throughput — requests per second (or transactions per second) the system sustains.
- Error rate — percentage of requests that fail (5xx, timeouts, connection errors). It must stay near zero at the target load.
- Concurrency — how many virtual users or open connections are active at once.
- Resource usage — CPU, memory, database connections, queue depth on the server side, correlated with the load timeline.
- SLO compliance and Apdex — SLO compliance is the share of requests inside the agreed budget (for example 95% under 300 ms). Apdex is a related 0–1 score against a target time T: responses up to T count in full, responses up to 4T count as half and slower ones count as zero.
Try it: Response Time Analyzer Try it: Response Size Analyzer
Planning a first performance test
- Define the goal as a number: "checkout completes in under 800 ms at p95 with 500 concurrent users and less than 0.1% errors".
- Pick the critical journeys — login, search, add to cart, checkout, the top three API endpoints — rather than every page.
- Model realistic traffic: the mix of journeys, think time between actions, ramp-up and steady state. Use production analytics if you have them.
- Prepare test data at scale: thousands of distinct users, products and payloads so caches and unique constraints behave as in production.
- Run in an environment that resembles production in size, or scale the results deliberately. A laptop-sized database will not tell you about a production one.
- Establish a baseline first with a single user, then ramp. Compare every later run against the baseline.
- Monitor the server side during the run; the client-side numbers alone cannot tell you why it got slow.
Try it: Load Test Data Generator Try it: Concurrent Request Test Generator
Tools: JMeter, k6, Gatling and browser tools
Apache JMeter is the long-standing open-source choice with a GUI, recorders and CSV data sets; k6 is script-based (JavaScript), lightweight and CI-friendly; Gatling is Scala/Java-based with excellent reports; Locust is Python-based. Cloud services (BlazeMeter, Grafana Cloud k6, Azure Load Testing) run the same scripts from many regions.
A browser cannot generate production-scale load, but browser tools are ideal for the steps around a load test: measuring a single endpoint's response time distribution, comparing two endpoints, checking payload size and compression, and generating the CSV and JSON data files the runners consume.
Try it: JMeter Test Data Generator Try it: JMeter CSV Generator Try it: HTTP Request Benchmark
Reading the results
- Latency rising while throughput flattens means a bottleneck was reached — CPU, a database, a connection pool or a downstream API.
- Error rate climbing before latency does often means a limit (max connections, rate limit) rather than slowness.
- p99 far above p95 points at garbage collection pauses, lock contention or a slow dependency hit by a few requests.
- Slow degradation during a soak test is a leak: memory, file handles or connections.
- A result that cannot be reproduced on a second run is noise; fix the environment before tuning the code.
Try it: API Performance Comparator Try it: Cache Header Analyzer
Common mistakes
- Testing with one user id or one product — everything hits the cache and the numbers are fantasy.
- Ramping to full load instantly, then blaming the system for a spike it was not designed for.
- Reporting averages. Always report p95 and p99 with the error rate.
- Running the load generator on the same machine as the system under test.
- Ignoring payload size: a 2 MB JSON response is slow no matter how fast the server is.
- Testing once before launch and never again. Performance regresses one commit at a time.
Try it: Response Size Analyzer Try it: Load Test Data Generator
Frequently asked questions
What is the difference between load testing and stress testing?
Load testing applies the expected traffic to confirm targets are met; stress testing keeps increasing traffic past the expected peak to find the breaking point and see how the system fails.
What is a good API response time?
It depends on the use, but common budgets are under 100 ms for cached reads, under 300 ms for typical API calls and under 1 s for heavy operations — measured at p95, not on average.
Why use p95 instead of the average response time?
Averages hide the slow tail. p95 says 95% of requests were at least this fast, which is what users notice; p99 shows the worst realistic case.
How many virtual users do I need?
Derive it from real traffic. Each virtual user spends time waiting for a response and time "thinking" between actions, so by Little's law virtual users ≈ target requests per second × (average response time + average think time per request). At 100 requests per second, 0.3 s responses and 5 s of think time that is about 530 users; requests per second × response time alone (30 here) counts only the requests in flight. Add headroom of 20–50% for growth.
Can I performance-test from a browser?
You can measure and benchmark individual endpoints and compare two of them, which is useful during development. Production-scale load needs a dedicated tool such as JMeter, k6 or Gatling running on separate machines.
Which is better, JMeter or k6?
JMeter has a GUI, recorders and the largest ecosystem; k6 is code-first, lighter and integrates naturally with CI. Teams comfortable with JavaScript often prefer k6; teams with existing JMeter plans keep JMeter.
References
- Apache JMeter User's ManualHow to build, parameterise and run load tests with JMeter, and how to read its listeners and reports.
- Web VitalsThe Core Web Vitals (LCP, INP, CLS): what they measure and the thresholds for a good user experience.
- ISO/IEC 25010:2023 — Systems and software Quality Requirements and Evaluation (SQuaRE) — Product quality modelThe product quality model: functional suitability, performance efficiency, compatibility, interaction capability, reliability, security, maintainability and flexibility.
Practice exercise
Plan a registration-day load test and read its results
You test the student portal of a university (fictional). Release 3.2 replaces the back end of course registration. Registration for the spring semester opens on Sunday 17 January 2027 at 08:00 and stays open for three weeks; about 18,000 students register, as last semester. At the planning meeting five people raised concerns. Product owner: "Registration must be fast and must not crash on day one." Registrar: "Last semester the portal was unusable for the first hour after 08:00; students kept refreshing the page." Operations lead: "The old system got slower and slower and had to be restarted every few days." Architect: "Would a second application server raise the limit? I need to know before we buy it." Developer: "Is the new seat-availability query faster than the old one?"
A developer has drafted the first load test plan: (1) run k6 on the staging application server itself, to avoid network noise; (2) all virtual users log in as the test account student01; (3) start 1,200 virtual users in the same second and run for 10 minutes; (4) report the average response time, and the test passes if it is under 1 s; (5) use the usual staging data, 300 students and 40 course sections. The material below holds last semester’s traffic figures and the results of a stepped load test of release candidate 3.2.0-rc1 that your team ran later with a corrected set-up.
Work on paper with the guide’s test types, metrics, planning checklist and rules for reading results. The Performance Test Data Generator can turn your workload into a ramp profile and estimate its throughput, but pen and paper are enough. Allow 40–45 minutes.
Last semester’s access logs (old registration back end)
Busiest 15 minutes 08:00-08:15 on opening day: 100 requests per second
Ordinary day at most 15 requests per second
Peak journey mix search courses 60%, view a section 25%,
register for a section 10%, log in 5%
Think time 7.5 s on average, from a response to the next request
Planning assumption for release 3.2: average response time 0.5 s
Stepped load test of release candidate 3.2.0-rc1, run by the QA team
Production-sized staging; k6 load generators on separate machines
Peak journey mix, 7.5 s think time, 10 minutes per step
Single-user baseline: p50 150 ms, p95 210 ms, no errors
Step Virtual Throughput p50 p95 p99 Errors App server DB connections
users (req/s) (ms) (ms) (ms) CPU peak (pool 50)
1 200 26 160 300 420 0.00% 21% 9
2 400 52 170 340 510 0.00% 39% 18
3 800 102 220 480 950 0.04% 72% 47
4 1,000 105 1,500 4,100 6,800 1.20% 75% 50
5 1,200 106 3,200 7,900 12,000 4.80% 76% 50- Match each of the five concerns to the type of performance test that answers it, with the load it applies and what it must show. Then put the tests in a sensible order.
- Rewrite the product owner’s sentence as measurable goals for the peak: journeys, load, percentiles, error rate, duration and environment. Add goals for the opening spike and for the three weeks.
- Compute how many virtual users the peak load test needs, with the formula in the guide’s FAQ, and add headroom. Say how far you trust last semester’s 100 requests per second.
- Review the draft plan: for each of its five points, say what is wrong and what to do instead. Then name what the plan is missing.
- Read the stepped results against your goals: is the peak goal met, where is the limit, what is the likely bottleneck and how sure can you be, what do you report today, and what runs next?
Show the model answer
Test types. Product owner, "fast" and "must not crash on day one": a load test at the expected peak, 100 requests per second with the peak journey mix, to confirm the response-time and error targets; then a stress test that keeps raising the load past the peak to find the limit and see whether the system fails gracefully (slower answers, a quick "try again later" response such as a 503) or catastrophically (crashes, lost or duplicate registrations). Registrar, "unusable for the first hour": a spike test, a jump from almost no traffic to the peak and beyond within a minute, as at 08:00, to see queueing and recovery. At 08:00 students refresh instead of thinking, so the same students send more requests: model the spike with a shorter think time or a higher request rate. Operations lead, "slower and slower": a soak (endurance) test at an ordinary day’s load for many hours, ideally days, watching memory, connections and disk for a leak. Architect: a scalability test, the same load steps with one and then two application servers; only the comparison shows whether throughput rises with the second server. Developer: a benchmark, a quick, repeatable measurement of the seat-availability endpoint on the old and the new build with the same data and environment, comparing p50 and p95. A sensible order: the single-user baseline and the benchmark first, then the load test, then stress and spike, then the scalability comparison and the soak, and the load test again after every fix.
Measurable goals. These are proposals to agree with the product owner; once agreed, they are the exit criteria of the performance tests. Peak: with the peak mix (search courses 60%, view a section 25%, register for a section 10%, log in 5%) at 100 requests per second, held for 30 minutes after a 10-minute ramp-up on production-sized staging, p95 is under 800 ms and p99 under 2 s for each journey, fewer than 0.1% of requests fail, and no registration is lost or duplicated. Headroom: the same targets hold at 150 requests per second (step 3 explains why 50%). Opening spike: from almost no traffic to 150 requests per second within one minute, held for 15 minutes, nothing crashes and no registration is lost or duplicated; requests the system cannot serve get a quick, clear answer instead of hanging; and p95 is back under 800 ms within 5 minutes after the load falls to 100 requests per second. Three weeks: at 15 requests per second for at least 48 hours, p95 at the end is no more than 10% above p95 in the first hour, memory and database connections stay flat, and nothing needs a restart. The old system degraded over days, so a shorter soak proves little unless you watch these trends. Your numbers may differ, but every goal must name the journey, the load, the percentile, the error rate, the duration and the environment; "fast" and "must not crash" name none of them.
Virtual users. Each virtual user waits for a response and then thinks before its next request, so by Little’s law virtual users ≈ requests per second × (average response time + average think time) = 100 × (0.5 + 7.5) = 800. Multiplying by the response time alone (100 × 0.5 = 50) counts only the requests in flight and would generate far too little load. With 20–50% headroom that is 960 to 1,200 virtual users, about 120 to 150 requests per second. Take the upper end here: 100 requests per second is what the overloaded old system handled, not the demand. Requests that were refused before they reached it are not in its logs, and students who gave up came back later. Treat the figure as a lower bound, and ask whether the web server logged refused or timed-out requests at 08:00. If you enter 800 users and a constant think time of 7,500 ms in the Performance Test Data Generator, it estimates about 104 requests per second, slightly more than 100, because it assumes about 200 ms per response instead of 0.5 s.
Draft plan. (1) A load generator on the server under test competes with it for CPU, memory and network, so the system looks slower than it is and the generator itself can become the limit: run k6 on separate machines and watch their CPU too. (2) One account for every user means the same student’s data comes from cache and every registration hits the same student record, so reads look faster than in production and registrations collide in ways no real day produces (a student cannot register twice for one section): generate thousands of distinct test students, ideally as many as will register, as a CSV data set, and spread the registrations across many sections. (3) 1,200 users in the same second is a spike, not a load test, and its failure would tell you nothing about the peak: ramp up over about 10 minutes, hold the steady state for 30 minutes, ramp down, and test the spike separately, with its own goal, once the load test passes. (4) The average hides the slow tail, "under 1 s" was never agreed, and errors are not checked at all: report p50, p95 and p99 per journey with the throughput and the error rate, against the agreed goals. (5) 300 students and 40 sections fit in memory and make every query fast, including the new seat-availability query the developer asked about: use production-sized data, as many students, courses and sections as production has, generated or copied from production with the students’ identities removed. Missing from the plan: a single-user baseline before the load, then the load in steps; the journey mix and the 7.5 s think time; server-side monitoring during the run (application server CPU and memory, database CPU, connection pool, slow queries); a second run to confirm that the result reproduces; and goals agreed with the product owner.
Reading the results. Check the workload first: 800 virtual users produced 102 requests per second, as the formula predicted. At the peak (step 3) the response-time and error targets are met for all requests together: p95 480 ms, p99 950 ms, errors 0.04%. But the table does not show each journey, and with a 10% share the registrations can have a much slower p95 while the overall p95 stays low; and the step lasted 10 minutes, not the 30 the goal asks for. So ask for the p95 per journey, and run the full 30-minute load test, before you call the peak goal met. The headroom goal fails. From step 3 to step 4, 200 more users added only 3 requests per second (102 to 105) where the formula expects about 125, while p50 jumped from 220 to 1,500 ms, p95 to 4,100 ms and errors to 1.2%. Latency rising while throughput flattens means a bottleneck: this build tops out at about 105 requests per second, only 5% above last semester’s logged peak and far below the 150 the headroom goal asks for.
The likely bottleneck is on the database side, not the application server’s CPU. CPU stays around 75% and follows the throughput, while the database connections peak at 47 of 50 in step 3 and are all in use from step 4. When every connection is busy, new requests wait for one, which is why even p50 jumps, and those that wait too long fail; the p99 of step 3, about twice its p95, fits the first requests waiting for a free connection. The client-side numbers cannot tell whether the pool is too small or the queries hold each connection too long, for example the new seat-availability query on production-sized data, so the developers need the database CPU, the slow-query log, lock waits and the time requests wait for a connection. Enlarging the pool is not the fix if the database itself is saturated: more connections then make it slower. The pattern repeats at step 5 and the server metrics agree with it, so it is unlikely to be noise; still, run step 4 again to confirm it reproduces before anyone tunes code. The architect’s question is still open: a second application server adds CPU, which is not the limit, and more database connections, which help only if the database has capacity to spare; the scalability test answers it once the database limit is understood.
What you report today, as a defect against the agreed goals, to the product owner and the developers: "Release candidate 3.2.0-rc1, production-sized staging, peak mix, 7.5 s think time, 10-minute steps. At 100 requests per second the overall p95 is 480 ms with 0.04% errors (per-journey figures requested). Throughput stops at about 105 requests per second: with 1,000 virtual users p95 is 4.1 s and 1.2% of requests fail; with 1,200, 7.9 s and 4.8%. Application server CPU 75%, database connections 50 of 50. The headroom goal of 150 requests per second is not met, so registration day risks a repeat of last semester." Attach the run data and the server graphs. After the fix, run the same stepped test with the same script, data and environment and compare it with this run (confirmation testing); then the 30-minute load test at 100 and at 150 requests per second, the stress test above 150 requests per second, the spike, the scalability comparison and the soak. Running them now would only hit the same limit again.
If your answers differ, check that every goal has a load, a percentile and an error rate, that your virtual users include the think time, and that you read the throughput and resource columns together with the response times. Another budget, such as p95 under 1 s, is fine once it is agreed; a conclusion that ignores the flat throughput or the full connection pool is not.
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.
Part of the QA Roadmap
Before this topic
Next topics
Tools mentioned in this guide
Measure an endpoint's response time over several samples: min, max, average, median and p95.
Run a small, safe benchmark against a public endpoint (bounded requests and concurrency) and get throughput and latency percentiles.
Compare response times and sizes of two endpoints (e.g. v1 vs v2, staging vs production) side by side.
Analyze response size: raw vs compressed bytes, headers size, JSON structure weight and largest fields.
Generate large parameter datasets (users, tokens, ids, payloads) for k6, JMeter, Gatling and Locust load tests.
Generate JMeter-ready test data (CSV Data Set Config files and User Defined Variables) from a field schema.
Generate CSV files for JMeter CSV Data Set Config with correct quoting, delimiters and encodings.
Generate concurrency test scripts (k6, JMeter JMX, Locust, Artillery, Node.js) with safe defaults for a target endpoint.
Analyze Cache-Control, Expires, ETag, Last-Modified, Vary and Age headers and explain how browsers and CDNs will cache the response.