views:

143

answers:

2

My Rails app needs to make a call to a partner's RESTful API for every request that it receives on a particular controller's action. I need to pass in request-specific parameters (e.g. IP, user-agent, etc.) to the partner API, and return the response I get to the user. Since the call to the partner API is very user-specific, I can't cache the response I get back from the partner API (e.g. in memcached). My requirement is to respond to the user in 1500ms or less.

My Rails app is hitting a wall in terms of requests / second because each mongrel gets blocked until the partner API returns a response. In a recent performance test, I saw a server eke out barely 5 requests/second (running 5 mongrels).

I have a few ideas on what to do:

1) Make the partner API respond faster. Realistically, I can only pull this lever so much - even with really hard work from the partner, average response time is 200ms.

2) Limit the amount of time I wait for the partner API to return. I tried using Rails::Timeout, but it only allows waiting periods in multiples of a second. Is there a way to make the timeout happen in milliseconds instead?

3) Is there a way to queue the calls to the partner API, have them execute asynchronously? In Java, I would have used threads for the same task.

Thanks for your help!

A: 

We have used delayed_job and it is working well. If you don't have to display the returned value from the partner apis, delayed_job seems to be a good solution. You can use the send_later feature of delayed_job to make any method call into a delayed_job. You can configure the number of workers in delayed_job as well and figure out at what point you get the right responsiveness.

Bharat

Bharat Ahluwalia
The requirement is to display returned value from partner APIs - so in that case, doesn't sound like delayed_job will cut it. Why can't the delayed_job render a response just like a controller's action can?
Bilal Aslam
The problem is that the delayed_job is run outside of the context of a request. By the time the delayed_job is finished there is no request to render a response into anymore. That boat has already sailed.
glongman
You could still connect the response coming in through the delayed job. For every request that you sent to the partner site, do it through the job and pass an id to it when you create the job. When it runs and the results are returned you could use the id to insert the response into the right location.
Bharat Ahluwalia
+3  A: 

You can tell delayed_job to poll the database every second, so the job is performed quickly. Then use a column in your database to poll for the result.

Example:

  1. Add the job to delayed job
  2. Redirect the user to the result page
  3. Display a progress indicator of some sort (circling arrow maybe)
  4. Poll that database column/row/whatever for a result every second through an action

That would give the user feedback, and allow them to wait without thinking it's timing out.

Jamie van Dyke
Yup, this is the answer if you are constrained using only standard Rails stack. If one were to get adventurous and investigate asynchronous evented stacks for Step 4 only you could turn 4 into one Ajax call that returns only when the Partner API responds. Not something one can whip up quickly as that stuff is fairly new. Some links in the next comment...
glongman
Event Machine - roll your own http://www.rubyeventmachine.com/Cramp - http://m.onkey.org/2010/1/7/introducing-crampChat-Server - and example of using EM for long polling http://github.com/tobi/chat-server
glongman
Jamie (and glongman) thanks for the articulate responses! Jamie, can you please confirm if the following scenario will work with delayed_job (I think it will :D)1) Controller's action gets request2) Action creates a delayed_job and renders a response which tells the client to 'wait for a response'3) delayed_job runs WITHIN a certain time interval (this guarantee is important to me) or if it can't run in this time, it fails out.4) client calls an action periodically to see if a response is readyAlso, can I tell delayed_job to poll the db in milliseconds instead of seconds?
Bilal Aslam
I'm not sure on how you set the delayed_job check time, it's set by default to 'within 5 seconds' but you could no doubt change that.Then just make sure the delayed_job that is fired off with :send_later sets a database column with results (pass/fail/whatever_you_need) and poll it with an ajax runner.
Jamie van Dyke
http://github.com/tobi/delayed_job/blob/master/lib/delayed/worker.rbYou could adjust the SLEEP_TIME to 1 second, I guess.
Jamie van Dyke
Alternatively, as I'm now implementing in one of my apps, you can use a messaging queue like http://www.reevoo.com/labs/beanstalk-messaging/
Jamie van Dyke