Stress Testing

Stress testing is a type of performance testing that evaluates a system at or beyond the limits of its anticipated or specified workload, or with fewer resources than it normally has. The load test of the previous lesson asked whether the system meets its targets at the expected peak. A stress test asks three different questions: how far beyond that peak does the system keep serving, what happens when it stops, and does it return to normal when the load drops?

Example: an online pharmacy's order API passed its load test at the expected peak of 200 requests per second. The stress test starts there and adds 50 requests per second every five minutes. At 400 the API serves only about 380 and the first requests time out; at 500 an application instance runs out of memory and restarts. The load is then dropped back to 200 to see whether the system recovers on its own.

Why it matters

Expected peaks are forecasts, and forecasts are sometimes wrong: a campaign spreads further than planned, a competitor's outage sends its customers over, a batch job runs in the busy hour, or one of three servers fails and the other two carry its traffic. The question is not whether the system will ever see more than its expected peak, but what it does when that happens. A system that slows down and rejects the excess cleanly loses a few orders; one that crashes, loses data or needs an hour to come back loses the day.

For the tester, a stress test replaces "it should hold up" with measured facts: the load at which the system stopped serving, the resource that ran out first, the failure mode and the recovery time. Those facts give the product owner the headroom between the expected peak and the limit, tell operations what to watch, and give developers a reproducible failure to fix before production produces it in front of customers.

Beyond the expected peak: the stress profile

A stress test starts where the load test ended. The expected peak is the reference line; the stress profile crosses it on purpose and keeps going until the system breaks or an abort rule stops the run. Because the result is "where and how" rather than "pass or fail", the plan carries the load test's objectives as a reading aid, plus an abort rule that protects the environment, for example "stop when errors exceed 20% for one minute or an instance crashes".

  • Steps, not one jump: raise the load in equal steps and hold each one until the metrics settle, for example +25% of the expected peak every five minutes. Each step is a small load test, and the step at which a metric changes behaviour is the finding. A continuous ramp is quicker but blurs where the change happened.
  • Confirm the peak first: the first step repeats the expected peak, and its numbers must match the load test. A system that is already unhealthy at the peak has a load-test finding, not a stress-test one.
  • Stress by subtraction: keep the expected load and remove resources instead. Take one of three instances out, halve the memory limit, throttle the network bandwidth. This is the second half of the ISTQB definition, and it is exactly what a failed server during the busy hour looks like in production.
  • Naming: the Grafana k6 documentation calls a run that keeps climbing until the system fails a breakpoint test, and uses "stress test" for a run held above the average load. ISTQB covers both with stress testing; write the profile you mean into the plan.

Try it: Performance Test Data Generator Try it: Concurrent Request Test Generator

Recognising the breaking point and the failure mode

The breaking point is the load beyond which the system no longer serves what it is sent. It rarely announces itself with a crash. Read the metrics from the performance metrics lesson together, step by step (throughput, response time, error rate and resource use), and look for the moment they stop moving in parallel: the load keeps rising, but the throughput does not.

  • Saturation: throughput flattens while the applied load climbs, and the difference queues somewhere. Response time, which grew slowly until then, now climbs steeply, because every request waits behind the ones before it.
  • Errors replace responses: gateway timeouts (504), refused connections, 503 Service Unavailable, and finally failures that are not HTTP at all: an instance killed for using too much memory, a database refusing new connections, a disk filled with logs.
  • The resource that ran out first: CPU at 100%, a connection pool fully busy, a thread pool exhausted, memory growing until the process is killed. (A pool is a fixed set of reusable database connections or worker threads; when all are busy, new requests wait.) This is the bottleneck, and the fact a developer needs most.
  • Failure mode, graceful: the system slows, rejects the excess quickly and explicitly (a 503 with a Retry-After header that tells the client when to try again, a bounded queue, a reduced result instead of an error page) and keeps serving the load it can. Rejecting the excess on purpose is called load shedding. Catastrophic: it hangs, crashes, takes other services down with it (a cascading failure, often amplified by retrying clients), or accepts work that it then loses.
  • Record per step: load sent, throughput served, p95 and p99, error rate by type, the saturated resource and the minute of the first symptom. The breaking point is a load level with a story, not a single number.

