Wednesday, September 17, 2008

Ruby on Rails: optimizing a slow-rendering page

Recently at work, I was faced with the problem of fixing a slow-rendering page. The page is part of an internal content management system, where the content includes video, images, and text. We have over a thousand pieces of content being managed with this tool and the slow page was a listing of all the content along with key overview-type information for each content package.

Everyone was fine with this page loading slowly, since we knew that there was a lot of content. The problem arose when the page would only show around 50 items and then stop loading; the server configuration was set to automatically stop requests that took too long to process.

I knew that the best course of action would be to start measuring. I took a look at the logs to see how long the request was taking and the approximate tasks where time was being spent. At JibJab, we take pride in caching the hell out of everything. Only a tiny fraction of time was spent hitting memcached. And it looked as though memcached was doing its job: almost no time was spent hitting the database. But a ridiculous percentage of the time was spent "rendering" the page.

That meant the onus was on my code to speed things up. Since the slowdown had become more and more apparent as we added more content, my guess was that the loop was the main source of the problem. Anything that would be only a tiny bit slow on its own would lead to a delay over a thousand times greater.

The first thing I removed was a call to cycle(), which I was using to zebra-stripe the table display for easier reading. In its place, I put in an index variable that would be modulo-two to determine its index into an array of two choices (even row or odd row). To keep the same feature, I had to dirty up my code if I wanted it to run more quickly. And indeed that shaved off about one-fourth of the rendering time.

But the page was still taking over ten seconds to render, so I continued my hunt. A pattern I saw many times within each iteration of the loop was an old friend, link_to. I am not terribly attached to this particular friend, however, so I got rid of it and replaced it with some good old <a> tags, which bought me a good second or two.

So far, I felt pretty good about what I had been able to do to extract render-time savings. But there was still room to shave the time down, and luckily for me, Inflector.humanize was being called in each iteration like there was no tomorrow. I decided that there indeed would be no tomorrow for these expensive-looking helpers, and after removing all five calls inside the loop, I got total render time down to a very respectable two-second neighborhood.

The lesson learned? Sometimes code has to be a little messier so it can be fast enough to be used. Specifically, watch out for those beautiful-seeming Rails helper functions; they do a lot of work for you, but sometimes you're just better off doing the work yourself.

blog comments powered by Disqus