Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
S

sidekiq-unique-jobs

> 数据库
Open source

Prevents duplicate Sidekiq jobs

1.5K stars0 likes1 views
WebsiteGitHub

About

Prevents duplicate Sidekiq jobs

sidekiq-unique-jobs

Prevents duplicate Sidekiq jobs. Uses Redis locks to ensure only one job with the same arguments runs at a time. Full documentation → — concepts, use-case guides, and reference.

Support Me

Want to show me some love for the hard work I do on this gem? You can use the following PayPal link: https://paypal.me/mhenrixon2. Any amount is welcome and let me tell you it feels good to be appreciated. Even a dollar makes me super excited about all of this.

Requirements

  • Ruby >= 3.2
  • Sidekiq >= 8.0
  • Redis >= 6.2 (for LMOVE support)

Installation

gem "sidekiq-unique-jobs", "~> 9.0"

Quick Start

# config/initializers/sidekiq.rb
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)
end
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
end

That's it. Duplicate jobs are silently dropped by default.

Lock Types

Type Locks at Unlocks at Use case :until_executing Enqueue Before perform Prevent duplicate enqueuing :until_executed Enqueue After perform Prevent duplicates until job completes :until_expired Enqueue TTL expiry Time-based uniqueness (e.g. daily jobs) :while_executing Perform After perform Prevent concurrent execution :until_and_while_executing Enqueue + Perform Before + After perform Full lifecycle protection

Conflict Strategies

When a duplicate is detected, the conflict strategy determines what happens:

Strategy Behavior :log (default) Log and discard the duplicate :raise Raise SidekiqUniqueJobs::OnConflict::Raise :reject Send to dead set :replace Delete the existing job and enqueue the new one :reschedule Schedule the duplicate to run later
sidekiq_options lock: :until_executed,
               on_conflict: :reject

# Or different strategies for client (enqueue) and server (execute):
sidekiq_options lock: :until_and_while_executing,
               on_conflict: { client: :log, server: :reschedule }

Configuration

…

Controlling Uniqueness

Custom Lock Arguments

By default, uniqueness is based on worker class, queue, and all arguments. To customize:

class MyJob
  include Sidekiq::Job

  sidekiq_options lock: :until_executed,
                 lock_args_method: ->(args) { [args.first] }

  def perform(user_id, timestamp)
    # Only user_id determines uniqueness, timestamp is ignored
  end
end

Lock TTL

sidekiq_options lock: :until_expired,
               lock_ttl: 3600  # Lock expires after 1 hour

Uniqueness Across Queues

sidekiq_options lock: :until_executed,
               unique_across_queues: true  # Same args on different queues = duplicate

ReliableFetch

v9 includes an optional reliable fetch strategy that provides crash recovery and lock-aware job acknowledgment:

Sidekiq.configure_server do |config|
  config[:fetch_class] = SidekiqUniqueJobs::Fetch::Reliable
end

Features:

  • Atomic LMOVE: Jobs move from queue to per-process working list atomically
  • Crash recovery: On startup, recovers jobs from dead worker processes
  • Lock-aware acknowledge: Confirms lock cleanup after job completion
  • Lock-preserving requeue: During shutdown, locks persist for requeued jobs

Web UI

Add to your routes:

require "sidekiq_unique_jobs/web"

This adds a Locks tab to the Sidekiq Web UI where you can browse, filter, and delete locks.

Testing

Disable uniqueness in your tests:

SidekiqUniqueJobs.config.enabled = false

Or use Sidekiq::Testing modes:

Sidekiq::Testing.inline! do
  # Jobs execute immediately, uniqueness still enforced
end

Upgrading from v8

v9 automatically migrates v8 lock data on first startup. No manual steps required.

Key changes:

  • Redis keys: 2 per lock (down from 13). Only digest:LOCKED hash and uniquejobs:digests sorted set.
  • Sidekiq 8+ only: Dropped Sidekiq 7 support.
  • Ruby 3.2+ only: Dropped older Ruby support.
  • Changelog removed: Use the reflection system for lock event observability.
  • Expiring locks unified: No separate expiring_digests sorted set. TTL-based locks use the same digests ZSET with expiry time as score.

Reflections

Observe lock lifecycle events without modifying behavior:

SidekiqUniqueJobs.reflect do |on|
  on.locked { |job| logger.info("Locked: #{job['class']}") }
  on.unlocked { |job| logger.info("Unlocked: #{job['class']}") }
  on.lock_failed { |job| logger.warn("Lock failed: #{job['class']}") }
  on.execution_failed { |job| logger.error("Execution failed: #{job['class']}") }
end

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-feature)
  3. Run tests (bundle exec rspec)
  4. Run linter (bundle exec rubocop)
  5. Commit and push
  6. Create a Pull Request

License

MIT

GitHub Issues· 21 open

View all on GitHub
  • #964

    until_expired locks leak Redis memory (expiring_digests + INFO keys) on 8.1.0

    Updated Sep 14, 2026
  • #993

    rspec.yml's gemfile matrix axis is inert: no step sets BUNDLE_GEMFILE, so 8 cells test 4 things

    Updated Sep 13, 2026
  • #975

    Installation guide version mismatch

    Updated Jul 28, 2026
  • #955

    Unable to unlock the job.

    Updated Apr 19, 2026
  • #861

    Lock digests not always removed after forced sidekiq worker shutdown

    Updated Mar 27, 2026
  • #821

    uniquejobs:digests sorted set seems to grow forever

    Updated Mar 27, 2026
  • #812

    After sidekiq shutdown (with running job) lock digest is not pushed back

    Updated Mar 27, 2026
  • #883

    until_executed lock not releasing after job finishes

    Updated Aug 26, 2025
  • #787

    PRIMED key grows indefinitely when job re-queues itself in `after_unlock` callback

    Updated May 19, 2025
  • #838

    Catched a deadlock on Sidekiq 7+ set up using middleware.

    Updated Mar 25, 2025

Highlights

  • •Ruby >= 3.2
  • •Sidekiq >= 8.0
  • •Redis >= 6.2 (for LMOVE support)
  • •Atomic LMOVE: Jobs move from queue to per-process working list atomically
  • •Crash recovery: On startup, recovers jobs from dead worker processes
  • •Lock-aware acknowledge: Confirms lock cleanup after job completion
  • •Lock-preserving requeue: During shutdown, locks persist for requeued jobs
  • •Redis keys: 2 per lock (down from 13). Only digest:LOCKED hash and uniquejobs:digests sorted set.
  • •Sidekiq 8+ only: Dropped Sidekiq 7 support.
  • •Ruby 3.2+ only: Dropped older Ruby support.

> Tags

Rubyredisrubysidekiq

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category数据库
PricingOpen source

> Related tools

P
PostgreSQL
功能强大的开源关系型数据库
R
Redis
内存数据结构存储,常用作缓存与队列
M
MySQL
广泛使用的开源关系型数据库