loading...
Cover image for How to Safely Navigate Redis with Ruby

How to Safely Navigate Redis with Ruby

molly_struve profile image Molly Struve (she/her) ・5 min read

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)
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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.

Dont do it man gif

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
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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.

Picture of a redis console embedded in the Redis website

Have fun poking around Redis!

Discussion

pic
Editor guide
Collapse
storrence88 profile image
Steven Torrence

Great read! I'm definitely interested to learn more about Redis' full capabilities and articles like these help a TON! Thanks!

Collapse
ngarbezza profile image
Nahuel Garbezza

Great article! Did you see any performance impact after moving from Memcached to Redis?

Collapse
molly_struve profile image
Molly Struve (she/her) Author

The migration has only just begun 😊

Collapse
lumega profile image
Luis Mejia

thanks for the scan script in ruby