How the Performance Lab Plugin Silently Killed Redis on a Client's WooCommerce Store

· 10 min read

The Dashboard Said Connected. Redis Said Otherwise.

A client's WooCommerce store — around 800 products, 200 orders a day — had been running smoothly for months. Redis object cache was enabled, response times sat around 250ms, and the Grafana dashboard I'd set up showed a consistent 94% cache hit rate. Then one Monday morning, I noticed the hit rate had dropped to zero. Not low — zero.

The site was still functional. No errors in the logs. No alerts from Uptime Kuma. The client hadn't noticed anything because the store was still loading, just slower. Page load times had crept from 250ms to 1.8 seconds over the weekend. Not catastrophic, but the kind of silent degradation that costs conversions before anyone flags it.

I SSH'd in expecting a crashed Redis process or a memory issue. What I found was stranger: Redis was running fine, full of cached keys, and nothing was reading from it.

Redis Was Healthy — WordPress Wasn't Talking to It

First check — is Redis actually running?

redis-cli ping
PONG

Healthy. Next — are keys still there?

redis-cli DBSIZE
(integer) 14837

Nearly 15,000 keys sitting in memory. Now the critical one — is anything actually hitting the cache?

redis-cli INFO stats | grep keyspace
keyspace_hits:0
keyspace_misses:0

Both counters were stuck at zero since the last Redis restart. WordPress had stopped talking to Redis entirely. Not a cache miss problem — a complete disconnection.

I opened the WordPress admin and navigated to Settings > Redis. Instead of the usual green "Connected" status, it showed:

Status: Disabled
Drop-in: Invalid

"Drop-in: Invalid" means the Redis Object Cache plugin looked at wp-content/object-cache.php and didn't recognise it as its own file.

The File That Shouldn't Have Changed

I checked who owned the drop-in:

head -20 wp-content/object-cache.php
<?php
/**
 * Server-Timing object cache drop-in.
 *
 * @package performance-lab
 * @since 3.6.0
 */

if ( file_exists( WP_CONTENT_DIR . '/object-cache-plst-orig.php' ) ) {
    require_once WP_CONTENT_DIR . '/object-cache-plst-orig.php';
}

That's not the Redis Object Cache drop-in. That's the WordPress Performance Lab plugin's file. It had quietly replaced the Redis drop-in with its own wrapper.

And there was the backup:

ls -la wp-content/object-cache*
-rw-r--r-- 1 www-data www-data  1247 Jul  6 03:14 object-cache.php
-rw-r--r-- 1 www-data www-data 18904 Mar 12 09:41 object-cache-plst-orig.php

The original Redis drop-in had been renamed to object-cache-plst-orig.php. The timestamp on the new file — 03:14 on Saturday — lined up perfectly with when WordPress runs automatic plugin updates overnight.

What Performance Lab Actually Does

The Performance Lab plugin is developed by the WordPress core performance team. It's a staging ground for features being tested before they ship in WordPress core. One of those features is Server-Timing — HTTP headers that report how long each part of a request takes (database queries, object cache lookups, template rendering).

To measure object cache timing, Performance Lab needs to wrap every cache operation. The way it does this is by replacing wp-content/object-cache.php with its own version — a thin wrapper that loads the original file and adds timing instrumentation around each call.

The mechanism works like this:

  1. On admin_init, Performance Lab runs perflab_maybe_set_object_cache_dropin()
  2. If it finds an existing object-cache.php that isn't its own, it renames it to object-cache-plst-orig.php
  3. It drops in its own object-cache.php that require_onces the original

In theory, the wrapper is transparent — it loads the original Redis drop-in from the renamed file and adds timing headers. In practice, several things break.

Why the Wrapper Breaks Redis Object Cache

The Redis Object Cache plugin (both the free version and Object Cache Pro) performs a self-check on every admin page load. It reads wp-content/object-cache.php, compares it to its own expected file contents, and if the header doesn't match, it reports the drop-in as "Invalid" and disables itself.

This is a reasonable safety check — if something has tampered with the drop-in, the plugin shouldn't blindly trust it. But it means Performance Lab's wrapper file, even though it loads the Redis code underneath, triggers the invalid drop-in detection.

The result: Redis Object Cache sees a foreign object-cache.php, marks itself as disabled, and stops providing its admin interface. The underlying Redis connection may or may not still function depending on the exact version combination and load order. On this client's store, it had stopped entirely. The Performance Lab wrapper was loading the renamed file, but the Redis plugin's initialisation hooks weren't firing correctly because the plugin believed it was disabled.

The net effect: every database query that should have been served from Redis was hitting MariaDB directly. On an 800-product WooCommerce store, that's the difference between 40 queries per page load and 400+.

Confirming the Impact

I ran redis-cli MONITOR in one terminal and loaded the homepage in another:

redis-cli MONITOR

Nothing. Complete silence. No GET or SET commands at all. WordPress wasn't even attempting to use Redis.

Then I checked the database query count using Query Monitor. A single product page was running 487 queries — most of them option lookups and postmeta reads that Redis would normally handle.

The before-and-after was stark:

