Custom uniqueness arguments
Choose exactly which arguments — and which parts of the digest — decide whether two jobs count as the same.
The problem: a transient argument#
Only the arguments that matter should count.
By default a job's uniqueness digest is built from three things: the worker class, the queue, and all of the arguments. That's the right default, but it breaks the moment an argument carries something transient — a timestamp, a request ID, a retry counter — that changes on every call yet has nothing to do with whether two jobs are "the same".
Consider a job that refreshes a user's cache. It takes the user_id that actually identifies the work, plus an enqueued_at timestamp used only for logging:
RefreshUserCacheJob.perform_async(42, "2026-07-09T10:00:00Z")
RefreshUserCacheJob.perform_async(42, "2026-07-09T10:00:01Z")These are the same piece of work — user 42 — but because the timestamps differ, the default digest treats them as two distinct jobs and both get enqueued. The fix is to tell the gem which arguments matter with lock_args_method: a hook that receives the full args array and returns just the subset that should drive uniqueness.
As a class method#
A symbol naming a class method on the worker.
Point lock_args_method: at a symbol naming a class method. The method receives the full arguments array and returns the subset that defines uniqueness — here, just the first argument:
class RefreshUserCacheJob
include Sidekiq::Job
sidekiq_options lock: :until_executed, lock_args_method: :unique_args
def self.unique_args(args)
[args.first]
end
def perform(user_id, _enqueued_at)
# Only one refresh per user_id is in flight at a time,
# no matter what timestamp was passed alongside it.
end
endNow perform_async(42, <any timestamp>) produces the same digest every time, so the second enqueue while the first is still locked is recognised as a duplicate.
As a lambda#
An inline proc when a named method is overkill.
For a one-liner you don't want to name, pass a lambda directly. It takes the args array and returns the subset — exactly like the class method:
class RefreshUserCacheJob
include Sidekiq::Job
sidekiq_options lock: :until_executed,
lock_args_method: ->(args) { [args.first] }
def perform(user_id, _enqueued_at)
# Same effect as the class-method form above.
end
endWidening the net: queues and workers#
Filtering arguments narrows what counts as a duplicate. Two options do the opposite — they remove a dimension from the digest so that more jobs collide into the same lock.
unique_across_queues
By default the queue is part of the digest, so the same job on default and on low are independent. Set unique_across_queues: true to ignore the queue — the same arguments on any queue are one lock:
class RefreshUserCacheJob
include Sidekiq::Job
sidekiq_options lock: :until_executed,
lock_args_method: :unique_args,
unique_across_queues: true
def self.unique_args(args)
[args.first]
end
def perform(user_id, _enqueued_at)
# Unique per user_id regardless of which queue the job lands on.
end
endunique_across_workers
By default the worker class is part of the digest, so two different worker classes never share a lock even with identical arguments. Set unique_across_workers: true to drop the class from the digest, so different workers with the same arguments count as duplicates. This one is typically set on both workers you want to deduplicate against each other:
class RefreshUserCacheJob
include Sidekiq::Job
sidekiq_options lock: :until_executed, unique_across_workers: true
def perform(user_id)
# Shares a lock with any other worker that also opts in.
end
endA note on the digest algorithm#
Whatever arguments survive filtering are hashed into the digest string. Two algorithms are available, chosen globally via config.digest_algorithm:
:legacy— MD5-based. The default.:modern— a FIPS-friendly alternative for environments where MD5 is unavailable.
The choice is invisible day to day, but note that changing it changes every digest, so locks created under one algorithm won't match locks created under the other. Pick one before you go to production and leave it alone.
SidekiqUniqueJobs.configure do |config|
config.digest_algorithm = :modern
endWhere to go next#
- How locking works — what the digest is and how the lock is acquired and released.
- Worker options — the full reference for
lock_args_method,unique_across_queues,unique_across_workers, and every othersidekiq_optionskey.