Load testing with k6: where production breaks
Production doesn't break where resources run out — it breaks where the queue runs out. The database connection pool gets exhausted first, requests start waiting, and a service that looks calm on CPU graphs starts returning timeouts.
Then the cascade kicks in: the client retries the request, retries finish off the pool, service degradation accelerates — and recovery now takes hours instead of minutes.
Load testing with k6 exposes these failure points under load before peak traffic finds them in the field. Scenarios are described in code: ramp-up profile, plateau, sudden spike, behavior when some nodes fail.
Metrics — response time by percentile, error rate, throughput — are captured at every step, and thresholds are set as a pass/fail condition for the run, not as a wish.
An infrastructure bottleneck is rarely just one. It could be connection pool exhaustion, a wrong timeout on an external call, missing concurrency limits, a cache that doesn't warm up after a restart, or a load balancer that drops sessions during scaling.
Individually, each node handles the load; together they cause failure at peak — and you can only see this in a scenario run with a realistic traffic profile.
Here's how we close this gap: which scenarios we write for your profile, where we set thresholds, and what we do with the failure points we find before release to production.
What a scenario run before release provides
A scenario run before release turns guesses about service behavior into measurements.
Instead of asking "will prod survive the peak," the team gets a load profile that is reproducible step by step: how many concurrent users, which scenario, where the saturation point is.
Capacity hypotheses are tested in minutes instead of days of waiting for real traffic, and every configuration change is immediately visible in the numbers — response time, error rate, resource consumption on each node.
Predictability under load is when the system's behavior at peak is known in advance. A scenario run shows at what number of concurrent sessions response time grows, where queues begin to accumulate, and which node gives out first.
This data forms the basis for thresholds and autoscaling rules: the system reacts to growth before the user notices the slowdown.
A separate effect is the eliminated class of failures. Errors that manifest only under concurrent requests — races for a shared resource, connection pool exhaustion, cache degradation under misses — are invisible at low traffic and surface at the most unfortunate moment.
The run pulls them out before release, when the cost of fixing is minimal, rather than in the midst of peak load.
Service resilience becomes a verifiable property rather than a hope. The team knows the capacity headroom, sees how the system behaves when an individual node fails, and understands which degradation scenarios are acceptable.
This changes the conversation about release: instead of "it will probably hold up" — concrete thresholds at which the system remains within normal limits, and a clear action plan for when they are exceeded.
Load profiles: steady, step, spike
A load profile is not "how many requests per second to send," but the shape of the curve over time.
The same service withstands a steady load and collapses under a spike: the connection pool, cache, and queues behave differently depending on how fast the intensity grows.
A steady profile holds a flat intensity and answers the question "what headroom in throughput exists right now." A step profile raises the load in steps with a hold at each step — this is how you find the point where response time shoots up and the error rate stops being zero.
A spike profile simulates a sharp burst: a sale, a newsletter, registration opening — we check whether the system survives the jump and how quickly it returns to normal afterward.
| Profile | When it fits | Limitations |
|---|---|---|
| Steady | Baseline, regression between releases, finding headroom | Does not show behavior under growth or degradation under queues |
| Step | Finding the limit point and headroom before failure | Long run; the result depends on step duration |
| Spike | Traffic bursts, testing auto-scaling and recovery | Does not give a degradation curve, only the fact of "withstood/failed" |
In practice, profiles are combined in a single scenario: a step ramp-up to working intensity, a hold at steady, then a spike burst and observation of recovery.
The limitation of any profile is the test environment: if the environment is weaker than production or the data doesn't resemble real-world data, the numbers describe the test environment, not the system.
Therefore, before a run we record the environment composition, data volume, and measurement points, and we compare results only between comparable configurations.
How we implement k6 load testing
Implementation starts not with installing a tool, but with an architecture audit: which services receive traffic, where the shared cache and database are, which dependencies are external. Without this map, scenarios hit random points, and the results don't match reality.
The output is a list of critical paths and target metrics: how many concurrent users we can hold, what response time we consider normal, and at what load degradation begins.
Next, we build the route so that each stage produces a verifiable artifact, not a report saying "everything works on the whole."
- Audit of architecture and failure points. We break down the service diagram, queues, limits, and external integrations. We record exactly what we are testing and which metrics we capture.
- Load target metrics. We agree on the profile: peak traffic, duration, behavior under failures. This is the basis for acceptance criteria, not an abstract "it should hold up."
- Scenario setup. We describe user paths as scenarios with different load profiles — gradual ramp-up, steps, sharp spike. We move parameters into configuration so they can be changed without editing code.
- Environment and data preparation. We deploy a staging environment close to production, prepare test data, and isolate it from real users. Otherwise, the measurements show not the system's capacity but its behavior on an empty database.
- Run and results verification. We capture metrics on response time, error rate, and throughput, and compare them with the targets. We find bottlenecks — slow queries, resource shortages, incorrect settings.
- Remediation and re-run. After the fixes, we repeat the same scenarios and compare them with previous measurements. This shows exactly what had an effect and what didn't.
- Handover to the team. We hand over the scenarios, configurations, run procedure, and results analysis. Your engineers then run the checks themselves — before releases and after changes.
This route turns load testing from a one-off event into a regular check. The team gets not a one-off report, but a reproducible set of scenarios and clear thresholds beyond which the system begins to degrade.
Scenarios, thresholds, and reports: what stays with you
Load testing with k6 only delivers results when scenarios, thresholds, and the environment are described in such a way that the team can repeat a run without the author. That is why we hand over not a one-off run, but a set of artifacts that let load checks continue on their own.
- Load scenarios — files with profiles: ramp-up, plateau, peak, ramp-down, and a "step" scenario for finding the degradation point. Each profile is tied to a real user journey, not an abstract request.
- Thresholds — acceptance criteria inside the scenarios themselves: error rate, response time at percentiles, throughput. A run fails automatically if the system goes beyond the limits — no manual chart analysis.
- Environment diagram — a map of the test bench: which services, dependencies, and stubs are involved, where the resource bottlenecks are, and how the environment differs from production. Without it, the run's numbers cannot be explained.
- Run regulations — when and what we launch: before a release, on a schedule, after configuration changes. With an indication of who reviews the result and which thresholds block the rollout.
- Load report — analysis of the results: where thresholds were met, where the limit was hit, what caused it, and what changes are proposed. With charts and a conclusion, not a raw log.
- Run instructions — how to bring up the environment, run a scenario, and collect metrics, including common errors and how to fix them.
- Development plan — which scenarios to add next: seasonal peaks, fault tolerance, long-duration endurance load.
Such a set closes the main risk of load checks — when a test exists, but only one person can repeat it and understand the result. Describe your system and goals — we will come back with an estimate and a work plan.
Case study: how a service stopped crashing under peak load
What it was like before
The service — a customer portal with payments and order history — ran reliably under average traffic and predictably went down during sales.
The symptoms recurred: checkout response time grew from 400 ms to 6-8 seconds, the share of 5xx errors rose to 12-15%, and some carts were lost at the payment confirmation stage.
Engineers fixed what they saw in the logs, but without a reproducible load profile, every fix was a guess — no one could measure its effect.
What was changed
We built a k6 scenario that replicated real behavior: login, catalog, add to cart, payment — with different proportions and pauses between steps.
We ran a series of test runs with virtual users ramping up to three times the peak and captured metrics for each part of the journey, not just for the service as a whole.
This uncovered two bottlenecks: a synchronous call to the payment gateway inside the checkout request and a database connection pool that hit its ceiling long before the peak.
We moved payment confirmation into asynchronous processing, expanded the connection pool, and added a cache for catalog reads.
After each fix, we ran the same scenario — the comparison was based on the same metrics, so regressions were visible immediately rather than a week later in production.
What it became
| Metric | Before | After |
|---|---|---|
| Response time at peak | 6-8 s | 700-900 ms |
| Share of 5xx errors | 12-15% | below 0.3% |
| Lost orders | a few at peak | none recorded |
| Behavior under 3× load | degradation to failure | runs without error growth |
The scenario stayed in the repository and is run before every major release — the team can see how a change affects resilience under peak load before rolling out to production.
We did a similar load analysis for a delivery service on k6: there, after moving heavy operations out of the request, response time at peak dropped from 5 seconds to 600 ms, and the share of errors — from 9% to a fraction of a percent.
How a scenario run differs from monitoring
Monitoring answers the question "what is happening right now": it collects metrics from a running service and shows that latencies have grown, a queue is building up, and the error rate has started creeping upward.
But it does not say at what load this began or what exactly caused it — observation captures the symptom, not the boundary beyond which the system breaks.
A scenario run works differently: we define the load profile ourselves and test a hypothesis — whether the service will withstand a peak, how it will behave under prolonged pressure, and where it will begin to degrade.
The key difference is controlled load. In service observation, traffic is generated by real users, and we control neither its volume nor its composition.
In a scenario run, we describe the behavior of virtual users: how many there are, what requests they send, at what frequency, and in what sequence.
This turns the check from passive into active — we do not wait for something to fall over, but instead create the conditions in which a weak spot will reveal itself.
Another difference is reproducibility. Monitoring shows a snapshot of a live system, where the result is influenced by dozens of random factors.
A scenario run is repeatable: the same profile yields comparable figures before and after changes, so it is possible to prove that an optimization worked rather than coincided with a lull in traffic.
In practice, these approaches do not replace but complement each other. Monitoring maintains constant oversight and catches anomalies in real time, while a load run answers the question "will we handle growth" before that growth happens.
We build scenarios so that they hit the most heavily loaded paths — sign-in, checkout, calls to external services — and the output is not a chart for a chart's sake but a concrete boundary: how many concurrent users the service sustains with acceptable response time, and at what load failures begin.
Questions asked before the start
Before the start, three topics almost always come up: whether the load will affect the production environment, how to separate environments, and what to do with scenarios that have already been written. Below are short answers that remove these doubts before work begins.
Can load be run on prod without bringing the service down?
Yes, if the run is limited in load and in time. We set a ceiling on virtual users and their ramp-up rate, set thresholds on response time and error rate — if they are breached, the run stops itself instead of finishing off the system.
Additionally, we limit the run to a low-traffic window, separate the load generator and the target environment by resources, and tag the traffic so it does not end up in product analytics and billing.
If the risk is still unacceptable — we move the run to a copy of the production environment with comparable data.
How are environments separated so that tests do not interfere with each other and with development?
Scenarios and configurations live in a repository and are launched from an isolated environment: separate load generators, their own limits and network rules.
This means that the run does not compete for resources with work tasks and does not depend on someone deploying at that moment. Results are stored separately from product metrics, so measurements remain reproducible.
We already have scenarios on another tool — will they have to be rewritten?
Not necessarily. First we analyze what exists: which scenarios cover real user paths, where there are duplicates, and where there are gaps. Some of the logic is transferred almost unchanged, some is rewritten for k6 — usually these are load profiles and response checks.
If the scenarios describe something that no longer matches the product, we will say so honestly rather than transfer the outdated as is.
What will happen to the access and data that you receive?
Access is granted for specific tasks and revoked upon completion of work; for runs we use test accounts and anonymized data where possible. All changes to configurations and scenarios go through the repository — you can see who changed what.
As a result, you are left with a working framework and documentation, not a black box that depends on us.
If any of these points is handled differently in your case — tell us how it is set up now: we will adapt the run scheme to your constraints, not the other way around.
Let's discuss your task and come back with a plan
To come back with an estimate and a plan, we need context about your system: what we're testing, which scenarios are critical, and what result you consider successful. Describe the task in free form — then we'll ask clarifying questions and propose a work plan.
- What the system is and how it's built. A service, API, web application, or a set of services; where the load boundary lies and which external dependencies are involved.
- Key scenarios. Which user paths matter most: login and authorization, search, checkout, report exports — the things that must not go down.
- Target metrics. Expected number of concurrent users, peak periods, acceptable response time, and the threshold beyond which we consider the system degraded.
- Current infrastructure. Where the environment is deployed, whether there's a separate load-testing stand or we test on prod, and what access restrictions exist.
- What's already been tried. Previous measurements, known bottlenecks, incidents under peak loads — this speeds up diagnostics.
- Constraints and timelines. When the result is needed, whether there are windows when load must not be run, and who on the client side accepts the work.
Based on these inputs, we prepare k6 scenarios, deploy the stand, collect metrics, and find the limit beyond which the system starts dropping requests.
In return, you receive a timeline estimate, a phased work plan, and a list of what will be included in the result: scripts, reports with metrics, a list of bottlenecks, and recommendations for fixing them.
Describe the task — we'll come back with an estimate and a plan, and discuss the next step and the order of work.







