Better Ruby on Rails and Heroku Performance

August 22nd, 2014

Heroku is great for deploying small to medium size Ruby on Rails applications. I have been deploying and working on quite a few different applications lately and thought I would share some of the ways that I keep them responsive and the # of dynos to a minimum. Most of these ideas will work on hosts other than Heroku as well and avoid common Rails performance pitfalls.

These are more ops/deployment ideas and should be done in addition to building a performant RoR application (e.g. proper database indexes, no n+1 queries, optimized images, non-blocking js, best Ruby Interpreter). Most of these things are fairly simple and inexpensive to implement.

Use Unicorn for multiple Rails instances per dyno

Most Rails servers(Mongrel, Thin, WEBrick...) can only handle a single request at a time, causing Heroku dynos to become blocked from slow requests, not enough dynos, etc... Unicorn is a server that allows you to handle multiple requests asynchronously by running multiple Rails instances per dyno. You should be able to run 2 to 5 instances per dyno and will likely be limited by the memory footprint of your application - check the logs! Setup entails installing the gem, creating a Unicorn config file and updating your Procfile.

GZip/deflate your assets

Heroku by default does not support compressed/GZipped assets. Compressed assets can easily be 75% smaller than their non-compressed counterparts. This cuts down on bandwidth and makes your application seem more responsive, especially on mobile and slow connections by making those large precompiled JS and CSS files load faster. I have tried doing this a few ways, but have found this gem to be the easiest way to enable GZipped assets on Heroku.

Serve assets through a CDN

In addition to having compressed assets, it helps to have them not be served through Rails; using a CDN for assets frees up your Rails instances from those requests and I/O. Similarly, CDNs will generally be faster in serving assets. I prefer using CloudFront with origin-pull works great and is easy to setup - just change the config.action_controller.asset_host setting in config/environments/production.rb and setup CloudFront. Instructions from Heroku.

Serve images/S3 content through a CDN

S3 is not a CDN; it is a 'Storage Service'. Files from S3 can take a while to download and putting CloudFront in front of them will make them load much faster. My S3 files are handled with CarrierWave and getting it to work with CloudFront was as easy as setting config.asset_host in my production CarrierWave configuration.

Upload straight to S3 (or some other service)

Large upload requests will block your Rails instances and ones taking 30+ sec on Heroku will be killed. It's never a good idea to have your Rails instances handle uploads. Instead, upload straight to S3 with a gem like CarrierWave Direct or use a service like Filepicker.io.

Handle mail, processing with workers

If there is anything happening in a request that is not needed for the response, eg: image processing, some network requests, mailing, etc... push it to a background worker. This will keep your web instances from getting tied up and keep response times low. Heroku guide to workers.

Cache view fragments, Memcache

Look at your server logs and see if there are any slow queries or pages/fragments that are slow to render. Some pages can be cached entirely, with the exception of user specific or rapidly dynamic content. Memcachier works well with Heroku and most of my fragment reads take just 1-2ms. Make sure you have the right expirations in place and be careful and deliberate when setting up caching - it can easily complicate your application and you may be serving the wrong content. Rails guide to Caching

I hope this helps and would love to hear other Rails and Heroku performance suggestions!