DEV Community

Magevanta
Magevanta

Posted on Originally published at magevanta.com

Magento 2 Load Testing & Capacity Planning: Know Your Limits Before Traffic Does

Every Magento 2 team has the same nightmare: a flash sale goes live, traffic triples, and the site turns into a spinning wheel of death. The store survives — barely — but orders drop, support tickets explode, and the post-mortem reveals the same sentence: "We didn't know it would break at that load."

You can know. Load testing is the difference between guessing your limits and measuring them. This article covers the full loop: designing realistic tests, generating load that actually resembles your shoppers, reading the results to find the real bottleneck, and turning those numbers into capacity decisions.

Why Load Testing Is Not Regression Testing

If you've read our article on automated performance regression testing in CI, you know that's about catching slowdowns between deploys — a few requests, tight budgets, fail the build if TTFB climbs.

Load testing answers a different question: how much traffic can this system handle before it degrades or dies? One focuses on change detection; the other on absolute capacity. You need both. Regression testing keeps you from getting slower; load testing tells you where the cliff is, and whether one node survives a flash sale or you need five.

Define What "Good" Means Before You Start

A load test without acceptance criteria is just a benchmark with anxiety. Define SLOs first, ideally from real traffic data:

  • p95 Time To First Byte (TTFB) under load — e.g., under 800ms
  • Error rate — under 0.5% (502s, timeouts, checkout failures)
  • Throughput — X requests/second sustainable for 30+ minutes
  • Business metrics — successful checkout completion rate over 99%

Then define the shape of traffic. Magento 2 is not a static site: different pages cost wildly different amounts. A realistic mix for a typical store looks something like:

  • 40% category/product listing pages (PHP + FPC + Elasticsearch aggregations)
  • 30% product detail pages (heavily cached, cheap when warm)
  • 15% home + CMS pages (nearly free with FPC)
  • 10% cart + checkout actions (uncached, DB-heavy, the real load)
  • 5% search, account, and API calls

Also model the audience. Returning customers with a valid session hit a different code path than anonymous shoppers — customer data sections, personalized blocks, and lesser FPC coverage. If 60% of your real traffic is logged in, a test with 100% anonymous visitors will flatter you dangerously.

Build a Test Environment That Looks Like Production

The #1 load-testing mistake: testing on a sandbox with 50 products, one customer, and a cold cache — then believing the results apply to your 200k-SKU storefront.

Your test environment needs:

  • Production-sized data. Same catalog size, same attribute count, realistic customer and quote tables. EAV lookups and index tables behave completely differently at 10k vs 200k SKUs.
  • Production-equivalent config. Same number of PHP-FPM workers (or scaled proportionally), same Redis setup, same Elasticsearch/OpenSearch cluster layout, same Varnish/FPC strategy.
  • Warm cache tests first, cold cache tests second. A warm-cache test measures steady-state capacity — what shoppers experience 99% of the time. A cold-cache test (flush Varnish + Redis, burst traffic) simulates the worst minutes after a deploy or a cache invalidation storm. Both are informative; most teams only test one.
  • Run load from a different machine. The load generator should never share resources with the app server. If possible, generate traffic from outside your CDN too — you want to see the origin's real behavior, not just the CDN's edge.

Tooling: k6 or JMeter?

For Magento 2 specifically, both work; pick based on your team:

  • k6 — scriptable in JavaScript, excellent ramp-up/ramp-down stages, cheap to run in CI, and produces clean threshold-based pass/fail. Great if you want to reuse the same journey definitions between load tests and synthetic checks.
  • JMeter — heavyweight, GUI-driven, familiar to many QA teams, with a huge plugin ecosystem (including Magento-specific CSV data sets or correlation helpers).
  • Locust — Python, fine for simple journeys, but its event loop can become the bottleneck at high concurrency.

A minimal k6 journey for a shopper flow looks like this:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 50 },   // ramp up
    { duration: '20m', target: 50 },  // steady state
    { duration: '2m', target: 0 },    // ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<800'],
    http_req_failed: ['rate<0.005'],
  },
};

export default function () {
  // Anonymous browse: home → PLP → PDP
  const home = http.get('https://store.example.com/');
  check(home, { 'home 200': (r) => r.status === 200 });

  const plp = http.get('https://store.example.com/women/tops.html');
  check(plp, { 'plp 200': (r) => r.status === 200 });

  const pdp = http.get('https://store.example.com/example-product.html');
  check(pdp, { 'pdp 200': (r) => r.status === 200 });

  // Every Nth iteration: add to cart + go to checkout (uncached, heavy)
  if (__ITER % 10 === 0) {
    // In reality: carry form_key from the PDP, POST to checkout/cart/add
    const cart = http.post('https://store.example.com/checkout/cart/add/uenc/...');
    check(cart, { 'cart add 200': (r) => r.status === 200 });
  }
  sleep(3); // realistic think time between pages
}
Enter fullscreen mode Exit fullscreen mode

