Skip to content

Instantly share code, notes, and snippets.

@schneems
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save schneems/84e82a6fb80249ee7812 to your computer and use it in GitHub Desktop.
Save schneems/84e82a6fb80249ee7812 to your computer and use it in GitHub Desktop.

ActionDispatch::Static

Speed

By removing ActionDispatch on a barebones app we were able to achieve between ~1% and ~9% more iterations per second on every non-asset request.

Speed testing technique.

Generated two example apps with a simple erb endpoint that renders a hello world page. The point is to benchmark rails internals and not work done in the user space.

$ cat app/views/welcome/index.html.erb
<h2>Hello World</h2>

There's no logic in the controller and this endpoint is mounted at the root of the app. One app has ActionDispatch::Static on, the other has it off.

Rails.application.config.serve_static_assets = true

In this example we're not rendering assets but even so the ActionDispatch::Static middleware is hit which has a performance impact. I was able to measure this impact by booting both apps and benchmarking the response times locally:

L3000 = URI.parse "http://localhost:3000/"
L3001 = URI.parse "http://localhost:3001/"

x = Benchmark.ips do |x|
  x.config(time: 10, warmup: 5)
  x.report("With ActionDispatch::Static")    { Net::HTTP.get_response(L3000)  }
  x.report("Without ActionDispatch::Static") { Net::HTTP.get_response(L3001) }
  x.compare!
end

The results for different runs will achieve different results:

Calculating -------------------------------------
With ActionDispatch::Static
                            18 i/100ms
Without ActionDispatch::Static
                            18 i/100ms
-------------------------------------------------
With ActionDispatch::Static
                          158.8 (±15.1%) i/s -        774 in   4.999340s
Without ActionDispatch::Static
                          173.7 (±13.8%) i/s -        864 in   5.085668s

Comparison:
Without ActionDispatch::Static:      173.7 i/s
With ActionDispatch::Static:       158.8 i/s - 1.09x slower

The one constant seems to be that removing ActionDispatch::Static makes requests faster which makes sense. How much faster? On some runs:

(173.7 - 158.8)/ 158.8 * 100
# => ~9%
(186.4 - 176.4)/ 176.4 * 100
# => ~5%

(213.1 - 210.9) / 210.9 * 100
# => ~1 %

The lower bounds seems to be 1%, the upper bounds is much higher.

If you want your requests to have optimal speed, you should disable ActionDispatch::Static. To do this we need to understand it's functionality

Purposes

What does ActionDispatch::Static do? It currently serves assets from the /public directory. It can serve asset pipeline assets as well as "unmanaged" assets you place in this directory.

  • Serving asset pipeline assets (managed under public/assets)
  • Serving non-asset pipeline assets (unmanaged, anywhere else)

It is possible for it to serve user uploaded assets (user uploads smile.png and it goes into /public and is served by Static), but this is not best practice in production, and the only time you would enable this is in production. Doing this makes it impossible to scale up to more than one server, therefore this is not a supported use of this middleware.

It can also be used to serve static html pages i.e. /public/index.html. While this is still technically under the "serving non-asset pipeline assets" it is a special enough case.

  • Serve static pages under /public

An exception to this is error pages. ActionDispatch::Static does NOT render exceptions. Instead ActionDispatch::PublicExceptions handles internal errors, this is how 500.html etc. is rendered. To manually issue an exception you render it yourself

(http://stackoverflow.com/questions/779784/how-do-you-issue-a-404-response-from-a-rails-controller-action) (http://stackoverflow.com/questions/2385799/how-to-redirect-to-a-404-in-rails)

On or Off

By default ActionDispatch::Static is off in production. To serve assets in production you either need to manually enable it or to serve your assets via a third party. The most common third party is NGINX which requires configuration and then it will serve assets directly before your Ruby/Rails server ever sees the requests.

  • NGINX: Keep ActionDispatch::Static off

People can use something like asset sync which I recommend against (https://gist.github.com/schneems/9374188) and push their content somewhere else like S3. In this case asset requests are never made to the Ruby/Rails server so we don't need it.

  • Asset Sync (or similar): Keep ActionDispatch::Static off

Using a CDN is the Fastest and best way to deliver assets period. Rails asset pipeline easily supports them. A CDN must know where to get an asset. You can either "push" to a CDN, an example would be using asset sync to push to S3 and putting a CDN in front of that S3 bucket. You can also have a CDN "pull" assets on demand. This is on first request there will be a cache miss and they will hit an origin URL (your rails server) for the asset. On all other requests they will serve the asset directly.

  • CDN "push": similar to Asset Sync keep ActionDispatch::Static off
  • CDN "pull": Asset is only ever served once, but our Ruby/Rails site must be able to serve the asset once, so ActionDispatch::Static should be turned on

Lastly you can serve the asset directly from your Ruby/Rails server, this is a horrible idea as it is slow and impacts the speed of other requests. Using this with a CDN is a pretty good idea as you will only serve the asset once then the CDN will serve the asset faster than you ever could, but having no caching is setting users up for a bad time

  • Self Serve (No CDN): Keep ActionDispatch::Static on

This last option is acceptable for hackathons, school projects, or anything else not expecting "serious" traffic. That being said adding a CDN is so quick and easy, and the gains are so great, I would not consider an application "production grade" if it does not have a CDN in front of it.

If you are running NGINX, you should still be running behind a CDN for data locality. In short you should be running behind a CDN regardless of how your assets are served.

NO pain Asset Serving

While there are a variety of ways to serve assets, is no need to incur a ActionDispatch "tax" on every request when we are not serving assets. Of the "on" cases there are only two:

  • CDN "pull": ActionDispatch::Static should be turned on
  • Self Serve (No CDN): Keep ActionDispatch::Static on

In this case for the "pull" we don't care if our asset is delayed by a few milliseconds while being served if it is only served once and is cached forever after that. In the case of "self serve" it is clear that the end user does not prioritize speed and would not be drastically impacted if a few milliseconds of delay were introduced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment