Padrino with Foreman and Puma on Heroku

Padrino works great on heroku with just a simple git push heroku master, but you can get a surprising bump in performance simply from using Puma in production rather than the default heroku web server.

That also goes for using it on your local development machine, but heroku uses Procfile based spinning up of apps, so you should also test locally on your dev box, using heroku’s handy foreman before pushing it live to heroku.

It doesn’t take anything other than a tiny bit of configuration and change from using padrino start (or padrino s) locally to foreman start while you’re developing.

There wasn’t explicit documentation on how to get padrino, puma and heroku working together, and while not too tricky to figure out on your own, I felt it made sense to write it down here as a nice easy recipe to save some time for those who follow (waves at inbound google searcher), and to add some help for the awesome folks in the padrino community.

Pre-requisites

This post assumes you have a working padrino app you can boot up via padrino start and is focused on the use case of using ActiveRecord as your ORM and Postgres as your DB (as Heroku likes these things). It further assumes you have the very handy heroku-cli set up on your machine (If you can type heroku apps and get a response of all the apps running in your heroku account, you’re good to go. If not, homebrew is your friend and if you do a brew install heroku, you should have everything you need.). A properly set up heroku-cli means foreman is installed in running.

Foreman is a simple manager for Procfile based applications, allowing you to kick up apps via rackup rather than in the case of padrino, via the padrino start script. As heroku uses Procfile based startups itself, using it in dev better emulates how your app will (or will not) run on heroku. If you have a Procfile already in place for deploying to heroku, you should be able to do a foreman start and have the app boot up on port 5000. You can check your padrino app is running there with a simple http://localhost:5000 much as you would check http://localhost:3000 with padrino start.

This post is working with padrino 0.14.0.1, puma 3.8.2, foreman 0.84.0, and the heroku cli tools 5.8.11-0e9b5ce (darwin-amd64) go1.7.5.

Preparing Pumas

# Gemfile
gem 'puma'

Do a bundle install and get it into your Gemfile.lock.

In your Profile, write the following a la the guide for Rails on Heroku:

# Procfile
web: bundle exec puma -C config/puma.rb

While it’s most likely obvious, this starts up puma by referring to a config file located at config/puma.rb.

So, now we need to create a config file for puma. Riffing off what heroku has in place for it Rails and Puma documentation, I created the following:

# config/puma.rb
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['PADRINO_MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Padrino.env])
end

So, to quickly explain…

Basically, Puma has workers and threads (do not confuse puma workers with heroku workers you tend to use for async tasks, they are completely different beasts). Loosely speaking, workers consume more RAM and threads consume more CPU. Both provide more concurrency for your app and throughput. And so, a faster experience for your users.

Note a few important things here:

  1. On your dev box, if you haven’t set the envs for WEB_CONCURRENCY or PADRINO_MAX_THREADS in your .bash_profile, these will default to 2 workers and 5 threads on your box. This will both increase the CPU and RAM dedicated to throughput on the web server. This will make your local app seem significantly faster than it would be on heroku, so you may want to adjust those numbers to what you’ll have on heroku.

  2. On heroku production and staging (you do run a staging instance, right? Ahem… ), you need to set these with config_vars via the excellent Heroku CLI. While you can set web concurrency to 2, it is very very likely your app is not thread-safe unless you really know what you’re doing. Most likely you should set this to heroku config:set PADINO_MAX_THREADS=1. If you are absolutely convinced you are thread safe in your padrino app, you can most likely set this number to somewhere between 3 and 5 for a padrino app on a single dyne depending on whether it is the free, hobby or standard-1x dyno. (note these are only guidelines and you should check the memory on your app and logs to make sure you’re not topping out memory if you’re playing with squeezing everything you can out of this.).

  3. The tricky bit in the config is the line regarding establishing the connection to the database via ActiveRecord on worker boot. For those of you who have trawled around your padrino config directory, you’ll notice there is an exact similar line in config/database.rb. While I do not have a definitive answer on this, it appears it’s safe to leave this uncommented (and in fact if you do comment it out, padrino console and padrino start will no longer work). As near as I can tell, if you Procfile rackup the application, the puma workers handle the database connection and if you go via the padrino start route that is handled within padrino from the config/database.rb.

And Go

From here, you should be able to foreman start the padrino application and have it spin up on http://localhost:5000 with it running via puma.

After that, and git committing everything, it’s a simple matter to push the app as you normally would to heroku. Simply git push heroku master and watch your new, speedier, and puma-powered app come up.

nb: Do remember to set your heroku config:set PADRINO_MAX_THREADS=1 to make sure you don’t have any major issues, but so far I’ve been running this in production for a while and it is just giving me a noticeably faster, snappier experience that users have commented on.

Wanna know more about the internals? You can read up on heroku and puma here, Deploying Rails Applications with the Puma Web Server | Heroku Dev Center though its targeted specifically to Rails.