Speed Up Your Drupal Website With Redis

In a previous post I briefly went through getting a basic instance of Redis going and how at a code level you to store and retrieve data. In this article I’m going to try to be a little more practical about things and actually use it for something. Specifically, we’re going to look at replacing Drupal’s database-oriented caching/locking system with Redis’s in-memory caching system.

I’m working off a fresh install of redis and Drupal but I’m going to side-step the installation procedure for each since you would usually do this to an already functioning website you wanted to integrate with your existing redis infrastructure.

For reference this is the system I’m using but the instructions found here should be widely applicable:

  • Platform: Ubuntu 16.04 (Xenial)
  • PHP Version 7.0 configured as FastCGI (PHP-FPM)
  • MariaDB 10.0.31
  • Drupal 7 (but the steps here should be identical for Drupal 8).

Contents

When Redis Caching Makes Sense

Considering database management systems often contain some of the performance-oriented behaviors (such as keeping data in-memory) you would get from Redis you may wonder: What use Redis does have for an application that’s already going to have a full DBMS?

Indeed, if it serves no purpose it’s just another thing that can break and make troubleshooting more convoluted. The primary reason you would use Redis for the Drupal cache to be moved to be more local to the application.

You might prefer a cache local to your application for several reasons but mainly:

  • You’re interested in keeping network out of the loop and want Drupal to pull the cache entries from something running on the local system rather than making another database query.
  • The server the database runs on is shared amongst several application. Moving to a small private Redis instance keeps the data handy without contending with other users during peak load.

Getting PHP Ready

Before we move onto modifying Drupal itself, we need to make sure PHP can even communicate with Redis. To do that we need to install the redis PECL module.

Since compilation of the extension via pecl requires access to PHP development libraries and headers I’ll also need to make sure the php-dev package is installed first.

Then we do the actual install of the PECL extension:

root@2287b3975d3a:/# pecl install redis
 downloading redis-3.1.4.tgz ...
 Starting to download redis-3.1.4.tgz (199,559 bytes)
 ..................done: 199,559 bytes
 20 source files, building
 running: phpize
 Configuring for:
 PHP Api Version: 20151012
 Zend Module Api No: 20151012
 Zend Extension Api No: 320151012
 enable igbinary serializer support? [no] :
 building in /tmp/pear/temp/pear-build-defaultuserPAA1mj/redis-3.1.4
 running: /tmp/pear/temp/redis/configure --with-php-config=/usr/bin/php-config --enable-redis-igbinary=no
 checking for grep that handles long lines and -e... /bin/grep
 checking for egrep... /bin/grep -E
 [...snip...]

Now we need to enable the extension in the appropriate php.ini file (or inclusion thereof). Knowing the proper .ini file to write to varies according to the platform and how you’re using PHP so in general you’ll have to figure that out on your own. My install though uses PHP-FPM on Ubuntu though:

root@2287b3975d3a:/# echo "extension=redis.so" >> /etc/php/7.0/fpm/conf.d/90-redis.ini

We’ll now need to refresh PHP for this to take effect, so I issue a kill -SIGUSR1 (where masterPID is the pid of the FPM master process). This will cause FPM to re-read it’s configuration and do a graceful/non-disruptive restart.

At this point, if you look at the output of phpinfo() (also available from the Drupal status section) you should see the Redis extension having been loaded.

Redis Drupal Module

Now that PHP can talk to Redis, we need to install the redis module so that we have access to its Drupal 7 caching backend.

Installing the module with drush is pretty straight forward:

root@ad4c3eee12b1:/var/www/html# drush en -y redis
 redis was not found. [warning]
 The following projects provide some or all of the extensions not found: [ok]
 redis
 Would you like to download them? (y/n): y
 Project redis (7.x-3.15) downloaded to /var/www/html/sites/all/modules/redis. [success]
 The following extensions will be enabled: redis
 Do you really want to continue? (y/n): y
 redis was enabled successfully. [ok]

Now that it's installed and enabled, we need to update the relevant settings.php for the website to switch the caching backend over to Redis. To configure my install I appended the following: $conf['lock_inc'] = 'sites/all/modules/redis/redis.lock.inc'; $conf['path_inc'] = 'sites/all/modules/redis/redis.path.inc'; $conf['redis_client_interface'] = 'PhpRedis'; $conf['redis_client_host'] = '994.55.193.122'; $conf['cache_backends'][] = 'sites/all/modules/redis/redis.autoload.inc'; $conf['cache_default_class'] = 'Redis_Cache'; $conf['cache_prefix'] = 'drupal7';

Note: your exact path to the redis module may vary. The above is just where my drush command happened to install mine. Other installs may place it underneath contrib

Let’s explain each line we’re adding so you understand what we’re actually doing here:

  • Overriding lock_inc with the redis module’s implementation moves all locking mechanisms from the database and into Redis. Quicker locks mean that contentious operations are finished sooner and thus get out of each others’ way.
  • We’re overriding the default path_inc implementation so that Drupal will now cache URL path mappings in Redis. By default, once Drupal figures out what user-supplied URL results in what “system URL” it stores this result in the database. If the value is already a system URL, the value saved is just an exclamation point.
  • redis_client_interface is used to specify what PHP client we want to use to communicate with Redis. The available choices are Predis (a purely PHP implementation bundled with the redis module) or phpredis (a compiled C extension to PHP). For performance reasons you’re going to want phpredis wherever possible which is why we installed it above.
  • (Optional) redis_client_host this merely sets the hostname/IP address of the Redis server. If left unspecified it defaults to localhost. A related (optional) configuration item is redis_client_port which does the same for the port of the remote Redis service.
  • cache_backends this loads the Redis functions during the Drupal bootstrap process.
  • cache_default_class Sets the actual cache handler class.
  • cache_prefix Sets a unique prefix for this Drupal instance to use when storing values to Redis. This is useful for cases where the Redis instance may be local but shared amongst several installs. Ultimately it’s optional if this redis instance only ever serves as a cache for this one Drupal website.

Troubleshooting Issues

Only issue with this setup I’ve ran into is with clearing the cache from the command line. At the time of this writing, if you issue a drush cc all (to clear all caches) on a Redis-backend cache you will receive an error from drush and an incomplete purge of cached items. To get around this, use the redis-cli command to connect to the server and issue commands directly to clear the cache for you. One possibility is simply a FLUSHDB command to clear all keys in the database out (assuming they’re all easily replaceable) or something similar to redis-cli --raw keys "$PREFIX" | xargs redis-cli del to delete anything that begins with the prefix you gave above.

This is a known issue with a fix available but not yet merged into the redis module yet.

Further Reading