Chunked transfer encoding in Rails (streaming)

Anyone that has written a little PHP knows what the flush() family of functions do. The ideal usage scenario for using chunked transfer[0] is when we have something costly to render e.g. the first three most recent articles on a blog. Why ? one might ask.

Is rather simple: in a normal request where the server responds with a Content-Length header the browser will wait until the whole page comes down the wire then it goes loading the assets et al.

Using the Transfer-Encoding: chunked header, the server will send chunks of the rendered page back to the browser so in the case of Rails, it starts with the layout and sends out the <head> part including assets like js and css.

It's clear how this helps the rendering of the page on the client side : get the first chunk containing the <head> with assets, immediately start loading the assets while waiting for the rest of the response. Of course, browsers nowadays include lots of micro-optimizations that might already do something similar but still this remains a good practice.

Implementation wise, you just need to add to your controller methods something like :

class YourController < ApplicationController
  def index
    @articles = Article.most_recent
    render stream: true
  end
  # other controller logic
end

The latest version of Unicorn (4.x) comes by default[1] with support for chunked response. You can always add to your unicorn_config.rb something like:

# :tcp_nopush This prevents partial TCP frames from being sent out
# :tcp_nodelay Disables Nagle’s algorithm on TCP sockets if true.
port = ENV["PORT"].to_i || 3000
# the ENV["PORT"] is a Heroku environment variable
listen port, tcp_nopush: false, tcp_nodelay: true

We also have some quirks when using streaming in Rails because of the inversion of template rendering order[2]:

When streaming, rendering happens top-down instead of inside-out. Rails starts with the layout, and the template is rendered later, when its yield is reached .

tl;dr: use provide instead of content_for when you have multiple calls to content_for otherwise it will break the purpose of streaming and/or it will concatenate the values from content_for.

There's also a “small” issue with NewRelic agent and Heroku: you need to disable browser instrumentation or else you'll get a blank page[3], thankfully the fix is rather trivial:

# config/newrelic.yml
  browser_monitoring:
    # By default the agent automatically injects
    # the monitoring JavaScript into web pages
    # Turn this to false
    auto_instrument: false

There's also ActionController::Live that can be used to create a simple Rails 4 chat application[4][5].

Tagged under: