Skip to content
Core Web Vitals specialists
Server & hosting

Load Testing for Peak Season: Surviving Traffic Spikes

A page answering in 400 ms on a single request can break at 300 concurrent visitors. How a load test makes capacity visible before the peak season starts.

12 min read ServerLasttestSkalierungE-CommerceInfrastruktur

The test run on Monday morning looks fine: the home page answers in 400 milliseconds, the category page in 600, the report is green. Six weeks later, on the evening of the first big campaign day, that same page stalls. Not because it grew slower overnight, but because it is asked to serve 300 people at once instead of a single measuring probe. German core retail earned 18.5 percent (HDE) of its annual revenue in the last two months of 2024 -- precisely the window in which a site should be at its steadiest. A load test therefore answers a different question than a speed test. Not: how fast is one request? But: how many requests at the same time does the system tolerate before response times and error rate tip over? This article shows how to derive a realistic load profile from field data, how test scenarios follow real user paths, which metrics actually matter under load, and how to put a number on the capacity headroom so that it holds on campaign day. Where the server turns out to be the bottleneck, our server optimization starts there.

Key takeaways

  • 18.5 percent (HDE) of German core retail revenue fell into November and December in 2024; for the 2025 Christmas season the retail association expected 126.2 billion euros (HDE) in revenue, of which 22.2 billion euros (HDE) online. In exactly this window capacity decides the outcome, not the fastest single request.
  • A single request measures duration, a load test measures concurrency. The relationship has long been described: the number of items in a system equals throughput multiplied by the time each item spends there (Little 1961). If the time in the system grows, concurrency grows with it -- and the worker pool fills up.
  • The real target metrics are the saturation point and the error rate, not the fastest single value. The saturation point is where throughput stops growing while response time keeps rising. Response times therefore belong in percentiles: Core Web Vitals are assessed at the 75th percentile (web.dev), and under load p95 and p99 deserve a look as well.
  • Test scenarios follow real paths: category with filters, search, cart and login. These paths differ widely in how cacheable they are -- a test that only fetches the cached home page measures the delivery network, but hardly the origin server.
  • Cache warming, emergency switches for expensive features and a quantified headroom belong to the result before the season starts. Anyone already facing around 70 percent (Baymard Institute) cart abandonment should not add waiting time as one more reason.

Why 400 Milliseconds Say Nothing About Peak Season

A speed test measures a page at rest: one request, a server with nothing else to do, a warm cache. The result is honest, but it describes a state that hardly occurs on campaign day. Under concurrency the physics of the situation change. Every request occupies a slot for the duration of its processing: a process in the worker pool, a database connection, a piece of memory, time on a CPU core. As long as free slots remain, response time stays roughly constant. Once the last slot is taken, new requests wait in a queue -- and from that moment on, waiting time adds to processing time. The transition is not linear but abrupt: between 'still relaxed' and 'timeout' there are often only a few dozen additional concurrent requests.

Where exactly the slots run out differs from system to system and is rarely where one expects. Often it is the number of PHP processes allowed to work at the same time; how runtime and worker configuration are tuned to each other is covered in the article on PHP runtime tuning with OPcache, JIT and workers. Just as often it is the database: a query that takes 40 milliseconds on a single request can take a multiple of that under load, because locks, sorting operations and temporary tables get in each other's way -- the approach is described in the article on database and query optimization for shops. And sometimes it is simply memory, because every additional process claims its share and the system starts swapping. A load test does not only say that something is stuck, but where.

What Concurrency Means Technically

For a queueing system a simple relationship holds: the average number of items in the system equals the arrival rate multiplied by the average time each item spends in it (Little 1961). Applied to a shop this means: at 35 requests per second and 0.8 seconds of processing time, an average of 28 requests are in flight at once. If processing time grows under load, the number of occupied slots grows in the same proportion -- so the effect reinforces itself. That is precisely why a system does not tip gently, but quickly.

Deriving the Load Profile From Field Data

