Local Queue
Local Queue is an optional worker-side cache for high-throughput workloads. When enabled, the worker fetches a batch of jobs from PostgreSQL, keeps them locked locally, and lets worker concurrency drain that local batch without another database round trip for every job.
Use it when many small jobs make database fetch overhead noticeable. For slow or rare jobs, the default direct database fetch path is usually simpler and keeps less work locked inside a single process.
Basic Configuration
Enable the feature with WorkerOptions::local_queue:
use graphile_worker::{LocalQueueConfig, RefetchDelayConfig, WorkerOptions};
use std::time::Duration;
let worker = WorkerOptions::default()
.concurrency(4)
.local_queue(
LocalQueueConfig::default()
.with_size(100)
.with_ttl(Duration::from_secs(300))
.with_refetch_delay(
RefetchDelayConfig::default()
.with_duration(Duration::from_millis(100))
.with_threshold(10)
.with_max_abort_threshold(500),
),
)
.define_job::<SendEmail>()
.pg_pool(pg_pool)
.init()
.await?;
The same settings are available through the generated builders:
use graphile_worker::{LocalQueueConfig, RefetchDelayConfig};
use std::time::Duration;
let config = LocalQueueConfig::builder()
.size(100)
.ttl(Duration::from_secs(300))
.refetch_delay(
RefetchDelayConfig::builder()
.duration(Duration::from_millis(100))
.threshold(10)
.max_abort_threshold(500)
.build(),
)
.build();
Config Fields
size controls the maximum number of jobs fetched and held by each local queue.
The default is 100. It must be greater than zero and must not exceed
i32::MAX.
ttl controls how long fetched jobs may remain unclaimed in the local queue
before they are returned to PostgreSQL. The default is five minutes.
refetch_delay is optional. It slows the next fetch after a low-yield fetch so
the worker does not immediately poll the database again when the queue appears
empty or nearly empty.
queue_count controls how many independent local queues run inside one worker.
The default is 1. It must be greater than zero. size applies per queue, so
the maximum local capacity is size * queue_count.
If queue_count is greater than worker concurrency, it is capped at the
concurrency value because each local queue needs a worker draining it.
Modes
Each local queue moves through a small state machine:
Polling: the queue may fetch jobs from PostgreSQL.Waiting: jobs are cached locally and are being served from memory.TtlExpired: the local cache TTL expired and unclaimed jobs are being returned to PostgreSQL.Released: the local queue has shut down and will not provide more jobs.
When the cached batch is drained, the queue returns to Polling. When TTL
expires, unclaimed jobs are returned to PostgreSQL and later fetches can poll
again.
Refetch Delay
Refetch delay starts when a fetch returns fewer jobs than requested and the number of jobs fetched is not greater than the configured threshold.
use graphile_worker::{LocalQueueConfig, RefetchDelayConfig};
use std::time::Duration;
let config = LocalQueueConfig::default().with_refetch_delay(
RefetchDelayConfig::default()
.with_duration(Duration::from_millis(200))
.with_threshold(5),
);
The actual delay is jittered between half of duration and the full duration.
duration must not be larger than the worker poll_interval.
PostgreSQL notifications can wake the local queue sooner. While a refetch delay
is active, incoming job pulses increment an internal counter. When that counter
reaches the abort threshold, the delay is aborted and the queue fetches again.
If max_abort_threshold is not set, the abort threshold is randomized up to
5 * size. If it is set, the randomized abort threshold is capped by that
value.
Distribution
Local Queue does not change job ownership rules. Jobs are still locked in PostgreSQL under the worker id, and the worker's normal concurrency controls how many handlers can run at the same time.
With a single local queue, one batch is shared by the worker tasks. With
queue_count greater than one, the worker starts multiple independent local
queues and checks them round-robin. This can improve throughput for very small,
high-volume jobs, but it can also lock more work inside one process:
use graphile_worker::LocalQueueConfig;
let config = LocalQueueConfig::default()
.with_size(50)
.with_queue_count(4);
In this example, up to 200 jobs may be locked locally if worker concurrency is
at least 4.
Shutdown And TTL
Local Queue returns unclaimed cached jobs to PostgreSQL in two cases:
- the queue TTL expires while jobs are still cached;
- the worker shuts down and releases the local queue.
Returning jobs is retried internally. Jobs that are already running are handled by the worker's normal shutdown behavior; the local queue release path is for jobs that were fetched into the local cache but not yet claimed by a handler.
Choose ttl based on how much work you are comfortable locking inside one
process if handlers are slower than the local batch drain rate.
Forbidden Flags
Workers configured with forbidden flags do not use the local queue cache. During worker initialization, a non-empty forbidden flag list disables the local queue configuration for that worker.
This preserves flag filtering semantics: jobs with forbidden flags are skipped by the worker, and eligible jobs are fetched directly from PostgreSQL with the forbidden flag filter applied.