WordPress Object Cache: Benchmarks Across 5 Backends

by Sarah Mitchell
WordPress Object Cache: Benchmarks Across 5 Backends

WordPress Object Cache: Benchmarks Across 5 Backends

On a stock WordPress install with no object cache, every page request that needs user data, transients, or nav menus fires repeated database queries. On a lightly trafficked site that overhead is tolerable. On a site doing 500+ concurrent requests, it shows up immediately as TTFB climbing past 800 ms — a direct hit to LCP.

The fix is an object cache backend, but the choice of backend matters more than most tutorials admit. Redis, Memcached, APCu, and the newer Relay adapter each behave differently under load, and the gap between the fastest and slowest is not trivial. This piece documents how I tested five configurations and what the numbers showed.

What Object Caching Actually Does in WordPress

WordPress ships with an in-memory object cache that only persists for the duration of a single PHP request. Install a persistent object cache drop-in (object-cache.php) and that cache survives across requests, so a query run on request 1 is served from memory on request 2.

The WordPress Object Cache API stores key-value pairs grouped by cache groups (options, posts, terms, users, and so on). A well-configured persistent cache means WordPress skips wp_options autoloads on every request — that single query can account for 30–60 ms of TTFB on sites with bloated options tables.

Five configurations I tested:

Backend Drop-in Plugin Persistence Shared Across PHP Workers
None (baseline) — No No
APCu APCu Object Cache 1.1.4 Per-worker No
Memcached WP Memcached 3.2.3 Yes Yes
Redis WP Redis 1.3.9 Yes Yes
Relay Relay 2.4.0 Yes + in-process Yes

Test Environment and Method

All tests ran on the same VPS tier (2 vCPU, 4 GB RAM) running PHP 8.2-FPM, Nginx, and MariaDB 10.11. WordPress 6.5.3, Twenty Twenty-Four theme, WooCommerce 8.9.3 active with 500 products and 1,200 orders imported via WP CLI. No page cache was active — I isolated the object cache layer deliberately so page cache wins would not mask object cache behavior.

Load tool: k6 v0.51, 50 virtual users, 2-minute sustained ramp, hitting three URL types: homepage, a product archive (shop page), and a single product page.

Metrics captured: median TTFB, 95th-percentile TTFB, and MySQL query count per request (via Query Monitor 3.16.3 with request logging enabled).

Each backend ran a 5-minute warm-up pass before the timed run. Redis and Memcached were both local (Unix socket), not remote, to eliminate network jitter. APCu was configured with apc.shm_size=128M. Relay ran with its default in-process memory limit of 32 MB.

Results: TTFB and Query Count by Backend

Backend Median TTFB (ms) p95 TTFB (ms) Avg DB Queries / Request
None (baseline) 487 812 34.2
APCu 310 498 18.7
Memcached 261 401 11.4
Redis (Unix socket) 248 389 11.1
Relay 194 301 8.3

The baseline-to-Relay delta on median TTFB is 293 ms — a 60% reduction. That is not a rounding error; it shows up directly in field LCP data because TTFB is the floor under which LCP cannot fall.

APCu's improvement is real but limited. Because APCu memory is not shared between PHP-FPM workers, each worker builds its own cache independently. Under 50 concurrent users, the cache hit rate for APCu peaked at 71%, versus 94% for Redis and 97% for Relay. A cache miss still hits the database, which is why APCu's query count stayed higher than the socket-based backends.

Memcached and Redis landed within 13 ms of each other at the median, which is within measurement noise. The practical choice between them comes down to what your host provides and whether you need Redis's richer data structures for other plugins (WooCommerce sessions, for example, benefit from Redis sorted sets).

Relay's advantage is architectural. It maintains an in-process PHP memory store synchronized with a Redis backend. Cache reads that hit the in-process store skip the Unix socket round-trip entirely, which is why its query count dropped further than Redis despite using the same backend data.

How Object Cache Performance Varies by Host

Not every managed WordPress host exposes the same backends. I cross-referenced the test results against what three common managed host tiers actually offer:

Host Tier Type Object Cache Available Notes
Shared / entry managed None or APCu APCu is per-worker; expect lower hit rates
Mid-tier managed Redis (TCP, shared instance) TCP adds ~0.5–2 ms vs. Unix socket
Premium managed Redis (Unix socket or dedicated) Closest to test conditions; some offer Relay

If your host provides Redis over TCP rather than a Unix socket, add roughly 1–3 ms per cache operation. On a page that makes 80 cache reads, that compounds to 80–240 ms of added latency — enough to push p95 TTFB past the 600 ms threshold where Core Web Vitals scores start to drop.

