Heroku and Unicorn: How to Get a 4x Performance Boost

Written by: Florian Motlik
5 min read

Update: After the shitstorm surrounding Heroku's routing they have updated their docs and now regard Unicorn as their default Rails server. They now provide 2x dynos with twice the RAM so you can run bigger applications with unicorn or run more unicorn workers on one server. Last but not least New Relic updated their gem to make sure that the queuing time shown for a Heroku application is correct. We have left the rest of the blogpost untouched as it still reflects how you can set up Unicorn on your Heroku machines. But take the changes in the recent Heroku blogposts into account.


This is the first of a two part series on how we set up Codeship with Heroku. The second part deals with Assets, Sprites and Amazon Cloudfront. This guide is only relevant and tested with the Heroku Cedar stack.

I have had several debates over the last couple of months whether Heroku is the way to go and especially if it is expensive or not. They provide a great service, but their documentation makes them look pretty bad when it comes to price. $35 for basically one single concurrent request seems very expensive, especially when starting out with a new project (although of course the first dyno is free). Heroku provides plenty of resources, but as it only allows to listen on one port you can run only one thin instance (as recommended by their documentation). What we need here is a webserver that listens on one port, but can work through several concurrent requests. Sounds like a job for Unicorn.

What is Unicorn

Unicorn is a ruby http server that starts one controller process listening on one port and forks several worker processes. Every incoming client request is handed to a worker by the controller and when finished the controller returns the result to the client. Thus it only needs to listen on one port, but can work on several concurrent requests. Defunkt wrote a nice blogpost about unicorn some time ago that goes into more detail how GitHub uses it.

Setup

To start using Unicorn all you have to do is:

1. Create a Procfile

https://gist.github.com/2621308.js?file=Procfile 2. Add unicorn config in config/unicorn.rb

3. Set the default Logger in application.rb (and not just production.rb) to STDOUT, otherwise logging doesn't work. Thx to @krainboltgreene for mentioning that just setting this in production.rb is not enough.

Unicorn config

You can find all config parameters in the unicorn documentation. Let's go quickly through the configuration we use for worker_processes: Setting the number of worker processes timeout: Time after which a worker is restarted if unresponsive preload_app: Load the application before forking workers. Set to true if you use NewRelic (which you should) or you won't see any data before_fork/after_fork: Disconnect in before_fork and reconnect in after_fork for your Database, Resque or other services. Without those handlers there will be regular database errors.

New Relic

Go to the NewRelic Addon of your Heroku application and check your dynos and memory consumption in the dyno tab.

Average memory consumption

Check your Memory Consumption in NewRelic and set the worker_processes accordingly. The average consumption is shown on the Dyno tab of your NewRelic dashboard.

One Heroku Dyno has 512Mb of Memory, so make sure your combined workers do not exceed that maximum amount, or your dyno will be shut down.

On the right hand side of that same tab you can see the number of dynos. Make sure it is the same as you set in worker_processes.

Benchmarks

https://www.google.com/jsapi// google.load("visualization", "1", {packages:["corechart"]}); google.setOnLoadCallback(drawChart); function drawChart() { var data = google.visualization.arrayToDataTable([ ['Workers', 'Seconds'], ['1', 45], ['2', 20], ['3', 17], ['4', 11] ]); var options = { title: 'Apache Bench', vAxis: {title: 'Workers', titleTextStyle: {color: 'red'}}, hAxis: {minValue: 0, maxValue:50} }; var chart = new google.visualization.BarChart(document.getElementById('ab_chart')); chart.draw(data, options); } //

I ran several tests with ApacheBench to determine how much the performance improved. I ran 1000 Requests with 100 concurrent connections against the landing page of our staging application. The following graph shows the time the requests took combined with 1-4 workers. Going from one process to several increases performance drastically, from then on it is still a boost to your application, but not as drastically. However you have to find the right spot on how many workers you want unicorn to fork depending on your application. Having too many may shut down your dyno due to memory constraints.

Conclusion

So in closing using Unicorn as your Heroku Webserver not only pays off, but should be put into the Heroku documentation at least as advanced information. I actually talked to people and showed them our Unicorn setup, which convinced them that Heroku is not as expensive as it seems and especially when starting your project is a very viable alternative to having your own Server.

With every new dyno you get several more concurrent requests, which is pretty neat. If you have any questions regarding the setup or anything else you can send an email to  flo@codeship.io or tweet to @Codeship.

Thanks

This post is very much built on Michael van Rooijen's Blogpost. Gists that helped with the setup were by leshill and jamiew

Posts you may also find interesting:

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.