# Quebert [![Build Status](https://travis-ci.org/polleverywhere/quebert.png?branch=master)](https://travis-ci.org/polleverywhere/quebert) [![Code Climate](https://codeclimate.com/github/polleverywhere/quebert/badges/gpa.svg)](https://codeclimate.com/github/polleverywhere/quebert) Quebert is a ruby background worker library that works with the very fast and simple [beanstalkd](http://kr.github.io/beanstalkd/) deamon. ## Why Quebert? Because it has really low latency. Other Ruby queuing frameworks, like [DJ](https://github.com/collectiveidea/delayed_job) or [Resque](https://github.com/resque/resque), have to poll their queue servers periodicly. You could think of it as a "pull" queue. Quebert is a "push" queue. It maintains a persistent connection with beanstalkd and when is enqueued, it's instantly pushed to the workers and executed. [Sidekiq](http://sidekiq.org) uses Redis's "push" primitives so it has low latency, but it doesn't support class reloading in a development environment. Sidekiq is also threaded, which means there are no guarantees of reliability when running non-threadsafe code. [Backburner](https://github.com/nesquena/backburner) is very similar to Quebert. It offers more options for concurrency (threading, forking, etc.) than Quebert but lacks pluggable back-ends, which means you'll be stubbing and mocking async calls. ## Who uses it? Quebert is a serious project. It's used in a production environment at [Poll Everywhere](https://www.polleverywhere.com/) to handle everything from SMS message processing to account downgrades. ## Features * **Multiple back-ends** (InProcess, Sync, and Beanstalk) * **Rails/ActiveRecord integration** similar to async_observer * **Pluggable exception handling** (for Hoptoad integration) * **Run workers with pid, log, and config files**. These do not daemonize (do it yourself punk!) * **Custom hooks** that may be called before, after & around jobs are run Some features that are currently *missing* that I will soon add include: * Rails plugin support (The AR integrations have to be done manually today) * Auto-detecting serializers. Enhanced ClassRegistry to more efficiently look up serializers for objects. ## How to use There are two ways to enqueue jobs with Quebert: through the Job itself, provided you set a default back-end for the job, or put it on the backend. ### Supported Ruby Versions Quebert officially is supported to run on the currently supported versions of MRI. This includes versions >= `2.4.3`. Have a look at the `.travis.yml` configuration file to see all Ruby versions we support. ### Jobs Quebert includes a Job class so you can implement how you want certain types of Jobs performed. ```ruby Quebert.backend = Quebert::Backend::InProcess.new class WackyMathWizard < Quebert::Job def perform(*nums) nums.inject(0){|sum, n| sum = sum + n} end end ``` You can either drop a job in a queue: ```ruby Quebert.backend.put WackyMathWizard.new(1, 2, 3) ``` Or drop it in right from the job: ```ruby # Run job right away! WackyMathWizard.new(4, 5, 6).enqueue # Run a lower priority job in 10 seconds for a max of 120 seconds WackyMathWizard.new(10, 10, 10).enqueue(ttr: 120, priority: 100, delay: 10) ``` Then perform the jobs! ```ruby Quebert.backend.reserve.perform # => 6 Quebert.backend.reserve.perform # => 15 Quebert.backend.reserve.perform # => 30 ``` ### Rails integration config/quebert.yml: ```yaml development: backend: beanstalk host: localhost:11300 queue: myapp-development test: backend: sync # etc. ``` config/initializers/quebert.rb: ```ruby Quebert.config.from_hash(Rails.application.config.quebert) Quebert.config.logger = Rails.logger ``` ### Global Job hooks Quebert has support for providing custom hooks to be called before, after & around your jobs are being run. A common example is making sure that any active ActiveRecord database connections are put back on the connection pool after a job is done: ```ruby Quebert.config.after_job do ActiveRecord::Base.clear_active_connections! end Quebert.config.before_job do |job| # all hooks take an optional job argument # in case you want to do something with that job end Quebert.config.around_job do |job| # this hook gets called twice # once before & once after a job is performed end ``` ### Beanstalk Job hooks Jobs can define their own business logic that will get called surrounding Beanstalk events: ```ruby class FooJob < Quebert::Job def around_bury # custom pre-bury code yield # custom post-bury code end end ``` Supported Beanstalk event hooks: `around_bury`, `around_release`, `around_delete` ### Async sender Take any ol' class and include the Quebert::AsyncSender. ```ruby Quebert.backend = Quebert::Backend::InProcess.new class Greeter include Quebert::AsyncSender::Class def initialize(name) @name = name end def sleep_and_greet(time_of_day) sleep 10000 # Sleeping, get it? "Oh! Hi #{name}! Good #{time_of_day}." end def self.budweiser_greeting(name) "waaazup #{name}!" end end walmart_greeter = Greeter.new("Brad") ``` Remember the send method in ruby? ```ruby walmart_greeter.send(:sleep_and_greet, "morning") # ... time passes, you wait as greeter snores obnoxiously ... # => "Oh! Hi Brad! Good morning." ``` What if the method takes a long time to run and you want to queue it? async.send it! ```ruby walmart_greeter.async.sleep_and_greet("morning") # ... do some shopping and come back later when the dude wakes up ``` Quebert figures out how to serialize the class, throw it on a worker queue, re-instantiate it on the other side, and finish up the work. ```ruby Quebert.backend.reserve.perform # => "Oh! Hi Brad! Good morning." # ... Sorry dude! I'm shopping already ``` Does it work on Class methods? Yeah, that was easier than making instance methods work: ```ruby Quebert.async.budweiser_greeting("Coraline") Quebert.backend.reserve.perform # => "waazup Coraline!" ``` * Only basic data types are included for serialization. Serializers may be customized to include support for different types. ### Backends * Beanstalk: Enqueue jobs in a beanstalkd service. The workers run in a separate process. Typically used in production environments. * Sync: Perform jobs immediately upon enqueuing. Typically used in testing environments. * InProcess: Enqueue jobs in an in-memory array. A worker will need to reserve a job to perform. ### Multiple queues To start a worker pointed at a non-default queue (e.g., a Quebert "tube"), start the process with `-q`: ```sh bundle exec quebert -q other-tube ``` Then specify the queue name in your job: ```ruby class FooJob < Quebert::Job def queue "other-tube" end def perform(args) # ... end end ``` ### Setting job defaults A `Quebert::Job` is a Plain Ol' Ruby Object. The defaults of a job, including its `ttr`, `queue_name`, and `delay` may be overridden in a super class as follows: ```ruby # Assuming you're in Rails or using ActiveSupport class FooJob < Quebert::Job def ttr 5.minutes end def delay 30.seconds end def queue_name "long-running-delayed-jobs" end def perform(args) # ... end end ``` Take a look at the [`Quebert::Job` class](https://github.com/polleverywhere/quebert/blob/master/lib/quebert/job.rb) code for more details on methods you may ovveride.