Vaporbase micro-CMS: Performance Improvement

(Warning: code talk ahead.)

When I started building the app I’m working on, I needed a quick CMS system that didn’t impose its own parsing language, and I was just learning RoR, so I implemented the Vaporbase micro-CMS based on this great tutorial. While you wouldn’t confuse this with a pro-grade CMS system, it works just fine for the most part, and doesn’t require you to learn a number of new things.

Since implementing it, I’d made a few minor cleanups, but nothing significant, until I had 60 pages in the CMS, and saw a problem: the list of pages was taking a very long time to load (21sec on avg on my dev machine).

Sure enough, there’s a problem: in the Edit and Show Tree Hierarchy section, here’s a line that says

<% unless index_item.children.nil? %>

Looks innocuous, right? Not exactly: for every single page in the tree, this does a separate SQL query (which is a full table scan, though likely on a tiny table) to find out if that item has any children. So creating the page is O(# pages in table), and while the queries itself aren’t that expensive, building the page just takes a while. If your tree is mostly flat – almost certainly the case for this kind of CMS – you’re wasting a lot of time.

Fortunately, there’s an easy solution: when you get the list of pages before creating the tree, figure out which pages have children, and then store that list to the side. It adds a bit of code, but not a lot.

# Original: from the index action in the controller
# This gets just the root nodes and then recurs down the tree
@pages = Page.find( :all, :conditions => [‘parent_id IS NULL’], :order => :position)
# Replacement:
# build two arrays –
# @pages consists of all of the root nodes (since the view walks down the tree)
# @pageIDs_with_children consists of all pageIDs which are the parent for >=1 page

@pages = []
@pageIDs_with_children = []

@all_pages = Page.find(:all)
@all_pages.each do |page|
if (page.parent_id.nil?)
@pages << page else @pageIDs_with_children << page.parent_id end

(Note that the list might have duplicates, but that doesn’t matter. If that makes you unhappy, check for existence in the else clause.)
Then replace the view line above with

<% if @pageIDs_with_children.index(index_item.id) %>

I saw a >80X performance improvement on this page with this change in development (from >20sec to ~0.25sec with 60 pages), and you’ve just replaced an expensive linear scaling step with a very inexpensive one.

0 Responses to “Vaporbase micro-CMS: Performance Improvement”

Leave a Comment

Twitter Updates

[aktt_tweets count="5"]