Shift-Left Performance Tests in CI/CD: k6, Gatling, Locust

Degradation is usually discovered after release, when users are already working with the system. We set up early load testing directly in the build pipeline: checks run on every change, before deployment. This reduces bottleneck detection time many times over and removes the risk of failures under peak load.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1359
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Shift left performance testing: where the pipeline breaks

A load test run a week before release almost always shows the wrong thing.

The load profile is captured from production a month earlier, caches are cold, data volume is smaller than production — performance degradation under peak emerges after rollout, when users leave, not in the report.

Late detection of failures is expensive not because of the fix itself but because of the chain of consequences: incident, rollback, rerun, schedule shift.

The later the stage, the more expensive the fix: at design it is one decision in the schema, at acceptance it is rebuilding the release and repeated approvals.

If a bottleneck is found at acceptance, the architecture can no longer be changed — all that remains is optimizing individual queries and adding resources, treating the symptom instead of the cause.

A separate problem is the build bottleneck. A full load run takes tens of minutes and holds up the release queue, so it is moved outside the pipeline.

Load testing in the pipeline becomes a manual operation before a major release: regressions accumulate for months, and the “slow environment” gets blamed.

Shift left performance testing is moving performance verification to a stage where the cost of a fix is still low: short scenarios per change, an extended profile per branch, a full one on schedule. Below we discuss how this is addressed in practice.

What early load testing provides

A load run started a day before release gives a verdict when nothing can be changed: the bottleneck is visible, but there is no time to rewrite the data collection loop.

Early load testing moves the same run into the build pipeline — for example, scenarios on k6 — and answers the question “how much can it handle” at a stage where fixing costs an edit, not a rewrite.

  • Feedback time. The verdict on the load profile comes in the same run as unit tests: the engineer sees regression minutes after submitting a change, not after days of waiting for a separate environment.
  • Share of performance defects found before release. Connection leaks, unbounded queries, locks under concurrent access are caught on test data and go into work before the production environment. We aim for 80% and above.
  • Stability under load. The peak profile — seasonal spike, nightly batch processing, mailing — is run regularly, so behavior at the limit is known in advance rather than discovered during an incident.
  • Downtime cost. A defect from the build pipeline means hours of fixing; the same defect in production means hours of service unavailability and incident investigation.
  • Regression against baselines. Response time of key operations and error response rate are stored next to the code and compared build to build: a 10–15% degradation is visible as a failed check, not as a user complaint.
  • Thresholds as a release condition. A scenario with a threshold on response time and error rate stops the build — one rule for everyone, nothing to interpret.

Load test execution modes: comparison

Three load testing modes do not compete but answer different questions: commit — whether regression has appeared on the hot path, schedule — where degradation goes over weeks, pre-release — whether the system will withstand the production profile.

The more frequent the run, the shorter the load profile and the narrower the scenario set; otherwise the check stops keeping up with the build pace.

Mode When appropriate What it is limited by
Short profile per commit Regression on hot scenarios: 95th percentile threshold, error rate, throughput A few minutes of build budget, small data volume and virtual users; won't show leaks and long-series degradation
Scheduled runs Trends and stability: series once a day, leak detection, comparison with past results Feedback arrives hours later; results need to be stored and compared
Pre-release measurements Capacity before rollout: long and stepped profile, data close to production Expensive in environment and time, isolation required; doesn't catch regression of a single commit

Modes complement each other: a scenario is transferred between k6, Gatling, and Locust without rewriting logic; only the load profile and build stop rules differ.

A threshold failure on commit blocks merge, the nightly run creates an investigation task, and the pre-release measurement stops rollout until causes are clarified.

Stages of implementing load checks in the pipeline

Load checks in the pipeline do not appear with a single commit: first you need to understand what exactly and in what mode to load. Below is the sequence of steps we follow during implementation: it provides the first reliable measurements before checks become release-blocking.

  1. Pipeline audit. We analyze which services the system consists of, which external dependencies participate in a request, and where in the pipeline the launch is integrated. The output is a pipeline map and a list of scenarios worth loading.
  2. Capturing the baseline load profile. We collect statistics on real requests: read/write ratio, peak windows, distribution by endpoints. Without this profile, there is nothing to compare the run result with.
  3. Designing the check scheme. We define what runs on every commit and what runs before deployment, set the time budget and thresholds beyond which the build stops.
  4. Setting up the environment and scenarios. We deploy an isolated environment comparable to production, describe scenarios on k6, and configure data feed and metric collection.
  5. Validation on a real release. We run the scheme on a production change, compare the forecast with actual behavior, and calibrate thresholds so checks do not trigger on natural fluctuations.
  6. Handover and support. We hand over configurations, metric dashboards, a response procedure for failures, and the procedure for updating scenarios when the product changes.

Implementation artifacts: configurations, schemes, procedures

Implementing load checks has an effect when the result remains in the repository and knowledge base, not in chats and people's heads.

