Getting started

Overview

sidekiq-unique-jobs stops the same job from being enqueued twice or run concurrently, using Redis locks keyed on the job's arguments.

What it does#

Sidekiq will happily enqueue the same job a hundred times. Usually that's fine — but sometimes it isn't: a webhook that fires twice, a user who double-clicks "export", a cron tick that overlaps the previous run. sidekiq-unique-jobs makes a job unique: while a lock is held, a second copy with the same arguments is prevented (or logged, rejected, rescheduled — your choice).

A job opts in with one line:

class ChargeCustomerJob
  include Sidekiq::Job

  sidekiq_options lock: :until_executed

  def perform(customer_id)
    # Only one job per customer_id at a time.
  end
end

Uniqueness is based on the worker class, the queue, and the arguments — so ChargeCustomerJob.perform_async(1) and ChargeCustomerJob.perform_async(2) are independent, but a second perform_async(1) while the first is still locked is a duplicate.

Two questions decide everything#

Which lock type, and what happens on a conflict.

Configuring a unique job comes down to two decisions:

  1. When should the lock be held? From enqueue until the job starts? Until it finishes? For a fixed time window? Only while it runs? That's the lock type — see Choosing a lock type.
  2. What should happen to a duplicate? Drop it, retry it later, replace the original, or send it to the dead set? That's the conflict strategy — see Conflict resolution.

Everything else — TTLs, timeouts, custom argument filtering, concurrency limits — is tuning on top of those two.

Two Redis keys per lock#

v9 is a ground-up rewrite. A lock is now just two Redis structures:

<digest>:LOCKED       # Hash  — the job IDs holding this lock, with metadata
uniquejobs:digests    # ZSet  — a global index of every active lock

Every lock operation runs inside a Lua script, so acquiring and releasing a lock is atomic even under heavy contention. If you're coming from v8, that's down from as many as thirteen keys per lock — see Upgrading to v9.

The middleware is not loaded automatically. You add it once in your Sidekiq initializer — see Installation.

Where to go next#

  • Installation — add the gem and wire up the middleware.
  • Quick start — your first unique job in five minutes.
  • Use cases — pick the page that matches what you're trying to prevent; each one is a complete, working worker.
  • Choosing a lock type — a decision table from "I want to prevent X" to the lock that does it.