Giter VIP home page Giter VIP logo

backburner's Introduction

Backburner Build Status

Backburner is a beanstalkd-powered job queue that can handle a very high volume of jobs. You create background jobs and place them on multiple work queues to be processed later.

Processing background jobs reliably has never been easier than with beanstalkd and Backburner. This gem works with any ruby-based web framework, but is especially suited for use with Sinatra, Padrino and Rails.

If you want to use beanstalk for your job processing, consider using Backburner. Backburner is heavily inspired by Resque and DelayedJob. Backburner stores all jobs as simple JSON message payloads. Persistent queues are supported when beanstalkd persistence mode is enabled.

Backburner supports multiple queues, job priorities, delays, and timeouts. In addition, Backburner has robust support for retrying failed jobs, handling error cases, custom logging, and extensible plugin hooks.

Why Backburner?

Backburner is well tested and has a familiar, no-nonsense approach to job processing, but that is of secondary importance. Let's face it, there are a lot of options for background job processing. DelayedJob, and Resque are the first that come to mind immediately. So, how do we make sense of which one to use? And why use Backburner over other alternatives?

The key to understanding the differences lies in understanding the different projects and protocols that power these popular queue libraries under the hood. Every job queue requires a queue store that jobs are put into and pulled out of. In the case of Resque, jobs are processed through Redis, a persistent key-value store. In the case of DelayedJob, jobs are processed through ActiveRecord and a database such as PostgreSQL.

The work queue underlying these gems tells you infinitely more about the differences than anything else. Beanstalk is probably the best solution for job queues available today for many reasons. The real question then is... "Why Beanstalk?".

Why Beanstalk?

Illya has an excellent blog post Scalable Work Queues with Beanstalk and Adam Wiggins posted an excellent comparison.

You will quickly see that beanstalkd is an underrated but incredible project that is extremely well-suited as a job queue. Significantly better suited for this task than Redis or a database. Beanstalk is a simple, and a very fast work queue service rolled into a single binary - it is the memcached of work queues. Originally built to power the backend for the 'Causes' Facebook app, it is a mature and production ready open source project. PostRank uses beanstalk to reliably process millions of jobs a day.

A single instance of Beanstalk is perfectly capable of handling thousands of jobs a second (or more, depending on your job size) because it is an in-memory, event-driven system. Powered by libevent under the hood, it requires zero setup (launch and forget, à la memcached), optional log based persistence, an easily parsed ASCII protocol, and a rich set of tools for job management that go well beyond a simple FIFO work queue.

Beanstalkd supports the following features out of the box:

Feature Description
Parallelized Supports multiple work queues created on demand.
Reliable Beanstalk’s reserve, work, delete cycle ensures reliable processing.
Scheduling Delay enqueuing jobs by a specified interval to schedule processing later
Fast Processes thousands of jobs per second without breaking a sweat.
Priorities Specify priority so important jobs can be processed quickly.
Persistence Jobs are stored in memory for speed, but logged to disk for safe keeping.
Federation Horizontal scalability provided through federation by the client.
Error Handling Bury any job which causes an error for later debugging and inspection.

Keep in mind that these features are supported out of the box with beanstalk and require no special code within this gem to support. In the end, beanstalk is the ideal job queue while also being ridiculously easy to install and setup.

Installation

First, you probably want to install beanstalkd, which powers the job queues. Depending on your platform, this should be as simple as (for Ubuntu):

$ sudo apt-get install beanstalkd

Add this line to your application's Gemfile:

gem 'backburner'

And then execute:

$ bundle

Or install it yourself as:

$ gem install backburner

Configuration

Backburner is extremely simple to setup. Just configure basic settings for backburner:

Backburner.configure do |config|
  config.beanstalk_url       = "beanstalk://127.0.0.1"
  config.tube_namespace      = "some.app.production"
  config.namespace_separator = "."
  config.on_error            = lambda { |e| puts e }
  config.max_job_retries     = 3 # default 0 retries
  config.retry_delay         = 2 # default 5 seconds
  config.retry_delay_proc    = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
  config.default_priority    = 65536
  config.respond_timeout     = 120
  config.default_worker      = Backburner::Workers::Simple
  config.logger              = Logger.new(STDOUT)
  config.primary_queue       = "backburner-jobs"
  config.priority_labels     = { :custom => 50, :useless => 1000 }
  config.reserve_timeout     = nil
  config.job_serializer_proc = lambda { |body| JSON.dump(body) }
  config.job_parser_proc     = lambda { |body| JSON.parse(body) }

end

The key options available are:

Option Description
beanstalk_url Address for beanstalkd connection i.e 'beanstalk://127.0.0.1'
tube_namespace Prefix used for all tubes related to this backburner queue.
namespace_separator Separator used for namespace and queue name
on_error Lambda invoked with the error whenever any job in the system fails.
max_job_retries Integer defines how many times to retry a job before burying.
retry_delay Integer defines the base time to wait (in secs) between job retries.
retry_delay_proc Lambda calculates the delay used, allowing for exponential back-off.
default_priority Integer The default priority of jobs
respond_timeout Integer defines how long a job has to complete its task
default_worker Worker class that will be used if no other worker is specified.
logger Logger recorded to when backburner wants to report info or errors.
primary_queue Primary queue used for a job when an alternate queue is not given.
priority_labels Hash of named priority definitions for your app.
reserve_timeout Duration to wait for work from a single server, or nil for forever.
job_serializer_proc Lambda serializes a job body to a string to write to the task
job_parser_proc Lambda parses a task body string to a hash

Breaking Changes

Before v0.4.0: Jobs were placed into default queues based on the name of the class creating the queue. i.e NewsletterJob would be put into a 'newsletter-job' queue. As of 0.4.0, all jobs are placed into a primary queue named "my.app.namespace.backburner-jobs" unless otherwise specified.

Usage

Backburner allows you to create jobs and place them onto any number of beanstalk tubes, and later pull those jobs off the tubes and process them asynchronously with a worker.

