WordPress Performance FAQ


Mike

My host says that admin-ajax.php and WooCommerce requests like wc-ajax=get_refreshed_fragments are causing very high CPU load.

I understand that I should not simply block these requests because some of them may be required for cart, checkout, sessions, or plugin functionality.

But why can a single AJAX endpoint create so much CPU load in WordPress/WooCommerce? Is the problem really the AJAX file itself, or is each AJAX request triggering a much larger WordPress/plugin execution path behind the scenes?

Sam

Yes—the high CPU load is usually not caused by admin-ajax.php itself. The AJAX endpoint is often just the entry point. The real cost comes from everything WordPress and WooCommerce execute after that request arrives.

Think of it this way:

Browser
   ↓
admin-ajax.php
   ↓
Load WordPress
   ↓
Load active plugins
   ↓
Load theme
   ↓
Run AJAX callback
   ↓
Database queries
   ↓
Object cache/session/cart logic
   ↓
Generate response

Every AJAX request can effectively bootstrap a large portion of WordPress.

Why admin-ajax.php is expensive

When a request hits admin-ajax.php, WordPress typically:

  1. Loads core WordPress code.
  2. Loads all active plugins.
  3. Loads the active theme (or much of its functionality).
  4. Fires numerous hooks and filters.
  5. Executes the specific AJAX handler.

Even if the AJAX action itself is simple, the initialization overhead may already consume significant CPU and memory.

For example:

  • 1 page view = 1 PHP request
  • 1 page view + 5 AJAX calls = 6 PHP requests

If 100 concurrent visitors generate several AJAX requests each, PHP workers become busy very quickly.


Why WooCommerce AJAX is often worse

WooCommerce adds substantial processing on top of WordPress.

A request like:

?wc-ajax=get_refreshed_fragments

may trigger:

  • Session loading
  • Cart reconstruction
  • Customer object creation
  • Cart total calculations
  • Shipping calculations (sometimes)
  • Tax calculations
  • Coupon validation
  • Fragment generation (mini-cart HTML)
  • Database reads/writes

The endpoint's purpose is usually to refresh cart information displayed in the header or mini-cart.

A simplified flow looks like:

get_refreshed_fragments
    ↓
Load WooCommerce session
    ↓
Load cart
    ↓
Calculate totals
    ↓
Render mini-cart template
    ↓
Return JSON

That is much heavier than a simple JSON API returning a few values.


Why CPU spikes become extreme

The biggest issue is often frequency, not individual cost.

Imagine:

  • Product page loads
  • Theme requests cart fragments
  • Several plugins make their own AJAX calls
  • Live search plugin polls
  • Analytics plugin sends AJAX events
  • Wishlist plugin refreshes state

One visitor can generate:

1 page request
+ 3 admin-ajax requests
+ 2 WooCommerce AJAX requests
+ 1 heartbeat request
------------------------
7 PHP executions

With 50 concurrent visitors:

50 visitors × 7 requests
= 350 PHP executions

Each execution may bootstrap WordPress and WooCommerce.


Why hosts often point at admin-ajax.php

Hosting dashboards commonly show the PHP script receiving the requests:

/wp-admin/admin-ajax.php

or

/?wc-ajax=get_refreshed_fragments

But that does not identify the underlying cause.

It's similar to saying:

"The front door is busy."

The door isn't the problem; it's what happens inside after people enter.

The real question is:

  • Which AJAX actions are being called?
  • How often?
  • By which plugin, theme, or custom code?
  • How expensive are the database queries inside those actions?

Common root causes

High CPU from these endpoints is often caused by:

1. Cart fragments on every page

Older WooCommerce setups refresh mini-cart fragments on every page view, even when visitors are not shopping.

This can generate a large amount of unnecessary AJAX traffic.

2. Heartbeat API abuse

WordPress Heartbeat uses admin-ajax.php repeatedly.

If many logged-in users are active:

admin-ajax.php?action=heartbeat

can become a major source of load.

3. Live search plugins

Search-as-you-type functionality may send an AJAX request on every keystroke.

4. Expensive custom code

A custom AJAX handler might execute:

  • large WP_Query operations
  • uncached database joins
  • external API calls
  • expensive WooCommerce product lookups

5. Bot traffic

Bots often trigger the same JavaScript and AJAX endpoints as real visitors.

A site receiving heavy bot traffic can end up processing thousands of WooCommerce AJAX requests.

6. Plugin conflicts

Multiple plugins may hook into cart updates or checkout events, causing one AJAX request to trigger unexpectedly large amounts of work.


How to investigate properly

Instead of blocking the endpoint, identify which action is expensive.

Useful things to measure:

  • Top AJAX actions in logs
  • Frequency of each action
  • Slow query logs
  • Plugin-level profiling
  • Database query counts

For example, look for patterns like:

admin-ajax.php?action=heartbeat
admin-ajax.php?action=my_custom_search
admin-ajax.php?action=elementor_ajax
?wc-ajax=get_refreshed_fragments
?wc-ajax=update_order_review

The specific action is often more important than the endpoint itself.


The key takeaway

Your understanding is correct:

admin-ajax.php and wc-ajax endpoints are usually not the true source of the CPU load. They are dispatchers that bootstrap WordPress/WooCommerce and then execute whatever plugin, theme, cart, session, database, and business logic is attached to that request.

A single AJAX endpoint can therefore generate surprisingly high CPU usage because every request may involve loading much of WordPress and WooCommerce, running dozens of hooks, performing database queries, rebuilding carts, and rendering templates before returning a response. The endpoint is simply where all that work enters the application.

Mike

If admin-ajax.php and wc-ajax are only dispatchers, and each request can bootstrap WordPress, WooCommerce, active plugins, hooks, sessions, cart logic, and database work, then is reducing the frequency of AJAX calls only one part of the solution?

For unavoidable AJAX requests such as cart fragments, checkout updates, or session-related WooCommerce calls, shouldn't the deeper question be: why does every small dynamic request have to carry the weight of the full WordPress/plugin runtime?

In other words, if these requests cannot simply be blocked and cannot always be cached, would reducing the execution scope of each AJAX request be a more direct strategy than only throttling, caching, or optimizing the callback after the full stack has already loaded?

Sam

Yes. In fact, once you've eliminated obviously excessive AJAX traffic, execution scope becomes the more interesting architectural question.

There are really two separate multipliers in play:

Total CPU Load
    =
Request Frequency
    ×
Cost Per Request

Most hosting advice focuses on the first multiplier:

  • reduce AJAX frequency
  • disable unnecessary polling
  • cache what can be cached
  • stop cart fragments on non-commerce pages

Those measures often work because they immediately reduce the number of PHP executions.

But for requests that are genuinely required—cart updates, checkout recalculations, session state, inventory checks—the second multiplier becomes critical:

Why does a request that only needs a few pieces of data incur the cost of loading most of WordPress and WooCommerce?

That's a valid architectural criticism of the traditional WordPress execution model.

The fundamental design issue

Classic WordPress was designed around page generation, not lightweight APIs.

Historically the model was:

Request
   ↓
Load WordPress
   ↓
Load plugins
   ↓
Load theme
   ↓
Run hooks
   ↓
Generate page

AJAX was added later.

Instead of creating a separate lightweight runtime, WordPress largely reuses the same bootstrap process:

AJAX Request
   ↓
Load WordPress
   ↓
Load plugins
   ↓
Run hooks
   ↓
Execute AJAX callback

The advantage is compatibility.

The disadvantage is overhead.

Every plugin can assume WordPress is fully loaded.

Every AJAX handler gets access to:

  • users
  • capabilities
  • options
  • post types
  • taxonomies
  • WooCommerce APIs
  • sessions
  • hooks
  • plugin integrations

That convenience has a cost.


WooCommerce amplifies the effect

WooCommerce is not just a shopping cart.

It is a large application layer on top of WordPress.

When WooCommerce initializes, many components become available:

Customer
Cart
Session
Coupons
Taxes
Shipping
Payments
Inventory
Extensions

A checkout-related AJAX request may legitimately require several of those systems.

For example:

Update shipping method

sounds small.

But it may require:

Load customer
Load session
Load cart
Recalculate totals
Apply coupons
Calculate taxes
Calculate shipping
Trigger extension hooks
Return updated totals

The actual business operation is not necessarily small.


Why "just load less WordPress" is difficult

Conceptually, it sounds attractive:

AJAX request
   ↓
Load only 10% of WordPress
   ↓
Run callback

The problem is dependency chains.

A plugin might do:

WC()->cart->calculate_totals();

That function may depend on:

  • session state
  • customer data
  • tax configuration
  • shipping zones
  • coupon logic
  • dozens of hooks

Removing parts of the bootstrap can break assumptions throughout the ecosystem.

WordPress prioritizes compatibility over minimal runtime.


Modern systems often solve this differently

Many modern architectures separate concerns:

