WordPress Performance FAQ


Mike

I run a high-traffic WordPress affiliate site with comparison tables, dynamic pricing widgets, product boxes, tracking links, and external price/API integrations. Most public pages are served from full-page cache and are fast.

But some requests bypass the cache or trigger dynamic generation, and those requests consume huge CPU and sometimes kill the server.

Why does this happen even though the site is mostly cached? Is the issue that cache-bypassing requests still have to execute WordPress, the theme, affiliate plugins, comparison-table logic, pricing widgets, hooks, database queries, and external API integrations instead of being served as static HTML?

Sam

Yes. In many high-traffic WordPress affiliate sites, that is exactly what happens.

A full-page cache only helps for requests that can be satisfied directly from the cached HTML. The moment a request bypasses the cache, WordPress has to generate the page dynamically, which can be surprisingly expensive.

A cache hit might look like:

Visitor
  ↓
Web server
  ↓
Cached HTML file
  ↓
Response

Very little PHP, database activity, or plugin logic runs.

A cache miss or cache bypass often looks like:

Visitor
  ↓
Web server
  ↓
PHP
  ↓
WordPress bootstrap
  ↓
Theme
  ↓
Plugins
  ↓
Hooks/filters
  ↓
Database queries
  ↓
Affiliate logic
  ↓
Price/API integrations
  ↓
HTML generation
  ↓
Response

That entire stack executes for every uncached request.

Why a small percentage of uncached requests can cause huge CPU usage

Suppose:

  • 99% of requests are cache hits
  • 1% bypass cache
  • Cached request costs ~1 ms of CPU
  • Dynamic request costs ~500 ms of CPU

For 100,000 requests:

99,000 cached requests
= 99 seconds CPU

1,000 dynamic requests
= 500 seconds CPU

The 1% of requests consume most of the CPU.

This is why servers can become overloaded even when cache hit rates look excellent.

What makes affiliate sites especially expensive

Many affiliate sites have components that are significantly heavier than a typical blog:

Comparison tables

Large comparison tables often require:

  • Custom post type queries
  • Metadata lookups
  • Sorting/filtering logic
  • Product attribute calculations

These can generate many database queries and PHP processing steps.

Dynamic pricing widgets

If pricing is updated dynamically:

  • Database lookups occur
  • Pricing calculations may run
  • External API calls may be triggered
  • Widget rendering logic executes

Even if the final output is small, generating it can be costly.

Product boxes

Many product-box plugins:

  • Query multiple custom fields
  • Generate tracking links
  • Compute ratings or scores
  • Render complex templates

Repeated dozens of times on a page, costs add up.

Affiliate link tracking

Some tracking systems:

  • Log clicks
  • Update statistics
  • Resolve redirects
  • Fire hooks and analytics code

High click volume can create substantial load.

External APIs

These are often the biggest problem.

If an uncached request triggers:

  • Amazon-related data retrieval
  • Price feeds
  • Merchant APIs
  • Availability checks

Then PHP workers can spend time waiting on remote services.

A request that waits 2 seconds on an API still occupies a PHP worker for those 2 seconds.

Why PHP workers get exhausted

The real bottleneck is often not raw CPU but PHP worker saturation.

Imagine:

  • 20 PHP workers available
  • Dynamic requests take 3 seconds

Then:

20 workers × 3 seconds

Only a limited number of dynamic requests can be processed simultaneously.

Once all workers are busy:

  • New requests queue
  • Response times increase
  • Timeouts occur
  • CPU may spike
  • Server can appear to "die"

Meanwhile, cached requests may still be fast because they never reach PHP.

Common cache-bypassing requests

Examples include:

  • Logged-in users
  • Admin-ajax.php requests
  • REST API endpoints
  • Search pages
  • Filtered/sorted comparison pages
  • Query-string URLs
  • Tracking URLs
  • Personalized content
  • Session-based content
  • Pages excluded from cache
  • Cache misses after expiration

Many site owners focus on page views but discover that a much smaller volume of these dynamic requests is consuming most server resources.

Why WordPress itself contributes

