Heroku Configuration for Performance Rails Apps

December 8, 2012 Link to post  Permalink

The Heroku Platform as a Service is a great way to deploy your Rails apps quickly, but there are a few differences in running a Rails app on their platform.

Serve Static Assets

In a typical Rails application, you want to have your web server, using configuration in Apache or Nginx, deliver static files to the browser. This avoids running any Rails code for the simple case of sending bytes to the network. Heroku doesn’t work this way. As your Rails app is deployed, Heroku injects the Rack::Static middleware into your middleware stack to serve static files from your /public directory. This includes your pre-compiled assets.

This is equivalent to the following entry in your production.rb file

  config.serve_static_assets = true

Since you are using the config.assets.digest = true option for your pre-compiled assets, you can afford to set these static assets to be cached by browsers for as long as you need. Add this to your production.rb file

  config.static_cache_control = "public, max-age=31536000"

GZip All The Bytes

One of the easiest ways to improve your web site load times is to make sure you serve compressed data to all the browsers that accept compressed content. Pre-compiling your assets creates a set of GZipped files that you can use in other deployments, but Heroku doesn’t serve these files at all.

The simplest way to serve compressed content is to use the Rack::Deflater middleware to handle all compression duties. If you configure it correctly, you can use this to compress all of the static content you have to serve up, as well as the HTML you generate from your Rails code.

Add this to your production.rb

  config.middleware.insert_before('ActionDispatch::Static', Rack::Deflater)

Since Heroku isn’t using your .gz files from the pre-compile stage, you can disable compression to speed up deployment:

  config.assets.compress = false

Fixing The Deflater

There is a bug in the Rack::Deflater code that needs to be patched. This fix is in the current Rack gihtub repository, but not in the latest version of the Rack Gem. The patch ensures that the @body.close code is always executed.

I patch code by adding a config/initializer script, in this case I call it rack_deflater.rb

require 'rack/deflater'

module Rack
  class Deflater
    class GzipStream
      def each(&block)
        @writer = block
        gzip  =::Zlib::GzipWriter.new(self)
        gzip.mtime = @mtime
        @body.each { |part|
          gzip.write(part)
          gzip.flush
        }
      ensure
        @body.close if @body.respond_to?(:close)
        gzip.close if gzip && !gzip.closed?
        @writer = nil
      end
    end
  end
end

Summary

For Heroku, the regular production Rails configuration needs to be different to get the best performance for your Rails application

  • Your app needs to serve static assets, so make sure that browsers cache them
  • Compress all compressible content
  • Patch the Rack compression code to fix a bug