Enqueuing Jobs

At the core, Backburner is about jobs that can be processed asynchronously. Jobs are simple ruby objects which respond to perform.

Job objects are queued as JSON onto a tube to be later processed by a worker. Here's an example:

class NewsletterJob
  # required
  def self.perform(email, body)
    NewsletterMailer.deliver_text_to_email(email, body)
  end

  # optional, defaults to 'backburner-jobs' tube
  def self.queue
    "newsletter-sender"
  end

  # optional, defaults to default_priority
  def self.queue_priority
    1000 # most urgent priority is 0
  end

  # optional, defaults to respond_timeout in config
  def self.queue_respond_timeout
    300 # number of seconds before job times out, 0 to avoid timeout. NB: A timeout of 1 second will likely lead to race conditions between Backburner and beanstalkd and should be avoided
  end

  # optional, defaults to retry_delay_proc in config
  def self.queue_retry_delay_proc
    lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 5) }
  end

  # optional, defaults to retry_delay in config
  def self.queue_retry_delay
    5
  end

  # optional, defaults to max_job_retries in config
  def self.queue_max_job_retries
    5
  end
end

You can include the optional Backburner::Queue module so you can easily specify queue settings for this job:

class NewsletterJob
  include Backburner::Queue
  queue "newsletter-sender"  # defaults to 'backburner-jobs' tube
  queue_priority 1000 # most urgent priority is 0
  queue_respond_timeout 300 # number of seconds before job times out, 0 to avoid timeout

  def self.perform(email, body)
    NewsletterMailer.deliver_text_to_email(email, body)
  end
end

Jobs can be enqueued with:

Backburner.enqueue NewsletterJob, '[email protected]', 'lorem ipsum...'

Backburner.enqueue accepts first a ruby object that supports perform and then a series of parameters to that object's perform method. The queue name used by default is {namespace}.backburner-jobs unless otherwise specified.

You may also pass a lambda as the queue name and it will be evaluated when enqueuing a job (and passed the Job's class as an argument). This is especially useful when combined with "Simple Async Jobs" (see below).

Simple Async Jobs

In addition to defining custom jobs, a job can also be enqueued by invoking the async method on any object which includes Backburner::Performable. Async enqueuing works for both instance and class methods on any performable object.

class User
  include Backburner::Performable
  queue "user-jobs"  # defaults to 'user'
  queue_priority 500 # most urgent priority is 0
  queue_respond_timeout 300 # number of seconds before job times out, 0 to avoid timeout

  def activate(device_id)
    @device = Device.find(device_id)
    # ...
  end

  def self.reset_password(user_id)
    # ...
  end
end

# Async works for instance methods on a persisted object with an `id`
@user = User.first
@user.async(:ttr => 100, :queue => "activate").activate(@device.id)
# ..and for class methods
User.async(:pri => 100, :delay => 10.seconds).reset_password(@user.id)

This automatically enqueues a job for that user record that will run activate with the specified argument. Note that you can set the queue name and queue priority at the class level and you are also able to pass pri, ttr, delay and queue directly as options into async.

The queue name used by default is {namespace}.backburner-jobs if not otherwise specified.

If a lambda is given for queue, then it will be called and given the performable object's class as an argument:

# Given the User class above
User.async(:queue => lambda { |user_klass| ["queue1","queue2"].sample(1).first }).do_hard_work # would add the job to either queue1 or queue2 randomly

Using Async Asynchronously

It's often useful to be able to configure your app in production such that every invocation of a method is asynchronous by default as seen in delayed_job. To accomplish this, the Backburner::Performable module exposes two handle_asynchronously convenience methods which accept the same options as the async method:

class User
  include Backburner::Performable

  def send_welcome_email
    # ...
  end

  # ---> For instance methods
  handle_asynchronously :send_welcome_email, queue: 'send-mail', pri: 5000, ttr: 60

  def self.update_recent_visitors
    # ...
  end

  # ---> For class methods
  handle_static_asynchronously :update_recent_visitors, queue: 'long-tasks', ttr: 300
end

Now, all calls to User.update_recent_visitors or User#send_welcome_email will automatically be handled asynchronously when invoked. Similarly, you can call these methods directly on the Backburner::Performable module to apply async behavior outside the class:

# Given the User class above
Backburner::Performable.handle_asynchronously(User, :activate, ttr: 100, queue: 'activate')

Now all calls to the activate method on a User instance will be async with the provided options.

A Note About Auto-Async

Because an async proxy is injected and used in place of the original method, you must not rely on the return value of the method. Using the example User class above, if my send_welcome_email returned the status of an email submission and I relied on that to take some further action, I will be surprised after rewiring things with handle_asynchronously because the async proxy actually returns the (boolean) result of Backburner::Worker.enqueue.

Working Jobs

Backburner workers are processes that run forever handling jobs that are reserved from the queue. Starting a worker in ruby code is simple:

Backburner.work

This will process jobs in all queues but you can also restrict processing to specific queues:

Backburner.work('newsletter-sender', 'push-notifier')

The Backburner worker also exists as a rake task:

require 'backburner/tasks'

so you can run:

$ QUEUE=newsletter-sender,push-notifier rake backburner:work

You can also run the backburner binary for a convenient worker:

bundle exec backburner -q newsletter-sender,push-notifier -d -P /var/run/backburner.pid -l /var/log/backburner.log

This will daemonize the worker and store the pid and logs automatically. For Rails and Padrino, the environment should load automatically. For other cases, use the -r flag to specify a file to require.

Delaying Jobs

In Backburner, jobs can be delayed by specifying the delay option whenever you enqueue a job. If you want to schedule a job for an hour from now, simply add that option while enqueuing the standard job:

Backburner::Worker.enqueue(NewsletterJob, ['[email protected]', 'lorem ipsum...'], :delay => 1.hour)

or while you schedule an async method call:

User.async(:delay => 1.hour).reset_password(@user.id)

Backburner will take care of the rest!

Persistence

Jobs are persisted to queues as JSON objects. Let's take our User example from above. We'll run the following code to create a job:

User.async.reset_password(@user.id)

The following JSON will be put on the {namespace}.backburner-jobs queue:

{
    'class': 'User',
    'args': [nil, 'reset_password', 123]
}

The first argument is the 'id' of the object in the case of an instance method being async'ed. For example:

@device = Device.find(987)
@user = User.find(246)
@user.async.activate(@device.id)

would be stored as:

{
    'class': 'User',
    'args': [246, 'activate', 987]
}

Since all jobs are persisted in JSON, your jobs must only accept arguments that can be encoded into that format. This is why our examples use object IDs instead of passing around objects.

Named Priorities

As of v0.4.0, Backburner has support for named priorities. beanstalkd priorities are numerical but backburner supports a mapping between a word and a numerical value. The following priorities are available by default: high is 0, medium is 100, and low is 200.

Priorities can be customized with:

Backburner.configure do |config|
  config.priority_labels = { :custom => 50, :useful => 5 }
  # or append to default priorities with
  # config.priority_labels  = Backburner::Configuration::PRIORITY_LABELS.merge(:foo => 5)
end

and then these aliases can be used anywhere that a numerical value can:

Backburner::Worker.enqueue NewsletterJob, ["foo", "bar"], :pri => :custom
User.async(:pri => :useful, :delay => 10.seconds).reset_password(@user.id)

Using named priorities can greatly simplify priority management.

Processing Strategies

In Backburner, there are several different strategies for processing jobs which are reflected by multiple worker subclasses. Custom workers can be defined fairly easily. By default, Backburner comes with the following workers built-in:

Worker Description
Backburner::Workers::Simple Single threaded, no forking worker. Simplest option.
Backburner::Workers::Forking Basic forking worker that manages crashes and memory bloat.
Backburner::Workers::ThreadsOnFork Forking worker that utilizes threads for concurrent processing.
Backburner::Workers::Threading Utilizes thread pools for concurrent processing.

You can select the default worker for processing with:

Backburner.configure do |config|
  config.default_worker = Backburner::Workers::Forking
end

or determine the worker on the fly when invoking work:

Backburner.work('newsletter-sender', :worker => Backburner::Workers::ThreadsOnFork)

or through associated rake tasks with:

$ QUEUE=newsletter-sender,push-message THREADS=2 GARBAGE=1000 rake backburner:threads_on_fork:work

When running on MRI or another Ruby implementation with a Global Interpreter Lock (GIL), do not be surprised if you're unable to saturate multiple cores, even with the threads_on_fork worker. To utilize multiple cores, you must run multiple worker processes.

Additional concurrency strategies will hopefully be contributed in the future. If you are interested in helping out, please let us know.

More info: Threads on Fork Worker

For more information on the threads_on_fork worker, check out the ThreadsOnFork Worker documentation. Please note that the ThreadsOnFork worker does not work on Windows due to its lack of fork.

More info: Threading Worker (thread-pool-based)

Configuration options for the Threading worker are similar to the threads_on_fork worker, sans the garbage option. When running via the backburner CLI, it's simplest to provide the queue names and maximum number of threads in the format "{queue name}:{max threads in pool}[,{name}:{threads}]":

$ bundle exec backburner -q queue1:4,queue2:4  # and then other options, like environment, pidfile, app root, etc. See docs for the CLI

Default Queues

Workers can be easily restricted to processing only a specific set of queues as shown above. However, if you want a worker to process all queues instead, then you can leave the queue list blank.

When you execute a worker without any queues specified, queues for known job queue class with include Backburner::Queue will be processed. To access the list of known queue classes, you can use:

Backburner::Worker.known_queue_classes
# => [NewsletterJob, SomeOtherJob]

Dynamic queues created by passing queue options will not be processed by a default worker. For this reason, you may want to take control over the default list of queues processed when none are specified. To do this, you can use the default_queues class method:

Backburner.default_queues.concat(["foo", "bar"])

This will ensure that the foo and bar queues are processed by any default workers. You can also add job queue names with:

Backburner.default_queues << NewsletterJob.queue

The default_queues stores the specific list of queues that should be processed by default by a worker.

Failures

When a job fails in backburner (usually because an exception was raised), the job will be released and retried again until the max_job_retries configuration is reached.

Backburner.configure do |config|
  config.max_job_retries  = 3 # retry jobs 3 times
  config.retry_delay      = 2 # wait 2 seconds in between retries
end

Note the default max_job_retries is 0, meaning that by default jobs are not retried.

As jobs are retried, a progressively-increasing delay is added to give time for transient problems to resolve themselves. This may be configured using retry_delay_proc. It expects an object that responds to #call and receives the value of retry_delay and the number of times the job has been retried already. The default is a cubic back-off, eg:

Backburner.configure do |config|
  config.retry_delay      = 2 # The minimum number of seconds a retry will be delayed
  config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
end

If continued retry attempts fail, the job will be buried and can be 'kicked' later for inspection.

You can also setup a custom error handler for jobs using configure:

Backburner.configure do |config|
  config.on_error = lambda { |ex| Airbrake.notify(ex) }
end

Now all backburner queue errors will appear on airbrake for deeper inspection.

If you wish to retry a job without logging an error (for example when handling transient issues in a cloud or service oriented environment), simply raise a Backburner::Job::RetryJob error.

Logging

Logging in backburner is rather simple. When a job is run, the log records that. When a job fails, the log records that. When any exceptions occur during processing, the log records that.

By default, the log will print to standard out. You can customize the log to output to any standard logger by controlling the configuration option:

Backburner.configure do |config|
  config.logger = Logger.new(STDOUT)
end

Be sure to check logs whenever things do not seem to be processing.

Hooks

Backburner is highly extensible and can be tailored to your needs by using various hooks that can be triggered across the job processing lifecycle. Often using hooks is much easier then trying to monkey patch the externals.

Check out HOOKS.md for a detailed overview on using hooks.

Workers in Production

Once you have Backburner setup in your application, starting workers is really easy. Once beanstalkd is installed, your best bet is to use the built-in rake task that comes with Backburner. Simply add the task to your Rakefile:

# Rakefile
require 'backburner/tasks'

and then you can start the rake task with:

$ rake backburner:work
$ QUEUE=newsletter-sender,push-notifier rake backburner:work

The best way to deploy these rake tasks is using a monitoring library. We suggest God which watches processes and ensures their stability. A simple God recipe for Backburner can be found in examples/god.

Command-Line Interface

Instead of using the Rake tasks, you can use Backburner's command-line interface (CLI) – powered by the Dante gem – to launch daemonized workers. Several flags are available to control the process. Many of these are provided by Dante itself, such as flags for logging (-l), the process' PID (-P), whether to daemonize (-d) or kill a running process (-k). Backburner provides a few more:

Queues (-q)

Control which queues the worker will watch with the -q flag. Comma-separate multiple queue names and, if you're using the ThreadsOnFork worker, colon-separate the settings for thread limit, garbage limit and retries limit (eg. send_mail:4:10:3). See its wiki page for some more details.

backburner -q send_mail,create_thumbnail # You may need to use `bundle exec`
Boot an app (-r)

Load an app with the -r flag. Backburner supports automatic loading for both Rails and Padrino apps when started from the their root folder. However, you may point to a specific app's root using this flag, which is very useful when running workers from a service script.

path="/var/www/my-app/current"
backburner -r "$path"
Load an environment (-e)

Use the -e flag to control which environment your app should use:

environment="production"  
backburner -e $environment

Reconnecting

In Backburner, if the beanstalkd connection is temporarily severed, several retries to establish the connection will be attempted. After several retries, if the connection is still not able to be made, a Beaneater::NotConnected exception will be raised. You can manually catch this exception, and attempt another manual retry using Backburner::Worker.retry_connection!.

Web Front-end

Be sure to check out the Sinatra-powered project beanstalkd_view by denniskuczynski which provides an excellent overview of the tubes and jobs processed by your beanstalk workers. An excellent addition to your Backburner setup.

Acknowledgements

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

References

The code in this project has been made in light of a few excellent projects:

Thanks to these projects for inspiration and certain design and implementation decisions.

Links

backburner's People

Contributors

a112121788 avatar alup avatar amatsuda avatar bfolkens avatar boncey avatar calasyr avatar contentfree avatar dplummer avatar eltone avatar fasterthanlime avatar itsmikeq avatar jc00ke avatar merwan avatar nathantsoi avatar nesquena avatar nicolasleger avatar nicotaing avatar nitrodist avatar rochefort avatar run26kimo avatar ryanjohns avatar shadowbelmolve avatar shkolnik avatar shrutisaagar avatar silentshade avatar sztheory avatar thwarted avatar utilum avatar zacviandier avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

backburner's Issues

Worker fails with undefined method bury

When queue is empty worker explodes with message:

/Users/jdudulski/.rvm/gems/ruby-1.9.2-p290/gems/backburner-0.1.0/lib/backburner/worker.rb:100:in `rescue in work_one_job': undefined method `bury' for nil:NilClass (NoMethodError)

Handling restarts or shutdowns

How does backburner handle restarts or shutdowns? Do the running jobs get put back into the queue or do they just "timeout"?

Is backburner:threads_on_fork:work reliable ?

Hello

I am running Ubuntu 12.04TLS and beanstalkd 1.9

I have a very simple Rails 4 application which uses Backburner to do some audio processing, image and video downloads in the background.

It kinda works but sometimes some jobs are not processed.

I can reproduce it fairly easily and I got this kind of behaviour.

The code to enqueue the image to download is

    Rails.logger.debug("[Asset.enqueue_download_from_url] id = #{id}, url = #{url}")
    Backburner.enqueue ImageDownloadJob, self.id, url

And the code in my Job is

class ImageDownloadJob
  include Backburner::Queue
  queue "image-download"
  queue_priority 1000 # most urgent priority is 0

  def self.perform(image_id, url)
    Rails.logger.debug("[ImageDownloadJob.perform] id = #{image_id}, url = #{url}")
    Image.find(image_id).download_from_url(url)
  end
end

and even though it works 9 / 10 times
sometimes I get the following in my development log

[Asset.enqueue_download_from_url] id = 71, url = http://upload.wikimedia.org/wikipedia/commons/8/89/Drosophilidae_compound_eye_.jpg

but no ImageDownloadJob.perform trace afterwards. It seems like the job is never enqueued or never processed.

I'm using backburner:threads_on_fork:work

Here is my Procfile

web:             bundle exec rails s
backburner:      env QUEUE=image-download:3:50:2,video-generation:1:50:1,audio-analysis:1:50:1 rake backburner:threads_on_fork:work
rails_logs:      tail -f log/development.log
backburner_logs: tail -f log/backburner.development.log

Is it a known bug of the backburner:threads_on_fork:work strategy ? Any advice ?

Thanks in advance, best regards

Geoffroy

Passing "complex" objects

Hi, I'm loving the backburner/beanstalk combo and am using it to process large amounts of data within my app.

From what I have read so far; the prevailing advice seems to be to pass database ids into enqueue and then have the worker read the information it needs from the database.
I am doing this now - I persist data for the backburner worker to then (almost) immediately read that data back out (then delete that row once processing is complete).
This (the database write/read/delete) is proving to be a bit of a bottleneck within my app.

What I'd like to do is not to have to touch the database at all but to pass all my data straight to backburner and then have my workers process it without having to read it back from the database.
My data is around 12 distinct text strings (none more than a few hundred characters long) although some can be non-ASCII (ie, UTF-8) text.

Am I likely to hit any real problems with this approach?

Is anyone else already doing it this way?

Thanks in advance for any help.
Darren.

Have priority shorthand names for convenience

From @bradgessler:

We had a mis-numbered priority in production once that brought our system down. We'd like to have "named" priorities that are stack-ranked. This is a simple DSL that looks like: Backburner.config.priorities = [:high, :medium, :custom_pri, :low], which is mapped to the int values that beanstalkd understands. When tossing a job on the queue, a named priority could be specified like User.new.async(pri: :high).blah.

When multiple connections are present, removing one should not cause failure

It is expected that the client should try to connect to each server. If a connection is not established it will remove the connection from its connection pool. As long as the number of successful connections to a server is greater than zero, work should continue to occur. Ideally, the client would be smart enough to retry the connection to failed server and add it back to the pool when its connection is restored.

The following will reproduce the error if you start a worker on port 11300 or 11301, not both.

require 'backburner'

Backburner.configure do |config|
  config.beanstalk_url = ['beanstalk://127.0.0.1:11300', 'beanstalk://127.0.0.1:11301']
end

class Job
  def self.perform(message)
    p message
  end
end

Backburner::Worker.enqueue(Job, ['Hello'])
Beaneater::NotConnected: Could not connect to '127.0.0.1:11301'
        from /usr/lib64/ruby/gems/2.1.0/gems/beaneater-0.3.2/lib/beaneater/connection.rb:96:in `rescue in establish_connection'
        from /usr/lib64/ruby/gems/2.1.0/gems/beaneater-0.3.2/lib/beaneater/connection.rb:92:in `establish_connection'
        from /usr/lib64/ruby/gems/2.1.0/gems/beaneater-0.3.2/lib/beaneater/connection.rb:36:in `initialize'
        from /usr/lib64/ruby/gems/2.1.0/gems/beaneater-0.3.2/lib/beaneater/pool.rb:25:in `new'
        from /usr/lib64/ruby/gems/2.1.0/gems/beaneater-0.3.2/lib/beaneater/pool.rb:25:in `block in initialize'
        from /usr/lib64/ruby/gems/2.1.0/gems/beaneater-0.3.2/lib/beaneater/pool.rb:25:in `map'
        from /usr/lib64/ruby/gems/2.1.0/gems/beaneater-0.3.2/lib/beaneater/pool.rb:25:in `initialize'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/connection.rb:27:in `new'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/connection.rb:27:in `connect!'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/connection.rb:13:in `initialize'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/worker.rb:58:in `new'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/worker.rb:58:in `connection'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/worker.rb:185:in `retry_connection!'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/worker.rb:173:in `rescue in retryable_command'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/worker.rb:170:in `retryable_command'
        from /usr/lib64/ruby/gems/2.1.0/gems/backburner-0.4.5/lib/backburner/worker.rb:33:in `enqueue'

The problem lies within Backburner::Worker#retry_connection!. Instead of the simple @connection = nil, the failed connection should be removed before retrying.

Enqueuing a job by instantiating the job with the arguments

Discussing with @bradgessler:

Currently:

Backburner::Worker.enqueue NewsletterSender, [self.id, user.id], :ttr => 1000

instead:

Backburner::Worker.enqueue Backburner::Job.new(NewsletterSender, [self.id, user.id], :ttr => 1000)

or:

# include module
Backburner::Worker.enqueue NewsletterSender.job(self.id, user.id).tap { |p| p.ttr = 200 }

Problems with environment when running backburner as daemon

When I run backburner as a daemon, it seems to always prefix the queue with 'backburner.worker' irrespective of what is in my apps config file or what I provide as command-line parameters.

Working 1 queues: [ backburner.worker.queue.myapp.mailer ]

I tried to change the app config to load work into the queue named above, but then it seems that the environment isn't loaded properly so jobs that get queued get immediately buried.

The rake task always works but is ugly and difficult to connect to monit or God. This is what I'm doing as a short-term work around.
nohup rake backburner:work &

Any ideas?

config.beanstalk_url = ["beanstalk://127.0.0.1:11300"]
config.tube_namespace = "myapp"
config.on_error = lambda { |e| puts e }
config.max_job_retries = 3 # default 0 retries
config.retry_delay = 5 # default 5 seconds
config.default_priority = 65536
config.respond_timeout = 120
config.default_worker = Backburner::Workers::Simple
config.default_queues = ["staging", "staging-mailer"]
config.logger = Logger.new("backburner-staging.log")

By default, have all jobs enqueued to the same app-specific tube

From @bradgessler:

I don't think having different queues per app is a good idea in a beanstalkd world. Resque does this because there's no concept of priorities in Redis. Since Beanstalkd lets people specify priories of jobs, its a moot point to run different numbers of workers for different queue names. Put more emphasis on priority to deal with this.

I like the idea of having all classes by default piping to the same tube and using priorities more heavily. If a job should be in a different tube, then that is of course possible using the same format today.

Possibility to run worker with reserve timeout

Wouldn't it be convenient if we could specify timeout in reserve method which is called from work_one_job method. So if worker waits on reserve command longer than specified timeout it stops automatically. Actually this is first parameter which can be passed to reserve method of laying below in hierarchy Beaneater::Tubes class.

I want to run such a worker from time to time so I would have it only to process current requests after processing it should stop.

I am going to send to beanstalkd 20 tasks at once. Each pack of 20 tasks will be assigned to different tube. Each Tube will have only one worker which will process tasks, so I will be able to log data from processing these particular 20 tasks to separate file. That is why I would like to have possibility to run worker which will stop after it sees that there is nothing more to do for him. And this 'seeing' could be basically timeout on reserve.

I believe that reserve-with-timeout is the way it may be achieved.

Please correct me if I am wrong and tell me if you are going to add this nice feature to backburner.

Regards,
Mateusz

Init script for linux systems

Is there any init script for linux to start/stop backburner?
For example, if you use monit and want to monitor your workers with pid - its really good to have init script for start/stop/restart.

CLI for querying and filtering jobs

From @bradgessler:

CLI for querying/filtering jobs for performing batch operations on jobs that are in the queue. This is important for when things go bad in production and certain jobs with certain payloads may need to be buried until a patch can be pushed to prod and the jobs are re-run. Web GUI might be able to use this CLI interface.

Move configuration to yml file.

Is it possible, to move this block to yaml file?

Backburner.configure do |config|
  ...
end

Its needed, when you have different configurations for development, staging and production.
Or is there any other convenient way to split configuration?

Each thread in ThreadsOnFork worker should get its own connection

I've noticed a problem with the ThreadsOnFork worker once the job queue goes empty. If there is a thread trying to reserve a job on an empty queue, it holds the mutex so no other communication can happen on that connection (note: the mutex code is in beaneater, though the mutex code itself is not necessarily a bug, IMO). This is problematic if another thread has just reserved a job and is trying to process said job. Most importantly, in order to do actual job processing, the 'stats-job' is run to retrieve the ttr from beanstalkd, and if this job is held up then the already-reserved jobs will fail to process, and you're deadlocked.

The only way I've seen this deadlock broken is when beanstalkd eventually returns 'DEADLINE_SOON' to the blocking reserve jobs, by default 120s later. This is pretty terrible latency, however.

You can alleviate this by having more connection addresses defined in your backburner config; beaneater will use these as a pool, but you'll still get collisions where two threads are trying to use the same connection. Really, no two threads should ever try to use the same connection/socket if a blocking reserve is involved.

An easy way to reproduce this is to instantiate a ThreadsOnFork worker for a particular tube with n (>= 2) max threads, and then queue m (n < m < 2n) jobs onto that tube. The first n jobs should process immediately, and the remaining ones should get hung in a state where they have technically been reserved, but can't communicate with the beanstalkd server so they won't process for 120 seconds (unless you've overridden ttr). In my particular case, I had 10 threads and 15 jobs.

Better support for testing jobs

From @bradgessler:

A stub could be provided for peeps that want to assert jobs are thrown on the queue in a test env. Make testing a job is performed work easily. Right now I use a hacky thing on my projects:

# Backburner::Worker.enqueue NewsletterSender, [self.id, user.id], :ttr => 1000
Backburner::Worker.class_eval do
  class << self; alias_method :original_enqueue, :enqueue; end
  def self.enqueue(job_class, args=[], opts={})
    job_class.perform(*args)
  end
end

to force the jobs to be executed automatically. Open to the right way to do this that is simple.

Beanstalk failover

The beanstalk-client gem allows you to define multiple beanstalk servers, but I don't see how that works with backburner's configure block.

This way the workers could listen to the same queue on multiple beanstalkd servers and if job enqueuing fails, backburner should try another server in the pool.

Command line enqueue?

As part of moving work away from rails runner based cron jobs into a backburner based system I've started on a simple way to enqueue jobs from the command line. Is that something you'd be interested in incorporating into backburner?

Updating Tube code.

When I Backburner, I am unable to run the worker with the updated code, it still seems to run the old code. Any pointers?

Create forking worker

Resque uses forking to control memory management and bloat and I think this could be a good idea to have a fork worker to apply the same principles.

Support jobs without need for queue mixin

From @bradgessler:

I'm not convinced that the Job mix-in is the best approach. A job should be a class itself. Mixin's make sense as syntatical sugar to make it easier to queue an instance of a class into a job, and from pulling the job off the queue and shoving it back into that class for processing.

I agree, would be ideal to support any ruby object that responds to perform ala resque. I still want to keep the mixin around for the nice syntactic sugar it affords.

Batch processing multiple jobs

At the moment I have 16 workers processing jobs in parallel.
The outcome of these jobs is either no action or a write to a database.

As each process saves to the database it invokes its own separate BEGIN/COMMIT database transaction. This is proving to be quite slow and I'd like to find a way to speed that bit up.

I was wondering about this approach instead:
Instead of saving to a database - send the data to a different beanstalk queue/tube.
For that queue I'd like to read a batch of jobs into a single worker and then commit them in a single database transaction.
If I could process jobs in a batch of 50 at a time I could significantly reduce the number of database commits.

Can I do such a thing with backburner?
Or if not, maybe using beaneater instead?
Any issues with it from a theoretical standpoint?

Thanks in advance, Darren.

Better retry support

Right now jobs are buried if they raise an exception or timeout. Instead, perhaps retry with a delay for some max times before burying.

Backburner::Worker#retry_connection! does not close connections before creating new ones

Backburner::Worker#retry_connection! does not call close, leaving open connections to servers until the process holding those connections is killed.

Can be reproduced by starting 2 servers. Then, queue jobs in a loop.

require 'backburner'

Backburner.configure do |config|
  config.beanstalk_url = ['beanstalk://127.0.0.1:11300', 'beanstalk://127.0.0.1:11301']
end

class Job
  def self.perform(message)
    p message
  end
end

loop do
  Backburner::Worker.enqueue(Job, ['Hello'])
end

In another process run the worker.

Backburner.work

Kill one of the servers. The other servers output will look like this.

$ beanstalkd -V -p 11300
pid 4318
bind 3 0.0.0.0:11300
accept 5
accept 6
accept 7
accept 8
accept 9
accept 10
accept 11
accept 12
accept 13
accept 14
accept 15

lsof of this process.

COMMAND     PID    USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME                                                                                                                                                                                                                                                                                                     [2/1880]
beanstalk 32480 vagrant  cwd    DIR    8,3     4096 261123 /home/vagrant
beanstalk 32480 vagrant  rtd    DIR    8,3     4096      2 /
beanstalk 32480 vagrant  txt    REG    8,3    63744 789844 /usr/bin/beanstalkd
beanstalk 32480 vagrant  mem    REG    8,3   156928 522458 /lib64/ld-2.12.so
beanstalk 32480 vagrant  mem    REG    8,3  1926800 522604 /lib64/libc-2.12.so
beanstalk 32480 vagrant    0u   CHR  136,4      0t0      7 /dev/pts/4
beanstalk 32480 vagrant    1u   CHR  136,4      0t0      7 /dev/pts/4
beanstalk 32480 vagrant    2u   CHR  136,4      0t0      7 /dev/pts/4
beanstalk 32480 vagrant    3u  IPv4  97801      0t0    TCP *:11300 (LISTEN)
beanstalk 32480 vagrant    4u   REG    0,9        0   3780 [eventpoll]
beanstalk 32480 vagrant    5u  IPv4  97810      0t0    TCP localhost:11300->localhost:55278 (ESTABLISHED)
beanstalk 32480 vagrant    6u  IPv4  98268      0t0    TCP localhost:11300->localhost:55306 (ESTABLISHED)
beanstalk 32480 vagrant    7u  IPv4  98274      0t0    TCP localhost:11300->localhost:55308 (ESTABLISHED)
beanstalk 32480 vagrant    8u  IPv4  98279      0t0    TCP localhost:11300->localhost:55310 (ESTABLISHED)
beanstalk 32480 vagrant    9u  IPv4  98284      0t0    TCP localhost:11300->localhost:55312 (ESTABLISHED)
beanstalk 32480 vagrant   10u  IPv4  98291      0t0    TCP localhost:11300->localhost:55314 (ESTABLISHED)
beanstalk 32480 vagrant   11u  IPv4  98296      0t0    TCP localhost:11300->localhost:55316 (ESTABLISHED)
beanstalk 32480 vagrant   12u  IPv4  98301      0t0    TCP localhost:11300->localhost:55318 (ESTABLISHED)
beanstalk 32480 vagrant   13u  IPv4  98306      0t0    TCP localhost:11300->localhost:55320 (ESTABLISHED)
beanstalk 32480 vagrant   14u  IPv4  98311      0t0    TCP localhost:11300->localhost:55322 (ESTABLISHED)
beanstalk 32480 vagrant   16u  IPv4  98236      0t0    TCP localhost:11300->localhost:55302 (ESTABLISHED)
beanstalk 32480 vagrant   17u  IPv4  98252      0t0    TCP localhost:11300->localhost:55304 (ESTABLISHED)

Identifying / differentiating between Backburner workers

I'm trying to use Backburner in my application to create, say, 50 workers all watching the same tube for incoming jobs. The problem is, each of those 50 workers, when performing a job, does some heavy file manipulation, etc., and each of them needs to perform some operations on a uniquely-named numbered directory. For example, worker 7 needs to work with a directory named dir7. How do I handle this situation using Backburner?

Initially I thought I could make a ThreadsOnFork worker with 50 threads, and I'd be able to access a number associated with each thread from the worker's perform() method, but I haven't been able to do this yet.

Please help. Thanks!

PS: Apologies for asking this question on GitHub Issues, but I couldn't find a link to any official forum / google group on the backburner page at http://nesquena.github.com/backburner/.

Reserving job fails when haproxy client timeout is exceeded

Hi I am looking in to setting up beanstalkd and backburned in a HA context.

To protect against a beanstalkd instance going down I have multiple instances fronted by haproxy.

My test is using the Simple worker.

Whilst executing the work_one_job method in worker.rb, backburner attempts to reserve a job without using a timeout. When there are no jobs on a particular tube, the connection is kept open until one arrives.

When connecting to beanstalkd directly, the connection is held open for as long as it takes for a job to arrive. However haproxy terminates the connection, after the timeout that it is configured with is exceeded.

At this point the exception is caught but a further exception is thrown as the job variable is nil. The fix to this problem is unfortunately not a one liner as the tcpsocket connection held in a class variable is also broken so subsequent retries would also fail.

What do you think the solution should be to make connections to beanstalkd more resilient, when operating in the this context? Should these terminations be handled? I would like to contribute to the project, but would like to know your thoughts before I spend to much time going in the wrong direction.

Thanks for your help,
lashd

Backburner looks great by the way.

Resetting backburner connection to beanstalkd

I am trying to use backburner in my rails application and am using Backburner.enqueue to add jobs to the beanstalkd queue. However, if the connection to the beanstalkd server breaks, all calls to enqueue fail with the exception Beaneater::NotConnected. Even if beanstalkd is back online, enqueue continues to fail since @connection in Backburner::Worker is still set and a new Connection is not made.

Maybe I am just missing something simple in the code/documentation, but what is the best way to reattempt/reset a connection without creating a setter for the connection method (patching) in the Worker? I do not want to restart my rails application in order for a new connection to be made.

Workers do not respond appropriately to SIGTERM during activerecord mysql queries

If a worker process receives SIGTERM while a mysql query is running, rails apparently rescues the SignalException and re-raises it as an ActiveRecord::StatementInvalid. This causes Backburner to gracefully recover from the exception, bury the job, and keep on working additional jobs, effectively ignoring the intent of the TERM signal.

Apparently this issue is at least somewhat common:

http://stackoverflow.com/questions/548048/activerecordstatementinvalid-when-process-receives-sigterm

I'm not sure if this is a problem on all workers, but we can confirm it is present on the simple worker, and the net effect is that once every few deploys, our old workers simply refuse to die when we attempt to shut them down.

Checking on a job after it's been put into the queue?

Is there a way for me to check on a job after it's been enqueued? Preferably after calling "async.command" I could get an id back that I can go back and check so I can follow up afterwards?

My use-case is this. I am triggering async jobs from several parts of my web application. I'd like to give the user feedback about the completion of those jobs and I can't move the user forward until they finish. There are other parts where we trigger a job but I need to give them a way to see if they were successful or failed. I'm trying to find a way where I don't need to wrap all of these with another layer of database tables to provide this information.

Delay within a worker

I didn't find it anywhere in docs, but what I would like to do is within a job call a delay method, for example:

class ImportSongs
  include Backburner::Queue

  def self.perform(api_token, songs)
    api = API.new api_token

    songs.each_with_index do |song, i|
      # make current worker proceed with another job while it's sleeping
      delay 60*60  if i != 0 && i % 100 == 0

      api.import_song song
    end
  end
end

Worker Hooks

Talking with @bradgessler want a way to hook into the worker and define arbitrary code to run before and after.

Backburner::Worker.on_start do |job|
  NewRelic.add_instrumentation "..."
end

Backburner::Worker.before_enqueue do |job|

end

Backburner::Worker.after_enqueue  do |job|

end

Backburner vs beaneater

Not really an issue, I just don't know where is the best place to ask this question.
I'm just starting with background jobs and wondering what is advantage of backburner over beaneater?

Shreko

License missing from gemspec

Some companies will only use gems with a certain license.
The canonical and easy way to check is via the gemspec,

via e.g.

spec.license = 'MIT'
# or
spec.licenses = ['MIT', 'GPL-2']

Even for projects that already specify a license, including a license in your gemspec is a good practice, since it is easily
discoverable there without having to check the readme or for a license file.

For example, there is a License Finder gem to help companies ensure all gems they use
meet their licensing needs. This tool depends on license information being available in the gemspec. This is an important enough
issue that even Bundler now generates gems with a default 'MIT' license.

If you need help choosing a license (sorry, I haven't checked your readme or looked for a license file),
github has created a license picker tool.

In case you're wondering how I found you and why I made this issue, it's because I'm collecting stats on gems (I was originally
looking for download data) and decided to collect license metadata,too, and make issues for gemspecs not specifying a license as a public service :).

I hope you'll consider specifying a license in your gemspec. If not, please just close the issue and let me know. In either case, I'll follow up. Thanks!

p.s. I've written a blog post about this project

Backburner jobs get stuck in 'ready' state.

I have some trouble with backburner. I have a setup with two workers running against two beanstalk instances. Somehow, it seems like jobs get 'stuck' somehow sometimes. I can see the workers in beanstalkd view like this:

beanstalkd_view_-_1_2_7

And if I keep restarting the workers, it eventually picks up the jobs and process them. I'm wondering if this could be related to having multiple beanstalk servers, but I am a bit at a loss about how to debug further.

Worker is not always watching for my queue.

Hello

I defined one job in my Rails app, as follows

class ImageDownloadJob
  include Backburner::Queue
  queue "image-download"
  queue_priority 1000 # most urgent priority is 0

  def self.perform(image_id, url, expected_mime_type)
    Rails.logger.debug ("ImageDownloadJob.perform #{image_id} #{url} #{expected_mime_type}")
    Image.find(image_id).save_file_from_url(url, expected_mime_type)
  end
end

In config/initializers/backburner.rb I put the following code

Backburner.configure do |config|
  config.beanstalk_url    = "beanstalk://127.0.0.1"
  config.tube_namespace   = "myapp.#{Rails.env}"
  config.on_error         = lambda { |e| Rails.logger.error "Backburner / Beanstalk error = #{e}" }
  config.max_job_retries  = 0 # default 0 retries
  config.retry_delay      = 2 # default 5 seconds
  config.default_priority = 65536
  config.respond_timeout  = 120
  config.default_worker   = Backburner::Workers::Simple
  config.logger           = Rails.logger
  config.primary_queue    = "backburner-jobs"
  config.priority_labels  = { :custom => 50, :useless => 1000 }
end

Beanstalkd is running.

Then I am starting a worker with the following command :

QUEUES=image-download bundle exec rake backburner:work

I get the following in my log

Working 1 queues: [ myapp.development.backburner-jobs ]

which does not make sense to me, because it should start waiting on my image-download queue.

Then if I start my Rails app with bundle exec rails s, the app enqueues some jobs that the worker never gets the work.

Then, if I kill the backburner:work process with a ctrl+c, and relaunch it, it will get my image-download queue and process the jobs.

Working 2 queues: [ myapp.development.image-download, myapp.development.backburner-jobs ]
Work job ImageDownloadJob with [45, "test", "image"]
ImageDownloadJob.perform 45 test image

Why do I have to start the worker after my Rails app has put something in the queue ?
Is it a bug or just me ?

Thanks for your help !!

Best

Geoffroy
Geoffroy

Backburner Specific Admin Panel

After chatting with @kr today, have a few ideas to jot down for a first class backburner admin frontend panel. First I would want visibility into the jobs in the ready queue as well as a view showing failed jobs with backtraces and a way to 'kick' a job, all available via a sinatra web UI.

In particular, some ideas on how to do this.

Ready Jobs

Create a sinatra view that reserve(0) 100 jobs and then collects the information and immediately releases them. Aggregate the 100 jobs and display them in the sinatra view. By reserving and releasing immediately, we can create a view for both development and production by showing the next 100 jobs.

Buried Jobs

I would want a place for buried jobs where you can see the next 100 buried jobs across all queues perhaps. For this we could have a special tube called 'failed-jobs' that is special and used by the admin panel. In Backburner, everytime a job fails and is buried, we can then insert the job into the 'failed-jobs' queue before burying it. Then when we want to show last 100 failing jobs simply reserve(0) and release 100 jobs from that tube. In this way we can have a buried jobs list. We can even have a button to kick the job (which will then kick the job based on the real id). The way I was thinking about it, the failed-jobs tube could have jobs with this format:

{ "job-id" : 1234, "tube" : "foo", "backtrace" : "...", "tries" : 3 }

and then this can be used to display the buried jobs in a table.

Implementation Thoughts

Also, I love beanstalkd_view and it's such a great start, I wonder @denniskuczynski if you would have any interest being a core contributor to backburner and helping out with the admin panel. Ideally we could have a familiar feel to https://github.com/defunkt/resque#section_The_Front_End and obviously be unabashedly clear we are inspired by that project's interface as a point of reference (as well as beanstalkd_view itself).

Resque Admin Interface

Support setting max_job_retries and retry_delay on a per-tube basis

Currently the max_job_retries and retry_delay settings apply to every tube that backburner watches. For some tubes that I'm processing I'd like a large number of retries for, while for others others I'd like a single retry. It would be great if back burner could be configured to support this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.