Increasing Performance by Caching Post-Processed Content

Problem

Contributed by: Ben Bleything

Your application allows users to enter content in a way that must be processed before output. You've determined that this is too slow and want to improve your application's performance by caching the result of processing the input.

Solution

First, open the model that contains the Textile-formatted fields. Add two methods to your model to render the body when the object is saved. We're assuming that the field is called body. We'll be creating body_raw and body_rendered in a minute.

class TextilizedContent < ActiveRecord::Base
 # your existing model code here
 def before_save
 render
 end
 private
 def render
 self.body_rendered = RedCloth.new(self.body_raw).to_html
 end end

NOTE

We use Textile for this recipe, but the examples can easily be modified to use Markdown or other text processors.

Next, create a migration to update your table schema and process all of your existing records:

db/migrate/001_cache_text_processing.rb:

$ script/generate migration CacheTextProcessing
class CacheTextProcessing < ActiveRecord::Migration
 def self.up
 rename_column :textilized_contents, :body, :body_raw
 add_column :textilized_contents, :body_rendered, :text
 # saving the record re-renders it
 TextilizedContent.find(:all).each {|tc| tc.save}
 end
 def self.down
 rename_column :textilized_contents, :body_raw, :body
 remove_column :textilized_contents, :body_rendered
 end end

The change implemented by this migration is that the database now saves both the original body, in textile format, and the rendered HTML format. Running the migration updates all existing records:

$ rake db:migrate

Finally, update your views to output our new body_rendered field:

<%= @tc.body_rendered %>

Now when someone reads a blog entry, the view returns the previously rendered content, rather than rendering it again.

Discussion

Consider a blogging application. The blog author might choose to format his posts using Textile, Markdown, or some other markup language. Before you output this to a browser, it needs to be rendered to HTML. Particularly in an on-demand application like a blog, rendering content to HTML on every view can get very expensive.

Caching the rendered content allows you to dramatically lessen this overhead. Instead of rendering every time the page is viewed, which slows down the reader's experience, the content is rendered only when it is created or updated.

This technique can be easily modified to support multiple markup formats. Assuming you have a column in your database called markup_format, which stores the format, modify the render method in the model to use the proper renderer:

def render
 case self.markup_format
 when 'html'
 self.body_rendered = self.body_raw
 when 'textile'
 self.body_rendered = RedCloth.new(self.body_raw).to_html
 when 'markdown'
 self.body_rendered = BlueCloth.new(self.body_raw).to_html
 when 'myfancyformatter'
 self.body_rendered = MyFancyFormatter.convert_to_html(self.body_raw)
 end end

This caching strategy is so simple, it's debatable whether it should even be called a cache. After all, we're just using the database to store the rendered version of the blog post: we're not doing anything fancy like keeping recent posts in memory, or anything of that sort.

There are other ways to alleviate the overhead of rendering content. See the other recipes in this chapter for more details.

See Also