Rails 3.1 introduced HTTP streaming, a feature that can speed up the perceived page load times for your users. This feature doesn’t actually speed up any of your code, but rather starts sending the response before the entire page is rendered.
What this means is that your app can start sending out parts of the page that do not require any heavy calculation almost immediately. This really helps if you have some complex actions which take a long time to completely render.
The best part of this feature is that by rendering the top part of the page quickly, the users browser can begin to download assets such as CSS straight away, while your application works on getting the rest of the page out the door. This can speed up end user page load times dramatically.
Before getting into how to setup HTTP streaming, I thought I’d share the actual results i’ve had in a production application. We’ve been running HTTP streaming on Desktoppr for the last month or so and here are some charts from NewRelic RPM:
You can see that once streaming was added the purple area drops to almost 0. The purple area represents time spent waiting for the Web application to respond (which was previously up to 350ms for our really complex pages).
Here our app server response times don’t actually change, rails is reporting the time it takes to get the first byte out the door, which does dramatically improves. Previously our complex pages were taking up to 350ms to render that first byte, now they take less than 50ms.
Needs to be 3.1 or newer.
You need to use a templating engine that supports HTTP streaming. Currently ERB and Slim both support HTTP streaming, I have not looked at other templating engines except for HAML which currently does not support it.
You will need to move away from HAML if you want to stream, fortunately if you only want to stream some pages you can just change the layouts + templates + partials used in that one response, this might save you some time.
content_for doesn’t play nice with streaming. Instead you can use
which works the similarly but only allows you to call it once per yield. Be
careful though as the template will block until the
yield statement has a
provide call which may negate the effects of streaming somewhat.
Unicorn supports HTTP streaming with a simple configuration option
tcp_nopush. Check the unicorn documentation
for more information. If you’re using something besides unicorn then be sure to
check first if it supports streaming.
You want to make sure you aren’t doing anything intensive in your controllers. The whole point of streaming is to get as much of the HTML out the door as quick as possible which means deferring any heavy database calls, any external calls.
Newer versions of ActiveRecord do not evaluate queries until it is needed, this means that if your code in your controller looks like any of these, then you’re all good:
1 2 3
One thing to look for is when returning a list of results, don’t use
as it will immediately convert the results to an array, use
1 2 3 4 5
Finally Enabling streaming
You can do this per controller action like so:
1 2 3 4 5
Or using a responder
1 2 3 4 5 6
If you have any Rack middleware that modifies your page in some way, then it’s not going to work with streaming unfortunately. Obvious examples of this are gems that add Google Analytics tracking codes to your page as well as the newrelic_rpm gem.
To get around this limitation you will need to manually add code for those 3rd party services to your layout, rather than having middleware tweak your response. You can disable newrelic auto instrumentation and manually add the code to your layout as well. See the newrelic documentation for more info.
Testing it works
The first thing to check is that the server isn’t calculating a content length and is instead given us a streamed response. You can confirm this by checking the response headers for your site like so:
1 2 3 4 5
If this doesn’t show that the transfer encoding is chunked then be sure that you’re running an app server that supports streaming (perhaps try unicorn).
Next is the final test, simply add a
sleep(5) call in your layout, just above
<body> tag is usually best. Now curl the page with:
http://your.web.site/. If working correctly you should notice the top of the
page download, then a few seconds later the rest of the page. If it all comes
down in one go without pausing then something isn’t setup correctly, perhaps
make sure you don’t have any rack middleware that is modifying your response.