Skip to content

Instantly share code, notes, and snippets.

@strarsis
Last active February 19, 2023 00:29
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save strarsis/95abe28f4761f489a8a1cc212d5c69e5 to your computer and use it in GitHub Desktop.
Save strarsis/95abe28f4761f489a8a1cc212d5c69e5 to your computer and use it in GitHub Desktop.
Object caching with Bedrock (and Trellis) using Redis

Object caching with Bedrock (and Trellis) using Redis

What is object caching

While nginx microcaching already solves page caching (as of static, rarely changing WordPress pages), the performance for dynamically generated pages (like WooCommerce shop pages and admin backend in general) can benefit greatly from additionally using an object cache. Object caching allows an application (in this case WordPress with its plugins, theme, etc.) to store prepared objects (mostly database queries) in a database and quickly retrieve them, therefore improving the performance of dynamic page generation. Object caching is (usually) transparent, which means that it shouldn't be noticeable by users and developers (except for the performance improvements of course).

Implementations

Currently there are three major object caches available for PHP (and WordPress):

  • apcu A PHP caching extension, apcu can be understood as a successor to apc, which is basically apc without the opcode caching as it is natively supported since PHP 7.x
  • Redis A high performance key-value database server (ampng other features) to which PHP can connect to and be used for caching.
  • memcached Similar to Redis.

To enable WordPress object caching, usually a caching plugin is used. There are caching plugins that do much more than just object caching, like page caching (which would be redundant for Trellis setups or where nginx microcaching is already used), on-site or offloaded image optimizations (which may have been already realized in-theme or by using other, more dedicated plugins) and asset concatenation (which may not be important anymore or even detrimental due to HTTP/2 multiplexing). Therefore a dedicated plugin should be preferred, that just realizes object caching and does it well (and configurable).

This guide uses Redis for object caching, primarily because after some research it was found that it is more stable and on par with apcu and memcached performance. If there is interest in using apcu or memcached instead or if you have found mistakes in this guide, please feel free to create a new issue on https://github.com/roots/docs/issues in order to further improve this guide.

Object caching using Redis

Redis server

When you already use Trellis on production for hosting your Bedrock sites, you can add the trellis-redis ansible role to your Trellis ansible setup to easily ensure a local Redis server. Apply the ansible playbook to the server (you may want to test it on a staging server first).

For custom Redis setups, like non-Trellis setups, remote Redis servers/clusters and alike you have to ensure the Redis server is not freely open to the outside world, by limiting its listening range, adding a password, configuring a firewall and so on.

Plugin

This guide uses the WP Redis plugin to enable WordPress object caching for Redis. It is a free, OpenSource, fully functional and well maintained plugin (one of its maintainers is the well-known WordPress hoster Pantheon). The plugin uses configuration variables, which makes it ideal for Bedrock sites (that embraces 12 factor app principles) and also supports the wp CLI tool which many developers already use.

Installation

As usual with Bedrock sites, you add the plugin as a composer dependency to the site's composer.json.

Either copy the version definition from wpackagist.org or from its GitHub repository and paste it into the site composer.json (here its latest version 1.1.1 at time of writing is used, also a ^ was prepended to allow for non-breaking updates).

(A) from wpackagist
(1) As command:

> composer require wpackagist-plugin/wp-redis ^1.1.1

(2) As composer.json:
{
  "repositories": [
    [...]
    {
      "type": "composer",
      "url": "https://wpackagist.org",
      "only": ["wpackagist-plugin/*", "wpackagist-theme/*"]
    }
    [...]
  ],
  [...]
  "require": {
    [...]
    "wpackagist-plugin/wp-redis": "^1.1.1"
    [...]
  }
  [...]
}

Run > composer update.

(B) from GitHub

As composer.json:

{
  "repositories": [
    [...]
    {
      "url": "https://github.com/pantheon-systems/wp-redis",
      "type": "vcs"
    }
    [...]
  ],
  [...]
  "require": {
    [...]
    "wpackagist-plugin/wp-redis": "^1.1.1"
    [...]
  }
  [...]
}

