Testing performance under heavy load conditionsfor Drupal 8 , 9 , 10 , and 11

Last updated :  

The load-testing lesson before this one introduced the tools (JMeter, Locust) — this one is about the discipline of actually using them well. Pointing a traffic generator at your homepage and cranking up the concurrency number tells you almost nothing useful on its own. Real traffic isn't uniform: it hits different pages, some visitors are logged in and some aren't, and it arrives in bursts rather than a perfectly smooth stream. Simulating that shape, not just a raw number, is what separates a load test that predicts real-world behavior from one that just makes a number go up.

What you'll learn in this lesson

  • Why testing a single URL in a tight loop gives you a misleading picture of real capacity
  • How to model a realistic traffic mix across anonymous and authenticated requests
  • The difference between ramping load gradually versus a sudden spike, and why both matter
  • What to actually watch on the server side while a test runs

Modeling a realistic traffic mix

A production Drupal site's traffic is a mix: mostly anonymous visitors hitting cached pages (fast, cheap), a smaller slice of logged-in users hitting uncached dynamic pages (slow, expensive), and an even smaller slice submitting forms (slowest, writes to the database). A test that only hits the cached homepage will report numbers that are wildly optimistic compared to what happens when real users start logging in and searching.

# Example traffic-mix weighting for a load-testing tool's scenario config
# (syntax varies by tool; the ratios are the part that matters).
scenarios:
  - name: anonymous_browse
    weight: 70
    path: /
  - name: anonymous_search
    weight: 15
    path: /search?keys=drupal
  - name: authenticated_dashboard
    weight: 10
    path: /user
    requires_login: true
  - name: form_submission
    weight: 5
    path: /node/add/article
    requires_login: true
    method: POST

The weights should come from your actual site's analytics, not a guess — check your web server logs or analytics tool for the real ratio of anonymous-to-authenticated traffic before picking these numbers.

Ramp-up versus spike

A gradual ramp (say, adding 10 concurrent users every 30 seconds up to a target) tells you your sustained capacity — the traffic level you can serve indefinitely. A sudden spike (jumping straight to peak concurrency) tells you something different: how your site behaves under a shock, like a link going viral or a marketing email blast landing all at once. Both matter, and they often reveal different bottlenecks — a gradual ramp usually finds database connection-pool limits first, while a sudden spike is more likely to expose PHP-FPM's max-children setting being too low, causing requests to queue and time out instead of failing gracefully.

Warm the cache before you measure sustained capacity: the very first requests in any test hit a cold render cache and page cache, making early results look artificially slow. If you're testing steady-state capacity (not cold-start behavior specifically), run a short warm-up phase first and discard those results before recording your real numbers.

What to watch while the test runs

The load-testing tool's own report (response times, error rate) is only half the picture. Watch the server itself in a second terminal while the test runs: top or htop for CPU and memory pressure, your database's own slow-query log, and PHP-FPM's status page (pm.status_path in its pool config) to see whether you're running out of available worker processes before you run out of raw CPU.

See it for yourself

Run a short test against your own DDEV site with a realistic mix like the one above (start small — 5-10 concurrent users for a minute) and watch ddev logs -s web -f in a second terminal alongside it. You should see response times creep up as concurrency increases, and you're looking for the exact point where that curve stops being gradual and starts climbing steeply — that inflection point is your practical capacity ceiling.

Quick check: your test shows response times staying flat and low right up until 200 concurrent users, then suddenly spiking to 10+ seconds. What does that shape suggest, versus a test where response times climb smoothly the whole way up? (A sudden cliff usually means you hit a hard resource limit — PHP-FPM's max_children, a database connection pool cap, or available memory running out — not gradual degradation. A smooth climb usually means the bottleneck scales with load more continuously, like CPU contention. The shape of the curve, not just the numbers, tells you what kind of limit you're up against.)

Key takeaways

  • Weight your test scenarios to match real traffic (mostly anonymous/cached, less authenticated, least form submission) — a single-URL loop test is misleading.
  • Test both a gradual ramp (sustained capacity) and a sudden spike (shock behavior) — they surface different bottlenecks.
  • Discard cold-cache warm-up results when measuring steady-state performance.
  • Watch server-side metrics (CPU, PHP-FPM worker availability, database slow-query log) alongside the tool's own report — the tool only tells you the symptom, not the cause.

Coming up next

You've now generated the data — response times, error rates, and a resource-usage picture from a realistic simulated load. The final lesson in this section covers how to actually analyze that data to pinpoint the specific bottleneck, rather than just knowing that one exists.