A collection of notes and some tips about using Redis.
Redis is super easy to setup, and in dev mode often just works right out of the box, but as you leverage and scale it inproduction, you might want to think more about it’s setup beyond just setting a default REDIS_URL
ENV var. Often a basic Redis for simple product is just setup like so…
Redis.current = Redis.new(url: ENV['REDIS_URL'])
This has some issues:
Redis.current
is deprecated
A better setup adding in configurable options:
Redis.new(
url: ENV['REDIS_URL'],
timeout: ENV.fetch("REDIS_TIMEOUT", 1).to_i,
reconnect_attempts: ENV.fetch("REDIS_RECONNECT_ATTEMPTS", 3).to_i,
reconnect_delay: ENV.fetch("REDIS_RECONNECT_DELAY", 0.5).to_f,
reconnect_delay_max: ENV.fetch("REDIS_RECONNECT_DELAY_MAX", 5).to_f
)
If you are wanting to configure a Redis and use it across threads, using a Redis connection pool is recommended.
pool_size = ENV.fetch("RAILS_MAX_THREADS", 10)
redis_pool = ConnectionPool.new(size: pool_size) do
Redis.new(
url: ENV['REDIS_URL'],
timeout: ENV.fetch("REDIS_TIMEOUT", 1).to_i,
reconnect_attempts: ENV.fetch("REDIS_RECONNECT_ATTEMPTS", 3).to_i,
reconnect_delay: ENV.fetch("REDIS_RECONNECT_DELAY", 0.5).to_f,
reconnect_delay_max: ENV.fetch("REDIS_RECONNECT_DELAY_MAX", 5).to_f
)
end
Although this means when using it you need to grab a pool connection first
# original style, which is deprecated and would block across threads
Redis.current.get("some_key")
# utilizing a pool
redis_pool.with do |conn|
conn.get("some_key")
end
thx @ericactripp, for sharing the link about connection pools
All the above helps when you are working with Redis directly, but often we are configuring common libraries with Redis, how many of them are able to leverage the same kinds of benifits like a connection pool?
inherit_socket: true
as described as one option to avoid needing a connection pool.# common config that won't leverage a redis connection pool
config.cache_store = :redis_cache_store, {
url: ENV["REDIS_URL"],
}
# by setting the pool side and timeout, you can leverage a connection pool with your Redis
config.cache_store = :redis_cache_store, {
url: ENV["REDIS_URL"],
pool_size: 8,
pool_timeout: 6
}
If you have an app that is making many sequential Redis calls, there is a good chance you could make a significant improvement by leveraging Redis pipelining or Mget. I think that that the Flipper codebase is a great way to learn and see various Ruby techniques. It is high quality and has a wide adoption so you can trust it has been put through the paces. If you want to dig into combinging calls, read about the differences between pipeline and mget in terms of code and latency.
As long as we are updating some of our calls, worth being aware of another depracation. “Pipelining commands on a Redis instance is deprecated and will be removed in Redis 5.0.0.”
redis.pipelined do
redis.get("key")
end
# should be replaced by
redis.pipelined do |pipeline|
pipeline.get("key")
end
If you are looking to do a bit more than the default Rails.cache
capabilities with Redis, you will find it supports a lot of powerful feature, and can along with pipelining be extremely performant. If you are looking to push for as performant as you can, setup hiredis-rb as your connection for redis-rb. It uses C extensions to be as performant as possible. This post goes into some details where direct caching wiht Redis can provide more powerful capabilities than using Rails.cache
A few useful tips around using Redis and understanding how your application is using Redis.
brew install redis
: install redis via homebrewbrew uninstall redis
: uninstall redis via homebrewbrew info redis
: Get info on currently installed redisredis-cli ping
: Check if redis service is runningredis-cli monitor
redis-cli slowlog get 100
A good deal of things will be changing in Redis-rb 5.0, we mentioned Redis.current
and the redis.pipelined
changes. These changes and others help support a move to a simpler and faster redis-client under the hood.
A move to simplify the redis-rb codebase and drop a mutex looks like it will roll out redis-client, which can significantly speed up some use cases. It looks like sidekiq for example with move to this in the near future.
New prototype Redis driver cuts Sidekiq job processing time from 61 sec to 39 sec. Incredible improvement! https://t.co/ZzIw7koMiq
— Mike Perham 🇺🇦 (@getajobmike) March 23, 2022
Update: Looks like that perf win was a bit to good to be true.
Sadly I have to walk this back. Looks like there was a bug in the new driver and the performance is not significantly different. https://t.co/2o7cgAUX0K
— Mike Perham 🇺🇦 (@getajobmike) March 28, 2022