Redis Object Cache Made My Test Store No Faster, Until the Database Was 2 ms Away

20 min read

A Redis object cache reduced the database queries for the cart on my test store from 116 to 17, but the cart needed the same time to build: 166 milliseconds without Redis, 167 with it. The product page, the checkout and the Products list in wp-admin didn't get faster either.

Then I made every query's trip to the database take 2 milliseconds (that's normal for a database on a separate server), and the same Redis made the cart 205 milliseconds faster. The farther away I put the database, the more time Redis saved.

So will Redis make your WooCommerce store faster? In my tests Redis saved time only when every query had to go over a network to reach the database, and only on pages that the page cache doesn't serve: the cart, the checkout, pages for logged-in customers and wp-admin. You can measure how long your web server waits for the database with one command, before you've installed anything.

If you run WooCommerce on a VPS, or on a hosting plan that includes Redis, I'll show you that command, what Redis changed on four kinds of pages in my tests, and the Redis settings that kept my test store working. (On Shopify there's nothing to install, because Shopify runs the servers.)

I ran every test on October 28, 2026, on the store I built for my WooCommerce speed tests: a catalog of 600 products on WooCommerce 11.1.2 and Storefront, with MariaDB 11.4, PHP 8.3, Redis 7.4 and Redis Object Cache 3.0.0, each service in a container of its own on my PC.

What a Redis object cache keeps between page views

While WordPress builds a page, it saves the answer to every lookup in its object cache: the options it loads first, each post with its meta, the categories, the user. When the same page asks for the same thing twice, the second answer comes from memory. "By default, the object cache is non-persistent," says the WordPress reference, so it's deleted when the page is done, and the next shopper's page asks the database again.

A persistent object cache keeps those answers for the next page view. Redis Object Cache, the free plugin by Till Krüss with more than 500,000 active installs, copies a file called object-cache.php into wp-content (a drop-in: WordPress loads it instead of its own cache code).

From then on, every answer is stored in Redis, a database that keeps its data in memory. Transients are stored there too: with a persistent object cache on, WordPress keeps them in Redis instead of in the wp_options table.

WordPress's own Site Health screen had recommended exactly this. On the test store, it listed "You should use a persistent object cache" as a recommended improvement, because the store is above one of the limits that WordPress has checked since version 6.1: more than 1,000 posts. Products and their variations count as posts, so a catalog of this size gets the message.

Here's what Redis changed on one view of each page with a warm cache. I counted with a must-use plugin of my own, which reads WordPress's query counter, its query timer and the statistics that Redis Object Cache keeps for each request:

PageQueries without RedisQueries with RedisCalls to RedisWaiting for MariaDBTime in Redis
Cart with one item1161716635 ms, then 8 ms28 ms
Checkout851610026 ms, then 6 ms15 ms
Products list in wp-admin1513116448 ms, then 11 ms27 ms
Product page942630135 ms, then 10 ms48 ms

Median of three page views with a warm cache, with the database and Redis on the same Docker network as PHP. The product page shows random related products, so its counts change from one view to the next.

The queries really were gone. But I hadn't expected the next column: with Redis on, the cart sent 166 commands to Redis, and each command is a round trip over the network, like a query.