A load test is only as good as the assumption it tests against. A round number pulled from thin air -- '1,000 users should do it' -- produces either false confidence or unnecessary cost. The solid basis is field data from the previous season: the busiest hour rather than the daily average, plus the distribution of landing pages and the number of page views per session. How field measurements differ from aggregated reports, and when each source is useful, is explained in the article on field data from RUM and CrUX. Resolution matters: an hourly average smooths away the spike that a newsletter send or a campaign launch creates within a few minutes. Whoever can should look at per-minute values.

A second share belongs in the profile and is easily forgotten: automated traffic. Search engine crawlers, price comparison bots and the newer content crawlers fetch pages that almost nobody else requests -- deep filter combinations, sorting variants, old detail pages. These requests frequently bypass the cache and land directly on the origin server; how much weight that carries is shown in the article on crawler load on the server. For the load profile this means the bot share belongs in the numbers as a separate figure and belongs in the test, otherwise the test measures a friendlier world than the real one. A realistic profile ends up containing three figures: peak requests per second, the mix of paths, and the expected growth over the previous season.

  1. Take the busiest hour of the previous peak season from the field data -- and within it the busiest five minutes
  2. Read out sessions, page views per session and the distribution across landing pages
  3. Report the share of crawlers and automated traffic separately, because it puts real load on the origin server
  4. Apply the planned growth from advertising budget, newsletter reach and a wider assortment as a factor
  5. Derive requests per second and the number of requests in flight at the same time

Test Scenarios Along Real User Paths

A test that only fetches the home page usually measures the delivery network and not the shop. The home page is cached, static and cheap. Things get interesting where personalisation, filter logic and write operations come into play. A usable scenario therefore reproduces the path people actually take -- with the same shares as in the field and with realistic think times between the steps. Testing without pauses produces a load that does not exist in reality and pushes the measured saturation point downwards. Four paths carry most of the load in a shop:

Category and filters

The most expensive path in many shops. Every additional filter combination produces its own result set that caches poorly. The test should combine sorting, pagination and several filters -- not just the first page without any selection.

Search

Full-text search hits the database or the search index directly and returns a different result for every input. The scenario belongs fed with real search terms from last season's logs, including typos and terms without results, because those are the expensive ones.

Cart and checkout

This is where writes happen, where caching does not help, where stock levels, shipping costs and tax logic hang together. Around 70 percent (Baymard Institute) of carts are abandoned anyway; additional waiting time at this point is particularly costly.

Login and account

Signed-in sessions largely bypass the page cache and continuously produce session data. On top of that, authentication itself is computationally heavy. How firmly the origin server answers depends on the server response time.

The mix of these paths determines how meaningful the test is. If the share of cached page views in the field is 70 percent, it should not be 95 percent in the test -- otherwise the measurement mostly shows how well the delivery network performs. Conversely, a test that routes every request past the cache paints an unduly dark picture. How content is sensibly distributed between edge and origin is covered in the article on CDN and edge caching for shops; which layers interact is summarised in our overview of caching strategies. For the test the rule is: the cache hit rate belongs in the measurement as its own metric, otherwise it stays unclear which layer was the bottleneck.

Saturation Point and Error Rate as Target Metrics

The decisive observation in a load test is not a single number but the shape of a curve. Concurrency is raised in steps while two quantities are watched at once: throughput in requests per second and response time at a high percentile. At first both rise together, throughput linearly, response time barely at all. Then throughput flattens while response time keeps climbing. The point where the two curves separate is the saturation point: beyond it, more load produces no additional revenue, only longer queues. Everything past it is overload. Knowing this point is worth more than any record time, because it can be compared directly with the load profile taken from field data.

load-target.js
// Derive the load target from field data (relationship after Little 1961)
const sessionsPeakHour = 18000;   // sessions in the busiest hour
const pagesPerSession = 7;        // page views per session
const p95ServerTime = 0.8;        // seconds per request, 95th percentile
const headroomFactor = 1.5;       // capacity headroom

