A Manageable Multi-Database Redis Development Setup

Redis has quickly become the companion to Postgres in most of our Rails apps. This creates a problem for development, since a Redis server only uses numbered databases. That's 1-16 by default. Hardly memorable, unless your app is called "5", in which case you're golden. On a single machine alone, it's easy enough to lose track of them once you have a few apps connecting to redis for both development and testing databases. Once you have multiple developers and designers working on an app, it's even worse. The solution I've found is to run a separate Redis server for each app, listening via a named socket.

This is easy to set up. First, take your standard Redis config file (mine is /usr/local/etc/redis.conf, thanks to Homebrew) and copy it to redis-common.conf. Edit this new file and change its port to port 0, ensuring it doesn't listen over TCP.

Then, create a separate config file for each app you want to use Redis. I call them /usr/local/etc/redis-server-<app_name>.conf. These files are nice and short:

include /usr/local/etc/redis-common.conf
pidfile /usr/local/var/run/redis-app_name.pid
unixsocket /tmp/redis-app_name.sock
dbfilename dump-app_name.rdb
vm-swap-file /tmp/redis-app_name.swap

These per-app config files are enough to run an entire Redis instance: they load the common config, followed by the app-specific paths that allow the server to run in isolation from other apps.

You can then start the Redis server like this:

redis-server /usr/local/etc/redis-server-app_name.conf

Currently, I have 5 different app-specific configs like this. This makes bringing Redis up and down a bit of a chore. I simplified the process with a couple of shell functions:

function redstart {
  redstop
  for file in `ls /usr/local/etc/redis-server-*.conf`; do
    redis-server $file
  done
}

function redstop {
  for file in `ls /usr/local/etc/redis-server-*.conf`; do
    pidfile=`grep pidfile $file | awk '{print $2}'`
    if [ -f $pidfile ]; then
      kill `cat $pidfile`
    fi
  done
}

Easy. Redis up and Redis down in one hit.

There's another benefit to running Redis like this: loading production data onto your development machine. Most cloud-hosted Redis providers give you backups in Redis' .rdb format, which is the data file for the whole server. You can't load this for a single numbered database. Splitting your local Redis servers by app means you can just replace the file specified in dbfilename with this backup file and now your app has the latest production data without interfering with any other app's Redis data.

Bonus: Redis config management functions

Want an easier way to build and manage those per-app config files? Here you go:

function redls {
  ls /usr/local/etc/redis-server-*.conf
}

function redgen {
  if [ -z "$1" ]; then
    echo "You need to specify an app name, eg. redgen decafsucks"
    return 1
  fi

  app=$1
  config="/usr/local/etc/redis-server-${app}.conf"

  redstop

  echo "include /usr/local/etc/redis-common.conf"     > $config
  echo "pidfile /usr/local/var/run/redis-${app}.pid"  >> $config
  echo "unixsocket /tmp/redis-${app}.sock"            >> $config
  echo "dbfilename dump-${app}.rdb"                   >> $config
  echo "vm-swap-file /tmp/redis-${app}.swap"          >> $config

  redstart
}

function redrm {
  if [ -z "$1" ]; then
    echo "You need to specify an app name, eg. redrm decafsucks"
    return 1
  fi

  redstop
  rm -f "/usr/local/etc/redis-server-${app}.conf"
  redstart
}

Bonus: Rails redis initializer

Here is the do-it-all Redis initializer I use in my Rails apps:

require "redis"

if (redis_url = ENV["REDIS_URL"] || ENV["REDISTOGO_URL"]).present?
  uri = URI.parse(redis_url)
  REDIS = Redis.new(
    host: uri.host,
    port: uri.port,
    password: uri.password)
else
  redis_socket = "/tmp/redis-myappname.sock"
  if File.exist?(redis_socket)
    REDIS = Redis.new(
      path: redis_socket,
      db: !Rails.env.test? ? 0 : 1)
  else
    REDIS = Redis.new(
      host: "localhost",
      port: 6379,
      db: !Rails.env.test? ? 0 : 1)
  end
end

This looks for Redis URL ENV vars first, which are used on Heroku to specify the Redis location. If they're not found, we must be in development, so it looks for the named Redis socket for the app (change redis_socket to make it your own). If that's there, it uses database 0 for Rails development and production modes, and database 1 for test. Finally, we don't want to fail completely if someone hasn't yet starting using the named redis sockets, so we fall back to the standard Redis TCP port.