/ redis

Caching with Redis

What is Redis?

Redis is an open-source, networked, in-memory, key-value data store.

Why should we use Redis?

By default, Rails uses the database to cache internal application data which can be expensive to generate (menu trees, views, filter results, etc), and to keep cached page contents. Since the database also handles many queries for normal page requests, it can create a bottleneck and increase load-times.
Redis provides an alternative caching backend for Rails, taking that work off the database, which is vital for scaling to a larger number of logged-in users.

How we used Redis?

In our project, we have a menu which included different categories of bottles, further these categories are divided into sub-categories. We have a menu table which stores all the parent, child and the grand-child menus. Every time we load the page, it executes queries depending upon the menus and sub menus available.

To avoid querying multiple times(i.e every time the page was loaded), we decided to cache the menu using redis and call the cached version of the menu unless any menu/sub-menu is added or removed by the Admin. Earlier it used to take around 2000 ms to load the page, after caching the menus the page was able to load in 1100 ms.

Thus, we used Redis to cache menus to avoid recurrent execution of sql queries, and to eventually, improve the overall performance of the app.

1] We used redis-objects gem to cache the menus

a) Add following in the gemfile

gem 'redis-objects'

b) Add the following line in "config/initializers/redis.rb" to configure Redis:
require 'redis'
require 'redis/objects'
unless ENV["REDISTOGO_URL"].blank?
$redis = Redis.connect :url => ENV["REDISTOGO_URL"]
Redis.current = $redis
end

2] We defined a method to return the cached menus from Redis and also to update cached copy of the menu in Redis. This method will be called from views.

In application_helper.rb

def main\_menu\_hash
      $redis = Redis.current if $redis.nil?
      # This will be called only when hash key for menu does not exist in redis
      # and scenario like data of redis gets deleted due to some reason.
      Menu.set_main_menu unless $redis.hexists('menu', 'main_menu')
      # This will retrieve the hash of menus form redis and return it as html content
      Menu.get_main_menu.html_safe
    end

3] Update cached shop menu when create, update, delete operations are performed on the Menu model

  after_commit :update_shop_menus

  # This method will retrieve the hash of menus from redis.
  def self.get_main_menu
    $redis.hget('menu', 'main_menu')
  end

  # This method will create/update the hash of menus to redis.
  def self.set_main_menu
    $redis.hset('menu', 'main_menu', ApplicationController.new
      .render_to_string(partial: 'shops/shop_nav'))
  end

  private
    # This method will be called after the commit operation is performed on Menus.
    # This will update the cached copy of menus in redis.
    def update_shop_menus
      Menu.set_main_menu
    end

4] Retrieve cached menu in the view

a) From index.html.haml
.shop_navigation
= main_menu_hash

b) From .js.haml
$('.shop_navigation').html(main_menu_hash);

5] BENCHMARK

a) Benchmark to render the menu (without caching)

Benchmark.measure { render partial: 'shops/shop_nav' }
    0.600000   0.000000   0.600000 (  0.632103)

b) Benchmark to render the menu (after caching using redis)

Benchmark.measure { main\_menu\_hash }
    0.010000   0.000000   0.010000 (  0.003018)
Sampat Badhe

Sampat Badhe

I am a ROR developer, Rubyist, Node.js developer. I enjoy building innovative applications and currently in love with TDD practice. I am practicing agile and love to collaborate...

Read More