WordPress Object Cache: Why I’m Sticking with Redis Object Cache Instead of FlyingPress

Since version 5.6, FlyingPress can manage a Redis object cache straight from its own dashboard. One less plugin, one less interface, metrics sitting right next to the page cache purge button: the case is real, and I hear it. Yet across every site I host and manage, I keep recommending a dedicated plugin, either Redis Object Cache or Object Cache Pro. This isn’t about distrusting FlyingPress, which I use and genuinely like. It comes down to maturity, configuration, and above all coupling. Once the feature has proven itself, the single-plugin argument will most likely win out. That day hasn’t come yet, and here is why, in detail.

What an object cache actually is

Before comparing two implementations, it’s worth recalling what we’re replacing, because that is exactly where the whole debate lives.

WordPress ships with a WP_Object_Cache class and an API of functions: wp_cache_get(), wp_cache_set(), wp_cache_delete(), and friends. By default, this cache lives in a plain PHP array. It is born at the start of the request and dies at the end. It keeps WordPress from reading the same option ten times while rendering a page, but it doesn’t survive from one page to the next.

A persistent object cache swaps that class for another one that stores data in an external service, in this case Redis. The swap happens through a very particular file, the wp-content/object-cache.php drop-in. When it exists, wp-settings.php loads it via wp_start_object_cache(), very early in the boot sequence, before must-use plugins and regular plugins. So this isn’t a plugin feature in the usual sense. It’s an infrastructure component, loaded ahead of everything else, and every data read in every request depends on it.

The consequences run deeper than most people realize:

  • Transients move house. As soon as wp_using_ext_object_cache() returns true, set_transient() stops writing to wp_options and writes to the cache instead, under the transient group. The old transients stay in the database like fossils.
  • alloptions becomes a Redis key. The big serialized array of every autoloaded option, the one I dissected in my archaeology of wp_options (in French), is now read from and written back to Redis. Its size directly drives memory usage and the latency of every request.
  • Metadata and queries go through batched reads. update_meta_cache(), _prime_post_caches(), and their cousins rely on wp_cache_get_multiple(). Depending on whether the drop-in implements these functions natively, you’re looking at a single network round trip or several dozen.

Page caching and object caching are two separate layers. FlyingPress says so itself in its documentation: one stores finished HTML, the other stores data. For a brochure site where every page is served statically, an object cache changes little on the visitor’s side. For a WooCommerce store, a members area, a forum, or a busy admin, it changes everything. And those are precisely the sites where you can’t afford guesswork.

The single-plugin argument, in fairness

Let’s start by acknowledging what the FlyingPress integration brings to the table, because it’s not nothing.

  • One less plugin, which means one less update, a smaller attack surface, and one less line in the plugin list that clients eye with suspicion.
  • A single interface: connection status, hit ratio, and memory usage over the last hour all appear in the same dashboard as the page cache purge.
  • Near-automatic setup when Redis runs locally with the phpredis extension.
  • Compatible constants. FlyingPress wisely adopted Redis Object Cache’s naming: WP_REDIS_HOST, WP_REDIS_PORT, WP_REDIS_DATABASE, WP_REDIS_USERNAME, WP_REDIS_PASSWORD, WP_REDIS_SCHEME, WP_REDIS_PATH, WP_REDIS_PREFIX, WP_REDIS_TIMEOUT. The day you switch, your wp-config.php is already almost ready.

For a single site, on a shared host that provides Redis, with little logged-in traffic, it’s a perfectly defensible option today. My argument is about everything else, which happens to be what I deal with every day.

Twelve years of bug fixes: maturity can’t be declared