Here's how the object cache works: WordPress asks it for one key at a time, and every key that the page hasn't used yet in this request means one trip to Redis. WooCommerce asks for many small things (each product's meta, its terms, its prices), so the number of trips grows quickly. On the cart, Redis saved 27 milliseconds of waiting for MariaDB and used 28 milliseconds for its own commands.

On one server, the store without extra plugins got no faster

PageNo object cacheRedis, PhpRedisRedis, Predis
Product page136 ms127 ms136 ms
Cart with one item166 ms167 ms168 ms
Checkout137 ms141 ms130 ms
Products list in wp-admin232 ms221 ms233 ms

Server time to the first byte, the median of three runs of 40 requests each, with the database and Redis on the same Docker network as PHP and no page cache.

Every difference is smaller than the normal variation between my runs: the same setup, run three times, differed by up to 41 milliseconds. MariaDB ran next to PHP with every table in its buffer pool (the memory where it keeps table data), so a simple query took 0.14 milliseconds, about the same as one Redis command.

Also, the queries didn't take most of the time on these pages. The cart spends most of its time while PHP loads and runs WordPress, WooCommerce and the theme, and an object cache doesn't change that part.

Predis gave the same result. Predis is a Redis client written in plain PHP, and the plugin includes it for servers without PhpRedis, the compiled extension (the plugin's documentation says the client "defaults to phpredis when installed, otherwise predis").

Plugins change the result a little. With the 15 popular plugins from my earlier test switched on (Yoast SEO, Elementor, Wordfence, Jetpack and the others), Redis made the product page faster, from 259 to 223 milliseconds, even on one server. The more queries your plugins add, the more time a local Redis can save, and here it saved about 14%.

Database 2 ms away: Redis made the cart 205 ms faster

Many stores have the database on a separate server: managed WordPress plans, cloud setups with a separate database service, and stores that became too big for one VPS. Then every query goes over a network, and the round trip time is added to each query.

Docker Desktop's virtual machine can't add network delay by itself (its kernel has no netem, the Linux feature for that), so I put a small proxy between PHP and MariaDB that holds every answer for a fixed time. At each step, I measured the real round trip of a simple query, from 0.14 milliseconds on the normal Docker network up to 5 milliseconds.

Two line charts of server time against the round trip to the database, from 0.14 to 5.01 ms. Product page without an object cache: 136, 191, 244, 342 and 668 ms; with Redis Object Cache: 127, 136, 161, 169 and 207 ms. Cart with one item without an object cache: 166, 276, 300, 467 and 827 ms; with Redis: 167, 176, 208, 261 and 280 ms.
Without an object cache, each extra millisecond to the database added about 109 ms to the product page and 136 ms to the cart; with Redis, about 16 and 23 ms
Round trip to MariaDBProduct pageCartCheckoutProducts list in wp-admin
0.14 ms (same network)136 / 127166 / 167137 / 141232 / 221
0.48 ms191 / 136276 / 176181 / 144289 / 236
1.13 ms244 / 161300 / 208233 / 155417 / 263
1.95 ms342 / 169467 / 261339 / 184578 / 298
5.01 ms668 / 207827 / 280620 / 2431,078 / 403

Server time in milliseconds without an object cache, then with Redis Object Cache (PhpRedis). Medians of three runs of 40 requests each, no page cache.

Without an object cache, the cart needed about 136 milliseconds more for every extra millisecond of round trip, because each of its queries waits the extra time, and the connection to MariaDB needs a few more trips. With Redis, it needed 23 milliseconds more, because there weren't many queries left, and Redis itself was still on the same network as PHP. At 5 milliseconds the cart spent 600 milliseconds waiting for MariaDB without Redis, and 88 with it.

Predis was about as fast at most distances, though on a few runs it was more than 30 milliseconds slower than PhpRedis, so I'd pick PhpRedis wherever the host offers it.

How far away is your database?

Run this on your web server, in the folder where WordPress is installed, as the user that your site runs as. It sends 100 simple queries through WordPress's own database connection and prints the average:

root@server:/var/www/example.com# sudo -u www-data wp eval '$t = microtime( true ); for ( $i = 0; $i < 100; $i++ ) { $GLOBALS["wpdb"]->query( "SELECT 1" ); } printf( "%.2f ms per query\n", ( microtime( true ) - $t ) * 10 );'
0.16 ms per query

That's my test store with MariaDB on the same network. With the proxy set to 2 milliseconds, the same command printed 2.00 ms per query.

If your number is close to mine or lower, the database is on the same machine, and Redis won't make a store with few plugins much faster. From half a millisecond up, every page in my tests that reached PHP got faster with Redis, and at 2 milliseconds each page was 155 to 280 milliseconds faster. Is your number above 0.5? Then it's cheapest to put Redis on the web server, and the other fix is to move the database to the same server as PHP.

With a page cache, Redis only helps the pages the cache doesn't serve

Page, with a page cacheSame networkDatabase 2 ms away
Product page for a guest, nginx cache1.0 / 1.00.9 / 0.9
Product page for a guest, Cache Enabler1.8 / 1.91.9 / 1.9
Product page for a logged-in customer122 / 126343 / 158
Cart with one item158 / 151415 / 199
Checkout125 / 123317 / 170

Server time in milliseconds without an object cache, then with Redis Object Cache (PhpRedis). In the nginx rows, requests go through fastcgi_cache, which doesn't cache pages for logged-in customers, for anyone with something in the cart, or the cart and checkout addresses. Medians of three runs of 40 requests each, from a later run than the other tables, so compare the rows of this table only with each other.

A cached page never reaches Redis. nginx's cache sends the page before PHP starts at all, and a plugin cache like Cache Enabler answers from its advanced-cache.php file, which WordPress loads in wp-settings.php before it starts the object cache. So there's nothing that either cache would ask Redis.

All other pages run the whole of WordPress, and they're the pages a shopper uses to place an order. WooCommerce's own guide to caching plugins names Cart, Checkout and My Account as pages that "need to stay dynamic since they display information specific to the current customer and their cart". It also lists the cookies that should stop a cache from serving the page, and a shopper gets those cookies with the first item in the cart. Most page caches also don't serve cached pages to logged-in customers.

With the database 2 milliseconds away, Redis made the product page for a logged-in customer faster, from 343 to 158 milliseconds, while the cached product page stayed under 2 milliseconds with and without Redis. (The cache rules that send those pages from nginx to PHP are in my nginx configuration for WooCommerce.)

Big autoloaded options made Redis slower

Autoloaded options are WordPress settings marked to load on every request, all at once, early in the request. Plugins also save logs, statistics and their own caches there, and many plugins don't delete those rows when you remove the plugin, so on an older store these options grow from kilobytes to megabytes. I added useless options of that type (serialized arrays of about 100 KB each) to the test store and measured the product page again without a page cache:

Autoloaded optionsNo object cacheRedis, PhpRedisRedis, PredisSent by Redis per page view
72 KB (the clean store)120 ms121 ms126 ms0.2 MB
1.1 MB122 ms123 ms128 ms1.2 MB
4.2 MB127 ms129 ms135 ms4.2 MB
16.6 MB142 ms158 ms175 ms16.3 MB

Product page, database and Redis on the same network, medians of three runs of 40 requests each. I ran these tests after the first tables, and that's why the clean store shows 120 ms here and 136 ms in the first table. The last column comes from Redis's own traffic counter over 20 page views.

Why would a cache make the page slower? WordPress keeps all autoloaded options under one cache key, alloptions, and with Redis on, every page view fetches that whole key. With 16.6 MB of options, Redis sent 16.3 MB to PHP for every page.

Without Redis, the same rows came from MariaDB in one query, the slowest query on the page at 15 milliseconds. With Redis, they arrive as one big value that PHP has to read and unpack completely, and the product page took 16 milliseconds longer on PhpRedis and 33 longer on Predis. Each PHP process also needed about 5 MB more memory at its peak.

On some hosts, a big alloptions causes more than slow pages. WordPress VIP, which keeps alloptions in Memcached, limits every cached object to 1 MB and answers with an error page when alloptions becomes bigger than that.

To see what's in your options, use these two commands. The first command prints the total size that WordPress loads on every request, counted the way Site Health counts it, and the second lists the five largest autoloaded options:

root@server:/var/www/example.com# sudo -u www-data wp eval 'require_once ABSPATH . "wp-admin/includes/class-wp-site-health.php"; echo WP_Site_Health::get_instance()->get_autoloaded_options_size(), "\n";'
74071
root@server:/var/www/example.com# sudo -u www-data wp eval 'global $wpdb; $in = "\"" . implode( "\",\"", wp_autoload_values_to_autoload() ) . "\""; foreach ( $wpdb->get_results( "SELECT option_name, LENGTH(option_value) AS bytes FROM $wpdb->options WHERE autoload IN ($in) ORDER BY bytes DESC LIMIT 5" ) as $o ) { echo $o->bytes, "  ", $o->option_name, "\n"; }'
18070  rewrite_rules
9557  wp_user_roles
7484  wpseo_titles
6783  rank-math-options-titles
4761  cron

That's the clean test store, and it already shows the problem: Yoast SEO and Rank Math are both switched off there, and their settings still load on every page. I didn't use wp option list --autoload=on --format=total_bytes, the command most guides give. It counted only about half of that total on the same store, because it doesn't count the options saved as auto, which WordPress 6.6 and later load too.

Site Health shows more than 800 KB as a critical issue, and since WordPress 6.6 a new option over 150 KB isn't autoloaded unless its plugin asks for that explicitly. You can delete an option from a plugin you've removed, after a database backup. For an option you still use, wp option set-autoload <name> off keeps the option but stops loading it on every page. Run wp cache flush afterwards, so Redis deletes the old alloptions.

Redis settings for a WooCommerce store

Ubuntu's redis-server package starts with defaults made for a database whose data must never be lost, and a cache needs different ones. On a server with more than one site, the plugin needs its own settings too.

Give Redis a memory limit and let it evict

I installed redis-server from Ubuntu's own packages on 24.04 and on 26.04 to see what a new server gets, and both started with maxmemory 0 and the policy noeviction. Zero means there's no limit, so the cache can grow until the server has no free memory left. Once you set a limit, the policy decides what a full Redis does, and with noeviction, redis.io says, "the server will return an error when you try to execute commands that cache new data".

Then I filled a 4 MB Redis with noeviction on purpose: I opened every product and category page of the test store once, which needs more than 4 MB of cache. When Redis couldn't store more data, 537 of the 612 pages answered with an HTTP 500 error and the plugin's message for a missing Redis. PHP's log had the real cause: "OOM command not allowed when used memory > 'maxmemory'".

A plain WordPress error box on a phone: Error establishing a Redis connection. To disable Redis, delete the object-cache.php file in the /wp-content/ directory.
What a shopper got on the product page once the 4 MB Redis with noeviction was full: an HTTP 500 with this message

The commands on the server didn't show the problem clearly either. wp redis status still said Connected with no errors, but wp option get stopped with the same message as the pages.

The same 4 MB Redis with allkeys-lru served every page. Redis evicted (removed) 26,634 keys during the test, and after the test the product page still loaded in 118 milliseconds.

The plugin also has a safety setting, WP_REDIS_GRACEFUL. When I set it to true in wp-config.php, the full noeviction Redis also served every page, and the errors went to PHP's log, not to shoppers.

For a cache, redis.io calls allkeys-lru "a good default option": when Redis is full, it deletes the keys that nobody has asked for in the longest time, and WordPress writes them again the next time a page needs them. How much memory does Redis need? After I'd opened every product and category page of the test store once, Redis held 24,856 keys in 11 MB, so 256 MB is far more than a store of that size needs. The evicted_keys line in redis-cli INFO stats shows you when Redis doesn't have enough memory for your store.

Switch persistence off

The same package also saves a snapshot of everything in Redis to disk at fixed intervals (save 3600 1 300 100 60 10000: after an hour if one key changed, earlier when more keys changed). For each snapshot, Redis starts a copy of itself with fork(), and on a cache that changes all day, that's useless work, because WordPress can build every key again from MariaDB. redis.io lists running with no persistence as an option that's "sometimes used when caching". The cost comes once, after a restart: the first page views find the cache empty and fill it again.

# /etc/redis/redis.conf: Redis as a cache next to WordPress
maxmemory 256mb
maxmemory-policy allkeys-lru
save ""
appendonly no

# PHP on the same server reaches Redis through a socket file instead of TCP
unixsocket /run/redis/redis-server.sock
unixsocketperm 770

Keep Redis on the web server, and use its socket

Where Redis runs is even more important than where the database runs, because a page sends more commands to Redis than it sent queries to the database. I kept the database 2 milliseconds away and tested Redis in different places:

How PHP reached RedisRedis round tripProduct pageCart
No Redis329 ms415 ms
Same server, TCP0.11 ms152 ms201 ms
Same server, unix socket0.08 ms141 ms191 ms
Another server0.38 ms216 ms258 ms
Another server, farther1.90 ms547 ms551 ms

The database is 2 ms away in every row. Server time is the median of three runs of 40 requests each; the Redis round trip is the median of 1,000 PING commands sent from PHP.

A Redis 1.9 milliseconds away made the product page 218 milliseconds slower than no Redis at all. Each call to Redis now had the round trip time that the queries no longer had. If a host sells Redis as a separate service, don't use it before you've measured the distance: redis-cli -h with the Redis address and --latency prints the round trip in milliseconds.

The socket file saves a little more time: a PING took 0.08 milliseconds instead of 0.11, and the pages were about 10 milliseconds faster than over TCP. You'll need two lines in redis.conf, two in wp-config.php and one permission: on Ubuntu, the socket belongs to the redis user and group, so PHP-FPM's user (www-data) has to be in that group. Before I added it, my test got "Permission denied".

root@server:~# usermod -aG redis www-data
root@server:~# systemctl restart redis-server php8.3-fpm

One Redis for several sites

Redis Object Cache puts its keys into Redis database 0 unless you set another number, and a flush (the plugin's Flush Cache button or wp cache flush) deletes everything in that database. I wrote the test store's cache into one Redis twice, with the prefixes shopa and shopb, like two sites that share one Redis, and flushed shopb. The key counts from redis-cli --scan:

before: shopa 60 keys, shopb 60 keys
after wp cache flush on shopb: shopa 0 keys, shopb 3 keys

The prefix kept the keys separate, but the flush deleted the keys of both sites. Two settings fixed this in my test. With WP_REDIS_SELECTIVE_FLUSH set to true, a flush deletes only the keys with the site's own prefix. With a separate database number for each site (WP_REDIS_DATABASE, from 0 to 15, because Redis has 16 databases), the flush stays inside that database. With either setting, shopa didn't lose a key.

Turn on Redis Object Cache and check that it's connected

I tested these steps on Ubuntu 24.04 with nginx and PHP-FPM 8.3, as root. On another Ubuntu release, the PHP version in the package and service names isn't 8.3. On a managed plan that offers Redis, the host switches Redis on for you, and the last two steps still apply. (I install the whole server from the beginning in my WordPress and WooCommerce install on Ubuntu 26.04.)

  1. Install Redis and PhpRedis with apt install redis-server php8.3-redis. On Ubuntu 24.04 that gave me redis-server 7.0.15 and PhpRedis 5.3.7.
  2. Add the lines of the redis.conf block from Switch persistence off to the end of /etc/redis/redis.conf, then run the usermod and systemctl commands from the socket part.
  3. Copy the four WP_REDIS constants of the wp-config.php block into your wp-config.php, anywhere before the "stop editing" comment.
  4. In wp-admin open Plugins > Add Plugin, search for Redis Object Cache, install and activate it, then open Settings > Redis and press Enable Object Cache. With WP-CLI it's wp plugin install redis-cache --activate, then wp redis enable.
  5. Check that the same screen says Connected, or run wp redis status.
// wp-config.php: Redis Object Cache through the local socket
define( 'WP_REDIS_SCHEME', 'unix' );
define( 'WP_REDIS_PATH', '/run/redis/redis-server.sock' );
define( 'WP_REDIS_PREFIX', 'shop1' );   // a short name, different for each site
define( 'WP_REDIS_DATABASE', 0 );       // 0 to 15, a different number for each site

This is what wp redis status printed on the test store. I removed everything after the first six lines; the rest lists the plugin's cache groups:

root@server:/var/www/example.com# sudo -u www-data wp redis status
Status: Connected
Client: PhpRedis (v6.3.0)
Drop-in: Valid
Disabled: No
Ping: 1
Errors: []
...
Redis Object Cache 3.0.0 settings on a phone, Overview tab: Status Connected, Filesystem Writeable, Redis Reachable; under Connection: Client PhpRedis (v6.3.0), Host redis, Port 6379, Database 0, Connection Timeout 1s, Read Timeout 1s, Redis Version 7.4.11; buttons Flush Cache and Disable Object Cache
Settings > Redis on the test store: Connected and Reachable mean the plugin found Redis, not that Redis has free memory

Connected only means that the plugin reached Redis, and my full test Redis also said Connected. So check the memory lines too. On that full test Redis, they said:

root@server:~# redis-cli INFO memory | grep -E '^(used_memory_human|maxmemory_human|maxmemory_policy):'
used_memory_human:4.00M
maxmemory_human:4.00M
maxmemory_policy:noeviction

Used memory at the limit with noeviction is the state that broke my test store. With allkeys-lru and free memory, the store keeps working.

A test server can't show you what your shoppers get, so after the change I'd watch the server response time of real visits for a week. We make a free tool for that, Core Web Vitals Monitoring.

Core Web Vitals Monitoring

See whether real shoppers get faster pages after the change to Redis

One snippet, or our WordPress plugin, shows the speed and traffic of every real visit, with an email the week your store gets slower. Free accounts keep 7 days of history, Premium keeps 180.

Start free monitoring

On a store whose database runs on the same VPS, I'd fix the page cache rules and remove the large autoloaded options first, and add Redis when the store gets more plugins or the database moves to another server. On a store whose database is a separate service, I'd put Redis on the web server immediately.

Sources

WordPress developer reference, WordPress core, WP-CLI and 4 more
7
Learning CenterModule 15/17Hosting & Security

This guide is part of the free CartHacker Learning Center. Create an account to track your reading progress and keep your place across devices, with playbooks matched to your store's platform.

Progress tracking is paused0%
Mark as Read
16Up NextHow Fast Are Online Stores? Speed Data by Platform 19 min readTrends & Strategy

Discussion 0

Ask a question or share what you saw on your own store.

No comments yet.