Rails page caching in a separate directory
How to setup Apache or NGINX to be aware of where rails stores page caches
Rails offers a really powerful caching technique where the contents of an entire page load will be stored into a HTML file. This is incredibly useful for performance enhancements for big and small Rails apps.
One issue, however, is that when you setup page caching into a different directory which isn’t the public folder root, then the generated HTML files won’t be picked up by Apache, Nginx, or whichever HTTP daemon you’re webserver is running since the direct path is different. Therefore, to get this to work properly, some tweaking needs to be done on the HTTP daemon so that it recognizes the caching directory path.
The nice thing is that when the caching files are in a subfolder within the public root folder then management of the cache as a whole becomes much easier. This is explained in detail here.
This article will quickly explain how to setup Rails and the HTTP daemon (Apache or Nginx) to properly behave with the cache change.
Last Updated
This blog entry was last updated on October 22nd 2011 and was first published on December 28th 2011
What exactly is the issue?
Apache or Nginx won’t notice if a page is cached because the URL to access that cached file is different from the actual URL that accesses the non-cached page.
# Non-Cached
/about/some/sub/page
# When cached it will get stored in
/cache/about/some/sub/page.html
The path is different and caching won’t work right away so we’ll need to configure the HTTP daemon.
So how do we fix this problem?
Configure Rails First
Be sure to add this to your production.rb (or application.rb) file.
config.action_controller.page_cache_directory = Rails.root.to_s + "/public/cache/"
Now all of your page cache files will be stored under /public/cache/PATH
URL Rewriting does the trick
Using mod_rewrite for apache and the built-in URL rewriting offered by NGINX, the cached directory can be recognized directly from a “non-cached” URL. The thing to keep in mind is that if a cached file does not exist then the URL rewrite will still apply itself, so its important to consider the rewrite conditions so that the http server is aware of this.
Here are the settings (keep in mind that this is for a cached directory of /public/cache):
Apache
Apache always has the strangest of syntax :p
RewriteEngine On
# default root uri
RewriteCond %{THE_REQUEST} ^(GET|HEAD)
RewriteCond %{DOCUMENT_ROOT}/cache/index.html -f
RewriteRule ^$ cache/index.html [QSA]
# all other pages
RewriteCond %{THE_REQUEST} ^(GET|HEAD)
RewriteCond %{REQUEST_URI} ^([^.]+)/?$
RewriteCond %{DOCUMENT_ROOT}/cache/%1.html -f
RewriteRule ^([^.]+)$ cache/$1.html [QSA]
NGINX
NGINX is a bit more straightforward…
# Index HTML Files
if (-f $document_root/cache/$uri/index.html) {
rewrite (.*) /cache/$1/index.html break;
}
# HTML Files
if (-f $document_root/cache/$uri.html) {
rewrite (.*) /cache/$1.html break;
}
# Catch all
if (-f $document_root/cache/$uri) {
rewrite (.*) /cache/$1 break;
}
Some streed cred’…
Here’s some lovin’ for some of the websites that helped me put together this article.
http://www.fngtps.com/2006/lazy-sweeping-the-rails-page-cache/
http://rel.me/2008/01/07/nginx-conf-with-alternate-cache-dir/
https://wincent.com/blog/rails-page-caching-vs-nginx
http://guides.rubyonrails.org/caching_with_rails.html
Have you actually used that Nginx example? From my own basic testing Nginx doesn’t allow (&&, ||, and, or) operators in if() tests.
I just get this error
nginx: [emerg] invalid condition “-f” in /opt/nginx/conf/nginx.conf
Or if I flip the conditions
nginx: [emerg] invalid condition “$request_method”
It also doesn’t appear to allow nested ifs, but you can use one if to set a variable, then test for that in another if statement, somewhat giving you the nested behavior.
Turns out you’re right about not being about to have multiple preconditions in if statements in the NGINX config. Not even nested if statements. I was going along with how Apache handles things.
I’m assuming what you mean by testing a variable is to do this:
This will give you a OR operation:
set $A 1
if ( statement ) {
set $A 0
}
if (statement2) {
set $A 0
}
if ( $A = 0 ) {
# do the rewrite
}
This will give you an AND operation:
set $A “0″
if ( statement ) {
set $A “1″
}
if (statement2) {
set $A “${A}1″
}
if ( $A = “11″ ) {
# do the rewrite
}
I have fixed the configuration code to only include the check to see if the file is there. Thank you