As far as Web servers go, life is simple. Caching either happens on the server, or out there on the client.
Server-side caching works well if you’re serving the same content to a lot of clients — Web speak for browsers and anything else with an HTTP library — and generally each client makes requests slower than data changes. But if you’re serving individual content to each client, soon enough you’re going to run out of cache space.
You can either buy more memory, or have the clients cache on their side, which opens up a new world of infinite cache size. Super.
HTTP has two methods for caching. If you know when changes are about to happen, you can tell the client how long to cache data using Expires or Cache-Control max-age. They work extremely well, as long as you can make an educated guess. But if you’re wrong, and users start asking where’s that item I just added to my order, then it ends up worse than not caching.
This article involves no guess work. Instead, we’ll use conditional GET.
Conditional GET
Basically, get(request) if unmodified.
First time the client makes a request, you send back the response along with two headers. Either or both, we’ll get to talk about the difference later on. The first header is Last-Modified and contains a timestamp of the last modification. The second header is ETag and contains a hash, we’ll use MD5 here, of the content.
There’s also Cache-Control which tells the client it must-revalidate, important but that’s all I have to say about it.
The second time the client makes a request, it sends along the value of Last-Modified in the If-Unmodified-Since header, and the value of ETag in the If-None-Match header. Again either or both, depending on which headers it got in the first request.
At this point the server does a quick comparison and decides what to do next. That’s where it gets to be conditional. If it notices that the data has updated between the two requests, it serves up a new response along with new Last-Modified and ETag headers. If it knows the data is the same, it doesn’t bother creating a response, and instead sends back the status code 304 (Not Modified). That tells the client to pick up the response from its cache.
The payoff? Instead of rendering and sending a 50KB page, which you already sent once, you get to answer the second request with a few headers, all neatly packed into a single TCP packet.
Fast is what we’re looking for here.
Rails ETag vs if_modified
Rails 2.0 already has a feature that uses ETag to great effect. At the end of each request it calculates an ETag from the response. The first time it will send the entire response, the second time it sees that the ETag matches the If-None-Match headed and sends back just 304.
By not sending the entire request the second time around, it gets both server and client off the hook early on. In quantitative terms, you get better throughput on the server by turning around requests faster, and better response time on the client. It can also lower your bandwidth bill.
So what’s not to love? Well, you’re still rendering the entire response before Rails can decide whether to send it all, or just the headers. So there’s a hit on the database, there’s controller logic, there’s view rendering going on. We want to do better.
Also, would be cool to have conditional PUT. More about this later.
if_modified works it magic before serving the request. It uses the data for the request — some, not all, and that’s an important distinction — to decide whether there’s any difference between what the client saw before, and what we would be serving if we didn’t know any better.
If there’s a difference, it goes right ahead and renders the new response, and will include Last-Modified and ETag headers. If it’s all the same, it sends back a 304 without passing through view, without collecting $200.
# Render only if modified since last GET/PUT.
def show()
@item = Item.find(params[:id])
if_modified @item do
render :action=>”item”
end
end
You still need to load something, but that’s far less work than running the entire controller/view logic. And you only need to load that which signifies change. Say my blog page includes the last 30 posts, with comments, categories for the side bar, and all other good things. But I only need to grab the last published post to decide between content and 304.
It’s not free, but it’s a hell of a lot cheaper.
Conditional PUT
Let me introduce you to another nifty feature of HTTP. It’s commonly known as conditional PUT, but its “what do I get out of this?” nickname is optimistic concurrency. For HTTP.
Here’s how it works. The GET part is the same as before, client gets Last-Modified and/or ETag and holds on to them. Client them makes some changes to the data on its side, doesn’t know or care what happens on the server. Next, happy with the new values, it updates the server with a PUT. But this time, it sends back Last-Modified as If-Unmodified-Since, and ETag as If-Match.
Like before, the server looks at these headers, compares them to what it knows, and decides. But now it makes a different decision. If it detects no change, both client and server agree that the original data is the same, it does the update. If it detects a change, which happens if someone updated the data before the client got around to doing a PUT, it sends back 412 (Precondition Failed).
In response, the client can give up, but it can also GET the data again, make the same change, and PUT back a new update. Such conflicts are rare, so we’re not too concerned about the performance cost. And we’re no longer concerned about race conditions, where one update overwrites the other, or end up with an inconsistency.
Basically, if you ever stopped to wonder “how do these people manage using HTTP without transactions?” or were just about to invent your own HTTP-TX protocol, most likely you just needed a conditional PUT.
# Update only if not modified since last GET/PUT.
def update()
@item = Item.find(params[:id])
if_unmodified @item do
@item.update_from params
render :action=>”item”
end
end
Sources of Influence
For me that would be the HTTP protocol, but this section is about influencing if_modified and if_unmodified.
You feed them any number of arguments, arrays and nils are just fine, they all get flattened and compacted. What matters is the list of objects after compacting, which may be empty. And if it is empty, there is no Last-Modified, and ETag is calculated from nil.
If there are objects to deal with, Last-Modified is calculated as the last (most recent) timestamp by calling updated_on or updated_at on each argument. That you already have, if your ActiveRecords use timestamps.
The ETag is calculated by calling etag on each of the objects, we’ll get to that in a second, but only if all the objects respond to that method. Important distinction, calling ETag on some argument would be a weak tag which is not currently supported.
All the values are then combined with the response format to create a hash. The hash depends on the order, so two lists of objects are identical only if they contain the same objects in the same order. Consistency. The response format is used to guarantee that if you’re asking for HTML first, and XML later, you don’t get back a 304. Semantically they’re the same, but the responses are different.
The implementation assumes ETag is a strong identity check, so you want something that changes when the object changes. You can implement the etag method yourself, but this is about making life easier, so there’s also an etag method on ActiveRecord::Base.
Typically it just runs over all the attributes and calculates a signature from the name/value pairs. If you’re using optimistic locks, it gets lazy and just combines the record ID with lock_version. Either way, it will also include all eager loaded associations.
Do We Need Both?
No. But Last-Modified is quicker to calculate so it’s used first, and typically will be enough to reach a decision without having to calculate ETag.
Sometimes not. Obviously it cannot detect sub-second modifications so that’s one reason to use both. Another one are associations. Say you created a relation between Manager and Employee, without modifying either one. So the timestamps are still the same, but the ETag calculated from the association is different.
Odds & Ends
Both if_modified and un_modified will send back new headers along with the response. It’s very rare, but if you need to send back headers from a different set of arguments, or want to roll your own, you can always call modified yourself.
Another good to know. Both If-Match and If-None-Match can take the special value ‘*’. In a GET, that will render a response if there are any objects, and 304 if there are none. Theoretically useful, but I’ve yet to establish for what.
In a PUT, this will perform the update only if there is no object, otherwise return a 412. That’s equivalent to saying “please create this resource if it doesn’t exist, but don’t change it if it does”. An idempotent way to create new resources, and the subject of another interesting feature called create_once. But more about that in a later post.
Getting It
if_modified is part of restfully_yours, once you install the plugin all these are added to your ActionController/ActiveRecord:
./script/plugin install http://labnotes.org/svn/public/ruby/rails_plugins/restfully_yours

