pgqueue v0.1.0 read-only
Throughput – jobs / second
Pickup latency – p50 / p95 · due to claimed
Run time – p50 / p95 · handler
Waiting–queued + retrying
Running–holding a lease
Retries–last 60 s
Failed–last 60 s

Throughput · finished jobs per second, last two minutes

succeeded failed / dead

Queues · depth over the last two minutes

No jobs yet

Workers · heartbeats and leases

No workers running

Live job feed

  1. Waiting for jobs…

Running jobs · each one holds a lease that its worker keeps extending

JobTaskQueueWorkerAttemptRunningLease left
Nothing is running right now

Problems

JobTaskQueueAttempts Last errorWhen
Nothing here

Periodic schedules

NameCron (UTC)TaskQueueNext runLast run
No schedules

How it works

Every job is a row in one PostgreSQL table. There is no broker: Postgres is the queue, and three of its features do the heavy lifting.

Producers insert jobs into PostgreSQL; workers claim them with SKIP LOCKED Producers insert rows into the pgqueue_jobs table. A trigger sends NOTIFY to idle workers. Workers claim rows with SELECT FOR UPDATE SKIP LOCKED, extend their leases with heartbeats, and write the result back. A reaper recovers jobs whose lease expired. producers enqueue() · cron PostgreSQL pgqueue_jobs partial index on ready jobs trigger → NOTIFY pgqueue_jobs pgqueue_workers heartbeats · leases worker A claim · run · ack worker B claim · run · ack worker C claim · run · ack 1 · INSERT 3 · SKIP LOCKED 2 · NOTIFY 4 · heartbeat
Solid arrows are SQL statements, dashed arrows are LISTEN/NOTIFY wake-ups.

SKIP LOCKED claiming

Workers claim a batch of jobs with SELECT … FOR UPDATE SKIP LOCKED inside a single UPDATE. Rows another worker already holds are skipped instead of waited on, so any number of workers pull different jobs at once and never block each other.

UPDATE pgqueue_jobs SET status = 'running', …
FROM (SELECT id FROM pgqueue_jobs
      WHERE status IN ('queued','retrying')
        AND run_at <= now()
      ORDER BY priority DESC, run_at, id
      LIMIT 8
      FOR UPDATE SKIP LOCKED) AS next

Leases and crash recovery

A running job holds a lease that its worker extends with heartbeats. If a worker dies, its leases run out and a reaper in every other worker puts the jobs back with the attempt counted. A result is accepted only from the current lease holder, so a stalled worker cannot overwrite a newer one (fencing).

LISTEN / NOTIFY

A trigger sends NOTIFY on every insert, so idle workers wake in milliseconds instead of waiting for the next poll. Polling stays as the fallback: a lost notification costs latency, never a job.

Retries and dead letters

Failed attempts are retried with capped exponential backoff and jitter. A task may ask for a specific delay (Retry-After) or fail permanently. Jobs that use up every attempt end up dead and stay visible until someone requeues them.