Spike Testing

Spike testing is a type of performance testing that hits a system with a sudden, large jump in load, holds it briefly and drops back, to see whether the system copes with the burst and returns to normal. The ISTQB Performance Testing syllabus describes it as testing that a system responds correctly to sudden bursts of peak load and then returns to a steady state.

Example: a store sends a push notification about a flash sale at 20:00, and within a minute traffic on its checkout API jumps from 100 to 600 requests per second. A spike test reproduces that jump in an environment sized like production and answers three questions: what do users experience in the first minutes, how quickly does extra capacity arrive, and how long does recovery take?

Why it matters

A stress test, as in the previous lesson, pushes load past the expected peak to find the limit. A spike test asks something else: the peak may be well within what the system handles once it has scaled, but it arrives faster than the system can react. New instances take minutes to start, caches are cold, connection pools are sized for normal traffic, and every request that cannot be served yet waits in a queue or fails.

This is how systems fail on their busiest day — a sale, a ticket release, a registration deadline — when lost orders and duplicate payments from retried requests cost the most. For the tester, a spike test turns "we have autoscaling" into evidence: how long the scaling lag is, what users see during it and whether the system really comes back.

The shape of a spike profile

A spike profile has five phases: a baseline at normal load, a ramp measured in seconds rather than minutes, a short hold at the spike level, a fast drop, and a recovery period at baseline. The recovery period is part of the test: it shows whether the system returns to normal. Take the spike level and its duration from the real event — a campaign's expected audience, last year's sale — not from a round number. Grafana k6's test-type guide draws a spike with no hold or a very brief one; if the real burst lasts minutes, as a flash sale does, hold it for that long.

Drive the load by arrival rate, not by a fixed number of virtual users. In that closed model each virtual user waits for its response before sending the next request, so when the system slows down the tool sends less and hides the very burst you want. The script below uses an arrival-rate executor; if k6 runs out of virtual users to keep the rate, it reports dropped_iterations: the tool fell short, not the system.

import http from 'k6/http';

export const options = {
  scenarios: {
    flash_sale_spike: {
      executor: 'ramping-arrival-rate',
      startRate: 100,          // requests per second (one request per iteration)
      timeUnit: '1s',
      preAllocatedVUs: 500,
      maxVUs: 6000,            // enough to keep the rate when responses slow down
      stages: [
        { duration: '5m', target: 100 },   // 1 baseline
        { duration: '30s', target: 600 },  // 2 spike up
        { duration: '5m', target: 600 },   // 3 hold
        { duration: '30s', target: 100 },  // 4 drop
        { duration: '10m', target: 100 },  // 5 recovery
      ],
    },
  },
};

export default function () {
  http.get('https://staging.example.com/api/checkout/summary');
}

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

Autoscaling and the scaling lag

Autoscaling adds instances when a metric crosses a threshold, and every step takes time: the metric is averaged over a window, the rule is evaluated on a schedule, and a new instance must boot, warm up and pass its health check before the load balancer sends it traffic. The sum is the scaling lag. Until it is over, the instances you already had carry the whole burst. A spike test therefore measures the scaling chain, not only the final instance count:

  • Time from the start of the spike to the scale-out decision, and from the decision to new instances serving traffic.
  • The maximum instance count, and whether the spike reaches it.
  • What does not scale with the application: database connections (each new instance opens its own pool), third-party rate limits, a single cache node.
  • Scale-in afterwards: the cooldown, and whether the instance count flaps up and down while load hovers near a threshold.

Queues, overload and recovery

When requests arrive faster than they are served, the difference waits in a queue: in the load balancer, a web server, a connection pool or a message broker. At 600 requests per second against a capacity of 400, the backlog grows by 200 every second; after 30 seconds 6,000 requests are waiting, and a new one waits about 15 seconds behind them. When the burst ends, the backlog drains only at the spare capacity, and that drain time is part of the recovery.

A system that handles overload well keeps its queues short and refuses what it cannot serve in time — 503 Service Unavailable, which RFC 9110 defines for a temporary overload and which may carry a Retry-After header, or 429 Too Many Requests (RFC 6585) for a per-client limit — instead of letting requests wait until clients time out. Retries make a spike worse: clients that retry at once multiply the load, so check that they use exponential backoff with random jitter, and model that behaviour in your script.

  • Every minute of the spike: error rate and error type (a fast 503 is a designed answer; 504 timeouts and 500 errors are not), p95 and p99 latency, and whether checkout keeps working while less important requests are refused.
  • Message queues: peak depth and drain time, with nothing lost or processed twice, and no duplicate orders where a client retried a request that had in fact succeeded.
  • Recovery: the time until latency, errors, queue depth and instance count are back at baseline, with nothing left stuck.

