Make Sure Your Rails Application is Actually Caching (and not just pretending)

11 March 2010

We recently worked on a Rails application that had page and action caching set up, only to find that it was not actually working. It occurred to me that many Rails/Passenger/Apache applications may have caching set up in a way that it appears to be caching, when it is not actually caching. Searching through the interwebs for various Passenger/Apache configurations, such as this snippet on Github or this discussion on Google Groups, I found that many did not work with the most recent version of Phusion Passenger. What's more, these configurations give the appearance that they are working.

For an introduction to caching with Rails, check out this post: Caching With Rails: An Overview

The Appearance That Caching Works

Here is what I mean by giving the appearance that caching is working:

  1. you add `caches_page :index` to your controller, for example
  2. configure and restart your server
  3. load the index page
  4. check your rails cache directory on your server, and see the index.html
  5. it works! (hint: this step is wrong, you're not quite there yet)

Just because Rails is generating your cached page does not mean that your server is subsequently serving the cached `.html` file instead of sending the request to Rails again.

Ensure It's Serving the Cached Page

To make sure it's subsequently serving the cached page, the easiest method is to look at the created date on the cached file on your server, for instance:

ls -l myapp/public/cache

You will get an output similar to this:

-rw-r--r-- 1 deploy deploy 10930 2010-03-09 17:49 index.html

Now wait a minute, and then reload the page in your browser. Once again type ls -l myapp/public/cache, and make sure that the file still shows the same created date/time. If it instead shows this:

-rw-r--r-- 1 deploy deploy 10930 2010-03-09 17:50 index.html

...then you have a problem. Your server is sending every request back to Rails, where Rails is then re-generating the cached page.

Alternative 1: You can also use Apache's RewriteLog to watch what is being served. Just add this to your site's .conf file and then tail -f the rewrite_log:

</VirtualHost *:80>
  ...
  # Comment out to disable rewrite debugging
  RewriteLog /path/to/myapp/current/log/rewrite_log
  RewriteLogLevel 9
  ...
</VirtualHost>

Alternative 2: You can also use the top command to watch your running processes and make sure the rails process doesn't rise to the top while reloading your page. This is obviously not very scientific, though, and won't work if you have any external traffic on the site.

The Solution: Proper Server Configuration

The problem here is that your server is not properly configured to intercept calls to cached pages. This could take a bit of Googling and debugging, but the important thing here is that we've identified the problem and how to tell when it's fixed. For completion's sake, I'll go ahead and show the proper configuration we've determined for Passenger 2.2.8:

</VirtualHost *:80>
  ...
  RailsAllowModRewrite on
  RewriteEngine On
  #apache should serve cached pages
  RewriteRule ^/$ /cache/index.html [QSA]
  RewriteRule ^([^.]+)$ /cache/$1.html [QSA]
</VirtualHost>

UPDATE: Christoffer Sawicki has a set of rewrite rules that I like much better than what I originally posted here. These rules explicitly check to make sure the cached file exists and that the current request is a GET request (instead of a POST, PUT, or DELETE), without relying on Rails magic. In short, use these instead:

</VirtualHost *:80>
  ...
  RailsAllowModRewrite On  
  RewriteEngine On

  RewriteCond %{THE_REQUEST} ^(GET|HEAD)
  RewriteCond %{REQUEST_URI} ^/([^.]+)$
  RewriteCond %{DOCUMENT_ROOT}/cache/%1.html -f
  RewriteRule ^/[^.]+$ /cache/%1.html [QSA,L]

  RewriteCond %{THE_REQUEST} ^(GET|HEAD)
  RewriteCond %{DOCUMENT_ROOT}/cache/index.html -f
  RewriteRule ^/$ /cache/index.html [QSA,L]
</VirtualHost>

Happy caching! For more tips for getting the best performance out of your server, check out Performance Tuning for Phusion Passenger (An Introduction), and How to Monitor Your Rails/Passenger App with Munin.

Update: Proper Nginx Configuration

For extra completeness, thank you to atambo on Hacker News for pointing me to this proper configuration for Rails caching on Nginx. You can read the comments for an explanation.

About the author:

Steve Schwartz // Owner of Alfa Jango Web-based Software, creator of RateMyStudentRental & LeadNuke, engineer, hacker, rubyist, guitarist, aspiring racecar driverist.



Comments are loading...