Redis Object Cache has been around since 2014. It has more than 400,000 active installs, it’s on version 2.8, and its changelog reads like a ledger of every trap an object cache can fall into. A few examples gathered across releases:

  • a race condition in add_or_replace() that allowed writing a key another request had just created;
  • corrupted increments and decrements when the igbinary serializer was enabled, fixed over several releases, followed by TTL preservation during those same operations;
  • incorrect results from wp_cache_get_multiple() with certain clients, reworked across half a dozen versions;
  • the drop-in wars: W3 Total Cache, LiteSpeed Cache, and the Performance Lab plugin all had to be stopped from overwriting object-cache.php;
  • refusing to enable the cache when Redis is unreachable, automatic flushing on activation and deactivation, and, in the latest release, deleting orphaned transients left behind in the database;
  • support for each API extension as it landed in core: wp_cache_get_multiple() (WordPress 5.5), wp_cache_add_multiple(), wp_cache_delete_multiple(), wp_cache_flush_runtime(), and then wp_cache_supports() and wp_cache_flush_group() with WordPress 6.1.

Each of these fixes corresponds to a real incident, on a real site, reported by someone who took the time to file it. A new implementation doesn’t benefit from that history. It isn’t necessarily bad, but it will rediscover some of those sharp edges, one at a time. The question isn’t whether the FlyingPress team is competent; they are. The question is which sites you’re willing to offer up as the testing ground.

Check what the drop-in can actually do

There’s a simple, objective test. Since WordPress 6.1, a drop-in can declare which features it implements natively through wp_cache_supports(). If a function isn’t provided, core loads the fallbacks in cache-compat.php, which loop over individual calls. That’s functionally correct, but every get_multiple turns into a burst of separate GETs instead of a single MGET.

wp eval 'foreach ( [ "add_multiple", "set_multiple", "get_multiple", "delete_multiple", "flush_runtime", "flush_group" ] as $f ) { printf( "%-16s %s\n", $f, wp_cache_supports( $f ) ? "native" : "fallback" ); }'
Code language: JavaScript (javascript)

Run this command with each drop-in, then open Query Monitor on a WooCommerce product page with variations. It’s not unusual to count 200 to 400 cache calls for a single page: options, metadata for the product and each of its variations, terms, attributes, prices, stock levels. Some of those reads arrive batched, for example when WooCommerce primes the metadata for all variations at once.

Let’s run a back-of-the-envelope calculation. Over a local Unix socket, a Redis round trip costs a few dozen microseconds. Against a remote Redis, even in the same data center, you’re closer to 0.3 to 1 millisecond. Take 300 reads at 0.5 milliseconds each:

  • with the fallback, each read is a separate GET, roughly 150 milliseconds spent waiting on the network;
  • with native support, the batched reads go out as a single MGET, roughly one millisecond for the whole lot.

In practice, not every read can be batched, so the real gain lands somewhere between those two figures. But the gap alone explains why two “Redis” drop-ins can post very different response times on the same server. With Redis on the same machine, the penalty shrinks considerably; with a managed or shared Redis on another host, it becomes the single biggest cost of the page.

Configuration: where multi-site hosting is won or lost

This is the most concrete point. When you host dozens of sites on the same servers, the question isn’t “does the cache work?” but “are each site’s caches isolated, properly sized, and purgeable independently?”

Here’s what each solution supports, according to their respective documentation as of October 2026. I write “undocumented” rather than “missing”: a feature can exist without being described, but you don’t run in production what you can’t describe.

RequirementRedis Object CacheFlyingPress 5.6
Host, port, database, Unix socketYesYes
Username and password (Redis 6 ACL)YesYes
Key prefixWP_REDIS_PREFIXWP_REDIS_PREFIX, default flying-press_
Client choice (phpredis, Predis, Relay)Yesphpredis only
TLS connectionYes (WP_REDIS_SSL_CONTEXT)Undocumented
Read timeout and retriesYesOnly the connection timeout is documented
igbinary serializerWP_REDIS_IGBINARYUndocumented
Enforced maximum TTLWP_REDIS_MAXTTLUndocumented
Global and ignored groupsYesUndocumented
Prefix-scoped flushWP_REDIS_SELECTIVE_FLUSHUndocumented
Replication, Sentinel, clusterYesUndocumented
WP-CLI commandswp redis …Undocumented
Query Monitor integrationYesUndocumented
MetricsDashboard and diagnosticsHit ratio and memory, last hour

