views:

1310

answers:

11

I have a Rails application that unfortunately after a request to a controller, has to do some crunching that takes awhile. What are the best practices in Rails for providing feedback or progress on a long running task or request? These controller methods usually last 60+ seconds.

I'm not concerned with the client side... I was planning on having an Ajax request every second or so and displaying a progress indicator. I'm just not sure on the Rails best practice, do I create an additional controller? Is there something clever I can do? I want answers to focus on the server side using Rails only.

Thanks in advance for your help.

Edit:

If it matters, the http request are for PDFs. I then have Rails in conjunction with Ruport generate these PDFs. The problem is, these PDFs are very large and contain a lot of data. Does it still make sense to use a background task? Let's assume an average PDF takes about one minute to two minutes, will this make my Rails application unresponsive to any other server request during this time?

Edit 2:

Ok, after further investigation, it seems my Rails application is indeed unresponsive to any other HTTP requests after a request comes in for a large PDF. So, I guess the question now becomes: What is the best threading/background mechanism to use? It must be stable and maintained. I'm very surprised Rails doesn't have something like this built in.

Edit 3:

I have read this page: http://wiki.rubyonrails.org/rails/pages/HowToRunBackgroundJobsInRails. I would love to read about various experiences with these tools.

Edit 4:

I'm using Passenger Phusion "modrails", if it matters.

Edit 5:

I'm using Windows Vista 64 bit for my development machine; however, my production machine is Ubuntu 8.04 LTS. Should I consider switching to Linux for my development machine? Will the solutions presented work on both?

+11  A: 

The Workling plugin allow you to schedule background tasks in a queue (they would perform the lengthy task). As of version 0.3 you can ask a worker for its status, this would allow you to display some nifty progress bars:

http://playtype.net/past/2008/10/2/workling_version_03_released/

Another cool feature with Workling is that the asynchronous backend can be switched: you can used DelayedJobs, Spawn (classic fork), Starling...

pantulis
I'll research it, however I would like to avoid gems/plugins as it seems many of them stop receiving maintenance after the developer gets bored.
JP
Unfortunately, I think you'll have to use a gem. Something like a Workling or Starling or even BackgroundRB is needed for a messaging/background task queue. You could also have a Rake task do the work for you, but I don't know how will you notify the front-end.
Srdjan Pejic
I've used Workling/Starling before and would recommend it. It is annoying that you have to run a seperate Starling server though.
Orion Edwards
+1  A: 

This really does sound like something that you should have a background process running rather than an application instance(passenger/mongrel whichever you use) as that way your application can stay doing what it's supposed to be doing, serving requests, while a background task of some kind, Workling is good, handles the number crunching. I know that this doesn't deal with the issue of progress, but unless it is absolutely essential I think that is a small price to pay.

You could have a user click the action required, have that action pass the request to the Workling queue, and have it send some kind of notification to the user when it is completed, maybe an email or something. I'm not sure about the practicality of that, just thinking out loud, but my point is that it really seems like that should be a background task of some kind.

railsninja
+1  A: 

I have a very large volume site that generates lots of large CSV files. These sometimes take several minutes to complete. I do the following:

  • I have a jobs table with details of the requested file. When the user requests a file, the request goes in that table and the user is taken to a "jobs status" page that lists all of their jobs.
  • I have a rake task that runs all outstanding jobs (a class method on the Job model).
  • I have a separate install of rails on another box that handles these jobs. This box just does jobs, and is not accessible to the outside world.
  • On this separate box, a cron job runs all outstanding jobs every 60 seconds, unless jobs are still running from the last invocation.
  • The user's job status page auto-refreshes to show the status of the job (which is updated by the jobs box as the job is started, running, then finished). Once the job is done, a link appears to the results file.

It may be too heavy-duty if you just plan to have one or two running at a time, but if you want to scale... :)

Sarah Mei
+3  A: 

calling ./script/runner in the background worked best for me. (I was also doing PDF generation.) It seems like the lowest common denominator, while also being the simplest to implement. Here's a write-up of my experience.

Wow. The simplicity of that is amazing! I'm definitely going to try it. However, status/progress is important. I wonder what would be the best method to try and report status back? Maybe via files??
JP
Just make script/runner write its progress to stdout and capture that from calling code.
Alex Lebedev
A: 

There is the brand new Growl4Rails ... that is for this specific use case (among others as well).

http://www.writebetterbits.com/2009/01/update-to-growl4rails.html

salt.racer
A: 

I use Background Job (http://codeforpeople.rubyforge.org/svn/bj/trunk/README) to schedule tasks. I am building a small administration site that allows Site Admins to run all sorts of things you and I would run from the command line from a nice web interface.

Kyle Boon
A: 

I know you said you were not worried about the client side but I thought you might find this interesting: Growl4Rails - Growl style notifications that were developed for pretty much what you are doing judging by the example they use.

srboisvert
+1  A: 

A simple solution that doesn't require any extra Gems or plugins would be to create a custom Rake task for handling the PDF generation. You could model the PDF generation process as a state machine with states such as submitted, processing and complete that are stored in the model's database table. The initial HTTP request to the Rails application would simply add a record to the table with a submitted state and return.

There would be a cron job that runs your custom Rake task as a separate Ruby process, so the main Rails application is unaffected. The Rake task can use ActiveRecord to find all the models that have the submitted state, change the state to processing and then generate the associated PDFs. Finally, it should set the state to complete. This enables your AJAX calls within the Rails app to monitor the state of the PDF generation process.

If you put your Rake task within *your_rails_app*/lib/tasks then it has access to the models within your Rails application. The skeleton of such a pdf_generator.rake would look like this:

namespace :pdfgenerator do
  desc 'Generates PDFs etc.'
  task :run => :environment do

    # Code goes here...
  end
end

As noted in the wiki, there are a few downsides to this approach. You'll be using cron to regularly create a fairly heavyweight Ruby process and the timing of your cron jobs would need careful tuning to ensure that each one has sufficient time to complete before the next one comes along. However, the approach is simple and should meet your needs.

John Topley
A: 

I've used spawn before and definitely would recommend it.

Incredibly simple to set up (which many other solutions aren't), and works well.

Orion Edwards
A: 

I'm using Windows Vista 64 bit for my development machine; however, my production machine is Ubuntu 8.04 LTS. Should I consider switching to Linux for my development machine? Will the solutions presented work on both?

Have you considered running Linux in a VM on top of Vista?

zetetic
A: 

Check out BackgrounDRb, it is designed for exactly the scenario you are describing.

I think it has been around for a while and is pretty mature. You can monitor the status of the workers.

It's a pretty good idea to develop on the same development platform as your production environment, especially when working with Rails. The suggestion to run Linux in a VM is a good one. Check out Sun xVM for Open Source virtualization software.

Ken Liu