const requestsPerSecond = (sessionsPeakHour * pagesPerSession) / 3600;
const concurrent = requestsPerSecond * p95ServerTime;
const targetConcurrent = Math.ceil(concurrent * headroomFactor);

console.log(requestsPerSecond, concurrent, targetConcurrent);
// 35  28  42  ->  target: 35 requests/s sustained, 42 in flight at once

The second target metric is the error rate, and it is regularly underestimated. A system that becomes slow under load is unpleasant; a system that refuses connections or returns timeouts loses orders and confuses search engines. The protocol itself offers tools for an orderly fallback: status code 503 signals a temporary overload and can use a Retry-After field to state when another attempt makes sense (RFC 9110); status code 429 rejects an excess of requests from one source and knows the same field (RFC 6585). That is markedly better than an open connection that eventually expires. It matters for visibility too: if a server answers slowly or with server errors over a longer period, the crawler reduces its request rate (Google Search Central) -- the consequences of an overload therefore reach beyond campaign day. How stable the response time is to begin with is settled beforehand by measuring the server response time.

AspectSingle request (speed test)Load window (load test)
What is measuredDuration of a single requestBehaviour with many requests at once
Leading metricMedian response timeThroughput and response time at the 95th percentile
Errorsbarely occurTimeouts, 5xx, refused connections
Visible bottleneckCode, queries, transfer sizesWorker pool, connections, memory, CPU
Relevance for peak seasonlimitedhigh
Typical misreadinggreen means it will holdconfusing saturation with an outage

Reading Response Time Percentiles Under Load

Under load an average is the least useful of all metrics. If nine out of ten requests are answered in 200 milliseconds and the tenth takes eight seconds, the average sits just under a second -- a number that describes not a single real visit. Percentiles, by contrast, describe experience: the value at the 95th percentile says how slow it became for the slowest five percent of requests. In peak season those five percent are often the people in the cart, because neither page cache nor delivery network helps there. It is no coincidence that Google assesses the Core Web Vitals at the 75th percentile (web.dev) rather than on average; our page on the Core Web Vitals places the three metrics in context. For a load test p99 is worth adding, because that is where queues become visible first.

Assessment needs thresholds that are fixed in advance. The field thresholds serve as a guide: 2.5 seconds (web.dev) for a good LCP, 800 milliseconds (web.dev) for a good server response time and 200 milliseconds (web.dev) for a good response to input. A load test translates these thresholds into a capacity statement, deliberately set stricter than the field assessment at the 75th percentile: up to which level of concurrency does the server response time at the 95th percentile stay below 800 milliseconds? That formulation is testable, it can be repeated, and it works as an acceptance criterion before the season. The rest of the picture stays the same: a lean page helps under load as well, since the median mobile home page weighed 2,362 KB (Web Almanac 2025) in July 2025 -- every request saved is one that does not occupy a slot in the worker pool.

The Core in One Sentence

A load test does not look for the quickest answer but for the limit: the level of concurrency up to which response time and error rate stay within thresholds agreed in advance -- and the distance between that limit and the expected peak.

Cache Warming and Emergency Switches

Two things often decide more on campaign day than raw computing power. The first is the state of the cache at the start. After a release, a restart or a change to the assortment the cache is empty, and the first thousand visitors meet a server that has to build every page from scratch. Launching an advertising campaign at exactly that moment is the most common avoidable cause of an outage. The countermeasure is unspectacular: the most important categories, landing pages and search terms are fetched on a schedule before the start so that the caches are filled. Which layers work together here and where they cancel each other out is described in the article on caching strategies with Varnish and Redis. A load test can measure both: once with a cold cache, once with a warm one. The difference is regularly larger than expected.