Three rows in this table deserve a closer look.

Prefixes and the silent collision

If two sites share the same Redis instance and the same numbered database, the key is the only thing that tells their data apart. A fixed prefix that’s identical from one site to the next, such as a default of flying-press_, would have site B reading site A’s alloptions key. I haven’t verified whether the FlyingPress drop-in adds an automatic discriminator, like the database name or table prefix, and that’s exactly the problem: on a shared server, you don’t bet on it. Whichever solution you choose, the rule is the same: a unique, explicit prefix per site, defined in wp-config.php.

The flush that wipes out the neighbors

With Redis Object Cache, wp_cache_flush() runs a FLUSHDB by default: the entire numbered database is emptied, including keys from any other site sharing it. A recent review in the WordPress plugin directory points this out, incidentally. There are two ways around it:

  1. WP_REDIS_SELECTIVE_FLUSH, which deletes only the keys carrying the site’s prefix by way of a Lua script. A word of caution: Lua scripts are atomic and block Redis while they run. On a large keyspace, that’s precisely why the plugin added WP_REDIS_FLUSH_TIMEOUT.
  2. One numbered database per site (Redis offers 16 by default, set by the databases directive), or even one instance per site on its own socket with its own memory cap. That’s my reference setup.

FlyingPress doesn’t document the scope of its flush. FLUSHDB or prefix-based deletion? On a single-site server, it doesn’t matter. On a shared server, it’s the difference between a harmless button and an incident on another client’s site.

A single client

FlyingPress requires phpredis. It’s the best default choice, and I have no complaint on that front. But Redis Object Cache also supports Relay, a client that keeps a copy of hot keys in PHP-FPM shared memory, with Redis invalidating them via notifications. On a busy WooCommerce store, eliminating the network round trip for the most frequently read keys isn’t a nicety: it’s often the biggest gain left once everything else has been optimized.

Serialization, memory, and eviction: the settings that matter

Choosing a drop-in isn’t just about the connection. It also determines how data occupies memory.

Serialization. By default, values are stored using PHP’s serialize(), which is verbose by nature. igbinary produces a binary representation that’s considerably more compact for nested arrays, which is exactly the shape of alloptions, metadata, and WP_Post objects. Fewer bytes means less Redis memory, less bandwidth, and faster unserialization. Redis Object Cache enables it with WP_REDIS_IGBINARY. Object Cache Pro goes further with compression (lzf, lz4, or zstd).

Eviction. This is the trap I run into most often on sites that land on my desk. WordPress writes most of its keys with no expiration: wp_cache_set() defaults to a TTL of zero. If Redis is configured with maxmemory-policy volatile-lru, it can only evict keys that have a TTL. Once memory is full, it finds nothing to remove and starts rejecting writes. For a Redis instance dedicated to caching, the correct policy is allkeys-lru (or allkeys-lfu). On top of that, WP_REDIS_MAXTTL lets you enforce a maximum lifetime on every key.

Persistence. A cache has no need to survive a restart. RDB snapshots trigger a fork() whose copy-on-write behavior can cause latency spikes on a large instance. For a cache-only instance:

# /etc/redis/redis-cache.conf
maxmemory 512mb
maxmemory-policy allkeys-lru
save ""
appendonly no
unixsocket /run/redis-cache/redis.sock
unixsocketperm 770
Code language: PHP (php)

And on the WordPress side, with Redis Object Cache:

define( 'WP_REDIS_SCHEME', 'unix' );
define( 'WP_REDIS_PATH', '/run/redis-cache/redis.sock' );
define( 'WP_REDIS_DATABASE', 3 );
define( 'WP_REDIS_PREFIX', 'client-shop:' );
define( 'WP_REDIS_IGBINARY', true );
define( 'WP_REDIS_MAXTTL', 86400 );
define( 'WP_REDIS_TIMEOUT', 1 );
define( 'WP_REDIS_READ_TIMEOUT', 1 );
Code language: JavaScript (javascript)

None of this is exotic. But every line corresponds to a behavior I need to be able to guarantee to a client, and half of these lines currently have no documented equivalent in FlyingPress.

When Redis goes down

An object cache should be judged as much by how it fails as by how it runs normally. Three questions come up.

What does the visitor see? By default, Redis Object Cache displays an explicit error page if Redis is unreachable, or falls back to degraded mode if you define WP_REDIS_GRACEFUL. Both choices are defensible, and more importantly, both are documented. FlyingPress says an error appears in its dashboard but doesn’t specify what happens on the front end.

How long does each request wait? With a TCP host that has stopped responding, each PHP request waits until the connection timeout expires, one second by default in both plugins. With a missing Unix socket, the failure is immediate. That’s one more reason to prefer the socket when Redis is local.

Is the data still correct when Redis comes back? This is the point almost nobody mentions, and it has nothing to do with which plugin you use. In degraded mode, WP_REDIS_GRACEFUL included, WordPress keeps writing to the database without being able to invalidate the corresponding keys in Redis. If the outage was a network cut or saturation rather than a restart, Redis still holds its old values. When the service returns, it serves them right back: an order with the wrong status, incorrect stock, an outdated option, an inconsistent user session.

Redis Object Cache’s degraded mode therefore protects availability, not consistency. This isn’t a flaw in one plugin or the other; it’s the very nature of a cache that has stopped being invalidated. The procedure is the same with Redis Object Cache, Object Cache Pro, or FlyingPress, and it isn’t optional:

wp cache flush

immediately after Redis comes back, ideally triggered by your monitoring rather than by a human who will remember too late. The only difference between the solutions is how much you can trust that flush: with Redis Object Cache, I know exactly what it deletes and what it leaves alone. With FlyingPress, I have yet to find out.

Monitoring and operations

When you manage a fleet of sites, you don’t administer them with a mouse. Redis Object Cache provides wp redis status, wp redis enable, wp redis disable, and wp redis update-dropin, which makes it possible to script deployments, check an entire server’s state in a single loop, and fix an outdated drop-in after an update. It also integrates with Query Monitor, which shows the hit ratio and the details of every cache call for the current request. That’s the tool for spotting the plugin that bypasses the cache or floods it.

On the Redis side, the tools are the same regardless of drop-in, and they’re worth knowing:

redis-cli -s /run/redis-cache/redis.sock INFO stats | grep keyspace_
redis-cli -s /run/redis-cache/redis.sock INFO memory | grep used_memory_human
redis-cli -s /run/redis-cache/redis.sock --memkeys
redis-cli -s /run/redis-cache/redis.sock SLOWLOG GET 10

keyspace_hits and keyspace_misses give you the true hit ratio, --memkeys exposes the bloated key (almost always alloptions or a poorly designed plugin transient), and SLOWLOG captures slow commands. On the other hand, never run KEYS * or MONITOR against a busy production instance: the first blocks Redis, and the second slashes its throughput.

FlyingPress’s one-hour chart is nice for reassuring a client. It’s no substitute for scriptable tooling.

The real issue: coupling

Everything so far is a matter of maturity, and maturity comes with time. The next argument, however, is structural.

A page cache plugin is a front-end component. It evolves quickly and touches CSS, JavaScript, lazy loading, preloading, and rewrite rules. It’s the first plugin you deactivate to diagnose a broken layout, and one of the first you replace when a competitor does better. An object cache is an infrastructure component. It needs to be stable, predictable, and change as rarely as possible.

Putting both in the same plugin ties their life cycles together. In concrete terms:

  • I deactivate FlyingPress to isolate a JavaScript conflict. What happens to the drop-in? If it’s removed, I’ve just changed the performance profile of the entire back office in the middle of my diagnosis, and flushed the cache without meaning to. If it stays, it keeps running without the plugin that’s supposed to manage it. The documentation doesn’t say.
  • A FlyingPress update fixes an image optimization regression. It also ships a new version of the drop-in. I can’t take one without the other.
  • One day I decide to switch page cache plugins. I lose my object cache along the way and have to rebuild it with a different tool.

