Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/NikolayS/PgQue/llms.txt

Use this file to discover all available pages before exploring further.

“Queue latency” in PgQue is not one number — it is three. Understanding which number matters for your workload determines whether PgQue is the right fit, and how to configure the tick rate.

Three latencies

LatencyWhat it measuresTypical value
Producer latencyTime for send() / insert_event() to completeSub-millisecond
Subscriber latencyTime for receive() over a pre-built batchSub-millisecond
End-to-end deliveryTime from send() to consumer visibility≈ tick period (default ~50 ms median)
Producer and subscriber latency are fast because send() is a simple row insert, and receive() reads from a pre-materialized batch — no row-level locking or conflict resolution. End-to-end delivery depends on the tick cadence. With the default 100 ms tick period, a consumer polls within at most 100 ms of the tick completing, so median end-to-end delivery is roughly 50 ms.

Tick cadence trade-offs

Tick periodTicks/secMedian e2e latencyWAL/day (per active queue)
1 ms1000~0.5 ms~24 GiB
10 ms100~5 ms~2.4 GiB
50 ms20~25 ms~480 MiB
100 ms (default)10~50 ms~240 MiB
200 ms5~100 ms~120 MiB
500 ms2~250 ms~48 MiB
1000 ms1~500 ms~24 MiB
WAL estimates assume ~280 bytes per materialized tick per queue, continuous activity. They apply only when the queue is actively receiving events. Change the tick period at runtime — no restart or rescheduling needed:
select pgque.set_tick_period_ms(50);    -- 20 ticks/sec, ~25 ms median e2e
select pgque.set_tick_period_ms(100);   -- 10 ticks/sec, default
select pgque.set_tick_period_ms(1000);  -- 1 tick/sec, original pgqd cadence
Valid values: exact divisors of 1000 in the 1–1000 ms range.

Idle queue behavior

The WAL estimates above are for queues with continuous producer activity. Idle queues are cheap. When no events arrive, most ticker() calls return NULL — no tick is materialized. PgQue backs off toward ticker_idle_period (default 1 minute), so an inactive queue produces only occasional metadata writes rather than the full WAL budget.
For WAL-constrained environments (logical replication subscribers, tight storage), start with set_tick_period_ms(1000) for 1 tick/sec. This gives ~500 ms median delivery latency but ~24 MiB/day per active queue — orders of magnitude less WAL.

Side effects of high tick rates

Higher tick rates increase more than just WAL: NOTIFY rate. pgque.ticker() emits pg_notify('pgque_<queue>', ...) on each materialized tick. Postgres’s NOTIFY queue is global (8 GiB SLRU); slow LISTEN consumers can fall behind at very high rates. Metadata-table churn. pgque.tick and pgque.subscription are updated on every tick. At sub-50 ms tick periods, drop the rotation period proportionally to keep dead-tuple peaks bounded. pg_cron worker pool. ticker_loop() holds one pg_cron background worker for ~1 second per slot. With pg_cron’s default cron.max_running_jobs = 32, this limits roughly 30 pgque-bearing databases per Postgres cluster. Not a per-database concern, but relevant when running many databases on one instance.

When PgQue is the wrong tool

If your workload requires single-digit-millisecond end-to-end dispatch, PgQue’s snapshot-based design is not the right fit. At 1 ms tick period, WAL overhead reaches ~24 GiB/day per active queue, NOTIFY traffic is very high, and metadata churn is significant. For sub-3 ms latency requirements, consider a job queue framework that uses advisory locks or visibility timeouts. PgQue’s strengths — zero bloat, stable throughput under sustained load, native fan-out — apply at the 50 ms–500 ms delivery latency range.

Build docs developers (and LLMs) love