Johan Sørensen

Dynamic page caching with Nginx & SSI

Zed Shaw mentioned this on the Rails podcast, but you see, Nginx has this ability to do virtual SSI includes from another url. The documentation on it is right here but half of it is in russian to confuse the spies. It’s pretty straightforward stuff though, so lets play around with it for a bit. Like Zed says on the podcast, this kinda stuff would really be useful in situations where you can page cache pretty much the entire page, except this little part that needs to be dynamic (like a “Welcome <\%= -\%>”)

Server Side Includes where these things we all used in the nineties for sprinkling random dynamic(-ish) stuff over our homepages. Nginx has support for virtual includes that looks something like this

which will include whatever the url /foo returns straight into the document where the include is defined (you can also throw it into another SSI block if you like, as the docs say).
Our little Rails testapp for this does about 205 req/s without any caching and using render(:partial => "foo") for the “welcome” bit (I feel really bad mentioning Zed Shaw and stupid/naive statistics like the above in the same place, but the precise performance gains aren’t that important. Think big picture stuff for now).
So here’s a little helper for outputting the SSI in our template:

  def ssi_include(options={})
    #(options hash so we can pass in a SSI block target or whatever, YAGNI really).
  # and in our view we'd use it like this:
  <%= ssi_include :url => {:action => "greet", :name =>} -%>

Not exactly rocket science. With that and page caching turned on, it’s slightly faster (about 250 req/s), but not that much. Chances are we can cache those fragments in memcache to gain just a bit more.

By now you’ve hopefully realized that that the actual greet don’t even need to come from rails to begin with; with a shared session storage and because Nginx forwards us the cookies to the virtual included url, we can just as well hook up a small Merb or Rack (or whatever) app to fetch the session_id and/or objects needed from the database (or cache storage) and display the correct text, all without the luggage from rails which we really don’t need just to render some tiny text fragments.
Doing that in our stupid little test scenario here gives us just over 1000 req/s. That’s a bit closer to the 5K req/s that nginx does for straight up html from disk (on my local machine) than the 205req/s we started off with. Yet another thing to pull out of the olde bag of tricks when you really do need it. I’d be interested in hearing if others have experimented in practice with this kinda approach?

update: passing in the “name” querystring like the example code is about the worst example I could think of, since its static once it’s written to the cached template, but cookies and such are still go