0-60: Deploying Goliath on Heroku Cedar

Earlier this week Heroku rolled out a major upgrade to their webstack. The HTTP/1.1 support and the billing upgrades are both great improvements, but the “process model” definitely takes the crown: all of the sudden Heroku is much more than a Ruby+Rack hosting platform. Prior to this release, Heroku hosted Ruby only apps on top of the Thin web-server. Each “dyno” represented an instance of Thin, and as long as you deployed a Rack-compatible app, then Heroku would spin it up and run it for you. This was great, but also somewhat limiting - for one, you couldn’t run non-Rack apps!

The process model changes all that. You can now run any application within their cloud, with the help of a simple Procfile. In fact, it doesn’t even have to be Ruby! Node, Clojure apps, long-running Ruby workers, it is all fair game.

Goliath on Heroku Cedar

Goliath is an async web server and framework we developed at PostRank. While it does follow the Rack spec, it is also substantially different: it requires Ruby 1.9 runtime for Fiber support, and it uses an entirely different HTTP parser from Thin. Combined, these differences meant that up until now, deploying Goliath on any existing cloud environment was a no-go. However, with the new cedar stack, this is no longer an issue:

source :gemcutter gem 'goliath' , :git => 'git://github.com/postrank-labs/goliath.git' gem 'em-http-request' , :git => 'git://github.com/igrigorik/em-http-request.git' gem 'em-synchrony' , :git => 'git://github.com/igrigorik/em-synchrony.git' gem 'yajl-ruby'

require 'goliath' require 'em-synchrony/em-http' require 'em-http/middleware/json_response' require 'yajl' # automatically parse the JSON HTTP response EM :: HttpRequest . use EventMachine :: Middleware :: JSONResponse class Hello < Goliath :: API # parse query params and auto format JSON response use Goliath :: Rack :: Params use Goliath :: Rack :: Formatters :: JSON use Goliath :: Rack :: Render def response ( env ) resp = nil if params [ 'query' ] # simple GitHub API proxy example logger . info "Starting request for #{ params [ 'query' ] } " conn = EM :: HttpRequest . new ( "http://github.com/api/v2/json/repos/search/ #{ params [ 'query' ] } " ) . get logger . info "Received #{ conn . response_header . status } from Github" resp = conn . response else resp = env # output the Goalith environment end [ 200 , { 'Content-Type' => 'application/json' }, resp ] end end

web: bundle exec ruby hello.rb -sv -e prod -p $PORT

goliath - Goliath is an async Ruby web server framework

A simple “Hello World HTTP proxy” example. Our API will parse an incoming request, dispatch an async HTTP request and return the results back to the user - no callbacks! We specified our libraries in a Gemfile, and we provided a Procfile with the startup parameters for our API. Believe it or not, that is all what we need. To deploy it:

$ git init . $ git add . && git commit -a -m 'hello world' $ heroku create --stack cedar goliath-demo $ git push heroku master $ curl http://goliath.herokuapp.com/ $ curl http://goliath.herokuapp.com/?query = eventmachine

Running a simple benchmark and checking the logs ( heroku logs ) shows that an average request to our new Goliath API takes approximately 0.35ms (2500+ req/s). Not enough to handle the load? Simply start another API: heroku scale web=2 .

Goliath, Heroku & the Cloud

Combine easy deployment, support for HTTP/1.1 and the async nature of Goliath, and all of the sudden you can develop and deploy simple streaming API’s and async API endpoints with near minimal effort! If you can’t tell, definitely something I am very excited about - a huge step for Heroku! Now, we just need to wait for a response from the CloudFoundry team - this should get interesting.