ruby 101 lines · 3 tabs

Background Job Backpressure with Queue Depth Guard

Shared by codesnips Jan 2026
3 tabs
require "sidekiq/api"

class QueueDepthGuard
  class QueueSaturated < StandardError
    attr_reader :queue, :depth

    def initialize(queue, depth)
      @queue = queue
      @depth = depth
      super("queue #{queue} saturated at depth #{depth}")
    end
  end

  LIMITS = {
    "default" => 5_000,
    "ingest" => 20_000,
    "critical" => 500
  }.freeze

  def self.limit_for(queue)
    LIMITS.fetch(queue.to_s, 5_000)
  end

  def self.depth(queue)
    name = queue.to_s
    ready = Sidekiq::Queue.new(name).size
    scheduled = Sidekiq::ScheduledSet.new.count { |j| j.queue == name }
    retries = Sidekiq::RetrySet.new.count { |j| j.queue == name }
    ready + scheduled + retries
  end

  def self.saturated?(queue)
    depth(queue) >= limit_for(queue)
  end

  def self.admit!(queue)
    current = depth(queue)
    raise QueueSaturated.new(queue.to_s, current) if current >= limit_for(queue)
    current
  end

  def self.overflow_backoff(attempt)
    base = 5
    [base * (2**attempt), 300].min + rand(0..3)
  end
end
3 files · ruby Explain with highlit

When a background system can enqueue work far faster than it drains, unbounded queues grow until Redis memory is exhausted or latency for critical jobs becomes unacceptable. Backpressure is the pattern of refusing or deferring new work once the system is saturated, pushing the pressure back onto the producer instead of silently piling it up. This snippet shows a small, focused implementation of a queue-depth guard for Sidekiq that measures backlog before enqueueing and reacts when a threshold is exceeded.

The QueueDepthGuard tab wraps Sidekiq's introspection API. depth sums the size of a Sidekiq::Queue plus scheduled and retry jobs targeting that queue, giving a realistic view of pending work rather than just the ready count. saturated? compares against a per-queue limit, and admit! raises QueueSaturated when the caller must be told to back off. The guard is deliberately cheap: these are LLEN/ZCARD-style reads against Redis, so calling it on the enqueue path adds negligible latency.

The IngestJob tab is a normal worker that also acts as a producer — it fans out per-record child jobs. Before enqueuing each child it calls QueueDepthGuard.admit!. On QueueSaturated it does not drop the work; instead it reschedules the remaining batch with perform_in and an exponential-ish delay derived from overflow_backoff. This converts a hard failure into cooperative deferral, so the producer naturally slows to match consumer throughput. Recording records_deferred keeps the deferral observable.

The EnqueueController tab shows the synchronous entry point. Here saturation is surfaced to the client as HTTP 429 with a Retry-After header rather than being swallowed, because a web request cannot politely wait. This distinction matters: producers that can retry cheaply (jobs) should defer, while user-facing callers should be told to retry later.

The main trade-off is that depth is a sampled snapshot and slightly stale under high concurrency, so the threshold should sit below the true danger point. It also does not coordinate across many producers beyond what Redis observes, so thresholds act as soft limits, not exact caps. Still, a guard like this is often enough to keep queues bounded, protect latency-sensitive queues from noisy neighbors, and avoid the classic failure mode where a retry storm buries everything.


Related snips

Share this code

Here's the card — post it anywhere.

Background Job Backpressure with Queue Depth Guard — share card
Link copied