Skip to content

Backburner Tutorial

nesquena edited this page Jul 28, 2012 · 7 revisions

Introduction

This is a guide towards how to use Backburner in production. In real applications, you will likely have all sorts of jobs, some of which are more important then others and some that require special queues to keep them separated from the other tasks.

By default, every job class in Backburner has a custom queue named after the class. For instance, a NewsletterSender would have a tube called "newsletter-sender". This can all be changed easily, but Backburner provides sensible defaults.

Next, let's imagine a sample application and how this uses Backburner in order to demonstrate practical usage.

SampleBlog

Let's suppose we have an application called SampleBlog which is a very simple blog with certain background tasks. The tasks that need to be backgrounded are as follows:

  • Fetching connected facebook information for a user
  • Sending a new user welcome after signup
  • Sending a reset password email after signup
  • Blog post cache pre-warming when a new post is created

These four tasks are the first things needed to be backgrounded. Let's suppose we already have a UserMailer object setup which sends these emails:

class UserMailer
  def self.deliver_user_welcome(user_id)
    user = User.find(user_id)
    # ...send email to user_id...
  end

  def self.deliver_reset_password(user_id)
    user = User.find(user_id)
    # ...send reset password email to user_id...
  end
end

We also have a User model that sends the appropriate emails using the UserMailer:

class User
  after_create :deliver_welcome_email

  protected

  def reset_password!
    # ...reset password
    UserMailer.deliver_reset_password(user_id)
  end

  def deliver_welcome_email
    UserMailer.deliver_user_welcome(self.id)
  end
end

We also have a method we wrote that fetches a user's facebook account information:

class User
  after_create :fetch_fb_data

  protected

  def fetch_fb_data
    facebook = FacebookFakeClient.new(self.access_token)
    fb_user = facebook.get(:user) # Sends a fb api request
    update_attributes(:avatar => fb_user.profile_picture, :location => fb_user.location)
  end
end

And we currently have no way to pre-warm the cache for a post in our application. Ok, now we want to add Backburner and background process these jobs!

Let's Backburn

Setting up Backburner is pretty straightforward. First, let's install beanstalkd:

$ apt-get install beanstalkd

Next, add to the Gemfile:

# Gemfile

gem 'backburner', '~> 0.0.3'

and then run bundle and you are setup. Next, let's configure our backburner settings:

# app.rb

Backburner.configure do |config|
  config.beanstalk_url = "beanstalk://127.0.0.1"
  config.tube_namespace = "sampleblog.jobs"
  config.on_error = lambda { |e| Airbrake.notify(e) }
end

Here we have setup beanstalk to a local instance and setup a prefix for all backburner tubes. The prefix ensures no tube name collisions with other apps using beanstalkd. We also have every job error reporting to airbrake so we can track jobs that failed and why.

Backgrounding Tasks

Time to start backgrounding tasks. Let's start with the emails that need to be sent for welcome and password reset. If you recall above, we have access to a UserMailer which delivers the mail.

In Backburner, the easiest way to kick off background jobs is to include Backburner::Performable in any object:

class UserMailer
  include Backburner::Performable
  # queue "user-mailer"

  # ... sending emails ...
end

There is no need to change the methods that were already available. Next, when we invoke the methods we can background them by adding async in front of the method call:

class User
  after_create :deliver_welcome_email

  protected

  def reset_password!
    # ...reset password
    UserMailer.async.deliver_reset_password(user_id)
  end

  def deliver_welcome_email
    UserMailer.async.deliver_user_welcome(self.id)
  end
end

And that's all! Now those emails are sent asynchronously through backburner. Next, let's tackle fetching facebook data to store in the user:

class User
  include Backburner::Performable

  after_create lambda { |u| u.async(:queue => "facebook").fetch_fb_data }

  protected
  
  def fetch_fb_data
    # ...same as above...
  end
end

Here we just changed the after_create hook to use async. Notice we also specified the queue as "facebook" so that this job goes into a special job queue and not into the default "user" queue. Now that is fully backgrounded. Finally, let's setup the pre-warming cache. Let's create a new caching class:

class PostPrecache
  include Backburner::Performable
  
  # Caches the post into memcache after creation so that it is fast for all readers.
  def self.cache(post_id)
    post = Post.find(post_id)
    Padrino.cache.fetch("post-#{post_id}-html") { post.to_html }
  end
end

and then use this when a post is created:

class Post
  after_create :prewarm_cache

  def prewarm_cache
    PostPrecache.async.cache(self.id)
  end
end