Frontend
   ↓
API Gateway
   ↓
Dedicated Cart Service

or

Frontend
   ↓
REST Endpoint
   ↓
Focused Business Logic

Instead of loading an entire CMS runtime.

In such systems:

Get cart count

might execute:

Read session
Read cart
Return JSON

and nothing else.

The execution scope is much smaller.


This is why the REST API sometimes performs better

WordPress's REST API can be somewhat more efficient than admin-ajax.php in some situations.

Not because WordPress suddenly becomes lightweight, but because:

  • endpoints are more structured
  • routing is cleaner
  • developers are encouraged to build focused handlers
  • some admin-specific loading can be avoided

However:

A WooCommerce REST endpoint that calls the same cart-calculation code may still end up consuming similar CPU.

The business logic often dominates the cost.


The most effective optimization hierarchy

For unavoidable WooCommerce requests, the usual order is:

1. Eliminate unnecessary requests

Biggest wins often come from:

  • disabling unused fragment refreshes
  • reducing polling
  • removing plugins generating AJAX traffic

2. Reduce work inside the request

Examples:

  • optimize database queries
  • reduce hooks
  • avoid repeated calculations
  • add object caching
  • cache expensive lookups

3. Reduce bootstrap overhead

Harder, but sometimes possible:

  • custom REST endpoints
  • lightweight custom endpoints
  • selective plugin loading
  • avoiding WooCommerce initialization where unnecessary

4. Architectural redesign

Largest potential gain, largest effort:

  • headless commerce
  • dedicated cart services
  • external session stores
  • decoupled APIs

The subtle but important point

Hosts often report:

admin-ajax.php = 80% CPU

which leads site owners to think:

AJAX is the problem.

More accurately:

AJAX frequency × WordPress bootstrap × WooCommerce business logic
= CPU load

The endpoint is merely where those factors converge.

So your intuition is correct: for requests that are truly necessary and cannot be cached, reducing the execution scope and dependency footprint of the request is often a more fundamental optimization than merely reducing request frequency. The challenge is that WordPress and WooCommerce were designed around a highly integrated runtime, making execution-scope reduction significantly harder than it sounds. That's why most optimization efforts focus first on eliminating requests and optimizing callbacks before attempting deeper architectural changes.

[[Mike]]

That makes sense, but does reducing AJAX execution scope necessarily require moving to headless commerce, external cart services, or custom lightweight endpoints outside the normal WordPress/WooCommerce runtime?

Or could the same prevention-oriented principle be applied inside WordPress by classifying AJAX requests early?

For example, a wc-ajax=get_refreshed_fragments request has a much narrower purpose than a full product page, checkout page, or admin request. If the request type is known before the full plugin stack is loaded, could a WordPress-native system apply a reduced execution profile for that AJAX context?

In other words: instead of treating every AJAX request as “load WordPress, load WooCommerce, load all active plugins, then run the callback,” could the better model be:

identify the AJAX context early → load only the required runtime for that context → prevent unrelated PHP/plugin execution before it begins?

Sam

You're describing something that many performance engineers eventually arrive at:

The biggest optimization is not making code run faster. It's preventing unnecessary code from running at all.

And yes, in principle, your proposed model is entirely possible inside WordPress, without going headless.

The challenge is not technical feasibility. The challenge is compatibility.

The architecture you're proposing

Today, WordPress effectively behaves like:

Request arrives
    ↓
Load WordPress
    ↓
Load all active plugins
    ↓
Initialize environment
    ↓
Determine request purpose
    ↓
Execute callback

You're suggesting:

Request arrives
    ↓
Determine request purpose
    ↓
Load only required components
    ↓
Execute callback

From a systems-design perspective, that's generally superior.

It follows the same principle used in:

  • microservices
  • serverless functions
  • API gateways
  • event-driven architectures

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


Why WordPress doesn't do this by default

The problem is that WordPress plugins are not written with strict dependency declarations.

Imagine WooCommerce receives:

?wc-ajax=get_refreshed_fragments

At first glance you might think:

Need:
✓ WooCommerce cart
✓ Session
✓ Customer

Don't need:
✗ SEO plugin
✗ Form plugin
✗ Analytics plugin
✗ Gallery plugin

But WordPress has no reliable way to know that.

A plugin author may have written:

add_action(
    'woocommerce_before_calculate_totals',
    'my_plugin_logic'
);

or

add_filter(
    'woocommerce_add_to_cart_fragments',
    'modify_fragments'
);