Try it: HTTP Status Code Lookup

Recovery: what happens when the load drops

A stress test is not finished when something breaks. Its second half drops the load back to a normal level, the expected peak or below, holds it, and watches whether the system returns to its baseline on its own. The load test already checked recovery after a ramp-down from a load the system could carry; here the system is recovering from an overload, which is a different and harder question. Recovery is a measured result: how long until the baseline is back, what was lost on the way, and whether anyone had to intervene.

  • Time to recover: minutes from the load drop until p95, error rate and resource use are back to the load-test baseline. A system that stays slow at normal load has not recovered: a queue is still draining, a pool is still leaking, or an instance never came back.
  • Automatic or manual: crashed instances restart, circuit breakers (switches that stop calls to a failing dependency for a while) close again and traffic resumes without a restart or a cache flush by hand. Every manual step is a finding, because at two in the morning nobody will take it.
  • Backlog: queued work such as emails, notifications, payment confirmations and search indexing is processed once, neither dropped nor duplicated, and the queue reaches zero within an agreed time.
  • Data integrity: compare what the system accepted with what it recorded; unique order or user ids in the test data make the two lists easy to match. Orders confirmed to the user without a payment record, half-written rows and duplicates from retried requests are the most expensive stress findings, and no latency graph shows them.

Try it: Response Time Analyzer Try it: Load Test Data Generator

Worked example: an order API stepped past its peak

The pharmacy's order API (release 5.1, three application instances, one database) passed its load test at the expected peak of 200 requests per second with p95 at 620 ms against an objective of 800 ms. The stress run below adds 50 requests per second every five minutes, aborts when errors exceed 20% for a minute or an instance crashes, and then holds 200 requests per second for fifteen minutes. The tester reads it in this order:

  • Objective first crossed at 350: p95 at 1,250 ms with the database connection pool at 48 of 50 connections busy. This is the first limit, the kind of number a capacity test measures precisely (see the capacity lesson).
  • Breaking point at about 385 requests per second: at the 400 and 450 steps the API serves only 380 to 385, whatever is sent; requests queue for the pool, response times jump and the first timeouts appear. The bottleneck is the database connection pool.
  • Failure mode: queued requests hold memory; at 500, instance 2 is killed for using too much memory, the other two inherit its load and errors reach 71%. The abort rule fires at minute 36. Catastrophic rather than graceful: no 503, no load shedding, only gateway timeouts (504) and a crash.
  • Recovery: back at 200 requests per second the instance restarts on its own and p95 is at 650 ms within four minutes; the email queue is empty by minute 49. The data check finds 12 orders confirmed without a payment record. Three defect reports follow: no load shedding when the connection pool is full, the memory growth under queueing, and the confirmation sent before the payment was recorded.
Order API stress run: load stepped from the expected peak of 200 to 500 requests per second; from 400 the API serves only about 385, an instance crashes at minute 36, and back at 200 the baseline returns within four minutes.
  1. Load sent: steps past the expected peakWhat the load tool sends: +50 requests per second every 5 minutes until the abort rule fires.

    Reference line:expected peak: 200 requests per second (the load test passed here)

    1. 1at the expected peak 0–10 minramp to 200 req/s and hold: the load-test result confirmed
    2. 2steps beyond the peak 10–37 min250 to 500 req/s; abort at minute 36 when instance 2 crashes
    3. 3recovery 37–55 minback to 200 req/s for 15 minutes, then to zero
  2. Throughput served: what the API completedServed requests per second; the gap below the load sent is queueing, then failure.

    Reference line:breaking point: about 385 served whatever is sent (database pool full)

    minute 36: instance 2 killed (out of memory), errors 71%

    1. 1served follows sent 0–25 minup to 350 req/s; p95 past the 800 ms objective from 350
    2. 2saturation, then the crash 25–37 minonly 380–385 served while 400 and 450 are sent; timeouts, then instance 2 dies at minute 36
    3. 3recovery 37–55 mininstance restarts; p95 650 ms by minute 41; email queue empty at 49

Numbers are the lesson's worked example, not measurements; the load steps are drawn as vertical jumps.

Stress test: order API, release 5.1, perf environment (3 app instances, 1 database)
Start:       expected peak 200 req/s (load test passed: p95 620 ms, 0.1 % errors)
Profile:     +50 req/s every 5 min; abort if errors > 20 % for 1 min or an instance
             crashes; then back to 200 req/s for 15 min (recovery)
Objectives:  p95 <= 800 ms, errors < 0.5 % (a reading aid here, not pass criteria)
Columns:     Sent and Served in req/s (Served = successful responses);
             Errors = % of the requests sent

Step  Sent  Served         p95 Errors  Observed
200   200   200         620 ms  0.1 %  baseline, as in the load test
250   250   250         690 ms  0.1 %
300   300   300         780 ms  0.2 %  app CPU 85 %
350   350   350       1,250 ms  0.4 %  DB pool 48/50 busy; p95 past the objective
400   400   380       2,900 ms  3.1 %  pool full, requests queue; first timeouts
450   450   385       4,800 ms   14 %  gateway 504s; throughput flat since 400
500   500    90            n/a   71 %  min 36: instance 2 killed (out of memory)
                                       -> abort, load back to 200 req/s

Recovery at 200 req/s (min 37-52)
  min 37-41   instance 2 restarting; p95 2,100 -> 650 ms; errors 8 % -> 0.1 %
  min 41-52   p95 640 ms, errors 0.1 %, DB pool 31/50; email queue empty at min 49
  data check  12 orders confirmed with no payment record; 0 duplicate orders

Common mistakes

  • Stress testing a system that has not passed its load test, so every finding is really a load-test finding.
  • Jumping straight to an extreme load instead of stepping, so you learn that the system breaks but not where, or what ran out first.
  • Stopping the run when something breaks and skipping the recovery phase, where the expensive findings are.
  • Treating a crash as the only failure: silent timeouts, fast error pages and accepted-but-lost orders are failures too.
  • Reading only response times; the breaking point shows first as throughput that stops following the load and as a saturated resource.
  • Stressing a shared environment or production without written permission, abort rules and operations on standby; a test designed to break the system can break its neighbours too.

The QA perspective

Stress tests are planned when the load test passes and the system has headroom to prove: before peaks whose size is uncertain (a launch, a campaign, a public announcement), after architecture or infrastructure changes, and for anything that must degrade safely, such as payments or bookings. The tester owns the profile, the abort rules and the observations; operations provide the monitoring and agree the time window; developers want the saturated resource and a reproducible run; the product owner wants the headroom (how far past the expected peak the system still works) and the failure mode in plain words.

Ask before the run: what does "acceptable failure" look like for this product, and is the fallback (a 503 with a retry message, a reduced feature) specified anywhere? Which dependency will suffer first? Who restarts what today, and should that be automatic? Report the breaking point as a load level with its bottleneck, the failure mode as graceful or catastrophic with evidence, the recovery time and the data check. Each catastrophic behaviour, each manual step and each integrity problem becomes its own defect report.

Practice exercise

Read a stress run of a ticket booking API

Your task

A ticket seller's booking API passed its load test at an expected peak of 300 requests per second (objectives: p95 ≤ 700 ms, errors below 1%). The material below is the step log of the stress run and the recovery phase. Identify the step at which an objective was first crossed, the breaking point and its bottleneck, classify the failure mode, judge the recovery and list the defects you would report.

Material
Booking API, 4 instances, 1 database, 1 payment provider (sandbox)
Expected peak 300 req/s; objectives p95 <= 700 ms, errors < 1 %
Profile: +75 req/s every 5 min from 300; then back to 300 req/s for 15 min
Abort rule: errors > 20 % for 1 min, or any instance down
Columns: Sent and Served in req/s (Served = successful responses);
         Errors = % of the requests sent

Step  Sent  Served         p95 Errors  Observed
300   300   300         480 ms  0.2 %  baseline
375   375   375         560 ms  0.3 %
450   450   450         690 ms  0.6 %  app CPU 78 %; payment calls 1.1 s on average
525   525   518         980 ms  1.2 %  payment thread pool 64/64 busy
600   600   522       3,400 ms    9 %  queue in front of payment calls; 503s with
                                       Retry-After from the API gateway
675   675   518       4,100 ms   23 %  abort at min 32; no instance down

Recovery at 300 req/s (min 33-48)
  min 33-36   p95 1,900 -> 500 ms; errors 4 % -> 0.2 %; 503s stop at min 34
  min 36-48   p95 490 ms, errors 0.2 %, payment pool 22/64 busy
  backlog     confirmation SMS queue: 9,400 messages, empty at min 47
  data check  0 bookings without payment; 37 payments charged twice (retried
              requests created a second booking for the same seat, later cancelled)
Steps
  1. Find the first step at which an objective fails, and note which one.
  2. Find the step at which served throughput stops following the load; name the resource that saturated.
  3. Classify the failure mode as graceful or catastrophic, with the evidence from the log.
  4. Judge the recovery: time to baseline, manual steps, backlog and data integrity.
  5. Write the list of defect reports, one per behaviour.
Show the model answer
Model answer

Objective first crossed at 525 requests per second: p95 980 ms against 700 ms and errors 1.2% against 1%, with the payment thread pool fully busy (64 of 64).

Breaking point at about 520 requests per second: from step 525 onward the API serves 518 to 522 whatever is sent, so the payment thread pool is the bottleneck; the slow payment provider calls (1.1 s on average at step 450) filled it.

Failure mode: largely graceful. The gateway rejected the excess with 503 and Retry-After, no instance went down and throughput held at about 520. The exception is the data check: 37 double charges from retried requests, a catastrophic outcome inside a graceful shape.

Recovery: automatic; p95 and errors were back at baseline within three minutes, the 503s stopped at minute 34, and the SMS backlog drained in 14 minutes, which should be compared with what the business accepts.

Defects: the booking request is not idempotent (a retry of a request that was already processed creates a second booking and a second charge), the most serious finding; and, if 14 minutes exceeds the agreed limit, the slow drain of the 9,400-message SMS backlog. Report the payment thread pool as the bottleneck rather than as a defect, because the API degraded gracefully: it is the first thing to change if more headroom is needed. Also note the headroom for the product owner: between 450 and 525 requests per second, 50% to 75% above the expected peak, before the first objective fails.

Frequently asked questions

What is the difference between stress testing and load testing?

A load test applies the expected load and checks the results against agreed pass criteria. A stress test deliberately goes beyond the expected peak, or removes resources, to find where the system stops serving, how it fails and whether it recovers. A load test has a verdict against its pass criteria; a stress test has mostly findings.

Is the breaking point the same as capacity?

No. Capacity is the most load at which every performance objective still holds; the breaking point is the load beyond which the system no longer serves what it is sent. There is usually a gap between them where the system works but misses its targets. Capacity testing, a later lesson, measures the first number precisely; stress testing finds the second.

Does a system fail a stress test when it breaks?

No. Every system breaks at some load; that is expected. The findings are how far beyond the peak it broke, whether the failure was graceful or catastrophic, whether it recovered without help and whether the data stayed consistent. It fails only where it breaks a requirement agreed for overload, such as a maximum recovery time or no lost orders; a system that rejects the excess cleanly and recovers in minutes has done what a stress test hopes to see.

Can I stress test production?

Only with written permission, abort rules, operations on standby and a time window agreed with the business, and preferably never for a first run; a stress test is designed to break the system, and it can break shared dependencies too. Prefer a production-like environment, and keep production for load tests at or below the expected peak.

References

Practise with Mutqan tools

Generate performance test inputs with realistic distributions: think times, request mixes, payload sizes and ramp profiles.

Performance

Generate concurrency test scripts (k6, JMeter JMX, Locust, Artillery, Node.js) with safe defaults for a target endpoint.

Automation

Generate large parameter datasets (users, tokens, ids, payloads) for k6, JMeter, Gatling and Locust load tests.

Performance

Measure an endpoint's response time over several samples: min, max, average, median and p95.

Performance

Go deeper

Your progress on this topic

Part of the QA Roadmap