The think time matters more than people assume. 50 virtual users hammering pages back-to-back with zero delay is 50 users acting like robots — your real visitors read, scroll, compare, and hesitate. Without think time you'll overestimate load on cheap cached pages and misread the results.

Extract the Right Data From the Test

A load test produces two data sets: the client-side response times, and the server-side telemetry. The second one is where the diagnosis lives. During the test, watch:

  • PHP-FPM. Is pm.max_children exhausted? You'll see listen queue grow and 502s appear. This is the classic first bottleneck for PHP apps.
  • MySQL/MariaDB. Run SHOW PROCESSLIST and watch Threads_running, Threads_connected, and slow query log entries. A wave of identical slow queries (often the same category listing or price filter query) points straight at the culprit.
  • Redis. INFO stats — look at evicted_keys and rejected_connections; evictions under load mean the cache is thrashing, not helping.
  • Elasticsearch/OpenSearch. Watch search latency and rejection counts (thread_pool stats). Faceted navigation is often the hidden load generator.
  • nginx access log. Triage the response code mix and the slowest URLs. awk over the log for p95 per URL pattern tells you which page types degrade first.

The bottleneck is almost always a queue filling up: PHP-FPM workers, DB connections, or search threads. When response times climb linearly while CPU idles, you're queueing somewhere — find the queue.

Reading the Results: The Three Load Phases

Healthy systems show three phases in a ramp-up test:

  1. Linear phase — response time stays flat as concurrency grows. The system is bored. This is your linear capacity region.
  2. Knee phase — response time starts climbing, but throughput still grows. Some queue is starting to fill.
  3. Cliff phase — throughput plateaus or drops, error rate spikes. Saturated. This is your breaking point.

Never capacity-plan at the cliff. Plan at the knee: the concurrency where p95 stays inside your SLO. If your knee is at 40 concurrent sessions per node and you expect 400 at peak, you need ~10 nodes with zero headroom — plan for 30-40% headroom on top of that for spikes, deploys, and cache misses.

Turning Numbers Into Capacity Decisions

This is where load testing pays for itself. Concrete examples of decisions the test output should drive:

  • Node count. One web node sustains X req/s at p95 under SLO → peak demand is 3.5X → you need 4-5 nodes, not 2.
  • Autoscaling thresholds. Set scale-out at 60-70% of the knee value, not at error-rate spikes. Scaling on errors is like braking after the crash.
  • Where to spend money. If the bottleneck is MySQL connection saturation, buying two more web nodes won't help — add a read replica or enable connection pooling instead. Load tests redirect your budget from symptoms to cause.
  • Cache warming before launches. If the cold-cache test fails and the warm-cache test passes, your launch procedure needs a warming step (sitemap crawl or prioritized warm) before traffic switches over.
  • Queue-based offloading. If checkout actions are the cliff, move email, order export, and inventory updates to async message queues so a traffic spike doesn't compound into a DB pileup.

When to Test — And How Often

  • Before every major event: Black Friday, seasonal sales, product launches, flash deals. Re-run the test with the expected peak traffic at least a week ahead — enough time to fix what it finds.
  • After architecture changes: moving to a new hosting stack, adding a CDN, switching search engines, changing PHP versions. Every one of these shifts the knee.
  • Quarterly baseline: as your catalog grows, your capacity profile drifts. A quarterly 30-minute soak test keeps your numbers honest.
  • After the fact — always validate. During your next real peak, compare actual server metrics to the test predictions. If reality is 2x better or worse than the test, your test data or think times are off — fix the model, or the next test will lie to you again.

The Bottom Line

Load testing doesn't prevent traffic spikes — but it removes the surprise. It tells you exactly how many requests your stack absorbs before it complains, where the bottleneck lives, and what one more euro of infrastructure should buy (another node, a replica, or a cache). Combined with CI regression testing, it forms the complete picture: you stay fast and you know your limits.

Run warm and cold tests, model real shopper behavior, watch the server-side queues during the run, and plan at the knee with headroom. Do that before the next flash sale, and the only thing spinning will be your load generator — not your storefront.

Top comments (0)