Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RoR: multiple calls in a row to the same long-time-response controller

Update:

Read "Indicate to an ajax process that the delayed job has completed" before if you have the same problem. Thanks Gene.


I have a problem with concurrency. I have a controller scraping a few web sites, but each call to my controller needs about 4-5 seconds to respond.

So if I call 2 (or more) times in a row, the second call needs wait for the first call before starting.

So how I can fix this problem in my controller? Maybe with something like EventMachine?

Update & Example:

application_controller.rb

def func1
    i=0
    while i<=2
        puts "func1 at: #{Time.now}"
        sleep(2)
        i=i+1
    end
end

def func2
    j=0
    while j<=2
        puts "func2 at: #{Time.now}"
        sleep(1)
        j=j+1
    end
end

whatever_controller.rb

puts ">>>>>>>> Started At #{Time.now}"
  func1()
  func2()
puts "End at #{Time.now}"

So now I need request http://myawesome.app/whatever several times at the same times from the same user/browser/etc.

I tried Heroku (and local) with Unicorn but without success, this is my setup:

  • unicorn.rb http://pastebin.com/QL0wdGx0
  • Procfile http://pastebin.com/RrTtNWJZ
  • Heroku setup https://www.dropbox.com/s/wxwr5v4p61524tv/Screenshot%202014-02-20%2010.33.16.png

Requirements:

  • I need a RESTful solution. This is API so I need to responds JSON

More info: I have right now 2 cloud servers running.

  • Heroku with Unicorn
  • Engineyard Cloud with Nginx + Panssenger
like image 901
skozz Avatar asked Feb 18 '14 20:02

skozz


3 Answers

You're probably using webrick in development mode. Webrick only handles one request at a time.

You have several solutions, many ruby web servers exist that can handle concurrency.

Here are a few of them.

Thin

Thin was originally based on mongrel and uses eventmachine for handling multiple concurrent connections.

Unicorn

Unicorn uses a master process that will dispatch requests to web workers, 4 workers equals 4 concurrent possible requests.

Puma

Puma is a relatively new ruby server, its shiny feature is that it handles concurrent requests in threads, make sure your code is threadsafe !

Passenger

Passenger is a ruby server bundled inside nginx or apache, it's great for production and development

Others

These are a few alternatives, many other exist, but I think they are the most used today.

To use all these servers, please check their instructions. They are generally available on their github README.

like image 138
Intrepidd Avatar answered Sep 21 '22 01:09

Intrepidd


For any long response time controller function, the delayed job gem is a fine way to go. While it is often used for bulk mailing, it works as well for any long-running task.

Your controller starts the delayed job and responds immediately with a page that has a placeholder - usually a graphic with a progress indicator - and Ajax or a timed reload that updates the page with the full information when it's available. Some information on how to approach this is in this SO article.

Not mentioned in the article is that you can use redis or some other memory cache to store the results rather than the main database.

like image 43
snow Avatar answered Sep 21 '22 01:09

snow


Answers above are part of the solution: you need a server environment that can properly dispatch concurrent requests to separate workers; unicorn or passenger can both work by creating workers in separate processes or threads. This allows many workers to sit around waiting while not blocking other incoming requests.

If you are building a typical bot whose main job is to get content from other sources, these solutions may be ok. But if what you need is a simple controller that can accept hundreds of concurrent requests, all of which are sending independent requests to other servers, you will need to manage threads or processes yourself. Your goal is to have many workers waiting to do a simple job, and one or more masters whose jobs it is to send requests, then be there to receive the responses. Ruby's Thread class is simple, and works well for cases like this with ruby 2.x or 1.9.3.

You would need to provide more detail about what you need to do for help getting to any more specific solution.

like image 35
Tom Harrison Avatar answered Sep 20 '22 01:09

Tom Harrison