Perspectives

Cache

Just want to skip to the good stuff? Check out our new ERB Caching technique now!

I've always had a love/hate relationship with Rails caching. The guys on the Rails team have supplied us with several tools for the job and have taken most of the pain out of it. Between page caching, action caching and fragment caching, you usually can find a method that will suit your application.

Easy as it may be, however, these caching techniques do come at a cost. The more caching you use,the more control you lose over your site and your user experience. These tradeoffs have proventhe most significant when developing social networking sites, where high traffic demands efficient caching but the need for user personalization demands constant fresh content.

So which do you use? Here are the pros and cons of the existing Rails caching methods, plus a brand new technique we are using at Killswitch we call ERB Caching.

Page Caching (The Unattainable Ideal)

From the perspective of performance and speed, nothing can beat the rewards of page caching. When rendering a page, Rails saves the generated markup as HTML into the /public folder. From that point on, subsequent requests for the same URL will be served by Apache, leaving Rails completely out of the process. This takes tremendous strain off of Rails and allows a single server to handle significant traffic. Even if the cached pages' time to live (ttl) is only a few minutes, the performance savings are fantastic.

Using page caching has worked well for us on some of our large scale projects. For one such project, we used the Akamai service to handle requests for cached pages, taking even more load off of our server. With this setup, a single server with only a few mongrel instances has been able to easily serve thousands ofunique visitors daily on a high profile, media-intensive site.

As ideal as this is, using page caching becomes an impossibility if you want any kind of personalization on your site (and you probably do). Since Rails is left out of the equation and Apache is serving static HTML files, it's nearly impossible to include any personalized content on the site (except for using Javascript trickery, of course). This is why page caching is used infrequently in the industry as users demand personalized, community oriented experiences.

Action Caching (Close, But Not Quite)

For more flexibility (and fewer performance gains) Rails supplies action caching. This feature is similar to page caching in that the entire output page is still cached. This time, however, Rails is in charge of serving thefile. This means that you can use before/after filters and sessions to determine what to send to the user. For example, you can send one page if the user is a logged in member, another if they are not.

With this additional layer of control, this technique is more applicable to community-driven apps. However, simple things like putting the logged-in user's name at the top of the page is still difficult/impossible, since all logged in users would be served the same cached page.

Many of the deficiencies of this method were fixed by the action_caching plugin, which allows for conditional caching, control over cache storage options and more. The functionality of action caching has also been significantly extended with our creation of ERB Caching, described below, which uses action caching as its foundation. More on that coming up in a few paragraphs...

Fragment Caching (It Works, But Still...)

Fragment caching has become the standard caching mechanism of large, dynamic, user-driven sites. It gives you all the flexibility you need, though the performance savings aren't much compared to those of page and actioncaching. This method allows you to mix generated content with chunks of the view that are cached and then reassembled on every render.

Using fragment caching helps speed up page renders, reduces database queries and is definitely better than using no caching at all! Still, I couldn't help feeling like there must be a better way. Working on social networking sites that could almost use action caching became frustrating - I wanted the benefits of fully cached pages and was tired of working with views littered with <% cache ... %> fragment caching code.

With that goal in mind, the idea for ERB Caching was developed.

ERB Caching (Finally, the Best of Both Worlds!)

I always had a sense that action caching was more powerful than it let on. You get the control and power of Rails, but still are given the performance benefits of caching the entire page.

My specific challenge was building a social networking site where every page on the site would have extra user-centric modules on it if the user was logged in. The modules are fairly simple, just some links and buttons that would initialize user actions. Still, their creation was dependent on knowing whether the user was logged in and knowing who they were.

The solution was to actually store ERB code in the cached views. The cache file is loaded into Rails, after which the ERB Caching code does a quick parsing to render any remaining ERB. The final file is then sent to the user, fresh and personalized.

Here's an arbitrary example:

In main_controller.rb:

before_filter :setup_vars
caches_action :index

def index
  @message = "Hello from index!"
end

def setup_vars
  @message = "Hello from before_filter!"
end

In index.rhtml:

<p>This time will get cached and be the same on every page view: <%= Time.now %><p>
<p>This time, however, will update on every page view: <%%= Time.now %><p>
<p>The message set in the controller is: <%%= @message %><p>

When this view is first rendered, any ERB tags with two percent signs will not be rendered, but simply replaced by single percent signs. This is what the contents of the cache file will then look like:

<p>This time will get cached and be the same on every page view: Fri Feb 08 14:43:54 -0600 2008<p>
<p>This time, however, will update on every page view: <%= Time.now %><p>
<p>The message set in the controller is: <%= @message %><p>

The trick is then to render this a second time in an after_filter. Any variables set in a before filter will be available in this second rendering.

This is what the first page render produces:

<p>This time will get cached and be the same on every page view: Fri Feb 08 14:43:54 -0600 2008<p>
<p>This time, however, will update on every page view: Fri Feb 08 14:43:54 -0600 2008<p>
<p>The message set in the controller is: Hello from index!<p>

... and the second page view produces:

<p>This time will get cached and be the same on every page view: Fri Feb 08 14:43:54 -0600 2008<p>
<p>This time, however, will update on every page view: Fri Feb 08 14:47:17 -0600 2008<p>
<p>The message set in the controller is: Hello from before_filter!<p>

As you can see, any "double" ERB tag that is in a view will be cached as a single ERB tag and then rendered freshly on every request. This becomes very useful when you want to cache whole pages, but also want to have the current user's name displayed on the page. Simply set @user in a before_filter and then use <%%= @user.name %> in your view.

The code required to make this work is surprisingly simple and is all included in the erb_cache.rb file. Just place the file in /lib and put require 'erb_cache' in your environment.rb file. You will also have to put ActionController::Base.perform_caching = true in environment.rb if you want to test this out in development.

Also, don't forget to get the ActionCache plugin, it provides some excellent features.

This is still under development, so if you have any trouble with it, please let us know!

Download the ERB Caching File




RSS Feed


CATEGORIES


ARCHIVES


BOOKMARKED


Add to Technorati Favorites