Where developers come to connect, share, build and be inspired.

141

1600% faster app requests with Rails on Heroku

25301 views

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

Comments

  • Matt_aimonetti_portait_2011_sq400
    mattetti

    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.

  • 584d40f9ab5bca65869216007777d93e_normal
    dozba

    This website looks amazing on an iPad. Well done.

  • Blank-mugshot
    dommmel

    @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
    

  • Blank-mugshot
    barapa

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

    Where are request and response defined?

  • Blank-mugshot
    dommmel

    @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.

  • Blank-mugshot
    danielgrippi

    you are my hero <3

  • Blank-mugshot
    sourabhupadhyay

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

Add a comment