Make Your Rails Application Multi-Threaded on Heroku

Multi Thread Image I recently read a pretty good e-book on parallel and concurrent programming in Ruby Working with Ruby Threads by Jesse Storimer. I decided to try out running something with Rails and threading enabled, so I converted this blog to run with Puma and Rubinius on Heroku. Here’s how I did that

Configuring Puma

Add Puma to the Gemfile and disable Unicorn

gem 'puma'
# gem 'unicorn'

Also change the Heroku Procfile to use Puma with a minimum of 5 threads and a maximum of 16 - the defaults are 0 and 16

web: bundle exec puma -t 5:16 -p $PORT

Change to Rubinius in Gemfile

Update the Gemfile to tell Heroku to use Rubinius:

ruby "1.9.3", :engine => "rbx", :engine_version => "2.0.0.rc1"

Configure the Rails Database Pool

Since Heroku overwrites the database.yml file during deploy, you can’t configure the database pool size in that file. Heroku documents how to configure a connection pool

Create a new initializer file called database_connection.rb (config/initializers/database_connection.rb) with the following contents

Rails.application.config.after_initialize do
  ActiveRecord::Base.connection_pool.disconnect!
  ActiveSupport.on_load(:active_record) do
    config = Rails.application.config.database_configuration[Rails.env]
    config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
    config['pool'] = ENV['DB_POOL'] || 16
    ActiveRecord::Base.establish_connection(config)
  end
end

Tell Rails We’re Threadsafe to Avoid Single Threaded Requests

In the application.rb file, add the following configuration line, inside the class Application < Rails::Application block.

config.threadsafe!

Caveats

Rubinius has much slower startup than MRI 1.9.3 and 2.0.0, but seems to have a very similar runtime performance to MRI 1.9.3. If you can live with this, you may have an application that performs better for your workload.

This slow startup actually causes some problems with Heroku, as requests start coming into the app before it is really ready. This means the first few requests after an application restart are likely to fail and produce errors to your users. If you deploy frequently, this will be a problem for you.

Does This Improve My Application?

The only way to tell if your application performance is improved is to measure the before and after speed of your application. If your application runs slower and gives your users a worse experience, then you want to know this and revert back to your current configuration. Only you can tell if this is worth it for your workload.

Heroku Memory And Load Logging

Heroku has a setting to dump to your log stream every few seconds a set of useful information including memory usage and system load. In your command line, type the following

heroku labs:enable log-runtime-metrics

There’s also a useful application that shows this data, when enabled in your application, in a single page display. Read about log2viz, but it seems a bit flakey for me, often missing data from the application.

Conclusion

Some time ago, multi-threading in Ruby was a dark art, and many common Gems had problems running in a multi-threaded environment. However, we’re now at a point where a lot of the common Ruby software will work fine in a multi-threaded way, but you still need to know how to write your own code in a way that doesn’t break in strange and unhappy ways. Jesse Storimer’s e-book, Working with Ruby Threads, can help you get started on that path.

Working with Ruby Threads