graphile_worker 0.13.3

High performance Rust/PostgreSQL job queue (also suitable for getting jobs generated by PostgreSQL triggers/functions out into a different work queue)
Documentation
# Queues and Concurrency

Graphile Worker RS uses PostgreSQL as the source of truth for runnable jobs, and
worker concurrency decides how many jobs a worker may execute at the same time.
Queues add another layer of control: they let you group jobs by workload and
serialize jobs that share the same queue name.

Use queues when one class of work should not interfere with another, or when a
sequence of jobs must not run in parallel.

## Assigning Jobs to Queues

Jobs can be assigned to a queue with `JobSpec::queue_name`. The field is
optional; when it is not set, the job is added without an explicit queue name.

```rust,ignore
use graphile_worker::JobSpecBuilder;

worker
    .create_utils()
    .add_job(
        SendEmail {
            to: "user@example.com".to_string(),
        },
        JobSpecBuilder::new()
            .queue_name("mail")
            .build(),
    )
    .await?;
```

The same option is available when building `JobSpec` directly:

```rust,ignore
use graphile_worker::JobSpec;

let spec = JobSpec {
    queue_name: Some("exports".to_string()),
    ..Default::default()
};
```

Queue names are data, not task identifiers. Different task handlers can share a
queue when they must be serialized together, and the same task handler can use
different queues for different tenants, accounts, or workload classes.

## Serial Queues

A queue is useful when jobs for the same resource must run one at a time. For
example, jobs that update the same external account can all use a queue derived
from that account id:

```rust,ignore
let queue_name = format!("account:{}", account_id);

worker
    .create_utils()
    .add_job(
        SyncAccount { account_id },
        JobSpecBuilder::new()
            .queue_name(queue_name)
            .build(),
    )
    .await?;
```

While a queued job is in progress, Graphile Worker RS records the queue lock in
the database. Tests assert that a named queue has a `locked_at` timestamp,
`locked_by` worker id, and a job count while its job is running. When the job
finishes, the completed job is removed and the queue can be used by later work.

This makes queue names a practical serialization primitive:

- Use one queue name for all jobs that must run in order.
- Use distinct queue names for jobs that may run at the same time.
- Keep queue names stable and deterministic when they represent a shared
  resource.

## Worker Concurrency

Worker concurrency controls how many jobs a worker can execute at once.

```rust,ignore
let worker = graphile_worker::WorkerOptions::default()
    .concurrency(5)
    .define_job::<SendEmail>()
    .pg_pool(pg_pool)
    .init()
    .await?;
```

With higher concurrency, independent queues can run in parallel. The
concurrency tests enqueue five jobs into five different queues and configure
the worker with `concurrency(10)`; all five jobs are picked up and left
in progress at the same time.

Without increasing concurrency, work is effectively drained one job at a time in
the tested `run_once` path. That is useful for small deployments or jobs that
should not overlap, but it means one slow job can delay unrelated work.

## Workload Isolation

Queues and concurrency solve different parts of workload isolation.

Use queue names to protect resources:

```rust,ignore
let spec = JobSpecBuilder::new()
    .queue_name(format!("project:{}", project_id))
    .build();
```

Use worker concurrency to decide how many independent jobs the process can run:

```rust,ignore
let worker = graphile_worker::WorkerOptions::default()
    .concurrency(8)
    .define_job::<RenderPreview>()
    .define_job::<SendEmail>()
    .pg_pool(pg_pool)
    .init()
    .await?;
```

Common patterns:

- Per-tenant queues: `tenant:42`
- Per-project queues: `project:abc123`
- Per-external-resource queues: `stripe:acct_123`
- Shared workload queues: `mail`, `exports`, `webhooks`

Choose the narrowest queue name that protects the resource you care about. A
single global queue is simple, but it serializes everything behind the slowest
job in that queue. Very fine-grained queue names allow more parallelism, but
they only protect resources that are named consistently.

## Local Queue

The local queue is an in-worker cache of jobs fetched from PostgreSQL. It exists
to reduce polling and provide low-latency processing while the database remains
the durable source of truth.

```rust,ignore
use graphile_worker::LocalQueueConfig;

let worker = graphile_worker::WorkerOptions::default()
    .concurrency(3)
    .local_queue(LocalQueueConfig::builder().size(10).build())
    .define_job::<SendEmail>()
    .pg_pool(pg_pool)
    .init()
    .await?;
```

`LocalQueueConfig` includes:

- `size`: maximum number of jobs each local queue may fetch and hold at once.
  The default is `100`.
- `ttl`: how long locally fetched jobs may stay unclaimed before being returned
  to the database. The default is five minutes.
- `refetch_delay`: an optional delay strategy used when a fetch returns fewer
  jobs than requested.
- `queue_count`: number of independent local queues to run inside this worker.
  The default is `1`.

When `queue_count` is greater than one, `size` applies to each local queue. For
example, `size = 3` and `queue_count = 4` allow up to twelve jobs to be locked
locally across the worker. Tests also assert that `queue_count` is capped by
worker concurrency, because each local queue needs at least one worker draining
it.

```rust,ignore
let local_queue = LocalQueueConfig::default()
    .with_size(3)
    .with_queue_count(4);

let worker = graphile_worker::WorkerOptions::default()
    .concurrency(5)
    .local_queue(local_queue)
    .define_job::<SmallFastJob>()
    .pg_pool(pg_pool)
    .init()
    .await?;
```

Multiple local queues can improve throughput for very small, high-volume jobs
by allowing several fetch batches in parallel. They can also lock more jobs
inside one worker, so keep `size` lower when increasing `queue_count` and
benchmark with realistic workloads.

## Practical Guidance

Start with worker concurrency and explicit queue names before tuning local queue
internals.

- Increase `concurrency` when unrelated jobs should run in parallel and the
  database pool, runtime, and downstream services can handle the extra work.
- Add `queue_name` when jobs for the same tenant, project, account, or external
  system must not overlap.
- Use separate queue names for unrelated workloads so slow serial work does not
  block everything else.
- Enable and tune `local_queue` when polling overhead or very small jobs become
  a bottleneck.
- Raise `queue_count` only after measuring; it increases parallel fetch capacity
  and the number of jobs a worker can hold locally.