WordPress. Poor performance out of the box
Most serious frameworks come with some kind of persistent cache readily available. Sadly, WordPress is not such a framework. By default, it will only make use of the object cache that has a lifetime of… a request. So this only helps to improve performance by a tiny bit. Because the cache lives only for the time when a page is loading. On a new request to the same page, WordPress has to query SQL database all over again with likely all the same queries…
To bring WordPress closer to more mature frameworks, you can and should install a persistent cache plugin.
In this post, we review both the basics of full-page cache vs persistent object cache and how to supercharge your WordPress performance using the latter.
What is a persistent object cache
Let’s clear up some terminology. And let’s not confuse persistent object cache and full page cache (FPC).
Persistent object cache, in WordPress, is storage with frequently accessed data (often configuration). It typically does not store fully generated pages’ HTML. Instead, it stores pieces of data which are expensive to generate; blocks of HTML; configuration data. By storing those in the cache, WordPress can greatly reduce the number of queries against the MySQL server, and reduce CPU usage in general. Persistent object cache helps to display highly dynamic pages, like checkout, or WP admin, faster. An example of persistent cache storage in other frameworks is Magento’s “general” cache.
The full-page is another cache that stores completely generated page HTML and it completely eliminates PHP processing on a cached page. It is efficient with mostly static pages like a blog post.
On any dynamic website, you want both cache types to be enabled:
- Full-page cache, for “static” pages like blog posts
- Persistent Object Cache, for pages wherever full-page cache is not applicable: checkout pages, account pages, etc.
Configure persistent object storage powered by Redis
Redis is a highly efficient database engine which often used as cache storage.
For our persistent cache implementation, we need both the Redis server and PHP PECL extension which allows PHP code to communicate with Redis.
Install Redis
Redis 6 is now generally available with threading for I/O.
Using I/O threads it is possible to easily speedup two times Redis without resorting to pipelining nor sharding of the instance.
The following steps for a CentOS/RHEL 8 server, will install Redis 6, as well as the latest PHP 7.4 and Redis PHP extension:
dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
dnf -y install yum-utils
# installing latest PHP-FPM, as well as common extensions required by WordPress
dnf -y module reset php
dnf -y module install php:remi-7.4
dnf -y install php-common php-fpm php-opcache php-mysqlnd php-mbstring php-json php-pecl-zip php-soap php-gd php-pecl-imagick php-pecl-apcu php-xml php-tidy php-pecl-memcached php-ioncube-loader
# installing php-redis extension
dnf -y install php-redis
# installing Redis 6
dnf module reset redis
dnf module install redis:remi-6.0
If you’re upgrading your installing, ensure restarting PHP-FPM with:
systemctl restart php-fpm
Configure Redis
By default, threading in Redis is disabled.
The documentation suggests enabling it only in machines that have at least 4 or more cores, leaving at least one spare core.
Using more than 8 threads is unlikely to help much.
So for instance if you have four cores boxes, try to use 2 or 3 I/O threads, if you have 8 cores, try to use 6 threads.
In order to enable I/O threads use the following configuration directive in /etc/redis.conf
:
io-threads 4
The default Redis configuration that is shipped by the Remi repository’s package, does not set a memory limit on your Redis instance.
This is because Redis is both a cache and a database.
Since entries in a cache might set with “forever” expiration, we are better to add a fail-safe.
Setting the memory limit is applicable for our use case of Redis as a cache.
Likewise, in /etc/redis.conf
, specify also the following:
maxmemory 64mb
maxmemory-policy allkeys-lfu
The allkeys-lfu
is an eviction policy that removes any key using approximated LFU.
This will ensure that frequently used data stays in the cache, while the least used data can be removed in order to keep our cache size to a sane limit.
For a typical all-in-one server setup, it is recommended to use UNIX sockets for inter-process communication.
Thus, in the same configuration file, enable Redis to listen on a UNIX socket with these lines:
unixsocket /var/run/redis/redis.sock
unixsocketperm 0660
Next, start and enable Redis:
systemctl enable --now redis
Authorize website user for Redis access
A proper PHP-FPM permissions setup includes a separate UNIX user for each website.
If you’re not aware of the concept, check out “NGINX and PHP-FPM. What my permissions should be?”).
We should authorize our website user, e.g. example
, to read and write to the Redis UNIX socket.
This can be easily done by adding it to the redis
supplementary group:
usermod -a -G redis example
Enable Persistent Object Cache in WordPress
Now we’re ready to set up the persistent object cache in WordPress.
For this, we need a plugin that is capable of talking to Redis and saving cache data there.
There are two prominent plugins that do the job.
Using the “Redis Cache” plugin
This is the most popular plugin. This is because it has some unique features like ignoring cache on some data.
First, ensure Redis connection details at the top of your wp-config.php
:
define( 'WP_REDIS_CLIENT', 'phpredis' );
define( 'WP_REDIS_SCHEME', 'unix' );
define( 'WP_REDIS_PATH', '/var/run/redis/redis.sock' );
define( 'WP_REDIS_DATABASE', 0 );
If you set up the Redis cache for different WordPress websites on the same server, ensure unique database
values for each of them!
To set up the plugin, use WP-CLI:
wp plugin install redis-cache --activate
wp redis enable
Head over to configuration options for more details about advanced configuration.
Using “WP Redis” plugin
WP Redis is one such plugin.
First, ensure Redis connection details at the top of your wp-config.php
:
$redis_server = array(
'host' => '/var/run/redis/redis.sock',
'database' => 0, // Optionally use a specific numeric Redis database. Default is 0.
);
If you set up the Redis cache for different WordPress websites on the same server, ensure unique database
values for each of them!
To set up the plugin, use WP-CLI:
wp plugin install wp-redis --activate
wp redis enable
That’s it, things are going to be faster. Read the next section for more details.
How does it all work?!
Even for a WordPress developer, it may be hard to immediately grasp how things work.
There are many terms related to WordPress, and many choices as well.
A few questions you might have if you’re coding themes/plugins, or otherwise interested in how Persistent Object Cache is beneficial:
- I’ve enabled the Persistent Object Cache plugin. Since it provides only an API, will there be any improvement in site load?
- Which plugins support Persistent Object Cache and thus will benefit from it?
- How do I use this cache in my custom code?
The plugins which will benefit from persistent caches are the ones that invoke either wp_cache_*
or set_transient
functions. You might already have a few plugins installed that use those functions. WordPress Admin definitely does use those.
The difference between the two sets of functions is that the set_transient
(in absence of a persistent object cache plugin) will be always persist in the MySQL database in the wp_options
table.
So the immense benefit we can have from the mere act of setting up Persistent Object Cache as we did, is that a lot of queries against wp_options
will be gone,
and thus performance will be improved.
Subsequently, we can shrink wp_options
because the plugin will essentially ensure that transients are stored in Redis!
wp transient delete-all
Success: 243 transients deleted from the database.
Warning: Transients are stored in an external object cache, and this command only deletes those stored in the database. You must flush the cache to delete all transients.
You can monitor the Redis cache memory usage by running redis-cli info memory
and will display some stats.
Watch the used_memory_human
, and if is coming close to the configured memory limit, you might want to increase your cache size.
# Memory
used_memory:3648912
used_memory_human:3.48M
used_memory_rss:4673536
used_memory_rss_human:4.46M
used_memory_peak:4283992
used_memory_peak_human:4.09M
used_memory_peak_perc:85.18%
used_memory_overhead:861640
used_memory_startup:804832
Conclusions and what will really be faster
The wp-admin
is one example location where it uses transients API for update checks.
You will find immense improvement in its performance, and beyond that, other dynamic pages will have a noticeable performance increase.
No custom coding required! And if you are developing something of your own, use wp_cache_*
(or set_transient
) in your code, where applicable.
A persistent object cache is a must-have cache type for WordPress.
It can greatly complement a full page cache like Varnish.