Those hooks may affect the AJAX response.

If that plugin isn't loaded, behavior changes.

So WordPress takes the safe approach:

Load everything

because it guarantees compatibility.


The key insight: request classification can happen early

Technically, WordPress already knows quite a bit very early.

For example:

/wp-admin/admin-ajax.php?action=my_action

reveals:

action=my_action

before most business logic runs.

Likewise:

?wc-ajax=get_refreshed_fragments

reveals:

wc-ajax=get_refreshed_fragments

immediately.

So from a purely architectural perspective:

Request type
↓
Execution profile
↓
Selective loading

is absolutely possible.


In fact, some systems already do partial versions of this

Several performance-oriented approaches already attempt this idea.

Conditional plugin loading

Some optimization systems effectively do:

Product page
    → load plugins A B C

Checkout page
    → load plugins A B C D E

AJAX request
    → load plugins A only

This can significantly reduce runtime.

The limitation is that the site owner must know which plugins are safe to exclude.


MU-plugin bootstrap filters

Advanced developers sometimes intercept requests extremely early and decide:

This is a heartbeat request

or

This is a specific WooCommerce AJAX action

then selectively disable functionality.

Again, this can work well but requires careful dependency analysis.


WooCommerce itself already uses context flags

WooCommerce knows when it's handling:

frontend
admin
REST API
AJAX
checkout
cron

and avoids certain operations in some contexts.

So the concept already exists.

The issue is that the ecosystem is not built around strict runtime profiles.


The deeper obstacle is hook-based architecture

The biggest reason your model is difficult is not WordPress itself.

It's the hook system.

Consider:

do_action('woocommerce_before_calculate_totals');

WooCommerce has no idea which plugins might be attached.

Potentially:

Inventory plugin
Subscription plugin
Membership plugin
Pricing plugin
Rewards plugin
ERP integration
Tax plugin
Shipping plugin

could all be listening.

Therefore:

Load WooCommerce

often implies:

Load every plugin that might hook into WooCommerce

which quickly becomes:

Load almost everything

A more modern WordPress could theoretically support this

If WordPress had a dependency-aware execution model, plugins could declare something like:

Needed for:
✓ frontend pages
✓ checkout
✗ cart fragments
✗ heartbeat
✗ REST inventory lookups

Then the runtime could build a profile:

get_refreshed_fragments
    ↓
Load:
    WooCommerce Core
    Session Module
    Cart Module

Skip:
    SEO
    Forms
    LMS
    Analytics
    Gallery

That would be a substantial architectural improvement.

But it would require ecosystem-wide conventions that WordPress currently lacks.


Applied specifically to get_refreshed_fragments

Your example is particularly interesting because many stores no longer need the historical implementation.

The request's purpose is narrow:

Return updated mini-cart fragments

In theory, a runtime could load:

Session
Customer
Cart
Fragment renderer

and little else.

The question is whether some plugin has attached logic to that process.

WordPress assumes the answer may be "yes."

Therefore it loads the full environment.


So is your model valid?

Yes.

In fact, from a software architecture perspective, your model is closer to how high-performance systems are typically designed:

Classify request first
        ↓
Load only required subsystems
        ↓
Execute business logic
        ↓
Return response

rather than:

Load entire application
        ↓
Figure out request purpose
        ↓
Execute business logic

The reason WordPress and WooCommerce do not fully implement this isn't that it's impossible or even undesirable. It's that the platform's plugin ecosystem is built on the assumption that any request may need access to any hook, filter, or extension. Compatibility became the primary design goal, and compatibility naturally pushes the runtime toward "load everything first."

So you've identified a genuine architectural tension: performance favors early classification and selective loading, while extensibility favors loading a complete runtime where every plugin can participate in every request. WordPress historically chose extensibility. The CPU cost you're seeing is one of the consequences of that choice.

Mike

This is exactly where LiteCache Rush applies its principle of Performance by Prevention. High CPU load from admin-ajax.php or wc-ajax=get_refreshed_fragments is not caused by the endpoint name itself, but by the WordPress, WooCommerce, plugin, hook, session, cart, and database work triggered behind it. Traditional optimization tries to reduce request frequency or make the callback cheaper. Rush targets the earlier layer: identify the AJAX/request context before the full plugin stack is loaded and prevent unrelated PHP and plugin execution from entering that request at all. For unavoidable dynamic WooCommerce and AJAX requests, this turns the question from “How do we make this heavy request faster?” into “Why should unrelated application code run for this request in the first place?”