Run > composer update.

Enable

Deploy the changes and enable the plugin.

Configuration

The plugin is installed and enabled now and the Redis server runs and is available. What is left to do is some configuration for the plugin to actually connect and make use of the Redis server. Currently the plugin would show a notice in admin backend that it isn't confiured yet to be used.

The WP Redis plugin we use in this guide uses configuration variables, which is ideal for a Bedrock site. As usual with Bedrock sites one can define environment-specific configuration by using the appropriate configuration file (config/environments/<environment>.php).

// Redis cache server (WP Redis plugin)
$_SERVER['CACHE_HOST']        = 'localhost';
$_SERVER['CACHE_PORT']        = 6379;
$_SERVER['CACHE_PASSWORD']    = '';
$_SERVER['WP_CACHE_KEY_SALT'] = 'my-site.com'; // salt to prevent collisions of cached data between different sites
$_SERVER['CACHE_DB']          = 0; // redis DB index, which can be used as additional means of separating cached site data

When you run multiple sites, but also as a general precaution, a non-empty cache key salt should be defined. One of the canonical/primary site domains or some other site-unique name should be used.

Additionally a Redis database index can be set (from 0 to 15, but a much higher limit can be configured for Redis), which can futher help to keep the cached data separated. With the configuration file in place (saved/deployed), the object cache should now be functional and you should notice improvements in site performance.

For the development environment ([...]/development.php) a different configuration can be used, e.g. a Redis Docker container.

Cache status and stats

The WP Redis plugin status and stats can be retrieved using the wp CLI tool (you can use aliases like wp @production; wp @staging; wp @development to switch between sites on different environments).

wp redis info can be used for caching status and statistics.

Further notes

Performance and profiling

In practice, significant performance improvements on production/staging (Trellis) and development (WSL 2 + Docker) were experienced for WooCommerce and admin backend. No further adjustments were necessary, object caching was enabled and works fine.

Benchmarks

Benchmarks with object caching enabled and disabled and with different caches and configuration for a plain Bedrock site, Sage theme and Trellis server setup could be helpful.

Profiling

For further improving the performance you may want to use a WordPress profiling plugin like Laps. E.g. there may be the case that, due to a bug, a plugin does a custom, uncached HTTP request on each page request, drastically reducing the performance, even with an object cache. Using profiling can help to track these causes down.

@perforsberg77
Copy link

Hi,

Thanks for a great guide, but I can´t make it work. It looks like the php-redis module is not correctly installed, but I don´t get any error messages when ansible installing or provisioning, but when i run phpinfo() I can´t find the module. In wp dashboard I get message "Warning! PHPRedis extension is unavailable, which is required by WP Redis object cache." I use the https://github.com/im-mortal/trellis-redis fork.

Any ideas?

@strarsis
Copy link
Author

strarsis commented Mar 23, 2021

@perforsberg77: Have you completely ran the Trellis playbook (using ansible-playbook) with the redis role added?
Note that provisioning (apply playbook) of the Trellis server is not the same as deploying a particular site to the Trellis server.

@perforsberg77
Copy link

I ran "ansible-galaxy install -r galaxy.yml --force" and after that "vagrant reload --provision". What else should I run?

@strarsis
Copy link
Author

strarsis commented Mar 24, 2021

@perforsberg77:

  1. What PHP version are you currently using on the Trellis system?
  2. Does php -m |grep redis on the Trellis server (no root needed) list redis?
  3. Is there a PHP configuration file to actually load the PHP Redis extension, as under /etc/php/7.4/fpm/conf.d/20-redis.ini?
    Does the folder with the PHP release (as 7.4) match the one you are currently using?

@perforsberg77
Copy link

  1. 7.4.14
  2. No, doesn´t list anything
  3. No, can´t se no 20-redis.ini file.
  4. This php.ini file is running on trellis server /etc/php/7.4/fpm/php.ini

@strarsis
Copy link
Author

