Use cases

Bounded concurrency

Let up to N jobs for the same arguments run at once — no more — so you can pace work against a rate-limited resource.

The problem#

You want concurrency, but capped.

Serializing to one at a time is often too strict. A vendor's export API might allow three concurrent requests per account before it starts returning 429 Too Many Requests. You want to use all three — running one at a time would waste the headroom — but never a fourth.

That's bounded concurrency: not "only one," but "at most N." Set lock_limit: N and up to N jobs for the same arguments may hold the lock simultaneously; the N+1th is treated as a conflict.

A worker capped at three#

while_executing with lock_limit: 3.

Here at most three ExportToVendorJob runs for the same account_id may execute at once. A fourth makes one attempt, finds no free slot, and is rescheduled to run later instead of being dropped.

class ExportToVendorJob
  include Sidekiq::Job

  sidekiq_options lock: :while_executing,
                  lock_limit: 3,
                  on_conflict: :reschedule

  def perform(account_id, report_id)
    report = Report.find(report_id)
    VendorClient.new(account_id).upload(report.to_csv)
  end
end

:while_executing locks just before perform and releases just after, so the cap governs concurrent execution — exactly what a rate-limited API cares about. It does not stop duplicates from piling up in the queue; it controls how many run at the same time. For the details of that lock type see Choosing a lock type.

How lock_limit works#

lock_limit is the number of jobs allowed to hold the same lock at once. It defaults to 1 — plain uniqueness, one holder. Raise it to N and the Nth holder still acquires; the (N+1)th is a conflict and runs your on_conflict strategy.

OptionTypeDefaultDescription
lock_limitInteger1Maximum concurrent holders of the same digest
on_conflictSymbolnilWhat to do with the (N+1)th job when every slot is taken
The limit applies per digest — that is, per set of unique arguments. With lock_limit: 3, three ExportToVendorJob runs for account 1 and three for account 2 can all run at once (six total); it's the fourth for the same account that's capped.

What happens to the overflow job#

Locks in v9 are non-blocking: the (N+1)th job makes a single attempt to acquire a slot, and if every slot is taken it does not wait — the on_conflict strategy fires immediately. Choose that strategy to decide where the surplus job goes.

  • on_conflict: :reschedule — re-enqueue the job to try again later (about five seconds out by default), which is what you usually want for pacing.
  • Leaving on_conflict unset means the surplus job is silently discarded — nothing is logged. Set on_conflict: :log if you want the conflict recorded.

See Conflict resolution for every strategy and exactly what each one does with a job that can't get a slot.

lock_timeout does not make a job wait for a slot in v9 — acquisition is a single non-blocking attempt. Overflow jobs retry later only because on_conflict: :reschedule re-enqueues them, not because they blocked.

Related