Metric With Redis (before) Without Redis (after Performance Lab)
Database queries per page 38 487
TTFB (homepage) 240ms 1,820ms
Redis hit rate 94% 0%
MariaDB active connections (peak) 3 18

The store had been running like this since Saturday's auto-update. Three days of degraded performance, higher database load, and slower checkouts — all completely silent.

The Fix

Step 1: Prevent Performance Lab from touching the drop-in

Add this constant to wp-config.php, before the /* That's all, stop editing! */ line:

define( 'PERFLAB_DISABLE_OBJECT_CACHE_DROPIN', true );

This tells Performance Lab to skip its object-cache.php replacement entirely. You lose the Server-Timing instrumentation for object cache operations, but if you're running Redis, the cache hit/miss data from Redis itself is more useful than HTTP headers anyway.

Step 2: Restore the Redis drop-in

Delete Performance Lab's wrapper and the backup file:

rm wp-content/object-cache.php
rm wp-content/object-cache-plst-orig.php

Then re-enable the Redis Object Cache drop-in from the WordPress admin (Settings > Redis > Enable Object Cache), or via WP-CLI:

wp redis enable

Step 3: Verify Redis is working again

redis-cli INFO stats | grep keyspace

Load a few pages, then check again:

redis-cli INFO stats | grep keyspace
keyspace_hits:2847
keyspace_misses:312

Hit rate back to 90%+. The queries are flowing through Redis again.

For a final check, run redis-cli MONITOR while loading a page — you should see a stream of GET and SET commands:

redis-cli MONITOR | head -20
"GET" "wp_:options:alloptions"
"GET" "wp_:options:notoptions"
"GET" "wp_:transient:doing_cron"
"GET" "wp_:site-transient:update_core"

That's what healthy Redis traffic looks like.

Why This Keeps Happening to People

This conflict has been reported repeatedly — GitHub issue #612 and #630 on the WordPress/performance repository, plus multiple threads on the WordPress.org support forums. The Performance Lab team has discussed alternative approaches to the drop-in, but as of mid-2026, the plugin still uses the same mechanism.

The problem is especially insidious because:

  1. It happens during auto-updates. WordPress updates Performance Lab overnight. The drop-in gets replaced while nobody's watching.
  2. There are no errors. The site loads, pages render, no PHP warnings. The only symptom is slower performance.
  3. The WordPress admin doesn't warn you. Site Health doesn't flag the conflict. You only see it if you go specifically to the Redis Object Cache settings page.
  4. Monitoring must be in place. Without Redis hit rate monitoring, you'd never know the cache stopped working until the database buckles under load.

Prevention for Every Managed Site

I now include three things in every site setup where Redis is active:

1. The constant in wp-config.php on every site running Redis:

define( 'PERFLAB_DISABLE_OBJECT_CACHE_DROPIN', true );

Even if Performance Lab isn't installed today, this protects against it being added later — by another admin, a hosting control panel, or a plugin that bundles it.

2. A Redis hit rate alert in monitoring.

If you're running Telegraf + InfluxDB + Grafana, the Redis input plugin exposes keyspace_hits and keyspace_misses as counters. I set an alert that fires when the hit rate drops below 50% for more than 10 minutes — that's an unmistakable sign something has gone wrong with the object cache.

For a simpler setup, a cron job works:

#!/bin/bash
HITS=$(redis-cli INFO stats | grep keyspace_hits | cut -d: -f2 | tr -d '\r')
if [ "$HITS" -eq 0 ]; then
    echo "Redis hit count is zero — object cache may be disconnected" | mail -s "Redis Alert" [email protected]
fi

3. A file integrity check on the drop-in.

I keep a checksum of the expected object-cache.php and verify it weekly:

EXPECTED="abc123..."  # md5sum of the Redis Object Cache drop-in
ACTUAL=$(md5sum wp-content/object-cache.php | awk '{print $1}')
if [ "$EXPECTED" != "$ACTUAL" ]; then
    echo "object-cache.php has been modified"
fi

If another plugin replaces the file — Performance Lab or anything else — the check catches it before three days of degraded performance go unnoticed.

The Lesson

Performance Lab is a good plugin doing useful work. The Server-Timing feature is genuinely helpful for performance profiling. But its approach to the object-cache.php drop-in is a footgun for anyone running Redis or Memcached — and that's exactly the audience most likely to care about performance instrumentation.

The broader point is that wp-content/object-cache.php is a shared resource with no conflict resolution mechanism. WordPress treats it as a single file that one plugin owns. When two plugins both need to modify it, the last one to write wins — silently. Until WordPress core introduces a proper drop-in registration system, this kind of conflict will keep happening.

For now, the fix is simple: add the constant, monitor your hit rate, and don't trust that "the cache is working" without checking the numbers.


Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans | Get in Touch

Stop Firefighting. Start Maintaining.

I manage 70+ WordPress sites for agencies and businesses. Whether you need ongoing maintenance, emergency support, or a one-off performance fix — I can help.

View Maintenance Plans Get in Touch

Get in Touch to Discuss Your Needs