Contact Me
Rails Recipes
My Job Went to India

These days, with all the buzz around rich browser-based client development, our applications are more and more likely to include a large number of external resources. Beautiful little icons, JavaScript infrastructure and effects libraries, and external CSS files add up quickly. As soon as you add a line such as:

<%= javascript_include_tag :defaults %>

to your Rails application (never mind whether this practice is optimal—it’s pervasive), the browser has to download several separate files to completely load a single page.

I’m pretty far to the left on the premature optimization continuum, so I wouldn’t worry about this kind of thing unless it were a problem. It turns out it really is a problem. Especially if your application is being accessed via HTTPS. Not only does HTTPS add the overhead of encryption and encryption to your request, but browsers will not cache resources requested via HTTPS. This leads to very noticeable performance issues—the kind your users complain about (and rightly so).

It turns out this problem is even worse than I previously realized. According to this page (which I found out about in the patch I’ll mention in the next paragraph), most browsers impose a limit on how many connections can be made to a single named host at any given time. And on the internet’s most popular browser, this number is a whopping two. Add to this the fact that HTTP Pipelining is disabled by default, and a resource-heavy page is a recipe for a sluggish user experience.

So it was with great interest that I noticed this change late last night in Edge Rails. Jeremy Kemper checked in a new feature allowing Rails developers to configure multiple asset hosts using a numbered naming convention.

As a refresher, the ActionController::Base.asset_host variable tells your Rails application to generate external resources’ links pointing to the host of your choosing. This is a good way to ensure that your Mongrel or FastCGI process isn’t being asked to serve static content.

With Jeremy’s change, you can now do this:

ActionController::Base.asset_host = "http://assets%d.example.com"

This tells Rails to generate links to the following four hosts: assets0.example.com, assets1.example.com, assets2.example.com, and assets3.example.com.

Keep in mind that for this technique to be effective, you don’t have to configure four separate machines to respond to these requests. Simply creating these four DNS aliases and pointing them at one server will still make a difference, since the browser per-host limitations are per name—not per IP.

UPDATE: According to Dan Kubb and DHH, setting the Cache-Control and/or Expires headers properly on your web server will get around the SSL caching issue I mentioned above. Somehow I must have gotten it wrong when I tried on my own. Thanks for pointing that out, guys!

15 Comments

  1. Matthew King Says:

    Add Amazon S3 to taste, and serve immediately.

  2. Jeff Coleman Says:

    Would this improve performance on a non-https server as well, or does it only make a difference on secure hosts?

    Jeff

  3. Chad Fowler Says:

    Matthew, indeed.

    Jeff, it would improve performance on non-HTTPS as well. I only mention HTTPS, because that’s where it is the worst currently.

  4. Alex Says:

    Jeff:

    Browsers limit how many connections they will make to a host for both HTTP and HTTPS connections. This is therefor an improvement for both.

    Browsers do not however cache for HTTPS connections so the improvement will be much more pronounced for these.

    Hope that helps.

    Alex.

  5. Tim Lucas Says:

    Of course the real solution is to reduce the number of assets you serve, rather than tricking the user agent into requesting from different hosts, but it’s a good hack. I guess the concurrent limit HTTP spec was there to serve the web host from being flooded, so if it’s your app then you could argue it’s your right to hack around that limitation.

  6. chrisfarrms Says:

    intresting idea…. my first thought is that this could be incredibly detrimental to the browser’s own caching mechanics…. it may (for example) download a javascript file 4 times because the url keeps changing?

    so I guess the handing out of the asset_host should be consistant in someway…. maybe css.domain.com , js.domain.com etc could solve this

  7. Michael Schuerig Says:

    For Rails, there is the very easily usable Asset Packager Pluging. Given that, there really is not reason, on most pages, to serve multiple javascript and stylesheet files. Of course, in development envrionment you still can work with as many files as you like.

  8. Dan Kubb Says:

    chrisfarms, after a quick glance at this patch, it looks like inside compute_asset_host it uses String#hash (source.hash) to return the same hostname given the same filename. That means each file will always be accessed from the same host, and the URL should be the same provide the asset_id in the query string hasn’t changed.

  9. Dan Kubb Says:

    Michael, it looks like the Asset Packager’s functionality snuck into core too: http://dev.rubyonrails.org/changeset/6164

    For anyone that’s never used Asset Packager, it basically just combines all your stylesheets into a single file, and all your javascripts into a single file. It speeds things up alot by reducing the number of HTTP requests the browser has to make.

    Combine that with distributed assets, gzip compression and maybe offloading to S3 and you’ll increase the first-download of your assets, while subtantially cutting back on repeat downloads. Save bandwidth AND noticable increase percieved performance of your apps.

    Chad, I’m also pretty sure that if you set the Cache-Control and/or Expires headers you can cache assets even if they are served over SSL.

  10. Chad Fowler Says:

    “Chad, I?m also pretty sure that if you set the Cache-Control and/or Expires headers you can cache assets even if they are served over SSL.” Dan, oh yea? I seem to recall eating an afternoon trying to get those headers to do something to no avail. Any pointers would be greatly appreciated.

  11. diego Says:

    Chad, I released a plugin exactly a month ago that does this kind of asset split in a slightly different way. It currently allows you to set a domain for each type of host. I allows you to pass either a string (one host for all) or a hash (one host for each asset type. like so:

    ActionController::Base.asset_host = {:images => “http://images.domain.com”, :javascripts => “http://javascripts.domain.com”, :stylesheets => “http://stylesheets.domain.com”}

    It would be a quick fix to allow an array of hosts for each type. btw, I do it this way because I might serve images from s3, but javascripts and stylesheet from apache with mod_deflate. (can’t do mod_deflate in s3)

    check it out: http://jroller.com/page/dscataglini/?anchor=multi_asset_locations_plugin

  12. DHH Says:

    A few clarifications. Browsers actually cache assets just fine on SSL if you set the proper headers (which most web servers will do by default). Also, the asset host strategy in Rails core actually uses the same asset host for the same asset all the time. So no need to manually designate different asset types to different hosts.

  13. diego Says:

    DHH, I didn’t imply that there is a need to designate different asset type to different hosts with Jeremy’s implementation in edge. I was talking about my implementation, based on my needs :). I needed to serve the 3 asset types from 3 specific, predefined locations. Hence my plugin.

    BTW, If you’re interested, since it seems lately that I am creating ( for myself ) stuff that is making it in edge anyway, one plug-in that I am toying with is multi-db-connections.

    Pretty much you can define a set of read and write database connections so that for example: all your read queries go to your replicated servers and your writes (cud :D) go to the master(s). The plugin will then just round-robin through the available connections when given an array.

    Let me know if you guys are working on this one too :D

    Cheers!

  14. Chris Says:

    Diego, that would rock … can’t tell you how cool that would be.

  15. Yong Bakos Says:

    Thanks for sharing this Chad.

Sorry, comments are closed for this article.