This is exactly the principle I’ve long defended when writing about the Unix philosophy (in French): do one thing and do it well. One less plugin on the list isn’t one less dependency. It’s the same dependency, packed into a box that holds other things too.

What about Object Cache Pro?

Object Cache Pro is the commercial rewrite of Redis Object Cache, by the same author. What it adds is substantial: compression, TLS connections, prefetching of the most requested keys, detailed analytics, WooCommerce-specific optimizations, and deep integration with Relay. It claims full unit test coverage, which, for a component loaded before everything else, is not a marketing argument.

It comes at a price: around $95 per month per site at list price, which reserves it for stores and platforms where every millisecond of dynamic page time translates into revenue. Some hosts include it in their plans, notably Cloudways and GridPane. For a WooCommerce store with heavy logged-in traffic, a sizable multisite network, or a membership platform, it’s my first choice. For everything else, the free Redis Object Cache does the job, and does it well.

The objections I expect

“One less plugin is always better.” In terms of lines in the admin screen, yes. In terms of components to understand, no: the object cache is there either way. The only difference is who maintains it and at what pace.

“FlyingPress is responsive; they’ll fix things quickly.” I don’t doubt it. But fixing a bug quickly presupposes someone has hit it first. I’d rather other people hit it on their sites before mine.

“FlyingPress’s metrics are enough.” To confirm the cache is working, yes. To understand why an admin page takes 800 milliseconds, no.

“My site is small, none of this applies to me.” That’s true, and I said as much above: a standalone site on a dedicated Redis with no logged-in traffic can enable the FlyingPress option without much risk. My advice is aimed at sites that have something to lose.

What would change my mind

I’m not dogmatic, and I sincerely expect to revisit this position. Here are the criteria I’ll be watching:

  1. Documentation of critical behaviors: flush scope, behavior when Redis goes down, and what happens to the drop-in when the plugin is deactivated.
  2. wp_cache_supports() reporting “native” across all batched functions.
  3. Configurable igbinary, TLS, read timeout, and retries.
  4. A default prefix unique to each site, or documentation that clearly states otherwise and requires you to set one.
  5. WP-CLI commands for status, activation, and flushing.
  6. Six to twelve months of changelog without regression fixes to the object cache component.

Once those boxes are checked, migrating will be trivial, since the constants are already shared:

# Flush and cleanly remove the current drop-in
wp cache flush
wp redis disable
wp plugin deactivate redis-cache

# Make sure no drop-in remains
ls -l wp-content/object-cache.php

# Enable the option in FlyingPress, then verify
wp eval 'var_dump( wp_using_ext_object_cache() );'
Code language: PHP (php)

Just remember to keep your WP_REDIS_PREFIX or flush after switching: changing the prefix leaves all the old keys behind, and they’ll occupy memory until they’re evicted.

My take

FlyingPress made the right call by adding an object cache, and it did so intelligently by adopting the constants of the reference tool. For a standalone site, it’s already a reasonable option. But an object cache isn’t just one optimization among many: it’s the layer that sits between WordPress and its own data, loaded before everything else, on every single request. On a store, a forum, or a fleet of shared-hosted sites, I expect that layer to come with a decade of fixed bugs, complete configuration, and a life cycle independent of my page cache.

Today, Redis Object Cache checks all three boxes, Object Cache Pro checks them even more firmly, and FlyingPress checks the box of good intentions. I’ll revisit this post once the feature has aged. Until then, two plugins beat one.


Write a few shards of soul...

In the flickering shadow of a candle, where the whispers of the wind mingle with the secrets of an old parchment, I invite you to weave a web of words. Write a few shards of soul – dream, star, abyss, embrace, mist – and let them dance on the page, like fireflies in an inky night. How about weaving them into a sentence, a breath, a story?

Subscribe
Notify of
guest
0 Commentaires
Oldest
Newest Most Voted

Source: https://paul.argoud.net/en/wordpress-object-cache-redis-object-cache-flyingpress/