List Kung Fu uses Ajax a lot and so far it works out pretty well. Over the past two weeks I’ve looked into improving the performance of the UI and there are a couple of things that helped a lot and which I want to share here.
Returning the correct HTTP status code and optimize browser caching
There are two status codes I mean specifically:
- 200 – “Ok“. Tells the browser that the server was able to respond successfully to the request. The response contains the data from the server in the HTTP payload.
- 304 – “Not modified“. Tells the browser that the data for the sent request did not change and that data should be loaded from the cache. In this case the response does not contain any payload.
Since a “Not Modified”-message contains much less data (no content, only header) that’s what you preferably want to return to the browser. Of cause you only want to that if you are sure that the browser already has the data.
For my application I found that the server was returning far more often 200-responses than 304-responses which caused
- More data than necessary had to be transmitted.
- More data had to be processed by the UI.
Both things were slowing down the application. Just a little, but enough to be noticed in the UI. Luckily there are only a few things you need to change in your Rails application to get the right behaviour.
1) Use stale? in your GET methods
def show @list_item = @list.list_items.find( params[ :id ] ) if stale?( :etag => @list_item, :last_modified => @list_item.updated_at.utc, :public => true ) respond_with( @list_item ) end end
Stale? sends back an etag and a last_modified date in the response. With the next request to the same URL the browser will send this etag and last_modified date to the server. The stale? method then analyses both parameters and returns 304 in case they are the same or 200 plus the new content if the newly calculated values are different.

More information on stale? you get in Rails’ API documentation and on Rails Guides.
2) Make sure the browser asks for new data on each request
After above modifications something interesting was happening. If the same Ajax action was triggered multiple times within a short period of time, the browser wouldn’t send a request at all to the server, but instead take the data from the cache. While obviously this made the UI really fast, it wasn’t exactly what I wanted. My goal was maximum performance while guaranteeing correct and up to date data on the screen.
The browser’s caching behaviour is influenced by three HTTP header flags: cache-controll, pragma and expires.
To “disable” caching in the browser all together you could send the following:
def set_cache_buster response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" end
What I wanted however is this:
def set_must_revalidate response.headers["Cache-Control"] = "must-revalidate" end
This advices the browser to check for new / updated data with each request. I added this method to my application_controller.rb and I’m calling it in a before_filter of controllers for which I want to have this flag set.
3) Using stale? for GET methods which return collections (e.g. index)
Above stale? example is taken from a controller’s show method. This is what you find in most examples on the web. To make this work in a method which returns a collection, like typically a controller’s index method you need to find some way to figure out whether the current collection is the same as it was for the last request.
List Kung Fu has a model List which has_many ListItems. Each ListItem belongs_to a List. To be able to determine in list_items_controller whether a collection of ListItems changed, I added code which updates the list.updated_at timestamp for each write operation on a list_item.
In ListItem.rb:
class ListItem < ActiveRecord::Base belongs_to :list after_save :update_list after_destroy :update_list # [...] def update_list self.list.updated_at = Time.now self.list.save end end
Thus, in the list_items_controller the index method could look like this:
def index @list_items = @list.list_items if stale?( :last_modified => @list.updated_at ) respond_with( @list_items ) end end
Instead of using the updated_at field I could have added a version field to the List model, but that seemed unnecessary. If the model doesn't have a model it belongs_to, you need to find another strategy for checking whether the collection has been modified. Maybe it's possible to calculate a checksum over all objects in the collection...
So much for this post. I also switched from using jQuery's getJSON to Wycats jquery-offline. More about that in my next post.


I'm a Software Developer, currently working at