Every year, the same failure repeats: a store that loads quickly in October collapses in the first ten minutes of the sale. The homepage is cached, the product pages look fine, and then checkout starts timing out. The team adds a bigger server, which helps for five minutes, and then the database write queue becomes the bottleneck. The problem was never traffic alone. It was the shape of the traffic and the capacity of the path that cannot be cached.

This plan starts 30 days out and works backward from the checkout. It assumes a WooCommerce store on a managed host or a cloud server, a marketing campaign with a known start time, and a team that can run a load test. If you have less than 30 days, compress the schedule but keep the order: baseline, capacity, cache, rehearsal.

The non-negotiables
  • Baseline the checkout at current peak before you change anything.
  • Forecast concurrency, not just visits. Cart and checkout requests are the capacity driver.
  • Buy 1.5–2× headroom across workers, database, Redis, and disk.
  • Run a game day with a mid-test cache purge and a rollback drill.
  • Freeze changes a week out; rehearse the deploy and the rollback.
  • Decide the kill list before the sale: which non-essential features go first.

Days 1–7: baseline and forecast

Start with evidence. Export the last 60 days of traffic, conversion, and checkout latency. Identify the peak hour, the peak concurrent session count, and the percentage of sessions that reach checkout. If you do not have checkout-specific metrics, add them now: success rate, p95 duration, payment-provider latency, and error type.

Forecast the campaign in concurrency, not visits. A visit-based forecast hides the fact that campaign traffic is concentrated: 40,000 visits over a day can be harmless, while 4,000 concurrent shoppers at the drop is a different problem. Ask marketing for the send time, the expected click rate, and the offer window. Then model a pessimistic case at 1.5× the expected peak.

Baseline worksheet / fill this in before changing infrastructure
MetricCurrent peakCampaign forecastTarget
Concurrent sessions——1.5–2× forecast
Checkout requests / min——Peak + 50%
Checkout p95——< 500 ms
Checkout error rate——< 0.1%
PHP worker utilization——< 70% at peak
DB write latency p95——< 20 ms
Redis evictions——0

If the baseline already shows checkout p95 above 800 ms at normal peak, do not wait for Black Friday. Fix the application first: slow plugin, unindexed query, oversized autoload, or too few workers. A campaign will amplify the problem, not reveal a new one.

Days 8–14: capacity and load test

Size the origin for the concurrency you forecast. The key resource is usually PHP workers, not CPU. A worker is occupied for the duration of a dynamic request; when all workers are busy, new requests queue in the web server and p95 climbs. The PHP workers guide covers the formula in detail. The short version: concurrent dynamic requests × average request time ÷ target utilization.

Run a load test against a staging clone with a mocked payment gateway. Use the same theme, plugins, product catalogue, and cache configuration as production. Ramp to your forecast peak, hold for 20–30 minutes, and record p50/p95/p99, error rate, worker utilization, database latency, and queue depth. Then purge the cache mid-test and see how the store recovers.

Test the journey, not the homepage. A load test that only requests catalog pages measures the cache. A test that browses, adds to cart, applies a coupon, and checks out measures the store.

Use the result to choose a plan with headroom, not a plan that exactly matches the forecast. Hosts can usually scale vertically within minutes, but a plan change may require a restart or a migration. Confirm the upgrade path before the sale, and know the difference between a plan limit and a hard platform limit.

The capacity math, without the hand-waving

A simple model is enough to avoid the obvious mistakes. Start with concurrent shoppers at peak. Estimate the share that are on uncached, stateful requests: cart, checkout, account, and AJAX. Multiply to get concurrent dynamic requests. Multiply by the average request duration in seconds, then divide by your target utilization (0.7 is a reasonable ceiling). The result is the number of workers you need.

# Example: 800 concurrent shoppers, 25% on dynamic paths,
# 0.35 s average dynamic request, 70% target utilization
concurrent_dynamic = 800 * 0.25        # 200
worker_seconds     = 200 * 0.35        # 70 worker-seconds per second
workers_needed     = 70 / 0.70         # 100 workers

