What this module covers: Where every number in this course comes from — the rate limits, pool sizes, concurrency caps, shed thresholds and autoscaling targets that earlier modules kept describing as "derived from measurement." What a load test is actually looking for, which is the knee of the curve and the identity of the first resource to saturate, not a maximum requests-per-second figure. Five test types that find genuinely different bugs. Then the methodological error that invalidates most load tests: a closed-loop generator self-throttles under overload, so it cannot observe the failure you are testing for, and coordinated omission makes the latency it reports dramatically optimistic. Realistic key distributions and data volumes, because a test with a hot key measures a cache. Testing in production, and why only production has the real traffic mix. Then capacity planning: headroom derived from what you must survive, the arithmetic that turns "lose one of three availability zones" into a defensible utilisation target, autoscaling on the right signal, and the planning point that matters most — the lead time on your fix must be shorter than the time until you need it, which is why sharding is started before it is needed.
What You Are Actually Looking For
A load test's output is not a number to put in a slide. It is four findings:
The knee of the curve — the offered load beyond which goodput degrades (Module A-8). Not the maximum throughput; the point where latency leaves its budget.
Which resource saturates first, because that is the only thing worth scaling and everything else is wasted money.
The failure mode at overload — do you shed cleanly with 503s, or do queues grow until everything times out? This is a test of Module A-8's mechanisms, and it is the finding teams most often skip.
The numbers for rate limits, pool sizes, concurrency caps and autoscaling thresholds.
An untested capacity claim is a guess, and a launch is where guesses become incidents.
Analogy: a lift's weight limit. The useful number is not "the cables snapped at 2,400 kg" — nobody operates there. It is the load at which the lift starts behaving badly (slow, juddering, doors mistiming), which component reaches its limit first (the motor, not the cable), and what happens when someone exceeds the limit anyway: does it refuse to move and beep, or does it move and then stop between floors? A sign saying "8 persons" is the output; the test was about finding the knee and confirming the refusal works.
Five Test Types, Five Different Bug Classes
Test
Shape
Finds
Load / ramp
increase until latency degrades
the knee; the binding resource
Soak / endurance
moderate load for hours or days
leaks, exhaustion, slow accumulation
Spike
instant 10× then back
admission control, cold caches, autoscaler lag
Breakpoint
push until it fails
how it fails — shed or collapse
Recovery
break it, then remove the load
whether it comes back without intervention
Each finds things the others cannot:
Soak tests find the accumulation bugs, which are invisible in a twenty-minute run: a memory leak in a request-scoped cache; file descriptors or connections leaked on an error path; a log disk filling; an unbounded in-process Map used as a deduplicator (Module P-16); a bloat feedback loop where a high-churn table's dead tuples outpace autovacuum until queries slow enough to lengthen transactions enough to further block vacuum (Module F-13); a replication slot growing because a consumer is slightly slower than the write rate (Module P-23). Every one of those needs hours, not minutes.
Spike tests test the mechanisms of Module A-8 and expose the four things that make a spike worse than steady load: cold caches on new instances raise per-request cost; the autoscaler needs minutes while the spike lasts seconds; a connection storm from new instances multiplies pool size by instance count (Module P-4); and every client that got an error retries at once (Module A-6).
Recovery tests are the ones nobody runs and everybody needs. Module A-8's metastable failure state is precisely the case where removing the cause does not fix the system, and the only way to know whether yours exits on its own is to drive it into overload, stop, and watch. If it does not recover without a human, that is a finding worth more than the throughput number.
The Methodological Error That Invalidates Most Load Tests
Closed-loop generators cannot observe overload
Two ways to generate load:
Closed loop — N virtual users, each sending a request, waiting for the response, then sending the next. This is the default model in many tools, and it has a fatal property:
text
The generator is self-throttling, and it is negotiating with your system rather than testing it. Real users do not behave this way: a browser's arrival rate does not fall because your server is slow — if anything, users reload and it rises (Module A-6).
Open loop / arrival rate — the generator sends at a specified rate regardless of response times, so queues build exactly as they would in production. This is the only model that can find the knee, observe the failure mode, or test admission control.
If your load test cannot make the system fail, check whether the generator is closed-loop before concluding the system is robust.
Coordinated omission
The related measurement error, and it makes reported latency wildly optimistic. In a closed-loop test, when the system stalls for two seconds, the virtual user is waiting — so it does not send the forty requests it would have sent during that stall, and therefore does not record forty slow measurements. The stall is represented by one sample instead of forty.
text
The fix is either an open-loop generator that records the intended send time (so latency is measured from when the request should have gone out) or a tool that explicitly corrects for it. Without one, your p99 is a description of the requests that got through, which is not the same as your users' experience.
The test must resemble reality in three specific ways
The traffic mix. Hammering one endpoint measures that endpoint. A realistic mix — the actual ratio of reads to writes, cheap to expensive endpoints, authenticated to anonymous — is what reveals contention between them, which is where the interesting failures are (Module P-8's noisy neighbour, Module A-7's shared pool).
The key distribution. This is the one most often wrong. A test that requests the same twenty entities achieves a near-100% cache hit ratio and measures your cache. Real traffic has a long tail, so the working set is far larger than the hot set and the hit ratio is much lower (Module F-12). Generate keys with a realistic distribution — typically Zipfian — or you will size your cache and database from a fiction.
The data volume. A query that is fast on 100,000 rows is not fast on 400 million: different plan, different index depth, different buffer-cache behaviour (Modules F-13, P-22). This is Module O-3's structural-versus-behavioural parity point, and it is why load testing against a small staging dataset produces numbers that do not transfer.
Also: discard the warm-up. JIT compilation, cache fill, connection-pool growth and autoscaling all make the first minutes unrepresentative. Measure steady state, and separately measure the warm-up, because that is the spike behaviour.
Testing in Production, Carefully
Staging gives structural parity. Only production has the real data volume, the real traffic mix, the real dependency behaviour and the real infrastructure. So the highest-fidelity techniques run there:
Shadow / mirrored traffic (Module O-4): duplicate real requests to the new version and discard responses. Safe only if the path is read-only or side-effect-free — otherwise you have double-charged people.
Traffic replay from access logs, which gives a genuinely realistic mix and key distribution, and which needs care about replaying writes and about personal data in the recorded requests (Module O-9).
Canary at elevated weight: send one instance 5× its share and watch it approach saturation. Cheap, safe, and continuous — a standing measurement of your knee rather than a quarterly event.
Direct load testing in production during low-traffic hours, with a kill switch and a clear abort criterion.
The controls that make this responsible: a bounded blast radius (one instance, one shard, one region), a separate test tenant so generated data does not pollute real accounts, and an explicit check on the side-effect paths — the classic accident is a load test that sends ten thousand real emails or charges a real card, which is why test accounts and provider sandboxes exist.
Reading the Results
Find what saturated, and expect it not to be the thing you were testing. The candidates, roughly in order of how often they are the answer:
Resource
Signal
Database connections
pool wait time (Module P-4) — the most common real bottleneck
Database CPU or I/O
query time rising with unchanged plans
Event loop (Node)
event-loop delay (Module A-8)
Lock contention
wait events, deadlocks, one hot row (Module P-24)
A downstream service's own limit
its 429s, or its latency rising before yours
CPU
utilisation, run-queue length
Memory
GC pause time, then OOM
Network / bandwidth
interface saturation, or egress cost (Module O-8)
Read the distribution, not the average. A bimodal shape usually means cache hit versus miss, and the p99 describes neither mode. A cliff means a resource hit a hard limit rather than degrading gradually.
Read the errors at the knee, because they tell you whether your protective mechanisms work. 503s with Retry-After mean shedding is functioning (Module A-8). Client timeouts and pool-exhaustion errors mean it is not, and you are collapsing instead of refusing.
Use Little's Law to connect the numbers (Module F-4): concurrency = arrival rate × service time. It converts a throughput target into a required concurrency, which is how you size pools and bulkheads rather than guessing. And it tells you what happens when service time triples: the concurrency requirement triples with it, which is the arithmetic behind almost every cascading failure in Phase 3.
Capacity Planning: Headroom Derived, Not Chosen
The question is not "how much traffic can we serve" but "how much of our capacity can we routinely use." The answer comes from what you must survive:
Losing a failure domain. If you run three availability zones and must survive losing one, you can use at most ⅔ ≈ 66% of total capacity in steady state. With two zones, 50%. This is a concrete, defensible number that turns a budget conversation from opinion into arithmetic.
A deploy. A rolling update removes instances; maxUnavailable of 25% means another 25% of headroom during every deploy.
Growth between capacity reviews. If you review quarterly and grow 10% a month, you need ~30% of headroom just to reach the next review.
A retry storm. Module A-6's amplification means offered load can rise sharply during an incident, and headroom is what lets you absorb rather than amplify.
Cold caches. After a restart or a scale-out, per-request cost is temporarily higher.
Stack those and a steady-state utilisation target of 50–70% of the knee is not conservatism; it is the sum of specific, named requirements. Write them down that way, because "we run at 60%" is arguable and "we run at 60% because we must survive an AZ loss during a deploy while growing 10% a month" is not.
Model resource-per-request, so you can forecast without re-testing. For each significant endpoint, record CPU-milliseconds, database queries, cache operations and bytes transferred. A traffic forecast then converts directly into a resource forecast — and into a cost forecast (Module O-8), which is the same arithmetic with prices attached.
Plan against the binding constraint, and re-test after fixing it. Scaling the wrong tier adds cost and no capacity (Module A-8), and removing one bottleneck reveals the next, which is often in a completely different place. Capacity work is therefore iterative by nature: measure, fix, re-measure.
The stateful problem, and lead time
Stateless capacity is easy: add instances. The plan is really about the database, and the options have very different lead times:
Action
Lead time
Ceiling
Vertical scale-up
minutes (with a failover)
the largest instance available
Add a read replica
tens of minutes
read capacity only
Add a cache tier
days
read capacity, with invalidation cost
Partition the workload
weeks
good
Shard (Module P-6)
months
high
Which produces the single most important planning rule in this module:
The lead time on your fix must be shorter than the time until you need it.
If sharding takes four months and you will exhaust vertical headroom in three, the project needed to start last month. This is why sharding is a planning decision rather than a scaling action, and why the useful capacity metric is not current utilisation but months of headroom remaining at the current growth rate — a number that should be on a dashboard and reviewed, because it is the only one that tells you when to start a long project.
Autoscaling, done correctly:
Scale on the right signal. Queue age or in-flight concurrency, not CPU — I/O-bound workers have low CPU precisely while their backlog grows, so CPU-based autoscaling routinely scales down during a backlog (Modules P-19, A-8).
Set the floor high enough to survive a spike during scale-up, because the autoscaler's reaction time is the window you must cover with static capacity.
Scale up fast, scale down slowly, so a brief dip does not remove the capacity a returning spike needs.
Remember the multipliers. More instances means more database connections (Module P-4) and more cold caches, so autoscaling the stateless tier can saturate the stateful one.
Launch Capacity
A launch is the event where all of this is tested at once, usually with no ability to iterate. The checklist that matters:
Load test at the projected peak times a margin (2–3×), because launch projections are usually wrong in both directions and being wrong upwards is the expensive one.
Verify shedding and degradation actually fire at the limit — this is Module A-7's untested-fallback warning and Module A-8's admission control, confirmed rather than assumed.
Pre-scale rather than trusting the autoscaler. A launch spike arrives faster than instances boot.
Check your quotas. This is the mundane launch blocker that catches people: cloud instance quotas per region, elastic IP limits, load-balancer target limits, provider API rate limits, third-party API tier limits, and database connection maximums. Quota increases can take days to approve, so they are checked weeks ahead, not on the day.
Warm the caches before opening the doors, since a cold cache raises per-request cost exactly when volume peaks.
Have the degradation switches ready and named — which features you will turn off, in what order, and who decides (Module A-7).
Watch the leading indicators, not the error rate: queue depth, pool wait time, saturation (Modules O-1, A-8).
Why this matters in production: the recurring load-testing failure is a test that proved nothing — closed-loop, one endpoint, one hot key, a small dataset, twenty minutes — producing a confident number that the first real traffic peak contradicts. The recurring capacity-planning failure is different and worse: a team that knew utilisation was rising, could not name the binding constraint, and started the sharding project the month after they needed it. The first is fixed by methodology; the second by tracking months-of-headroom rather than percent-utilised.
Where This Shows Up in Your Stack
Load generators: prefer tools with an arrival-rate (open) model — k6's constant-arrival-rate executors, Vegeta, Gatling's injection profiles. Tools defaulting to a fixed thread or virtual-user pool are closed-loop and will self-throttle under overload; if you must use one, verify how it handles latency growth before trusting the result. Generate keys with a realistic (Zipfian) distribution, and run the generator from outside the network you are testing so its own resources are not the constraint.
PostgreSQL:pgbench measures the database in isolation, which is useful for separating database limits from application limits. During a test watch pg_stat_statements for the query that dominates total time (usually not the one you expected), pool wait time from the application side, max_connections headroom, replica lag, and dead-tuple accumulation on the write-heavy tables. The database is the most common binding constraint and the one with the longest lead time on a fix.
Node.js: event-loop delay is the saturation signal (Module A-8), and one process uses one core — so a load test against a single process tells you about one core, not about a machine. Watch GC pause time and heap growth, which is where the soak test's leaks appear.
Redis: rarely the bottleneck itself; the round trips to it usually are, so measure operations per request rather than Redis throughput. Watch evicted_keys under load — an eviction storm means your test's key distribution has exceeded the instance and your hit-ratio numbers are about to become fiction (Module P-12).
Observability: you need Module O-1's instrumentation before the test, and the test is also a test of it — cardinality that survives normal traffic can take out a metrics backend at 10×, which is worth discovering deliberately rather than during a launch.
Cost: a load test at 10× costs roughly 10× for its duration, plus any egress and per-request charges from real dependencies. Budget it, and prefer testing against sandboxes where the provider offers them (Module O-8).
Summary
A load test produces four findings, not a number: the knee of the curve, which resource saturates first, the failure mode at overload (shed cleanly or collapse), and the values for limits, pools and autoscaling thresholds.
Five test types find different bugs. Ramp finds the knee; soak finds accumulation — leaks, descriptor exhaustion, log-disk fill, unbounded in-process maps, vacuum/bloat feedback loops, a slowly growing replication slot; spike exposes cold caches, autoscaler lag, connection storms and retry amplification; breakpoint shows how you fail; and recovery — the one nobody runs — shows whether you exit Module A-8's metastable state without a human.
A closed-loop generator cannot observe overload, because rising latency makes each virtual user send fewer requests, so the generator reduces its own load precisely when you needed it to persist. If your load test cannot make the system fail, suspect the generator before concluding the system is robust. Real users are open-loop.
Coordinated omission makes reported latency wildly optimistic: a stalled virtual user does not record the requests it would have sent, so a two-second stall contributes one sample instead of forty. Use an open-loop generator that measures from the intended send time.
The test must match reality in traffic mix, key distribution and data volume. A hot key measures your cache; use a Zipfian distribution. A query fast on 100,000 rows is not fast on 400 million. Discard the warm-up from steady-state numbers — and measure it separately, because that is your spike behaviour.
Only production has the real data, mix and dependencies: shadow traffic (read-only or side-effect-free), log replay, an elevated-weight canary as a continuous measurement of the knee, and careful direct testing at low traffic. Bound the blast radius, use a separate test tenant, and check the side-effect paths — the classic accident is ten thousand real emails.
Expect the bottleneck not to be what you were testing. Database connections and pool wait time are the most common answer; then database CPU/IO, event-loop delay, lock contention, and a downstream service's own limit. Read the distribution rather than the average, and read the kind of error at the knee — 503s mean shedding works, timeouts and pool exhaustion mean it does not.
Headroom is derived, not chosen. Surviving the loss of one of three availability zones caps steady-state utilisation at ~66% (two zones: 50%); add deploy churn, growth until the next review, retry amplification and cold caches, and 50–70% of the knee is the sum of named requirements rather than caution. State it that way, because the arithmetic is not arguable.
Model resource-per-request (CPU-ms, queries, cache operations, bytes) so a traffic forecast converts into a resource and cost forecast without another test.
Plan against the binding constraint and re-test after fixing it, since scaling the wrong tier buys nothing and removing one bottleneck reveals the next somewhere else.
The lead time on your fix must be shorter than the time until you need it. Vertical scaling takes minutes, a replica tens of minutes, sharding months — so the useful metric is not current utilisation but months of headroom at the current growth rate, which is the only number that tells you when to start a long project.
Autoscale on queue age or concurrency, not CPU (I/O-bound workers have low CPU while backlogged), keep the floor high enough to cover the autoscaler's reaction time, scale up fast and down slowly, and remember that more instances means more database connections and more cold caches.
For a launch: test at 2–3× the projection, verify shedding and degradation actually fire, pre-scale rather than trusting the autoscaler, check cloud and third-party quotas weeks ahead because increases take days to approve, warm the caches, name the degradation switches and who flips them, and watch saturation rather than error rate.
The next module, Chaos Engineering and Fault Injection, is the natural sequel. This module measured what happens under load; O-6 asks whether the failure-handling built across Phase 3 actually works — because a circuit breaker, a fallback, a retry budget and a failover are all untested code paths until something makes them run, and the first time they execute should not be during an incident.
Knowledge Check
A team load tests with 500 virtual users, each sending a request and waiting for the response before sending the next. They ramp to 500 users, observe p99 latency of 180 ms and no errors, and conclude the service comfortably handles their projected 4,000 requests per second. On launch day the service collapses at around 2,600 requests per second. What went wrong with the test?
A service runs across three availability zones at 85% steady-state CPU utilisation, which the team considers efficient. A capacity review asks whether this is safe. What does the module say, and how should the target be justified?
A team's database is at 70% of its largest available instance size and growing 8% a month. They have added read replicas and a cache. An architect suggests planning a sharding project "once we hit 90%." What does the module say?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Latency rises from 50 ms to 2,000 ms.
Each virtual user now sends 0.5 requests/second instead of 20.
100 virtual users offer 50 req/s instead of 2,000 req/s.
→ The generator REDUCED its load exactly because the system slowed down.
→ You never reach the overload you were testing for. The system looks fine.
Real experience: 1 request at 2,000 ms + 39 that would have queued behind it
Recorded: 1 sample at 2,000 ms
Reported p99: excellent