When evaluating a managed host, ask specifically: Is Redis on a Unix socket or TCP? Is it a shared Redis instance across all customers on the node? A shared Redis instance under memory pressure will evict your keys, collapsing hit rates unpredictably.

Recommended Configuration Settings

Once you have chosen a backend, the default plugin settings are not always optimal. Here are the settings I changed from defaults and the measurable impact of each.

Redis (WP Redis 1.3.9)

  • WP_REDIS_SCHEME: set to unix and point WP_REDIS_PATH to the socket file. Switching from TCP to Unix socket dropped median TTFB by 22 ms in isolation testing.
  • WP_REDIS_MAXTTL: set to 86400 (24 hours). Without a max TTL, transients with no expiry accumulate and inflate Redis memory use over weeks.
  • WP_REDIS_IGNORED_GROUPS: add counts and plugins if you are on a multisite install. These groups are invalidated on every admin action and pollute the cache with short-lived data.

Relay (2.4.0)

  • relay.maxmemory: the default 32 MB in-process limit is conservative. On a server with 4 GB RAM running a single site, I raised it to 96 MB. Cache hit rate on the in-process layer rose from 81% to 93%, and median TTFB dropped a further 18 ms.
  • relay.eviction-policy: set to lru (least recently used) to match WordPress's access patterns, where options and terms are hot and post meta for old posts is cold.

APCu (when Redis is unavailable)

  • apc.shm_size: the default 32 MB fills quickly on a WooCommerce site. Set to at least 128 MB.
  • apc.ttl: set to 3600. Without a TTL, stale data persists until the PHP-FPM pool restarts.
  • apc.slam_defense: set to 1 to prevent cache stampedes when a key expires under load.

Do This First: A Diagnostic Sequence

Before installing any object cache plugin, run this sequence. It takes about 10 minutes and prevents you from adding a cache layer on top of problems that caching will not fix.

Step 1 — Measure your baseline TTFB. Use WebPageTest with a single run from a data center close to your server. Record median TTFB across three page types: homepage, an archive, and a single post or product. You need this number to verify that the cache is actually helping after installation.

Step 2 — Audit your wp_options autoloads. Run this query in phpMyAdmin or TablePlus:

SELECT option_name, LENGTH(option_value) AS size_bytes
FROM wp_options
WHERE autoload = 'yes'
ORDER BY size_bytes DESC
LIMIT 30;

If the total autoloaded data exceeds 800 KB, you have a plugin hygiene problem. Object caching will cache that bloated payload, but you are still serializing and deserializing hundreds of kilobytes on every request. Clean the autoloads first.

Step 3 — Install Query Monitor and record the baseline query count on your three test URLs. Note which query groups are repeating (look for duplicate wp_options selects or repeated term queries). These are the exact queries the object cache will eliminate.

Step 4 — Install the object cache drop-in appropriate for your backend. Confirm the drop-in is active: WP_CLI command wp cache type should return Redis, Memcached, or Relay, not Default.

Step 5 — Re-run WebPageTest and Query Monitor. Compare TTFB and query count against your Step 1 and Step 3 baselines. If TTFB did not drop by at least 15%, either the cache hit rate is low (check with wp redis info or wp cache stats) or a page cache is already absorbing the database load and the object cache is redundant for anonymous traffic.

When Object Caching Is Not the Bottleneck

Object caching does not help with every TTFB problem. If your slow TTFB is caused by a PHP process waiting on an external HTTP request (a payment gateway ping, a remote font load, a third-party analytics endpoint), adding Redis changes nothing. Use Query Monitor's HTTP API calls panel to rule this out.

Similarly, if your server is CPU-bound rather than I/O-bound, reducing database queries frees up I/O but the PHP worker is still the constraint. In that case, a full-page cache (Nginx FastCGI cache, or this guide to modern caching patterns) is the correct next step, not a more sophisticated object cache backend.

Object caching and page caching are complementary, not interchangeable. Object caching benefits logged-in users and WooCommerce sessions where page caching is bypassed. Page caching benefits anonymous traffic where the full HTML response can be served from disk or memory without touching PHP at all.

Conclusion

Across the five WordPress object cache backends tested, Relay produced the lowest median TTFB at 194 ms, followed by Redis on a Unix socket at 248 ms. The baseline with no object cache sat at 487 ms — a 293 ms gap that maps directly to LCP regression in the field. APCu is a meaningful improvement over no cache but is structurally limited by its per-worker memory model.

The configuration details — socket vs. TCP, in-process memory limits, ignored cache groups — account for a measurable share of that performance gap and are worth tuning after the initial installation. Run the five-step diagnostic before installing anything, and verify with a before-and-after TTFB measurement rather than assuming the cache is working.