A dynamic request does not load only the requested page.

WordPress typically:

  1. Loads core files.
  2. Loads all active plugins.
  3. Loads the theme.
  4. Registers hooks.
  5. Executes plugin callbacks.
  6. Performs database queries.
  7. Builds the page.
  8. Sends the response.

If you have dozens of plugins, each uncached request may execute a significant amount of PHP before your page-specific logic even starts.

The key insight

For a heavily cached affiliate site, server load is often determined not by total traffic but by:

(number of uncached requests)
×
(cost per uncached request)

So yes—the underlying issue is usually that cache-bypassing requests must execute the full WordPress stack (core, theme, plugins, hooks, database work, comparison tables, pricing widgets, tracking logic, and potentially external API integrations) instead of being served as pre-generated static HTML. A relatively small number of these expensive dynamic requests can consume the majority of CPU, memory, PHP workers, and database resources on the server.

Mike

That makes sense. So the real load problem is not total traffic alone, but:

uncached request volume × cost per uncached request.

But shouldn't we also ask what makes each uncached affiliate request so expensive?

For example, a comparison-table or pricing-widget request may genuinely need product data, affiliate-link logic, cached price data, tracking rules, and maybe some external feed/API integration. But it probably does not need unrelated form builders, popup systems, WooCommerce checkout logic, page-builder modules, admin/reporting tools, import/export systems, or other plugins that do not contribute to generating that specific response.

So if these cache-bypassing requests cannot always be avoided, would the deeper optimization layer be reducing their execution scope - identifying the request type early and preventing unrelated plugins, hooks, services, widgets, and PHP code from entering the request lifecycle in the first place?

Sam

Yes. In many mature WordPress systems, that's where the biggest gains come from after you've already achieved a high cache-hit ratio.

Your formula:

Load = uncached request volume × cost per uncached request

can be expanded further:

Load = uncached request volume × (bootstrap cost + plugin cost + query cost + external service cost + rendering cost)

Most site owners focus on reducing uncached request volume. That's important, but eventually you reach a point where some requests must remain dynamic. Then the next question becomes:

"Why is this request executing hundreds of thousands of lines of PHP that have nothing to do with the response?"

And that's often where affiliate sites lose enormous amounts of performance.


The hidden problem: WordPress loads almost everything

A standard WordPress request doesn't load only the code needed for that request.

Typically:

Request
  ↓
WordPress bootstrap
  ↓
Load active plugins
  ↓
Plugin initialization
  ↓
Hook registration
  ↓
Theme initialization
  ↓
Widget registration
  ↓
Shortcode registration
  ↓
Request-specific work

Even if the request only needs:

comparison table
cached product data
affiliate links

the server may also initialize:

form builder
popup system
WooCommerce
email automation
analytics plugins
admin reporting
page-builder modules
import/export tools
A/B testing
social sharing
SEO modules

before it ever reaches the code that matters.


A useful way to think about it

Imagine an uncached comparison-table request needs:

Actual business logic:
  50 ms

But WordPress spends:

Plugin A init: 20 ms
Plugin B init: 35 ms
Plugin C init: 15 ms
Plugin D init: 50 ms
Theme startup: 40 ms
Unused hooks: 60 ms

before the comparison table logic even runs.

Now the request costs:

50 ms useful work
220 ms framework/plugin overhead
------------------------------
270 ms total

The majority of execution time isn't generating the page.

It's preparing code paths that are never used.


This is especially common on affiliate sites

Affiliate sites often evolve over years.

They accumulate:

  • comparison plugins
  • table plugins
  • schema plugins
  • tracking plugins
  • page builders
  • lead-capture systems
  • email integrations
  • analytics
  • SEO tools
  • redirect managers
  • WooCommerce remnants
  • import systems

Each plugin may seem harmless individually.

But on every uncached request:

Plugin startup cost
+
hook registration
+
autoloaded options
+
dependency loading
+
database lookups

gets paid again.


Early request classification is a powerful optimization

The architectural idea is:

What kind of request is this?

Determine that as early as possible.

Examples:

Product comparison page

Needs:

comparison engine
product data
affiliate links
pricing cache

Does not need:

contact forms
newsletter popups
WooCommerce checkout
admin reports
import/export

Tracking redirect

Needs:

link resolution
click logging
redirect

Does not need:

theme rendering
page builder
widgets
SEO rendering
comparison tables

Pricing API endpoint

Needs:

product cache
pricing service
response serializer

Does not need:

frontend theme
marketing plugins
popup systems

The earlier you can identify the request, the more work you can avoid.


This is essentially reducing the request surface area

Think of each request having a "working set."

Bad architecture:

Every request
→ Load everything
→ Use 5%

Better architecture:

Request type A
→ Load only A components

Request type B
→ Load only B components

Request type C
→ Load only C components

Large web applications outside WordPress often do this aggressively.


Why plugin count is a misleading metric

People often say:

"I only have 25 plugins."

The number itself doesn't matter much.

What matters is:

How much code executes
on every uncached request?

A single poorly designed plugin can consume more CPU than twenty lightweight plugins combined.

The expensive part is usually:

  • plugin initialization
  • autoloaded options
  • hook callbacks
  • database queries
  • remote API calls

not the plugin count itself.


External APIs are a special case

Even if you eliminate unnecessary plugins, a request can still be expensive if it waits on remote systems.

Example:

WordPress work: 80 ms
Database work: 30 ms
Remote pricing API: 1.5 s

Total:

1.61 s

The PHP worker remains occupied for the entire wait.

For affiliate sites, a common optimization is:

Background refresh
        ↓
Store locally
        ↓
Serve cached data

instead of:

Visitor request
        ↓
Call remote API
        ↓
Wait
        ↓
Render page

Where the biggest wins often come from

Once caching is already good, the highest-leverage improvements are often:

  1. Eliminate unnecessary code from uncached requests.
  2. Prevent irrelevant plugins/services from initializing.
  3. Reduce hook execution paths.
  4. Reduce database queries and object creation.
  5. Move API calls out of the request path.
  6. Use precomputed product/comparison data where possible.
  7. Create lightweight request handlers for tracking, redirects, and data endpoints.

At a certain scale, the question shifts from:

"How do I cache more pages?"

to:

"For the requests that must remain dynamic, how can I ensure only the minimum required code executes?"

That's often the next major performance frontier for high-traffic affiliate WordPress sites.

Mike

That makes sense. Moving live price/API calls out of the visitor request path and serving precomputed or cached affiliate data can reduce a major source of latency.

But even after pricing data is cached locally, a cache-bypassing comparison-table, tracking, search, or pricing-widget request may still bootstrap WordPress, the theme, active plugins, hooks, widgets, shortcodes, and services before it generates the response.

So shouldn't we distinguish between two separate optimizations?

  1. Avoid live external/API work during the request by precomputing or caching affiliate data.
  2. Reduce the WordPress/plugin runtime that participates in the request at all.

In other words, for dynamic affiliate requests that still need WordPress context, would the next layer be early request classification and known-safe execution profiles, so only the plugins and PHP subsystems required for that specific comparison, pricing, tracking, or content request are loaded, while unrelated runtime falls back to full stack only when uncertainty exists?

Sam

Yes. Those are two different optimization layers, and high-scale WordPress systems often need both.

A useful mental model is:

Dynamic Request Cost
=
Data Acquisition Cost
+
Application Runtime Cost

Where:

Data Acquisition Cost
=
API calls
feed retrieval
remote lookups

and:

Application Runtime Cost
=
WordPress bootstrap
plugin initialization
hook execution
theme loading
database work
rendering

Many affiliate sites successfully optimize the first term but never seriously address the second.


Layer 1: Eliminate live data acquisition

This is the optimization most people think about first.

Instead of:

Visitor
  ↓
Request
  ↓
Affiliate API
  ↓
Wait
  ↓
Render

you do:

Background job
  ↓
Affiliate API
  ↓
Local cache/database

Visitor
  ↓
Request
  ↓
Read local data
  ↓
Render

