Blog

Multi-Tenant Applications: Redis

Redis is a very fast, very flexible key-value store, with a number of data types available. It's convenient for data that needs to be accessed quickly, but still persisted (unlike, say, memcached). But we're not here to convince you to use Redis – and you're probably already using it anyway.

Separating tenant data in Redis

To separate various tenants' data in Redis, we have – like SQL – two options. The first is to maintain independent Redis server instances, and switch out the connection per request. Of course, this has the disadvantage of reducing or eliminating reuse of the connection pool, as the Rails server must open a new set of connections to the Redis server on each web request.

Instead, we recommend using a simple "namespace" concept to identify each key with its tenant.

Redis namespaces

When working with Redis, it's customary to namespace keys by simply prepending their name with the namespace, i.e. namespace:key_name. We often do this manually, and write classes to encapsulate this process, so that other parts of our applications needn't be aware of it.

Implementation

The redis-store gem, a dependency of redis-rails, includes a namespacing functionality that further simplifies working with namespaced keys.

Once you've added the line

gem redis-rails

to your Gemfile, you can switch namespaces at any time in the lifecycle of handling a request. The method Redis.current returns the current connection to Redis, as you're probably already aware and using. But redid-rails adds a subclass, Redis::Namespace, that automatically (and transparently) namespaces every request sent through it. We add it to our tenant-switching middleware like so:

def switch_redis(tenant)
  if Redis.current.is_a? Redis::Namespace
    Redis.current.namespace = tenant
  else
    Redis.current = Redis::Namespace.new(tenant, redis: Redis.current)
  end
end

A theme emerges

You'll recall from the previous post in this series that we advocated a similar process to transparently (via the Apartment gem) switch SQL databases in middleware as well. This isn't coincidence: by pushing as much of the multi-tenant aspects of our application to the edges (and in particular the middleware), we're able to continue writing the majority of the application as if it were single tenant. Indeed, this mimics the reality of most complex multi-tenant applications, where the end user is meant to be unaware that they're visiting a multi-tenant app. We're just taking that a step further, and making the application itself unaware that it's multi-tenant!

Up next

In the next post in this series, we'll tackle our next dynamic data store: the Rails cache.