How to make simple background jobs processing in Rails with Sucker Punch

Writing jobs, testing, pros and cons of this approach.

Sucker Punch — a single-process Ruby asynchronous processing library that runs with your existing app’s process.

Gem is built on top of concurrent-ruby and have no dependencies with any data storage, what is both an advantage and a disadvantage.

Running in the application process really simplifies the deployment process, but If the web processes are restarted with jobs remaining in the queue, they will be lost. Also, since all state persistence is in memory, Sucker Punch can operate extremely fast on small tasks. But if the application implies a large number background jobs, Sucker Punch turns out to be the wrong decision, because you’ll need to write your own logic of monitoring/restarting jobs.

Installing

Add this line to your application’s Gemfile:

gem ‘sucker_punch’

And then execute:

bundle install

If you want to use Sucker Punch with Active Job, you can to add it as an adapter to config/application.rb into Rails app.

Writing Jobs

Each job should be a separate Ruby class that includes SuckerPunch::Job and defines perform method.

To run synchronous execution initialize the instance class and call method perform on it.

This will return the result of code execution in perform method.

Asynchronous execution returns true and continue execution in the thread.

The default number of workers (threads) running against your job is 2. This can be configured using the class method workers

Sucker Punch provides an interface for delaying jobs. But remember that schedule will clear after restarting application process.

In the example above perform will be executed 60 seconds later.

Testing

To avoid problems such as cleared transactions using something like Database Cleaner, because the job class is running in a different thread and the Transaction operates on another thread so it can clear data before the job will be finished, you should disable transactional fixtures.

Requiring this library causes your jobs to run everything inline. So a call to the following will actually be synchronously:

For example, we have some job, which triggers service object which calculates statistics:

We should tag the spec as a job: true

Exceptions

You can customize how to handle uncaught exceptions that are raised by your jobs. For example, with third-party service like Sentry with raven-ruby gem.

If Sucker Punch is being used within a Rails application, Sucker Punch’s logger is set to Rails.logger by default.

Conclusions

If your application is designed for high load, it will be difficult to implement horizontal scale-up with Sucker Punch. In this case, you will need a tool that will enqueue jobs in the storage (e.g. Redis). For larger applications, I recommend using Sidekiq, Resque or Delayed::Job. But they all have a similar interface, and you do not have to significantly rewrite the code to switch from Sucker Punch to one of the alternatives.