Guide

Routing & ordering

Priority sub-queues, active/standby consumer priority, and single-active-consumer for strict order.

Priority queues#

High-priority work processed first.

Enable priority levels and each logical queue gains sub-queues (_p0 highest through _p2). A worker drains _p0 before it touches _p1, and _p1 before _p2:

config/initializers/pgbus.rb
Pgbus.configure do |config|
  config.priority_levels  = 3 # creates _p0, _p1, _p2 per logical queue
  config.default_priority = 1 # jobs without a priority go to _p1
end

Set a job's priority with Active Job's built-in queue_with_priority:

class CriticalAlertJob < ApplicationJob
  queue_as :default
  queue_with_priority 0 # highest
end

class ReportJob < ApplicationJob
  queue_as :default
  queue_with_priority 2 # lowest
end
With priority_levels unset (the default), priority queues are off and each logical queue is a single queue.

Consumer priority#

Active/standby workers on the same queues.

When several workers serve the same queues, higher-priority workers process first; lower-priority ones back off (3× the polling interval) while a higher-priority peer is active, and resume automatically when it goes stale.

Pgbus.configure do |c|
  c.capsule :primary,  queues: %w[default], threads: 10, consumer_priority: 10
  c.capsule :fallback, queues: %w[default], threads: 5,  consumer_priority: 0
end

Priority lives in heartbeat metadata; workers discover higher-priority peers by reading the pgbus_processes table.

Single active consumer#

Strict ordering via advisory locks.

For a queue that must be processed in order, mark its capsules single_active_consumer: true. Only one worker reads the queue at a time — others skip it and work elsewhere. It uses non-blocking PostgreSQL session-level advisory locks, which auto-release on connection close, so a standby takes over within one polling tick if the primary dies:

Pgbus.configure do |c|
  c.capsule :ordered_primary, queues: %w[ordered_events], threads: 1, single_active_consumer: true
  c.capsule :ordered_standby, queues: %w[ordered_events], threads: 1, single_active_consumer: true
end
Single active consumer serializes the queue to one worker thread — throughput is bounded by that one consumer. Use it only where ordering truly matters.