strarsis commented Mar 24, 2021

@perforsberg77: OK, so it looks very much that the Trellis redis role is not used and applied/installed by your Trellis playbook.
Have you added that Trellis redis role in the Trellis playbook?

(From the Trellis Redis role README)

Then, add the role into both server.yml and dev.yml:

roles:
... other Trellis roles ...

  • { role: trellis-redis, tags: [redis]}

@perforsberg77
Copy link

Yes, in both server.yml and dev.yml

The logs confirms and looks good:

  • changing role trellis-redis from 0.2.6 to 0.2.6
  • downloading role 'trellis_redis', owned by im_mortal
  • downloading role from https://github.com/im-mortal/trellis-redis/archive/0.2.6.tar.gz
  • extracting trellis-redis to /Users/perlindforsberg/projectx/trellis/vendor/roles/trellis-redis
  • trellis-redis (0.2.6) was installed successfully

TASK [php : Install PHP 7.4] ***************************************************
ok: [default] => (item=php-igbinary)
ok: [default] => (item=php-redis)

TASK [trellis-redis : Install Redis] *******************************************
ok: [default]
TASK [trellis-redis : Install php-redis] ***************************************
ok: [default]
TASK [trellis-redis : Configure Redis] *****************************************
ok: [default]
TASK [trellis-redis : Set Redis to Run on Boot] ********************************
ok: [default]

Any other ideas?

@strarsis
Copy link
Author

@perforsberg: Strange, I suspect some issue with the PHP version of the config.
Do you get anything when you run ls /etc/php/**/fpm/conf.d/*redis* ?
This lists all config files with redis in their filenames in /etc/php/.

@perforsberg77
Copy link

No, nothing.

@strarsis
Copy link
Author

@perforsberg77: Are you sure you are applying the Trellis playbook on the right server? And that you are connected to the right server?
Can you find any redis ini files on the server using the locate tool?

locate redis.ini

If you want you can also install the redis server and PHP redis extension manually on the server - ansible should just skip it then.
Does this uncover eventual issues during the installation?

sudo apt install php-redis
sudo apt install redis-server

When the installation of these two packages finished, try to find the PHP redis ini file again:

ls /etc/php/**/fpm/conf.d/*redis*

Does it appear now?

@strarsis
Copy link
Author

@perforsberg77: So I found the issue - the upstream trellis-redis repository has a still unfixed bug that makes it incompatible with latest Ansible:
jasonsbarr/trellis-redis#5

Luckily there is a fork for trellis-redis with a fix branch that can be used instead (github.com/im-mortal/trellis-redis).

The best and easiest way to install it is using the galaxy.yml in the Trellis project (which deviates a bit from the requirements.yml in the Ansible tutorials; also there may be a requirements.txt which is meant for Python extensions that Ansible and its roles could additionally use).
Comment out/remove the reference to the upstream trellis-redis repository, if you have added it.
Then add the reference to the new one:

# Redis server + PHP extension
# Fork with apt fix
- name: trellis-redis
  src: https://github.com/im-mortal/trellis-redis
  type: git
  version: update/apt

Run the ansible-galaxy command to update the roles:

ansible-galaxy install -r galaxy.yml

Now re-apply the ansible playbook and it should install.

@jnpkr
Copy link

jnpkr commented Apr 24, 2021

I've recently provisioned a Trellis site — that was working previously on another server — to a new server and experienced the same issues as above.

I replaced the original trelis-redis reference with the forked one but nothing changed, so I ended up installing redis and igbinary via pecl and then added references to both in the php.ini file and now it's working again.

@strarsis
Copy link
Author

@jnpkr: OK, so the current trellis-redis roles (source and forked) both don't work with latest Trellis/Ubuntu.
Please see jasonsbarr/trellis-redis#5 (comment)

@rsoury
Copy link

rsoury commented Jun 20, 2021

Seems im-mortal/trellis-redis works, however, it needs to install a PHP version specific Redis Extension sudo apt-get install php7.4-redis

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment