documentcloud / jammit

Industrial Strength Asset Packaging for Rails
http://documentcloud.github.com/jammit/
MIT License
1.16k stars 197 forks source link

Distributed Asset Hosts #6

Closed jashkenas closed 14 years ago

jashkenas commented 14 years ago

Yar wrote:

There is only one thing that seems to be missing: support for distributed asset hosts in the processed CSS files.

Some piece of code that would replace pieces like background: url('/images/backgrounds/topcapA1.jpg');

with

background: url('http://w2.domain-assets.com/images/backgrounds/topcapA1.jpg');

in accordance with config.action_controller.asset_host directive in production.rb. What procedure > or script do you actually use for this purpose?

jashkenas commented 14 years ago

Hey Yar.

There are a couple of reasons why I omitted distributed asset hosts, but I'd be glad to discuss the issue.

Modern browsers support more simultaneous connections than they used to -- it's up to 6 in Internet Explorer, and up to 8 in other browsers. Each connection to a new hostname incurs both the overhead of the slow TCP start, as well as a separate DNS lookup. I've heard the rule of thumb passed around that if you're making less than a dozen requests for separate assets, you're just as well with a single host -- even if your browser can only open two connections at a time. I would imagine with modern browsers that the number would be more on the order of 50 individual assets before you saw a speedup, perhaps even higher if you've got your keepalive correctly configured.

Hopefully, all of this is a moot point, because the whole idea of Jammit is to reduce the absolute number of HTTP requests you need to do on page load. If you've got more than a dozen HTTP requests, that's something that Jammit can help you fix with inlined images.

Finally, there's just something really ugly about the current distributed asset host mechanism in Rails. The hardcoded 4 asset hosts, the mucking around with DNS, it all seems like an attempt to fix the symptoms, not the real problem -- which is too many HTTP requests in the first place.

That said, Jammit already knows how to rewrite URLs within CSS, both for image embedding and for relative URL rewriting, so it wouldn't be too hard to add distributed asset hosts. What do you think?

yar commented 14 years ago

Hi Jeremy,

thank you for promoting my question to this discussion. It was mostly http://code.google.com/speed/page-speed/docs/rtt.html#ParallelizeDownloads that prompted me to consider using distributed asset hosts. Notice the paragraph that mentions serving static content from cookieless domains, and specifically http://code.google.com/speed/page-speed/docs/request.html. In short, asymmetrical DSL/cable/cellular connection speed, TCP "slow start" algorithm and lack of compression of request headers make HTTP request size much more expensive than response size - so we aim to make most requests fit into 1 packet. And cookies set on our main domain by 3rd party software like Google Analytics and others may prevent that. Cookies that are sent by the browser automatically with every single request. This is why at least one dedicated asset domain seems a very good idea - it separates the cookies reliably; one less thing to worry about.

One cookieless asset host is supported nicely by Jammit: if I load a CSS or JS file from a dedicated asset host, all asset paths from that file will default to the same domain.

Going from one asset host to several is more difficult to justify, exactly as you say. I too settled on using just one asset host for now.

However, here are the guys who definitely do not make optimization decisions by guesswork: www.amazon.com. And their homepage points to 3 asset hosts, sending 14 requests to first, 26 to second and 13 to third. They also make 3 requests to some 3rd party ad networks.

Another example, www.yahoo.com: 22 requests to one dedicated asset host, 18 to another.

These are, of course, big old websites with heritage. I am not completely sure that an average developer should use multiple asset hosts for a new site now. But definitely I would not use multiple asset hosts unless my CSS files took full advantage of them. Which, with Jammit, does not work right now.

The Rails default of 4 asset hosts may indeed be an overkill. To fix that, a Proc object can be provided in production.rb instead of the host name pattern. Would it be possible to teach Jammit to read that setting and use it to process CSS links? The opposite approach would be something along the lines of http://deaddeadgood.com/tags/rails. Alas, both ways are not very easy and fail-safe.

Yar

documentcloud commented 14 years ago

Just to be clear, there isn't an asset packager for Rails that already provides image URL re-writing within CSS files, is there? I'm wondering if there's a reference implementation somewhere we can take a look at.

The reason why it's a bit awkward to use ActionController::Base.asset_host is because at the moment Jammit (and the jammit command) don't need to load the Rails environment in order to package the assets -- just assets.yml. I'd rather keep it that way. If we do end up doing distributed asset hosts, it might be better to put that in assets.yml as well, and then Jammit can set it correctly inside of Rails, instead of the other way around.

What would your ideal asset-host setup look like?

yar commented 14 years ago

Uhm, let me think. Regardless of multiple or not multiple asset hosts, it seems beneficial to have versioned assets referenced from CSS files, so that we could set the expiration date far in future, and still have the option to quickly overwrite whatever we need.

background: url('/images/backgrounds/topcapA1.jpg?1234567890');

Sometimes the same images would be mentioned in arguments to IE-specific filters, like: filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/bubble-icon.png?1234567890',sizingMethod='scale');

So the syntax is not completely trivial.

Perhaps, the most universal approach would be to write CSS using image_tag(), image_path() and other helpers and then run CSS (and javascript) files through view rendering obtaining the final versions and then concatenating, minifying and compressing with Jammit. The approach of http://deaddeadgood.com/tags/rails is not perfect, though, because it does not allow to generate the files prior to switching to the new deployed version, potentially leading to a short downtime, and also does not allow to hook it up with Jammit. Otherwise, I find the idea of writing .css.erb (and .js.erb) very promising.

documentcloud commented 14 years ago

Alright -- it looks like there are a couple of directions this could go:

Because at DocumentCloud we're using mostly the Jammit-provided embedded images, these features wouldn't be much use for our project. I'm not going to start building out any of these, but will leave this ticket open for the time being, and would welcome any patches that address them.

Edit: It's been a couple of months, and no one has volunteered to pursue this issue. Closing it as a "wontfix" because it's probably not necessary for all of the common Jammit use cases.

triemstr commented 14 years ago

I use jammit and asset_host...asset_host does load css and js from multiple locations and I may soon use Amazon s3 for this, but all the css background urls are loaded inline with jammit's datauri. I could only see this as a use case for IE7 and below which cannot handle jammit's datauri (use 'embed_assets: datauri' in assets.yml for IE7 and below). The IE7 workaround is just to avoid too many background images in css and the rest of the asset requests (js/css/images loaded in html) will still use the asset host.

Here's my asset host configuration (which also avoids asset host requests for servers where I do not have ssl certs).

See http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html

ActionController::Base.asset_host = Proc.new { |source, request| if request.ssl? "#{request.protocol}#{request.host_with_port}" else

This does not work in a proc -> "#{request.protocol}assets%d.example.com"

### "#{request.protocol}assets#{rand(4)}.example.com"
"#{request.protocol}#{APP_URL}"

end }