What to cache and what not to! – Rails

With fragment caching floating around it always remain to decide how small piece of code should I cache. Is it worth caching a code as it renders a partial, or just because it has a few lines of HTML/ JS / Ruby code, or it makes a few DB calls to fetch data.

This blog is dedicated to my findings and observations regarding the same. I am using Rails 3 + Memcachedb + Ubuntu for my experiments. Please note that I have mentioned average response time after coupling a few requests.

1. Small partials – caching not needed

About partial: Few lines of HTML / CSS and JS code (Around 10 lines)

  • Response Time when cache was set:

    Started GET “/get_top_menu.js” for 127.0.0.1 at 2011-04-20 11:58:58 +0530
    Processing by HomeController#get_top_menu as JS
    User Load (9.9ms) SELECT `users`.* FROM `users` WHERE (`users`.`id` = 1) LIMIT 1
    Exist fragment? views/logged_in-true (1.9ms)
    Read fragment views/logged_in-true (0.1ms)
    Rendered home/get_top_menu.js.erb (1201.0ms)
    Completed 200 OK in 1259ms (Views: 1247.3ms | ActiveRecord: 9.9ms | Sphinx: 0.0ms)

  • Response time when cache was not set:

    Rails.cache.delete(‘views/logged_in-true’)

    Started GET “/get_top_menu.js” for 127.0.0.1 at 2011-04-20 12:07:53 +0530
    Processing by HomeController#get_top_menu as JS
    User Load (1.5ms) SELECT `users`.* FROM `users` WHERE (`users`.`id` = 1) LIMIT 1
    Exist fragment? views/logged_in-true (6.7ms)
    Rendered devise/menu/_logged_in_menu.html.erb (7.7ms)
    Rendered devise/menu/_logged_in.html.erb (19.2ms)
    Write fragment views/logged_in-true (1.2ms)
    Rendered home/get_top_menu.js.erb (1225.8ms)
    Completed 200 OK in 1284ms (Views: 1281.5ms | ActiveRecord: 1.5ms | Sphinx: 0.0ms)

    Please observe there was no time saved with cache here. Probably because sending a tcp request to memcachedb (even on localhost) is itself time consuming. Here we made 2 such requests, one to check if cache exists and second to actually fetch it. This nullifies the time we saved on rendering a small partial.

  • Response time when we did not implement any cache:

    Started GET “/get_top_menu.js” for 127.0.0.1 at 2011-04-20 12:14:25 +0530
    Processing by HomeController#get_top_menu as JS
    User Load (3.3ms) SELECT `users`.* FROM `users` WHERE (`users`.`id` = 1) LIMIT 1
    Rendered devise/menu/_logged_in_menu.html.erb (1.3ms)
    Rendered devise/menu/_logged_in.html.erb (7.6ms)
    Rendered home/get_top_menu.js.erb (425.6ms)
    Completed 200 OK in 462ms (Views: 457.6ms | ActiveRecord: 3.3ms | Sphinx: 0.0ms)

    Please observe there we got a faster response when cache was not at all used. Probably because sending a tcp request to memcachedb (even on localhost) is itself time consuming.

I guess results are pretty self explanatory and make sure that you use fragment cache when there is a need and not for every small request. The need arises when there are sql queries (they will anyway make tcp requests to db) or heavy computation in ruby code. You will also save memory by not dumping in huge contents (HTML / CSS / JS) which do not require much time to render.

Comments