0

Consider a multi-tenancy rails application. How would I namespace my redis connections on a per-request basis such that each tenant lives in its own namespace?

Multi-tenancy

For multi-tenancy, I'm using the apartment gem. The tenant is determined on each request by reading out the request.host.

# config/initializers/apartment.rb
#
Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|
  Tenant.find_identifier_by_host(request.host)
}

Redis

Redis is used for sidekiq, redis-analytics and, most importantly, rails caching using redis-rails.

# config/initializers/cache.rb
# http://stackoverflow.com/a/38619281/2066546
#
Rails.application.config.cache_store = :redis_store, {
  host: ENV['REDIS_HOST'],
  port: '6379',
  expires_in: 1.week,
  namespace: "#{::STAGE}_cache",
  timeout: 15.0
}
Rails.cache = ActiveSupport::Cache.lookup_store(Rails.application.config.cache_store) 

# config/initializers/redis_analytics.rb
#
RedisAnalytics.configure do |configuration|
  configuration.redis_connection = Redis.new(host: ENV['REDIS_HOST'], port: '6379')
  configuration.redis_namespace = "#{::STAGE}_redis_analytics"
end

# config/initializers/sidekiq.rb
#
Sidekiq.configure_server do |config|
  config.redis = {host: ENV['REDIS_HOST'], port: '6379', namespace: "#{::STAGE}_sidekiq", timeout: 15.0 }
end
Sidekiq.configure_client do |config|
  config.redis = {host: ENV['REDIS_HOST'], port: '6379', namespace: "#{::STAGE}_sidekiq", timeout: 15.0 }
end

Thank you very much for any suggestion you might have!

fiedl
  • 5,667
  • 4
  • 44
  • 57

2 Answers2

1

Using namespaces is a terrible way to isolate Redis in an attempt at multi-tenancy. Insider this: you have one single password for the instance. There is no concept of users in Redis.

There is nothing preventing user A from issuing a flushall and wiping out every bit of data for every "tenant". Nothing at all. Nor is there anything preventing user B from issuing a select command to get to other tenants' data.

Redis is single threaded. It customer C issues a sleep command it blocks the server for everyone. A keys command on a DB with lots of keys will result in the entire server being blocked until it completes.

Redis is not designed for multi tenant use. Attempting to shoehorn it into one will result in problems. If you truly need multi tenant usage, use something else or run individual instances for each tenant.

The Real Bill
  • 14,884
  • 8
  • 37
  • 39
  • Thanks! Which alternative would you recommend as an replacement? I'm experiencing the effect of unicorn workers waiting for redis already. Or, can one have multiple (synchronized) redis instances and a load balancer which knows which instance**s** belong to which tenant? – fiedl Nov 18 '16 at 07:35
0

You could use a redis database feature for this. So after creating the redis connection on a request send select $id to redis, where id is different for each tennant.

Each database has its own keyspace, so no interferences are to be expected. By default 16 dbs are allowed, but you can configure as much as you need in redis.conf.

See also http://redis.io/commands/select

edlerd
  • 2,145
  • 1
  • 16
  • 24
  • From the initializers I've pasted into the question I would think that the redis connections are established on application startup. Is this correct? How would I manage to create a new connection on each request? Is this a good idea considering performance? Thanks! – fiedl Nov 17 '16 at 15:42