que-locks

Job locking for que jobs such that only one can be in the queue or executing at once.


License
MIT
Install
gem install que-locks -v 0.4.1

Documentation

Que::Locks

que-locks adds an opt-in feature to Que::Jobs allowing jobs to specify that exactly one instance of a job should be executing at once. This is useful for jobs that are doing something important that should only ever happen one at a time, like processing a payment for a given user, or super expensive jobs that could cause thundering herd problems if enqueued all at the same time.

que-locks uses Postgres' advisory locks in a similar manner as que 0.x did to provide scalable and automatically cleaned up locking around job execution. que-locks provides slightly better atomicity guarantees compared to the locking functionality of Redis based job queues for the same reasons que can as well. Because locks are taken and released using the same database connection that the que worker uses to pull jobs, the transactional semantics apply just the same where locks are automatically released if the connection fails, unlike the multistep Redis logic that requires complicated crash cleanup.

This is also sometimes called "unique jobs", "job concurrency limiting", and "exclusive jobs".

Installation

Add this line to your application's Gemfile:

gem 'que-locks'

And then execute:

$ bundle

Or install it yourself as:

$ gem install que-locks

Usage

After requiring the gem, set the exclusive_execution_lock property on your job class:

class SomeJob < Que::Job
  self.exclusive_execution_lock = true

  def run(foo:, bar:)
    # useful stuff
  end
end

That's it!

Semantics

que-locks guarantees that the maximum number of instances given job class executing with a given set of arguments will be one. This means that:

  • two of the same job class each pushed with different arguments can both execute simultaneously
  • if the job takes no arguments, there can only ever be one executing globally
  • two of the same job class each pushed with the same arguments may result in one or two executions, but they won't ever be simultaneous

In some instances, multiple jobs with the same arguments can be enqueued and sit in the queue simultaneously. Despite this the semantics above will remain in tact: only one job will execute at once. The first one worked will get the lock and the second one worked will skip execution if the first job is still executing. que-locks tries to avoid extraneous job pushes by checking to see if the lock for a job is available at enqueue time, and skipping enqueue if so. This preemptive lock check helps keeps queues small in the event that a huge number of identical jobs are pushed at once.

que-locks uses a sorted JSON serialized version of the arguments to compute lock keys, so it's important that arguments that should be considered identical JSON serialize using Que.serialize_json to the exact same string.

que-locks adds no overhead to jobs that don't use locking, adds one more SQL query to check an advisory lock (which is only memory access) to enqueuing jobs, and adds two more SQL queries to lock and unlock to job execution.

Missing features

  • Configurable preemptive lock checking at enqueue time
  • Selective argument comparison for lock key computation
  • maybe a que-web integration to expose lock info

If you wish for any of this stuff, feel free to open a PR, contributions are always welcome!!

Non features

  • Locking to a limited concurrency greater than 1. If you want a lock that several different jobs can take out, a good option is to use Que's multiple queue support and run a limited number of workers working a certain queue so the concurrency is limited by the available worker slots.

Development

After checking out the repo, run bin/setup to install dependencies. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/hornairs/que-locks. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Que::Locks project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.