As some of you know I’ve been working full time for Matson Systems, Inc. building out LegalTorrents. I must apologize I have been neglecting parts of my blog. Fortunately, I’ve been swamped building out cool features for LegalTorrents and Matson wants to contribute back. After this caching article look for a rake task to convert a Rails app from one database platform to another, then a plugin for generating Activity Streams.
As a new community, we have to support tens of thousands of registered users. Yet on any given news day we need to support hundreds of thousands of non registered users.
This can be done using action caching and very modest hardware requirements. Given huge hardware resources, using memcached would solve the issue.
However, meeting initial demands can be done using action caching with
very modest hardware requirements. We use the built-in rails action
caching to disk with a
TTL hack from cron. We don’t want our
logged-in users subjected to a
TTL, as their changes should be
instantaneous. We simply don’t cache actions for users who are logged
in, and provide cached pages for everyone else. As the number of
registered users grows… then we’ll use memcached.
Let’s begin with conditional caching in Rails 2.1 (if 2.0, see below). Conditional action caching is a new feature of the Rails 2.1 API. First off, pre Rails 2.1 the default was disk. In rails 2.1, the default is RAM. Not going to work on limited resources:
1
2
3
|
# Put this in RAILS_ROOT/config/initializers/something.rb
ActionController::Base.cache_store = :file_store, "#{RAILS_ROOT}/tmp/cache"
|
Ok, the application controller is a great place to decide if we want to cache:
1
2
3
4
5
6
7
8
|
class ApplicationController < ActionController::Base
# ...
protected
# of course logged_in? needs to be defined...restful_authentication is what I recommend.
def do_caching?
!logged_in? && flash.empty?
end
|
Now we can use the new Rails 2.1 :if feature to conditionally cache actions:
1
2
3
4
5
6
|
class TorrentsController < ApplicationController
# ...
caches_action :show , :if => Proc.new { |controller|
controller.send(:do_caching?) }
|
The final piece of the puzzle is a
TTL. We use find to remove files older than 10 minutes, giving us a 10 minute
TTL:
1
2
3
|
# This cron entry that runs every 10 minutes and removes any files older than 10 minutes named '*.cache'
3,13,23,33,43,53 * * * * find /home/ltdeploy/legaltorrents/tmp/cache -mmin +10 -name '*.cache' -exec rm -f {} \;
|
And that is it. For those of you not familiar with caches action here is a more complex example for a page that integrates the will_paginate plugin using :cache_path:
1
2
3
4
5
6
7
8
9
10
|
class CategoriesController < ApplicationController
caches_action :show,
:if => Proc.new { |controller| controller.send(:do_caching?) },
:cache_path => Proc.new { |c|
c.params[:page] ?
"#{c.request.host}.#{c.request.port}/#{c.send(:category_path,c.params[:id])}/page/#{c.params[:page]}" :
"#{c.request.host}.#{c.request.port}/#{c.send(:category_path,c.params[:id])}/page/1"
}
|
End of Story for Rails 2.1
Now, Rails 2.0 doesn’t have :if in caches_action. To work around this we used a simple monkey patch:
1
2
3
4
5
6
7
8
|
class ApplicationController < ActionController::Base
# ...
protected
# Overrides Rails core to do action_cache when not logged in...Only works in Rails 2.0 and maybe earlier
def perform_caching
@@perform_caching && !logged_in? && flash.empty?
end
|
Then we cache as normal:
1
2
3
4
|
class TorrentsController < ApplicationController
# ...
caches_action :show
|