5cafjw
31.91K
· March 2013 ·
E6b625008c816ab3d8d742cc3eddbb00

1600% faster app requests with Rails on Heroku

Using rainbows! and em-http-request resulted in a 1600% performance increase compared to using unicorn and net_http.
(For the use case described below)

Situation

  • You have an app that does a lot of calls to a 3rd party API. This is often the case with facebook apps for example.
  • You want to host it on heroku (one dyno preferred, so it's free)

Problem

  • You want to call the 3rd party API from your controller and render the response
  • Even if you are using unicorn to forks several worker processes, they will be blocked and waiting for a response from Facebook (or any other potentially slow API) most of the time.

Solution: Use EventMachine and Rainbows!

Here is a quick outline on how to get you up and running with Rainbows! and em-http-requests on heroku.

We will be using the following gems:
Gemfile

gem 'rainbows'
gem 'em-http-request'
gem "faraday"

app/controllers/async_controller.rb

class AsyncController < ApplicationController
  def index
    conn = Faraday.new "http://slowapi.com" do |con|
      con.adapter :em_http
    end
    resp = conn.get '/delay/1'
    resp.on_complete {
      @res = resp.body 
      render
      request.env['async.callback'].call(response)
    }
    throw :async
end

app/views/async/index.html.erb

<%= @res %>

config/rainbows.rb

worker_processes 3
timeout 30
preload_app true

Rainbows! do
  use :EventMachine
end

before_fork do |server, worker|
  # Replace with MongoDB or whatever
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end
end

after_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
end

config/routes.rb

match 'async_test' =>   'async#index'

Procfile

web: bundle exec rainbows -p $PORT -c ./config/rainbows.rb

it is also a good idea to set config.threadsafe! in your Rails environment configuration

Finalize

  • Deploy to heroku
  • Run a benchmark, e.g. the following

    ab -n 600 -c 200 http://mycoolasync.herokuapp.com/async_test

  • Enjoy how fast it is

Some results

I set up a test app on heroku and ran
ab -n 600 -c 200 http://mycoolasync.herokuapp.com/async_test

Result:

  • Old-school method: 183 second
  • Evented method: 11 seconds

Detailed results for old-school synchronous requests

Using non-evented http request by settting con.adapter :net_http in app/controllers/async_controller.rb

Document Path:          /async_test
Document Length:        2755 bytes

Concurrency Level:      100
Time taken for tests:   183.373 seconds
Complete requests:      600
Write errors:           0
Non-2xx responses:      516
Total transferred:      652495 bytes
HTML transferred:       541903 bytes
Requests per second:    3.27 [#/sec] (mean)
Time per request:       30562.206 [ms] (mean)
Time per request:       305.622 [ms] (mean, across all concurrent requests)
Transfer rate:          3.47 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      101  175 260.7    109    1239
Processing:  1270 28308 5571.9  30115   30978
Waiting:     1146 28156 5882.4  30115   30665
Total:       1376 28484 5582.8  30224   32203

Detailed Results for new and fancy evented request method

Ensured by settting con.adapter :em_http in app/controllers/async_controller.rb

Document Path:          /async_test
Document Length:        2758 bytes

Concurrency Level:      100
Time taken for tests:   11.118 seconds
Complete requests:      600
Write errors:           0
Total transferred:      1734330 bytes
HTML transferred:       1655686 bytes
Requests per second:    53.97 [#/sec] (mean)
Time per request:       1853.048 [ms] (mean)
Time per request:       18.530 [ms] (mean, across all concurrent requests)
Transfer rate:          152.33 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:      101  194 283.0    114    1263
Processing:  1136 1485 416.1   1341    3558
Waiting:     1135 1453 346.2   1339    3159
Total:       1250 1679 472.5   1505    3669

Some final words

I wanted to have a link-baity title once. So here we are with a very unscientific 1600%. You'll be the judge - I think with the right test setting this number could be much much higher. If you replicated this and achieved a better result - let me know. I'll update this post and give you credit.

Cheers,
your friend
dommmel

Sign in or sign up to add your response.

7 Responses

4803
Matt aimonetti portait 2011 sq400

Using throw :async in a controller action makes me really sad :(
But that's not the point of this tip, sorry...I... couldn't resist.

over 1 year ago ·
4806

This website looks amazing on an iPad. Well done.

over 1 year ago ·
4813
E6b625008c816ab3d8d742cc3eddbb00

@mattetti Thanks for pointing it out. Would love to know how you would go about it.

From the top of my head: One could use Rack::FiberPool and EM-Synchrony like so

Gemfile

gem 'rack-fiber_pool', :require => 'rack/fiber_pool'
gem 'em-synchrony'

config.ru

...
use Rack::FiberPool
run MyApp::Application

app/controllers/asycn_controller.rb

conn = Faraday::Connection.new(:url => 'http://slowapi.com') do |builder|
   builder.use Faraday::Adapter::EMSynchrony
end

resp = conn.get '/delay/1'
@res = resp.body 
render
over 1 year ago ·
4926
Aee029760669bdd096685145a9c48ac6

Can you explain this line?
request.env['async.callback'].call(response)

Where are request and response defined?

over 1 year ago ·
4927
E6b625008c816ab3d8d742cc3eddbb00

@barapa Yeah - that bit is confusing. I am by no way an expert on this kind of stuff - maybe others can chime in and provide more insightful comments than me - anyway here's my answer

response and request objects

The two accessor methods response and request exist in every
Rails controller. Some details can be found in this section of the rails guides.

async.callback

The async.callback is part of a scheme that was first implemented in thin afaik (see this blog post) and has since found its way into some other server software, including Rainsbows!/EventMachine.

over 1 year ago ·
4972
Bfd07ad84c85d505fc79dc17c6b04c2e

you are my hero <3

over 1 year ago ·
13685
E5a4192e5401cf29ca9eca353376b523

this website is not working....http://mycoolasync.herokuapp.com/async_test

over 1 year ago ·
Featured Programming Job

Full Stack Rails Developer
·
New York or Remote
·
Full Time
Search all programming jobs