Heroku Tips for the Cheap

I've been playing around with the Ruby/Rails cloud provider Heroku a little bit lately just to try it out. It is somewhat like Google App Engine or Microsoft Azure in the way it works since you bundle your application and push it out to the Heroku cloud for deployment. It is very easy to get things going but I ran into a few interesting items that I figured I would share.

Some of the following grew out of the requirement that you verify for a lot of the Heroku addons and verification requires a credit card. For some of these tips there are easier paths if you don't mind paying a little extra or just verifying your account.

Configuration

For configuration Heroku has a way but for some reason it didn't seem like the best or easiest way to do things. I started off setting up a configuration file as described in this railscast on using yaml configuration files. I took a while to dig a little more and found something similar but even better way of doing configuration with Heroku.

Set up a custom domain name

To make a more professional looking app you probably want to have a non-Heroku base domain name. They make it very easy to set that up using a CNAME DNS entry.

Poor man's cron

One of the things I ran into pretty quickly was the need to run a task once every minute. Heroku offers both cron and delayed job. The cron jobs offered are a little limited since the finest grained execution for cron is once an hour. I believe delayed jobs could be made to run every minute but the next issue with both cron and delayed job on Heroku is that they are addons that require verification.

The solution for me was to create a poor man's cron. I added a controller that would execute the task and then ran wget to hit the controller from an external server. This isn't a great solution but for testing the service it worked fine. The main note on doing this is to keep in mind that you will tie up a dyno for the length of the request, see Understanding dynos for more.

Sending mail with GMail

If you want to send email you have a number of different options on Heroku. First off it is important to note that Heroku doesn't support sending email from their systems directly but instead they support outgoing SMTP. Sending mail with the Sendgrid addon is probably the most flexible option but the GMail SMTP option is the least costly. The GMail option gives you 500 emails a day and that was plenty for my use.

Even though there is an addon for the GMail option you don't actually need to use it. Instead install the action mailer optional TLS plugin. Follow the readme to get it installed and configured. Then you put something like the following into your production.rb file:

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.raise_delivery_errors = true
  ActionMailer::Base.smtp_settings = {
    :tls            => true,
    :address        => 'smtp.gmail.com',
    :port           => 587,
    :domain         => 'example.come',
    :authentication => :plain,
    :user_name      => 'support@example.com',
    :password       => 'password'
  }

Keeping your logs

One downside to Heroku is that they only retain a small portion of your logs. They indicate in their docs that they only retain 100 lines of logs. So if you want to track your log output you will need to store them outside of the service. They give two suggestions in the docs for logging but there are other ways to do something similar on your own.

The solution I found that seems like the easiest is to use MongoDB to log since it is "fantastic for logging". You will need to have a MongoDB server available first. If you don't want to install MongoDB on your own external server you can try the new MongoDB hosted solution.

Once you have the MongoDB server ready you will need to add the MongoDB gem to your .gems file:

mongodb-mongo --source gems.github.com

Then you will want to install the mongo db logger plugin in your rails project. Just follow the instructions they give on the project page to get it installed.

Your app/controllers/application_controller.rb will look something like this:

class ApplicationController < ActionController::Base
  include MongoDBLogging

  helper :all # include all helpers, all the time
  protect_from_forgery # See ActionController::RequestForgeryProtection for details
end

After you get the plugin installed you will have to hard code the MongoDB information into the plugin. This could probably be fixed and pulled out of a configuration file but it won't work the way it is set up out of the box. Edit the file vendor/plugins/mongo_db_logger/lib/mongo_logger.rb and change the db_configuration to match your setup. It should be something like this:

  db_configuration = {
    'host'    => 'my.mongohost.com',
    'port'    => 56700,
    'database'    => 'testapp',
    'capsize' => default_capsize}

Now you should be able to deploy to Heroku and see your logs show up in your MongoDB database.

A couple very important things to note are that this logging won't catch exceptions that happen above the application itself and if your MongoDB server goes down your app may hang trying to connect to it.

Rack works too

You aren't limited to just Ruby on Rails with Heroku, rack works as well. That opens the door for other frameworks like Sinatra, Merb and Camping on Heroku. It is also possible that more in depth logging could be done using clogger or even rack a mole.

Here is an example Camping application:

The .gems file:

camping

The config.ru file:

require 'hello'
run Rack::Adapter::Camping.new(Hello)

The hello.rb file:

require 'camping'
 
Camping.goes :Hello
 
module Hello::Controllers
  class Index < R '/'
     def get
        render :hello
     end
  end
end
 
module Hello::Views
  def hello
     p  "Hello World!"
  end
end

Compiled gems

Heroku also lets you use gems that need to be compiled before they are installed. You can see this if you try to use something like memprof however you will also notice that in the memprof case it won't actually work because there are missing libraries. So using gems that require libraries may be hit or miss.

If you are lucky you may find that the gem is already part of Heroku like RMagick is. Here is a little example of using Heroku and the RMagick gem to produce an image using Camping:

The .gems file:

camping
rmagick

The config.ru file:

require 'hello'
run Rack::Adapter::Camping.new(Hello)

The hello.rb file:

require 'camping'
require 'RMagick'

Camping.goes :Hello

module Hello::Controllers
  class Index < R '/'
     def get
       @headers["Content-Type"] = "image/gif"
       img = Magick::Image.new(200, 200)

       gc = Magick::Draw.new
       gc.gravity = Magick::CenterGravity
       gc.pointsize = 32
       gc.font_family = "Helvetica"
       gc.font_weight = Magick::BoldWeight
       gc.stroke = 'none'
       gc.annotate(img, 0, 0, 0, 0, "Hello world!")

       img.format = "GIF"
       img.to_blob
     end
  end
end

Understanding dynos

It is important to understand how dynos work. The bottom line is that you need one for every concurrent request. This is easy to demonstrate with the following modification of the Camping example above:

require 'camping'
 
Camping.goes :Hello
 
module Hello::Controllers
  class Index &lt; R '/'
     def get
        render :hello
     end
  end
end
 
module Hello::Views
  def hello
     sleep 10
     p  "Hello World!"
  end
end

If you deploy this application to the free version of Heroku and then open two requests to it at the same time the second request will hang until the first one completes. This should illustrate why it is important to keep your processing quick and have enough dynos to match the concurrent request needs of your application.

Leave a Reply

Your email address will not be published. Required fields are marked *