Below is the minimal set we hand over to the team: from it, a new engineer reproduces the run without verbal explanations, and the on-call engineer understands what to do with a failed build.

  • Load test scenarios — versioned next to the service code: user paths, data preparation, expected responses. The scenario is tied to a branch and runs on changes in affected modules, not "sometime before release."
  • Load profiles — stepped ramp-up, stable plateau, peak spike; for each, duration, intensity, and early stop conditions are set.
  • Thresholds as code — error rate, response time on the slow tail of requests, throughput. The build fails by a formal criterion, not by on-call interpretation.
  • Response procedure — who reviews the report and in what window, what to do on degradation: block merge, roll back release, or create an investigation with the attached profile.
  • Pipeline documentation — diagram of environments, test data sources, how the load environment differs from production, and which checks on it are unreliable.
  • Run instructions — running locally, in the pipeline, and on a dedicated environment: what parameters to set, where to view the report, how to reproduce a problematic run.
  • Performance baselines — recorded results of past releases and the rule for updating them, so there is always something to compare against.
  • Storage of reports and artifacts — retention period, link to build and commit.

After that, the set lives as regular code: changes go through review, and outdated scenarios are removed along with decommissioned paths.

Case study: eliminated degradation before a peak release

In one project — a service with planned load peaks — performance regression regularly surfaced after release: a bottleneck in a changed query was found in production when some orders stopped being placed.

Incident investigation and manual rollback consumed the night, and the next release attempt shifted by a day.

What we rebuilt

The load pipeline was moved into the main build: scenarios on k6 run on every branch change, and thresholds for response time and error rate are embedded in the scenario itself and fail the build when exceeded.

The load profile was brought closer to production — the same read/write ratios, the same peak windows, only on a smaller data volume. The release artifact became immutable: each build gets its own tag in the registry, and the previous version remains ready for switchover.

Metric Before After
Load pipeline run once a day, manually on every branch change
Performance defects found before release about 30% more than 90%
Rollback window on failed release hours of manual work minutes, tag switch
Run duration grew with number of scenarios fits into build budget due to parallel runners

Degradations are now caught at the build stage, when a fix costs minutes. The rollback window shrank to a tag switch — a failed release stopped being an event with overnight rework and schedule shifts.

How does shift left differ from pre-release measurements?

The difference is primarily in where the control point sits in the pipeline.

The shift left approach moves it to the very beginning — to a change not yet merged into the main branch: load scenarios run on a short set of operations against the branch build, and the author of the change sees the result.

Pre-release measurements sit at the exit: the environment is assembled from already accepted code, and the full run happens before release.

This determines who receives the degradation signal first. In a shifted-left pipeline, it is the developer — in the same change discussion, while they remember what and why they changed, and while the investigation costs one fix.

With pre-release measurements, the signal comes to the release engineer or tester a day or two before release, and then the search for the culprit begins across the entire accumulated history.

Behavior on threshold failure is also different. If a scenario exceeds the bounds for response time percentile or throughput, the merge is blocked: the degradation physically does not reach the main branch, and the baseline is revised consciously, by a separate decision.

On a pre-release environment, the choice is poorer — either shift the release date or release with a known deviation and close it in the next release.

The practical effect is in reducing the window between the appearance of degradation and its detection: from days to minutes, and the volume of code to investigate drops to a single change instead of a release build.

The downside is false positives in an unstable environment, so thresholds are calculated by percentiles and the environment is kept predictable: otherwise the check stops being trusted and is disabled.

At the same time, shifted-left checks do not replace pre-release ones. Short runs will not show behavior under prolonged load, memory accumulation, and work with a real data profile. The working scheme is two levels: fast control on every change and a full run before release. Below we discuss how this is arranged and by what criteria to choose thresholds.

Common technical doubts and answers

Doubts at the start are usually the same: will the pipeline become a source of false build failures, will environment resources suffice, what to do with test data, and will we have to break what has already been deployed by our own efforts.

Shifting load checks left (shift-left) rests not on tools but on these very questions, so we answer substantively.

Won't false positives start blocking the build?

We set thresholds on p95 and p99, not on average response time, and require reproducibility of the deviation in two consecutive runs. Degradation is separated from a one-off outlier at the data level — the build fails only on reproducible regression.

What to do with an unstable environment?

If the environment is shared by several teams, measurements are incomparable. Either move the load profile to an isolated pipeline or fix the baseline and compare only within it — with the same configuration and the same data set.

How is test data prepared?

The set is versioned together with the load profile and populated before the run, not during it. Composition and volume are fixed, personal data is anonymized — runs are reproducible and do not affect production records.

We already have our own pipeline deployed — will everything have to be changed?

No. We connect to what exists — k6, Gatling, or Locust — and build out what is missing: thresholds, reports, a gate in the build line, and a link between the result and a specific code change.

What happens after handover?

What remains is not only documentation: a procedure for investigating failures, instructions for updating profiles, team training, and incident analysis during the transition period. After that — support or targeted improvements by agreement.

Let's discuss your task and come back with a plan

To make the pipeline assessment based on facts rather than guesses, send what you already have: build and check configurations, a list of services with external dependencies, notes on where the process currently bottlenecks — build time, waiting for environments, manual runs before rollout.

No need to prepare cleaned-up reports and presentations: the closer the material is to the actual repository state, the more accurate the check implementation plan will be.

We review what is sent and come back with specifics:

  • task analysis — what exactly limits speed and where the limit of the current scheme is;
  • check scheme — which performance scenarios we put at the build stage, which we leave before rollout, and which we move to the nightly run;
  • environment map — where load environments are raised and how they are isolated from production data;
  • thresholds and failure rules — by which metrics the build stops, and by which it only marks the result;
  • list of changes — what we fix in configurations, what we add, and what we remove from the process;
  • stages and timelines — sequence of work with points where you see the result, not just a report.

Describe the task in your own words — we'll discuss it and come back with a pipeline assessment, a check implementation plan, and timelines for each stage.