tag:blog.hasmanythrough.com,2006-02-27:/tag/deploymenthas_many :through - deployment2008-01-30T23:41:12-08:00tag:blog.hasmanythrough.com,2006-02-27:Article/1032008-01-30T23:41:12-08:002008-01-30T23:41:12-08:00Segregated page cache storageJosh Susser<p>Page-caching is one of the highest leverage features in Rails. It doesn't take much to set up, and the payoff is huge. When building Teldra I knew from the start that page caching would be part of my production deployment, as it should be for any site with pages where content changes infrequently relative to number of views.</p>
<p>The only thing I find annoying about using the page caching feature is how the cached pages are stored in the RAILS_ROOT/public directory, right alongside all the app's other static pages. I greatly prefer having the cached pages stored in a separate directory. This makes it a lot easier to distinguish between static pages and cached dynamic pages, and if something goes wonky with your cache you can blow it away easily with a single command.</p><p>Page-caching is one of the highest leverage features in Rails. It doesn't take much to set up, and the payoff is huge. When building Teldra I knew from the start that page caching would be part of my production deployment, as it should be for any site with pages where content changes infrequently relative to number of views.</p>
<p>The only thing I find annoying about using the page caching feature is how the cached pages are stored in the RAILS_ROOT/public directory, right alongside all the app's other static pages. I greatly prefer having the cached pages stored in a separate directory. This makes it a lot easier to distinguish between static pages and cached dynamic pages, and if something goes wonky with your cache you can blow it away easily with a single command.</p>
<p>Here's the setup I have Teldra running on. I'm using nginx for the webserver and capistrano for deployment (using slight tweaks to the EngineYard standard configs and recipes). I created a shared/cache directory, told Rails to store cached pages there, and added rules to nginx to find cached pages there. Here's how things look in detail...</p>
<p>In config/environments/production.rb, tell Rails to put cached pages in the public/cache directory.</p>
<pre><code>config.action_controller.page_cache_directory = File.join(RAILS_ROOT, 'public', 'cache')
</code></pre>
<p>In nginx.conf, set up the precedence for locating static files. First look in public for regular static files. Next look in the cache directory for an exact match for the url. Lastly, look in the cache directory for the url with .html appended. That will let you cache pages for regular URLs with no .html extension as well as ones with extensions like .xml, .atom, .json, etc.</p>
<pre><code> if (-f $request_filename) {
break;
}
if (-f /cache$request_filename) {
rewrite (.*) /cache$1 break;
break;
}
if (-f /cache$request_filename.html) {
rewrite (.*) /cache$1.html break;
break;
}
</code></pre>
<p>The capistrano recipes have to do a couple things. You need to create the shared/cache directory when setting up the deployment.</p>
<pre><code>after "deploy:setup", "create_page_cache"
task :create_page_cache, :roles => :app do
run "umask 02 && mkdir -p #{shared_path}/cache"
end
</code></pre>
<p>When deploying a new release, create a symlink from public/cache to the shared/cache directory.</p>
<pre><code>after "deploy:update_code","symlink_shared_dirs"
task :symlink_shared_dirs, :roles => :app, :except => {:no_release => true, :no_symlink => true} do
run <<-CMD
cd #{release_path} &&
ln -nfs #{shared_path}/cache #{release_path}/public/cache
CMD
end
</code></pre>
<p>When doing a deploy, the standard behavior is to flush the cache, just to be on the safe side. If you want to retain cached pages, as when making a change you know won't affect rendering, tell capistrano not to flush.</p>
<pre><code># default behavior is to flush page cache on deploy
set :flush_cache, true
# page cache management
task :keep_page_cache do
set :flush_cache, false
end
after "deploy:cleanup", "flush_page_cache"
task :flush_page_cache, :roles => :app do
if flush_cache
run <<-CMD
rm -rf #{shared_path}/cache/*
CMD
end
end
</code></pre>
<p>With the above setup, you can deploy and retain the cache with the following capistrano command:</p>
<pre><code>$ cap keep_page_cache deploy
</code></pre>
<p>That's about it. I know I'm not the first fellow to think of this setup, but I'm surprised it's not more common. Anyone else doing something similar?</p>