# Add campaign buffer, then check the plan's worker limit.
workers_with_buffer = 100 * 1.5        # 150 workers

That number is usually higher than the plan's advertised worker count. The gap is why a store that looks fine at 200 concurrent shoppers fails at 800. It is also why “upgrade the CPU” is not a plan: CPU is not the constraint once the worker pool is saturated.

Days 15–21: cache, checkout, and the kill list

Warm the full-page cache for the catalog and content pages after the final deploy. Verify the edge returns a HIT for anonymous pages and a BYPASS for cart and checkout. Check that the cache key includes the currency, country, and any variation that changes the rendered price; a cached page showing the wrong currency is a support incident.

Audit the checkout path for plugins that add synchronous calls: address validation, tax lookup, fraud scoring, currency conversion, and recommendation widgets. Move what can be moved to async, remove what is not essential, and set timeouts on the rest. A third-party API that hangs for 10 seconds will consume workers and make the host look guilty.

Write the kill list now. Rank non-essential features by revenue impact and resource cost: social feeds, heatmaps, A/B tests, chat widgets, related-product recommendations, and non-critical tracking. During the incident, the on-call engineer should not be debating whether to disable the Instagram feed. The decision is already made.

Keep: payment, tax, shipping, inventory, order confirmation, fraud checks, and the minimum analytics needed to run the sale.

Cut first: social embeds, heatmaps, chat, A/B scripts, recommendation widgets, and any tag that blocks the main thread or writes on every page view.

Days 22–30: game day, freeze, and runbook

Run a full game day at production scale, with the real deploy, the real cache purge, and the real monitoring. Include a deliberate failure: kill a worker pool, simulate a payment timeout, or restore a backup. Measure how long the team takes to detect, decide, and recover. The goal is to find the coordination problem before it costs orders.

Freeze code and plugin changes seven days out. If a security release forces a change, deploy it through the same tested pipeline and re-run the checkout smoke test. Do not “quickly” update a payment plugin the night before the sale.

Baseline and owner

Export the last 60 days, name the incident owner, and open the capacity worksheet.

Load test

Ramp to forecast peak, measure checkout and queue depth, and purge the cache mid-test.

Upgrade and tune

Add workers, database headroom, and Redis memory. Confirm the upgrade path and the rollback.

Freeze and game day

Deploy the final build, warm the cache, run the incident drill, and print the runbook.

Monitor and decide

Watch p95, errors, workers, DB writes, and payment latency. Use the kill list if thresholds are crossed.

Sale

No deploys, no plugin updates, no DNS changes. One owner, one channel, one decision log.

The day-of runbook

Keep one dashboard with the metrics that trigger action: checkout p95, checkout error rate, worker utilization, database write latency, Redis evictions, and payment-provider latency. Set thresholds before the sale. When a threshold is crossed, the owner follows a pre-agreed sequence rather than improvising.

  1. Confirm the signal. Is the spike real, or is the monitoring noisy? Check two sources.
  2. Protect checkout. Disable the first item on the kill list, then the second. Do not disable payment or tax.
  3. Check the origin. Worker saturation, database writes, Redis memory, and error logs. Add workers if the platform allows.
  4. Check the edge. Cache hit ratio, 5xx rate, WAF blocks, and purge events. A purge during peak is a self-inflicted incident.
  5. Communicate. One status update per 15 minutes, even if the update is “still investigating.”
  6. Do not deploy a fix under peak unless it is a verified, tested rollback. A new code path during an incident adds a second variable.

After the sale

Write the post-mortem within 72 hours, while the timeline is fresh. Record the peak concurrency, the actual p95, the first bottleneck, the time to detect, the time to mitigate, and the orders affected. Compare the forecast with reality. The next campaign should start from that baseline, not from last year's assumptions.

Keep the load-test script and the runbook in version control. The second Black Friday is much easier if the first one produced a reusable asset.

Capacity planning is not about predicting the future perfectly. It is about making the failure small, visible, and reversible.

PWPH Test Lab

Peak-season testing is part of the commerce protocol. Read the methodology or the commercial disclosure.