New Feature on Rails 5.2: Redis Cache Store

  

cache store.jpg

Redis Cache Store is a new feature on Rails 5.2 although support for caching in Rails has been around for some time. A few cache stores already exist like MemCacheStore, FileStore, and MemoryStore. You can also use a custom store so no doubt some people are already using Redis as a cache store.

The new feature on Rails 5.2 is the built-in Redis Cache Store. You don't need to use a custom cache store anymore. All you need is to add a Redis client gem.

This post has the following sections:

Caching in Rails
Redis Cache Store
Hireds
Multiple Redis Servers
Redis in Development
Deploying in Production
Key Value Store
Feedback

Caching in Rails

Let's review how caching in Rails works. Rails supports fragment caching by default. In your views, you can do something like

<% @messages.each do |message| %>
  <% cache message do %>
    <%= render message %>
  <% end %>
<% end %>

This is typical view code. We iterate through @messages and render its partial. But notice the cache message block where render is located. This is fragment caching at work. When Rails runs this code, it will check the cache store if the data exists. If it does, it will use that data. If it doesn't, it will process the partial like usual and then write the data ready to be used for next time.

Redis Cache Store

To use Redis Cache Store, put this on config/environments/production.rb or config/environments/development.rb.

config.cache_store = :redis_cache_store

This uses Redis running on localhost on port 6379. This is mostly useful for development. In production, when using multiple servers running Rails, you'll have to specify the hostname or IP of the server running Redis.

config.cache_store = :redis_cache_store, {url: "redis://192.168.0.10:6379/0"}

In this example, all Rails servers should have access to port 6379 on 192.168.0.10. The 0 at the end is the database number. The default setting of Redis has 16 databases so you can choose any number between 0 and 15 but you usually don't have to change this.

You also have to install the redis gem. Add this to your Gemfile.

gem "redis", "~> 4.0"

When trying this on your development environment, you should also have

config.action_controller.perform_caching = true

Hiredis

Aside from the redis client, you can also use hiredis. The hiredis gem is a wrapper for the minimalistic C client library with the same name. To use this client with Redis Cache Store, pass driver as one of the options

config.cache_store = :redis_cache_store, {driver: :hiredis, url: "redis://192.168.0.10:6379/0"}

You also need to add hiredis to your Gemfile.

gem "hiredis"
gem "redis", "~> 4.0"

Note that you need to add both hiredis and redis. You don't need to add require like gem "redis", "~> 4.0", :require => ["redis", "redis/connection/hiredis"] as mentioned on the Hiredis documentation because Rails loads redis/connection/hiredis for you.

Multiple Redis Servers

You can use multiple Redis servers for sharding. If one Redis server goes down, you'll have cache misses for keys that are stored on that server, but the other keys on the remaining servers would still work. Writes to the unavailable server would be dropped. There would be no exceptions raised and your app will continue running.

To specify multiple servers, pass the url option.

redis_servers = %w[ 
  redis://hostname-1:6379/0
  redis://hostname-2:6379/0
  redis://hostname-3:6379/0
  redis://hostname-4:6379/0
]

config.cache_store = :redis_cache_store, {driver: :hiredis, url: redis_servers}

By specifying multiple servers, Redis::Distributed is used, which is part of the redis gem.

Redis in Development

You can use the package manager of your OS to install Redis. If you're using Homebrew on MacOS, you can run

brew install redis

You can also use Docker on any OS which proves to be convenient when testing multiple Redis servers.

docker run --rm -p 6379:6379 redis

To run multiple Redis processes, you can run the same command using different ports

docker run --rm -p 6379:6379 redis
docker run --rm -p 6380:6379 redis
docker run --rm -p 6381:6379 redis

On development.rb, specify these servers complete with the port numbers

redis_servers = %w[ 
  redis://localhost:6379/0
  redis://localhost:6380/0
  redis://localhost:6381/0
]

config.cache_store = :redis_cache_store, {driver: :hiredis, url: redis_servers}

Deploying in Production

It is recommended to use a dedicated Redis server for cache purposes. If you're already using Redis in your application, you should get a separate server or start a second Redis process.

Redis Cache Store is fault tolerant so even if you only have a single Redis server, your app won't go down if Redis goes down. It is important to make sure your app will be responsive during this time of cache misses. To avoid this problem, use multiple Redis servers. You would still have cache misses for some keys but they will be less than if you only have one server.

If you need advice on your Redis setup in production, please contact us here at Engine Yard. We have experience supporting Redis and Rails in production.

Key Value Store

Redis is a key-value store. In the context of the Redis Cache Store, keys are unique identifiers that are generated by Rails. The values are the cache entries. You don't need to know these to use the Redis Cache Store but it would be good to have an idea about how it works under the hood.

Here's a sample key that Rails used with the fragment caching above.

views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/1

You can see the name of the view views/messages/index, the MD5 hash of the template 51aef83ac5cf0aaa40efdd8640257171, and the ID of the message object 1.

You can check the keys on the Rails console.

pp Rails.cache.redis.keys
["views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/10",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/7",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/1",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/6",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/2",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/8",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/5",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/3",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/11",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/4",
 "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/9"]

You'll get the same output when using the redis-cli tool,

$ redis-cli 
127.0.0.1:6379> keys *
 1) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/10"
 2) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/7"
 3) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/1"
 4) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/6"
 5) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/2"
 6) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/8"
 7) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/5"
 8) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/3"
 9) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/11"
10) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/4"
11) "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/9"

To check the values, you can also use the Rails console or redis-cli.

Rails.cache.fetch "views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/1"
 => "    <li>Test 1</li>\n\n" 
127.0.0.1:6379> get views/messages/index:51aef83ac5cf0aaa40efdd8640257171/messages/1
"\x04\bo: ActiveSupport::Cache::Entry\t:\x0b@valueI\"\x1a    <li>Test 1</li>\n\n\x06:\x06ET:\r@versionI\"\x1920171218054020526607\x06;\aT:\x10@created_atf\x151517986248.75312:\x10@expires_in0"

You can see from redis-cli that the value is the serialized Entry object. When you deserialize the string, you'll get the output displayed on the Rails console.

Feedback

Are you using Rails caching and thinking of trying out the Redis Cache Store? Let us know if have any questions or feedback.

Start a Free Trial:

Engine Yard is so much more than just a Ruby on Rails PaaS platform. But don't just take our word for it. Request a free trial of Engine Yard platform today, and one of our Engineers will be in contact within one business day to get you going.

START MY TRIAL!
development-user.png

Related posts

Do we still need RVM Gemsets?

April 2, 2018

RVM or Ruby Version Manager allows you to install and manage Ruby environments. Similar tools

Read More

Top 5 reasons Not to miss Rails Conf

March 30, 2018

Every year, there is a gathering of developers, looking toward the future of a framework that

Read More

Rails Encrypted Credentials Use AES 128-bit Key

March 28, 2018

Rails 5.2 introduces Credentials which replaces Secrets and Encrypted Secrets from previous

Read More

Christopher Rigor

 
Christopher Rigor is a Senior Technical Evangelist at Engine Yard. He’s a long time Rails user, system administrator, and recently became a contributor of RailsInstaller. Previously, he was the DevOps Support Manager for Asia-Pacific at Engine Yard.
Find me on:

Comments

Subscribe Here!