Skip to content

restorando/sidekiq-unique-jobs

 
 

Repository files navigation

SidekiqUniqueJobs Build Status Code Climate

The missing unique jobs for sidekiq

Requirements

See https://github.com/mperham/sidekiq#requirements for what is required. Starting from 3.0.13 only sidekiq 3 is supported and support for MRI 1.9 is dropped (it might work but won't be worked on)

Version 4 requires redis 2.6.2!! Don't upgrade to version 4 unless you are on redis 2.6.2.

Upgrade instructions

Easy path - Drop all your unique jobs before upgrading the gem! Hard path - See above... Start with a clean slate :)

Installation

Add this line to your application's Gemfile:

gem 'sidekiq-unique-jobs'

And then execute:

$ bundle

Or install it yourself as:

$ gem install sidekiq-unique-jobs

A word on locking

Like @mperham mentions on (this wiki page)[https://github.com/mperham/sidekiq/wiki/Related-Projects#unique-jobs] it is hard to enforce uniqueness with redis in a distributed redis setting.

To make things worse there are many ways of wanting to enforce uniqueness.

While Executing

Is to make sure that a job can be scheduled any number of times but only executed a single time per argument provided to the job we call this runtime uniqueness. This is probably most useful forbackground jobs that are fast to execute. (See mhenrixon#111 for a great example of when this would be right.) While the job is executing/performing no other jobs can be executed at the same time.

Until Executing

This means that a job can only be scheduled into redis once per whatever the configuration of unique arguments. Any jobs added until the first one of the same arguments has been unlocked will just be dropped. This is what was tripping many people up. They would schedule a job to run in the future and it would be impossible to schedule new jobs with those same arguments even immediately. There was some forth and back between also locking jobs on the scheduled queue and the regular queues but in the end I decided it was best to separate these two features out into different locking mechanisms. I think what most people are after is to be able to lock a job while executing or that seems to be what people are most missing at the moment.

Until Executed

This is the combination of the two above. First we lock the job until it executes, then as the job begins executes we keep the lock so that no other jobs with the same arguments can execute at the same time.

Until Timeout

The job won't be unlocked until the timeout/expiry runs out.

Uniqueness Scope

  • Queue specific locks
  • Across all queues.
  • Timed / Scheduled jobs

Usage

All that is required is that you specifically set the sidekiq option for unique to true like below:

sidekiq_options unique: true

For jobs scheduled in the future it is possible to set for how long the job should be unique. The job will be unique for the number of seconds configured (default 30 minutes) or until the job has been completed. Thus, the job will be unique for the shorter of the two. Note that Sidekiq versions before 3.0 will remove job keys after an hour, which means jobs can remain unique for at most an hour.

*If you want the unique job to stick around even after it has been successfully processed then just set the unique_lock to anything except :before_yield or :after_yield (unique_lock = :until_timeout)

You can also control the expiration length of the uniqueness check. If you want to enforce uniqueness over a longer period than the default of 30 minutes then you can pass the number of seconds you want to use to the sidekiq options:

sidekiq_options unique: true, unique_expiration: 120 * 60 # 2 hours

Requiring the gem in your gemfile should be sufficient to enable unique jobs.

Usage with ActiveJob

Sidekiq.default_worker_options = {
   'unique' => true,
   'unique_args' => proc do |args|
     [args.first.except('job_id')]
   end
}
SidekiqUniqueJobs.config.unique_args_enabled = true

Finer Control over Uniqueness

Sometimes it is desired to have a finer control over which arguments are used in determining uniqueness of the job, and others may be transient. For this use-case, you need to set SidekiqUniqueJobs.config.unique_args_enabled to true in an initializer, and then defined either unique_args method, or a ruby proc.

The unique_args method need to return an array of values to use for uniqueness check.

SidekiqUniqueJobs.config.unique_args_enabled = true

The method or the proc can return a modified version of args without the transient arguments included, as shown below:

class UniqueJobWithFilterMethod
  include Sidekiq::Worker
  sidekiq_options unique: true,
                  unique_args: :unique_args

  def self.unique_args(name, id, options)
    [ name, options[:type] ]
  end

  ...

end

class UniqueJobWithFilterProc
  include Sidekiq::Worker
  sidekiq_options unique: true,
                  unique_args: ->(args) { [ args.first ] }

  ...

end

Note that objects passed into workers are converted to JSON after running through client middleware. In server middleware, the JSON is passed directly to the worker #perform method. So, you may run into issues where the arguments are different when enqueuing than they are when performing. Your unique_args method may need to account for this.

Unlock Ordering

By default the server middleware will release the worker lock after yielding to the next middleware or worker. Alternatively, this can be changed by passing the unique_lock option:

class UniqueJobWithFilterMethod
  include Sidekiq::Worker
  sidekiq_options unique: true,
                  unique_locks: :until_executing

  ...

end

After Unlock Callback

If you are using :after_yield as your unlock ordering, Unique Job offers a callback to perform some work after the block is yielded.

class UniqueJobWithFilterMethod
  include Sidekiq::Worker
  sidekiq_options unique: true,

  def after_unlock
   # block has yielded and lock is released
  end
  ...
end.

Unique Storage Method

Starting from sidekiq-unique-jobs 3.0.14 we will use the set method in a way that has been available since redis 2.6.12. If you are on an older redis version you will have to change a config value like below.

SidekiqUniqueJobs.config.unique_storage_method = :old

That should allow you to keep using redis in the old fashion way. See #89 for mor information.

Logging

To see logging in sidekiq when duplicate payload has been filtered out you can enable on a per worker basis using the sidekiq options. The default value is false

class UniqueJobWithFilterMethod
  include Sidekiq::Worker
  sidekiq_options unique: true,
                  log_duplicate_payload: true

  ...

end

Testing

To enable the testing for sidekiq-unique-jobs, add require 'sidekiq_unique_jobs/testing' to your testing helper.

SidekiqUniqueJobs uses mock_redis for inline testing. Due to complaints about having that as a runtime dependency it was made a development dependency so if you are relying on inline testing you will have to add gem 'mock_redis' to your Gemfile.

Contributing

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

Contributors

About

The missing unique jobs in sidekiq

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 98.9%
  • Lua 1.1%