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
|