Adding Concurrency to Rails Apps with Unicorn
Posted by Richard
With support for Node.js, Java, Scala and other multi-threaded languages, Heroku allows you to take full advantage of concurrent request processing and get more performance out of each dyno. Ruby should be no exception.
If you are running Ruby on Rails with Thin, or another single-threaded server, you may be seeing bottlenecks in your application. These servers only process one request at a time and can cause unnecessary queuing. Instead, you can improve performance by choosing a concurrent server such as Unicorn which will make your app faster and make better use of your system resources. In this article we will explore how Unicorn works, how it gives you more processing power, and how to run it on Heroku.
Concurrency and Forking
Process forking is a critical component of Unix's design. When a process forks it creates a copy of itself. Unicorn forks multiple OS processes within each dyno to allow a Rails app to support multiple concurrent requests without requiring them to be thread-safe. This means that even if your app is only designed to handle one request at a time, with Unicorn you can handle concurrent connections.
Unicorn leverages the operating system to do most of the heavy lifting when creating and maintaining these forks. Unix-based systems are extremely efficient at forking, and even take advantage of Copy on Write optimizations that are similar to those in the recently released Ruby 2.0.
Unicorn on Rails
By running Unicorn in production, you can significantly increase throughput per dyno and avoid or reduce queuing when your app is under load. Unicorn can be difficult to setup and configure, so we’ve provided configuration documentation to make it easier to get started.
Let's set up a Rails app to use Unicorn.
Setting up Unicorn
First, add Unicorn to your application
$ bundle install, now you are ready to configure your app to use Unicorn.
Create a configuration file for Unicorn at
$ touch config/unicorn.rb
Now we're going to add Unicorn-specific configuration options, that we explain in detail in Heroku's Unicorn documentation:
# config/unicorn.rb worker_processes 3 timeout 30 preload_app true before_fork do |server, worker| Signal.trap 'TERM' do puts 'Unicorn master intercepting TERM and sending myself QUIT instead' Process.kill 'QUIT', Process.pid end defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! end after_fork do |server, worker| Signal.trap 'TERM' do puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to sent QUIT' end defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection end
This default configuration assumes a standard Rails app with Active Record, see Heroku's Unicorn documentation for more information. You should also get acquainted with the different options in the official Unicorn documentation.
Now that we've got your app setup to use Unicorn, you’ll need to tell Heroku how to run it in production.
Unicorn in your Procfile
Change the web command in your
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
Now try running your server locally with
$ foreman start. Once you're happy with your changes, commit to git, deploy to staging, and when you're ready deploy to production.
A World of Concurrency
With the recent release of the Rails 4 beta, which is threadsafe by default, it's becoming increasingly clear that Rubyists care about concurrency.
Unicorn gives us the ability to take multiple requests at a time, but it is by no means the only option when it comes to concurrent Rack servers. Another popular alternative is Puma which uses threads instead of forking processes. Puma does however require that your code is threadsafe.
If you've never run a concurrent server in production, we encourage you to spend some time exploring the ecosystem. After all no one knows your app's requirements better than you.
Whatever you do don't settle for one request at a time. Demand performance, demand concurrency, and try Unicorn today.