sidekiq-unique-jobs: Redis-backed uniqueness for Sidekiq
Prevents duplicate Sidekiq jobs
At a glance
- What is it?
- sidekiq-unique-jobs adds configurable Redis locks to Sidekiq so duplicate or concurrent executions can be dropped, rejected, replaced, or rescheduled. It fits Ruby applications that need per-job or per-argument uniqueness, but it introduces Redis state and careful choices around lock expiry.
- Who is it for?
- Adopt sidekiq-unique-jobs when duplicate Sidekiq submissions would cause harmful repeated work and Redis is already part of the application infrastructure. Do not adopt it merely to suppress harmless retries, or when durable deduplication without infrastructure changes is required.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Where sidekiq-unique-jobs fits in a Sidekiq application
sidekiq-unique-jobs addresses a specific failure mode in background jobs: several Sidekiq entries for the same worker and arguments may exist at once, or the same logical action may be submitted more than once. Without coordination, those jobs can run together. The gem uses Redis locks to ensure that only one job with the same arguments runs at a time.
The default uniqueness key is based on three parts of the job: its worker class, its queue, and all of its arguments. That makes it appropriate for work where repeating the same operation would waste capacity or cause incorrect effects, such as updating one record several times because of retries. The documentation does not promise general deduplication across different worker classes. By default, identical arguments sent to different queues also remain distinct.
This is not a replacement for Sidekiq retries, rate limiting, or application-level idempotency. It controls lock conflicts at defined points in the job lifecycle, while the gem leaves the actual `perform` behavior to the worker. If a job can still cause an incorrect result after the lock is released, that worker still needs an application-appropriate idempotency design.
The project targets Ruby 3.2 or newer, Sidekiq 8.0 or newer, and Redis 6.2 or newer. That Redis floor is tied to the documented `LMOVE` support used by the optional reliable fetch strategy. The MIT license permits reuse and modification under its terms, although application owners remain responsible for reviewing those terms against their own distribution needs.
Client, server, and Redis lock flow in sidekiq-unique-jobs
The gem participates in two Sidekiq middleware chains. Client middleware runs when a job is enqueued, which allows uniqueness to be checked before the job enters normal execution. Server middleware participates when Sidekiq executes a job, which allows the gem to control execution-time conflicts.
By default, uniqueness includes the worker class, queue, and complete argument list. A job may narrow that identity with `lock_args_method`. For example, a worker receiving both `user_id` and `timestamp` can declare that only `user_id` determines uniqueness. The worker class and queue still participate unless the project changes those defaults. Setting `unique_across_queues: true` makes queue placement stop separating otherwise matching jobs, which allows one logical operation to be serialized even when submissions use different queues.
The selected lock type determines where Redis state begins and ends. A lock can expire before execution, remain until the worker starts, remain through completion, or cover enqueue and execution together. This timing matters more than the label alone. For example, `:until_executing` protects against duplicate enqueuing but releases before `perform`, so it does not by itself prevent two already admitted copies from running concurrently later.
Redis is therefore part of the operational path rather than an optional observability layer. The README documents Redis-backed state, atomic queue movement for reliable fetch, startup recovery from dead workers, and lock-preserving requeue during shutdown. The web interface is also Redis-backed: requiring `sidekiq_unique_jobs/web` adds a Locks tab for browsing, filtering, and deleting lock records.
Installing sidekiq-unique-jobs and defining the first locked worker
The documented installation uses Bundler. Add the gem to the application's Gemfile with the documented version constraint.
gem "sidekiq-unique-jobs", "~> 9.0"The README does not give the subsequent Bundler command, so no install command can be copied from it here. Before proceeding, confirm that the application already meets the stated Ruby, Sidekiq, and Redis minimums.
Next, register both middleware configurations and initialize the server integration. The client middleware belongs on the client and server configurations. The server middleware is also required on the server.
Sidekiq.configure_client do |config|
config.client_middleware do |chain|
chain.add SidekiqUniqueJobs::Middleware::Client
end
end
Sidekiq.configure_server do |config|
config.client_middleware do |chain|
chain.add SidekiqUniqueJobs::Middleware::Client
end
config.server_middleware do |chain|
chain.add SidekiqUniqueJobs::Middleware::Server
end
SidekiqUniqueJobs::Server.configure(config)
endThe documented job then selects `:until_executed` through `sidekiq_options`. With the default argument handling, the worker class, queue, and arguments identify the lock.
class MyJob
include Sidekiq::Job
sidekiq_options lock: :until_executed
def perform(user_id)
# Only one job per user_id runs at a time
end
endThe README states that duplicate jobs are silently dropped by default. For an important workflow, silently dropping is a policy that deserves explicit review rather than an invisible default.
Choosing a sidekiq-unique-jobs conflict policy for each lifecycle stage
The lock type answers when uniqueness exists. The conflict strategy answers what happens when another job encounters an existing lock. The gem documents separate lifecycle points. At the client stage, a conflict concerns enqueueing. At the server stage, it concerns execution.
`:until_executing` locks at enqueue and releases before `perform`. `:until_executed` keeps the lock through completion. `:until_expired` holds until the configured TTL expires, which can express time-based windows such as daily work. `:while_executing` starts at execution and ends afterward. `:until_and_while_executing` combines enqueue and execution coverage, releasing first before execution and again after completion.
A TTL can be set per job through `lock_ttl`. The global setting defaults to `nil`, described as no expiry, while an explicit job value can define a fixed expiration window.
sidekiq_options lock: :until_expired,
lock_ttl: 3600Conflict handling can be one strategy or a client-server pair. The documented strategies are `:log`, which logs and discards the duplicate; `:raise`, which raises `SidekiqUniqueJobs::OnConflict::Raise`; `:reject`, which sends the job to the dead set; `:replace`, which deletes the existing job and enqueues the new one; and `:reschedule`, which schedules the duplicate to run later.
sidekiq_options lock: :until_and_while_executing,
on_conflict: { client: :log, server: :reschedule }`:reject` deserves particular care because Sidekiq's dead set is not the same outcome as a completed business operation. `:replace` changes which queued copy survives, while `:reschedule` moves the duplicate to a later point. The best choice depends on whether the newest data should win, whether dropping is acceptable, or whether later execution remains safe.
ReliableFetch, recovery settings, and sidekiq-unique-jobs observability
Version 9 includes an optional reliable fetch strategy. It changes queue handling for applications that opt into it, using atomic `LMOVE` to move jobs from a queue into a per-process working list. The documented behavior includes startup recovery from dead worker processes, acknowledgment tied to lock cleanup, and preservation of locks when jobs are requeued during shutdown.
Sidekiq.configure_server do |config|
config[:fetch_class] = SidekiqUniqueJobs::Fetch::Reliable
endThis is more than a duplicate check added around an unchanged Sidekiq queue path. Its Redis version requirement, process lifecycle behavior, and recovery semantics need to be treated as part of the worker architecture. The README does not provide a separate deployment procedure for it, so the code example is the documented starting point rather than a complete operational guide.
Orphaned lock cleanup is configurable through `reaper`, with documented choices of `:ruby`, `:lua`, `true`, `:none`, and `false`. Related controls include `reaper_count`, `reaper_interval`, and `reaper_timeout`. Lock metadata can be stored through `lock_info`, while lock keys use the configurable `lock_prefix`.
SidekiqUniqueJobs.configure do |config|
config.lock_prefix = "uniquejobs"
config.lock_info = false
config.reaper = :ruby
config.reaper_count = 1000
endFor observability, the reflection system exposes lock lifecycle events without changing behavior. The documented callbacks cover successful locking, unlocking, failed locking, and execution failure. The README also says that v9 removed its changelog in favor of this event system. This supports targeted logging, but it is not a complete audit store unless an application persists what its callbacks receive.
Upgrade boundaries and operational costs of sidekiq-unique-jobs
The last push was on 2026-10-17. The listed releases include `v8.1.0`, `v9.0.0.alpha1`, and `v9.0.0.alpha2`, all dated in March 2026. Because the newest listed v9 artifacts carry an alpha designation, production adoption should account for the possibility of behavior changes before a final v9 release.
The documented v8 migration is automatic on first startup and requires no manual migration steps. However, the compatibility boundary is substantial: v9 requires Sidekiq 8 and Ruby 3.2, and support for Sidekiq 7 and older Ruby versions was removed. Existing applications must meet those runtime boundaries before treating the upgrade as automatic.
The README says v9 reduces each lock to two Redis keys: a `digest:LOCKED` hash and a `uniquejobs:digests` sorted set, down from thirteen. It also says expiring locks now use the `digests` sorted set with expiry time as the score. This indicates a deliberate reduction in Redis structure count, but it does not supply storage benchmarks or capacity guidance.
Operational cost comes from choosing correct lock boundaries, managing orphan cleanup, watching Redis alongside Sidekiq, and deciding whether the reliable fetch strategy fits the deployment. Jobs can also remain blocked until a lock is removed or expires if the policy does not match normal runtime. The web UI helps inspect and delete locks, but the documentation does not provide a formal rollback procedure for lock-state changes.
The project supports disabling uniqueness in tests with `SidekiqUniqueJobs.config.enabled = false`. Under `Sidekiq::Testing.inline!`, the README states that uniqueness is still enforced. That distinction matters for test design because one test mode bypasses uniqueness while the other executes immediately and retains it.
Editorial conclusion
Adopt sidekiq-unique-jobs when duplicate Sidekiq submissions would cause harmful repeated work and Redis is already part of the application infrastructure. Do not adopt it merely to suppress harmless retries, or when durable deduplication without infrastructure changes is required. Verify first that your intended lock type and expiry policy match the job lifecycle, then confirm how conflicts should be handled.
Frequently asked questions
What Ruby and Sidekiq versions does sidekiq-unique-jobs require?
The current README requires Ruby 3.2 or newer, Sidekiq 8.0 or newer, and Redis 6.2 or newer. Redis 6.2 is required for the `LMOVE` support used by the reliable fetch strategy.
What happens when sidekiq-unique-jobs detects a duplicate job?
The default `:log` strategy logs and discards the duplicate. The gem also documents `:raise`, `:reject`, `:replace`, and `:reschedule`, including separate client and server policies.
Can sidekiq-unique-jobs ignore selected job arguments when checking uniqueness?
Yes. `lock_args_method` receives the job arguments and returns the values that should determine uniqueness. `unique_across_queues: true` can also remove queue separation from the identity check.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zoolutions-sidekiq-unique-jobs)Was this useful?