The second is a prepared fallback. Not every feature of a shop is equally important on the busiest day. Personal recommendations, live stock levels per variant or elaborate analytics in the frontend cost processing time but contribute little to the purchase decision. An emergency switch makes these features disposable without anyone having to touch code or roll out a release -- as configuration that one person under pressure can flip within a minute. For that to work when it counts, the switch belongs in the load test: once with all features on, once in reduced operation. Only then is it known how much capacity it actually frees up.

  • Switch live search with its expensive full-text query to a leaner variant
  • Temporarily hide personal recommendations and recently viewed items
  • Reduce per-variant stock display to a cached summary figure
  • Remove filter combinations with very few results from the navigation
  • Pause frontend analytics scripts that are not relevant to buying
  • Extend cache lifetimes and postpone scheduled rebuilds

How a Load Test Goes Wrong

A load test against the live system without prior agreement is a self-inflicted outage. The sensible option is an environment close to production in hardware, data volume and configuration; if testing live is unavoidable, then during quiet hours, within an agreed time window and with an abort condition. Test traffic belongs clearly marked and excluded from reporting, otherwise it distorts the field data of the following weeks. Payment flows run against a test environment and outgoing messages are switched off. And results from an environment half the size cannot simply be doubled -- saturation rarely behaves linearly.

Putting a Number on the Capacity Headroom

At the end of a load test there should be one sentence that management understands as well: at the agreed response time threshold the system carries X concurrent requests, Y are expected, so the headroom is a factor of Z. A factor of 1.5 over the expected peak is a common starting point, and for campaigns with uncertain reach rather twice that. This headroom is not waste but the buffer for what cannot be planned: a newsletter that performs better than expected, a post that gets shared, a crawler wave at an inconvenient hour. Whether the headroom comes from more resources, better utilisation or a different operating model is a trade-off between cost and effort; the article on hosting choice and server response time sets out the differences.

The commercial frame makes the calculation tangible: German online retail turned over 83.1 billion euros (bevh) in goods in 2025, up 3.2 percent (bevh) on the previous year. A two-hour outage on the busiest day costs more, at this order of magnitude, than the capacity that would have prevented it -- and it costs trust on top, because 53 percent (Google) of mobile visits are abandoned when a page takes longer than three seconds to load. A capacity statement is therefore not a technical footnote but a planning figure alongside advertising budget and stock levels.

  1. Derive the load profile from the field data of the previous peak season and reconcile it with current planning
  2. Write scenarios along the four main paths, with a realistic path mix and think times
  3. Ramp up in steps until throughput and response time separate -- that is the saturation point
  4. Name the bottleneck, fix it and measure again: workers, connections, queries, caches
  5. Set the headroom factor, document cache warming and emergency switches, monitor the metrics continuously

That is exactly how a pre-season project is set up with us: we derive the load profile from your field data, build scenarios along the real paths, ramp up the load in steps and name the point at which throughput and response time separate. Work on the bottleneck follows -- server configuration, caching, database -- and a second measurement that documents the progress. For Shopware projects we combine this with the remaining levers of Shopware performance; which building blocks belong to the whole is shown in the overview of our performance services. After the season, monitoring remains, so that the headroom stays demonstrable and is not quietly eaten up.

Capacity is not a state but a measurement with an expiry date. Every new feature, every additional script and every grown data volume moves the saturation point down a little. Whoever measures it once a year knows in December where they stand.

Project experience from 50+ performance projects

Two additions round off the picture. First, capacity does not shift on the server alone: long browser sessions can accumulate memory over hours and make the page sluggish without the server noticing anything -- the topic is covered in the article on frontend memory leaks in long sessions. Second, a load test does not replace continuous checking in the build pipeline: whether a release worsened page weight or execution time belongs in an automated check, as described in the article on performance budgets in the build chain. The load test answers the question of concurrency, the budget the question of gradual decline -- together they keep the headroom stable. Where a shop stands today is settled most quickly by a performance analysis covering server, caching and frontend.

Sources and Studies

This article is based on data from HDE, bevh, web.dev and Baymard Institute. Additional references are Web Almanac 2025 (HTTP Archive), Google, Google Search Central, the protocol definitions in RFC 9110 and RFC 6585, and the relationship after Little 1961. The figures quoted refer to the state of the respective publication.

Related Articles