Chunked transfer encoding in Rails (streaming)

Anyone who 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?

Why stream

It is rather simple: in a normal Rails render, the server finishes producing the response before sending the body; that delays the browser from discovering assets placed in the layout.

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.

Rails implementation

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

Unicorn

The latest version of Unicorn (4.x) comes with support for chunked responses by default.[1] 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.fetch("PORT", "3000").to_i
# the ENV["PORT"] is a Heroku environment variable
listen port, tcp_nopush: false, tcp_nodelay: true

Rails rendering caveats

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.

New Relic

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].

References

Tagged under: