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.
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.
Once you've added the line
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!
In the next post in this series, we'll tackle our next dynamic data store: the Rails cache.