Try it: API Status Code Simulator Try it: HTTP Status Code Lookup

Worked example: a flash sale at 20:00

Staging is sized like production: 4 instances of about 100 requests per second each, behind a gateway that times out after 10 seconds. Every minute the autoscaler checks the average CPU of the last minute; above 70% it adds 4 instances, up to a maximum of 8, which need about 2 minutes to be ready, and it removes them after 5 minutes below 30%. Each instance holds 20 database connections; the database accepts 150. The tester runs the profile from the script above through the checkout journey, which places test orders, with 5,000 distinct test users, and summarises the per-minute results by phase:

Flash-sale spike test: arrival jumps from 100 to 600 requests per second in 30 seconds, but the 4 baseline instances serve about 400 and the 4 added instances arrive only at minute 8, three minutes into the spike.
  1. Arrival rate: the spike profileWhat the load tool sends, in five phases.

    Reference line:what the 4 baseline instances serve: about 400

    1. 1baseline 0–5 min100 per second
    2. 2spike up 5–5.5 minto 600 in 30 seconds
    3. 3hold 5.5–10.5 min600 per second
    4. 4drop 10.5–11 minback to 100
    5. 5recovery 11–21 minlatency, queues and scale-in watched
  2. Serving capacity: autoscalingAbout 100 per second for each instance in service.

    Reference line:spike arrival rate: 600

    database limit: 8 × 20 = 160 connections, 150 allowed

    1. 14 instances 0–5 minbaseline
    2. 2scaling lag 5–8 mintriggered at minute 6, ready at minute 8
    3. 38 instances, the maximum 8–16 minremoved after 5 minutes below 30% CPU
    4. 4back to 4 instances 16–21 min

Capacity is nominal, about 100 requests per second per instance; at 8 instances the database connection limit kept the service from reaching it.

  • Scaling lag: the spike began at 05:00 and new instances served traffic at 08:00. Meanwhile about 600 requests per second arrived for a capacity of 400: the backlog grew until requests hit the gateway's 10-second timeout, p95 sat at 10 s and from 06:00 a third of requests failed. The sale time is known, so the team can scale out before it starts.
  • Hidden limit: 8 × 20 = 160 connections, but the database accepts 150, so scaling out caused new errors. Report it as a defect with that arithmetic.
  • No load shedding: requests waited for the gateway's 10-second timeout instead of being refused at once with 503 and Retry-After. With 600 arriving and 400 served, a third could not be served either way; refusing them at once would have kept the queue short and the other two thirds fast.
  • Recovery: the backlog was gone about 30 seconds after the new instances arrived, and latency was at baseline as soon as the load dropped, but the order-confirmation email queue, whose workers do not scale, peaked at 9,000 messages and emptied only at 17:00, six minutes after the drop. Ask whether that delay is acceptable, and rerun the same profile after each fix.
Spike test, run 1 (staging sized like production)

time   arrival     instances  p95     errors  observation
00-05  100/s       4          220 ms  0%      baseline
05-06  up to 600   4          10 s    5%      CPU 100%, backlog builds, first 504 at 05:54
06:00                                         scale-out triggered: +4 instances
06-08  600/s       4          10 s    33%     504 from the gateway (10 s timeout)
08:00                                         4 new instances in service (maximum 8)
08-09  600/s       8          8.9 s   6%      backlog drains in about 30 s
09-11  600 to 100  8          450 ms  5%      DB: too many connections (160 > 150)
11-16  100/s       8          210 ms  0%      API at baseline; email queue peaks at 9,000
16-21  100/s       4          230 ms  0%      scale-in 16:00; email queue empty at 17:00

Try it: Load Test Data Generator

Common mistakes

  • Ramping up over several minutes and calling it a spike: the autoscaler keeps up with a slow ramp, so the scaling lag is never tested.
  • Driving the spike with a fixed number of virtual users, so the tool slows down with the system and the burst never fully arrives.
  • Judging the run by its overall summary: a 5-minute spike inside a 20-minute run disappears in the totals. Read per-minute results for each phase.
  • Stopping the test when the spike ends, so recovery, queue drain and scale-in are never seen.
  • Starting with warm caches and instances still scaled out from the previous run, which hides the cold start real users would meet.
  • Aiming a spike at a shared environment or a third-party API without warning its owners. Agree the time with operations, and test only systems you own or have written permission to test.

The QA perspective

Spike tests follow the business calendar. Ask product and marketing for upcoming sales, launches, ticket releases and notification campaigns, and turn each into a profile: baseline, spike level, how fast it arrives and how long it lasts. Before the run, agree the pass criteria with the platform team: the error rate and latency allowed in the first minutes, the maximum scaling lag, queue drain time and recovery time.

Run the test at an agreed time with operations watching. Record the profile, the starting state (instance count, cache state, data volume), the scaling events from the platform's logs and the per-minute metrics. Report each finding with the arithmetic that shows the risk — "scaling lag 3 minutes, expected spike 5 minutes" — and rerun the same profile after every fix.

Practice exercise

Plan a spike test for a ticket release

Your task

Concert tickets go on sale at 10:00. The booking API normally receives 20 requests per second; the organiser expects 900 per second within the first minute, for about 10 minutes, then 150 per second.

The platform runs 3 instances of about 150 requests per second each and may scale to 8; extra instances serve traffic from about 10:03. One request in three is a booking, sent to a message queue whose workers process 200 bookings per second and do not scale.

Steps
  1. Write the spike profile as phases with durations and target rates.
  2. Calculate the capacity before scaling and how many requests exceed it between 10:00 and 10:03.
  3. Calculate how fast the booking queue grows from 10:03, its size at 10:10 and how long it takes to drain.
  4. List what you will measure and the questions to settle with the team before the run.
Show the model answer
Model answer

Profile, driven by arrival rate: 20 requests/s for 5 minutes; up to 900/s in about 30 seconds; hold for 10 minutes; drop to 150/s; recovery at 150/s for at least 15 minutes, to see the queue drain and the scale-in.

Capacity before scaling: 3 × 150 = 450/s. Shortfall: 900 − 450 = 450/s for about 180 s, roughly 81,000 requests that cannot be served on time; the test shows whether they wait, time out or are refused. The spike needs 900 ÷ 150 = 6 instances, within the maximum of 8.

Queue: before 10:03 the API passes at most 450 ÷ 3 = 150 bookings/s, which the workers keep up with. From 10:03 the API serves the full 900/s, so bookings arrive at 300/s against 200/s processed and the queue grows by 100/s: about 42,000 waiting after 420 s, more if clients retry the requests refused earlier. Afterwards bookings arrive at 50/s, so it drains at 150/s: 42,000 ÷ 150 = 280 s, about 4 minutes 40 seconds.

Measure every minute: the arrival rate the tool actually generated, error rate and type, p95 and p99, instance count and scaling events, queue depth; afterwards, the time back to baseline, and that no booking was lost or duplicated.

Questions: are refusals acceptable in the first 3 minutes, and in what form — a waiting room, or 503 with Retry-After? Can the platform scale out before 10:00? What does a customer see while a booking waits in the queue? How do the app and the website retry? Can the database serve the connections of 6 instances?

Frequently asked questions

How is a spike test different from a stress test?

A stress test raises load past the expected peak, usually step by step, to find the limit and see how the system fails. A spike test is defined by how suddenly the load arrives; its peak may be within what the scaled system can handle. It tests the reaction — scaling, queues, cold caches — and the recovery afterwards.

Can I run a spike test in production?

Only as a planned exercise approved by the people who run production, at an agreed time, with someone watching and a way to stop at once, because real users share the system. Most teams run spike tests in a staging environment sized like production. Never aim a spike at a system or third-party API you do not own or have no written permission to test.

How long should the spike and the recovery period be?

Model the spike on the real event: if a campaign brings a burst that lasts about five minutes, hold the spike for about five minutes. Keep the recovery period running until latency, error rate, queue depth and instance count are back at their baseline values, which often takes longer than the spike itself.

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

Get a public endpoint that returns any HTTP status code, with optional delay and custom JSON body, to test client error handling.

API Testing

Go deeper

Your progress on this topic

Part of the QA Roadmap