This past week we started a big database migration for DEV. We setup a new Redis instance and with the help of A LOT of awesome contributors we began moving all of our existing cache keys to it. I am really excited about moving us over to Redis because Memcache is a bit of a black box. It is very hard to tell what is in it and how we are using it.
For this reason, we have chosen to switch to using Redis for our application cache. Redis will allow us more insight into how we are using the cache and give us more control over our cache keys. Now the question is, how do we go about gaining these insights? How do we sift through all of our Redis keys in a responsible manner while also making sure we don't upset the database in any way? THAT is exactly what I hope to teach you with this post!
redis-rb gem
In order to allow our application to interact with Redis the first thing we did was introduce the redis-rb gem. This gem allows us to talk to Redis via Ruby. Because we have a Rails application, in order to make life a little easier for implementing Redis as our Rails cache we took the Redis client that the gem gave us and wrapped it in the ActiveSupport Redis Cache class.
RedisRailsCache = ActiveSupport::Cache::RedisCacheStore.new(url: redis_url, expires_in: DEFAULT_EXPIRATION)
This allows us to easily change out Rails.cache commands with our new RedisRailsCache. Ok, now that we can talk to Redis, lets dive in and talk about HOW we go about doing that.
Redis Metrics
Many Redis implementations will have some sort of metrics that you can use to see things like memory consumption, connections, traffic in and out, etc. Below is a screenshot of our Redis dashboard in Heroku.
This gives us some good data but it doesn't really tell us much about what keys we are using, how long until they expire, etc. To break this down further, let's move to the command line and a Rails console.
Redis Ruby Client
When using the Ruby Redis client you have access to just about any command that Redis implements. The first thing you have to do before accessing these commands is setup your Ruby client. You can do this a couple different ways according to the Getting Started docs on the gem.
# This assumes Redis was started with a default configuration and is listening on localhost, port 6379
redis = Redis.new
# If you need to connect to a remote server or a different port
redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)
# You can also specify connection options as a redis:// URL:
redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
# For our setup we can get it from our ActiveSupport::Cache::RedisCacheStore object like this
redis = RedisRailsCache.redis
Once you have your Redis client set up you can use it to issue just about any Redis command you want. Below are some commands that you might find useful for poking around your Redis database and exploring your keys.
Redis Commands
INFO
This command is going to give you a lot of good high-level information about your Redis database. It will output a hash and below are a list of a few keys that you will probably find helpful. Most are pretty self-explanatory but I will add some details to a few.
pry(main)>redis.info
=> {"redis_version"=>"5.0.5",
"uptime_in_days"=>"10",
"used_memory_human"=>"40.42M", # Current amount of memory in use/being taken up by key/value pairs
"used_memory_peak_human"=>"41.96M", # The MAX memory you have hit since running Redis.
"total_commands_processed"=>"438487", # Total commands Redis has processed
"rejected_connections"=>"0", # Number of connections rejected. If Redis has to execute a long-running command it might be forced to reject connections. Or if it gets more connection requests than it can handle
"expired_keys"=>"2", # Number of keys that have expired
"evicted_keys"=>"0",
"keyspace_hits"=>"168937", # Number of hits when searching for a key, a hit means you searched for a key and found it in Redis
"keyspace_misses"=>"268187", # Number of misses. A miss results when you search Redis for a key and it does not exist. Ideally, you want your hits to be much higher than your misses otherwise you likely are not getting much of a performance boost from Redis. We have some work to do at DEV ;)
"db0"=>"keys=134091,expires=134091,avg_ttl=348733150"}
Now that we know how to get a birds-eye view of our Redis database, next I want to talk about how you can explore what keys you have in Redis.
⚠️ KEYS
⚠️
First and foremost, whenever you are in a production environment of ANY kind you DO NOT want to execute the keys command. I repeat, NEVER execute the keys command in production.
The keys command will return all keys from Redis OR you can pass it a pattern and it will match the pattern. Feel free to rip it up in your small local environment with this command because it can be useful for finding a group of keys quickly. However, it will wreck your performance if run against a large Redis database. A better option when working in production is SCAN
.
SCAN
SCAN iterates the set of keys in the currently selected Redis database.
This allows you to poke around all of the keys in Redis safely. Here is an example of how you can use scan with your Redis ruby client. Say you want to find all keys that contain the word "user"
# Set your client
redis = Redis.new
# Set a start value to tell Redis where to begin the scan
start = 0
# Optional, if you want to track the total keys initialize a total value
total = 0
# Create an array to stick your "user" keys into
key_collection = []
# Scan through all of the keys selecting those that have "user" in them until you get back to the beginning
index, keys = redis.scan(start);
while index != "0"
start = index
total += keys.count
keys.each do |key|
key_collection << key if key.include?('user')
end
index, keys = redis.scan(start)
end
Once you have your array of keys you can then do whatever you want with that array. One thing you might want to find out about those keys is how long until they expire and for that, you will need this next command.
TTL
Another great command you can use as you are inspecting your keys is ttl
which will allow you to look at the expiration of each key. TTL
in Redis stands for "Time To Live" and returns the remaining time in seconds that a key has to live before it expires. A value of -2 means the key does not exist and a value of -1 means the key does not expire.
redis.ttl("user-key")
GET
Another cornerstone command for inspecting your keys is good ole get
. get
will return the value of the key if it exists and nil if it does not exist.
redis.get("user-key")
=> "user-key-value"
Want to learn more?
Redis has a fabulous set of command docs that you can browse to find whatever command you are looking for. If you click into a command it will tell you what it does AND it will even give you a console for playing with the command under the Examples section.
Have fun poking around Redis!
Top comments (4)
Great read! I'm definitely interested to learn more about Redis' full capabilities and articles like these help a TON! Thanks!
Great article! Did you see any performance impact after moving from Memcached to Redis?
The migration has only just begun 😊
thanks for the scan script in ruby