This removes:

  • network latency
  • API failures
  • rate-limit issues
  • long worker occupancy

For many affiliate sites this is a huge win.

But after doing this, you often discover something surprising:

The request is still expensive.

Because you've removed the remote dependency but not the application overhead.


Layer 2: Reduce runtime participation

This is the deeper architectural problem.

Suppose a pricing widget request now reads from local cache in 5 ms.

The request may still spend:

40 ms WordPress bootstrap
60 ms plugin initialization
30 ms hook registration
25 ms widget setup
20 ms shortcode setup
15 ms theme logic
5 ms actual pricing lookup

Total:

195 ms

The business logic is only a tiny fraction of the request.


Early request classification becomes important

At this stage the question changes from:

"How do we make the work faster?"

to:

"How do we avoid doing unnecessary work at all?"

You classify requests as early as possible.

For example:

Tracking redirect
Comparison table
Pricing widget
Search endpoint
Product page
Admin request

Each class has different requirements.


Known-safe execution profiles

This is very close to how large application platforms think about performance.

Conceptually:

Tracking Request

needs:

link resolution
logging
redirect

and does not need:

theme
page builder
WooCommerce checkout
form systems
popup systems
comparison rendering

While:

Comparison Table Request

needs:

product data
comparison engine
affiliate logic

but may not need:

newsletter integrations
admin reporting
import/export systems
checkout flows

So you end up with something like:

Request Type
        ↓
Execution Profile
        ↓
Allowed Components
        ↓
Response

rather than:

Request Type
        ↓
Load Everything
        ↓
Response

Why this can produce larger gains than query optimization

Imagine a request currently costs:

Database queries:      30 ms
Business logic:        40 ms
Plugin overhead:      250 ms
Theme overhead:        60 ms

Total:

380 ms

You spend weeks optimizing queries and reduce them to:

15 ms

Now:

365 ms

Only a small improvement.

But if you eliminate most unrelated runtime:

Database queries:      15 ms
Business logic:        40 ms
Plugin overhead:       40 ms
Theme overhead:        10 ms

Total:

105 ms

A much larger gain.


The challenge is safety

The reason WordPress loads broadly is that dependency relationships are often implicit.

A plugin may:

  • register hooks globally
  • alter queries
  • modify post content
  • inject filters
  • add shortcodes
  • modify redirects

If you disable it for a request, something unexpected may break.

That's why the practical approach is usually:

Conservative mode

Unknown request
→ Full stack

Known profile

Tracking redirect
→ Minimal stack

Pricing endpoint
→ Minimal stack

Comparison endpoint
→ Reduced stack

Only requests whose dependencies are well understood receive the optimized path.


This is essentially moving from "cache-first" to "execution-aware" architecture

Many WordPress performance discussions stop at:

Increase cache hit ratio

But at scale the next frontier is:

What executes when cache is missed?

And after that:

What absolutely must execute?

Those are different questions.

A mature affiliate platform often ends up with three layers:

  1. Serve static content whenever possible.
  2. Precompute expensive affiliate/pricing data outside visitor requests.
  3. For unavoidable dynamic requests, execute the smallest verified subset of the application required to satisfy that request.

That third layer is where concepts such as request classification, execution profiles, selective plugin participation, lightweight endpoints, service boundaries, and dependency-aware loading become valuable. Once cache misses become the dominant source of resource consumption, reducing the runtime footprint of those misses is often the highest-leverage optimization remaining.

Mike

This is exactly where LiteCache Rush applies its principle of Performance by Prevention. A high-traffic affiliate site can already have full-page caching, cached pricing data, background feed updates, and local product data - yet cache-bypassing requests may still become expensive because they bootstrap the full WordPress, theme, plugin, hook, widget, shortcode, and service runtime. Rush targets that remaining layer: it identifies the request context early and applies a known-safe execution profile before the full plugin stack is loaded. For comparison tables, pricing widgets, tracking redirects, search endpoints, and other dynamic affiliate requests, the goal is not only to cache more data, but to prevent unrelated PHP and plugin execution from participating in the request in the first place.