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.

PgQue is a zero-bloat message queue built entirely in SQL and PL/pgSQL on top of Postgres. It revives PgQ — the queue engine originally designed at Skype in 2006 to handle messaging for hundreds of millions of users — and repackages it as a single SQL file that installs on any Postgres 14+, including every major managed provider. No C extension, no sidecar daemon, no shared_preload_libraries entry, no provider approval required.

Zero bloat by design

TRUNCATE-based table rotation instead of per-row DELETE means no dead tuples on the hot path — ever. No VACUUM tuning, no autovacuum starvation.

No performance decay

Snapshot-based batching keeps throughput stable under sustained load. PgQue does not get slower after months in production.

Native fan-out

Each consumer holds an independent cursor on a shared event log. Every subscriber sees every event — no data duplication, no copy-per-subscriber inserts.

Pure SQL install

One file: \i sql/pgque.sql. Works on RDS, Aurora, Cloud SQL, AlloyDB, Supabase, Neon, Crunchy Bridge, and any self-hosted Postgres 14+.

Why SKIP LOCKED queues develop problems under load

Most Postgres queues use SKIP LOCKED with UPDATE and DELETE to claim and remove jobs. This works well in small-scale demos and then turns into dead tuples, VACUUM pressure, index bloat, and performance drift under sustained load. When autovacuum cannot keep up — because a long-running transaction is pinning the xmin horizon, OLAP queries are running alongside, or throughput simply outpaces cleanup — dead tuples accumulate and the queue slows down. This failure mode is well-documented: PgQue avoids this class of problems entirely. The hot path uses snapshot-based batching and TRUNCATE-based table rotation, so dead tuples are never created in the first place. The queue is immune to xmin horizon pinning.

The anti-extension design

PgQue calls itself an “anti-extension.” The standard install is a single SQL + PL/pgSQL file — no .control file, no C code, no compiled binary. Install it with \i sql/pgque.sql inside psql, or with psql --single-transaction -f sql/pgque.sql from the shell. It works anywhere you can run SQL — RDS, Aurora, Cloud SQL, AlloyDB, Supabase, Neon, and any other managed Postgres 14+.
An optional pg_tle path exists for teams that want ALTER EXTENSION pgque UPDATE and DROP EXTENSION pgque CASCADE semantics. This is opt-in; see installation for details. pg_tle is itself a C extension that requires shared_preload_libraries — the dependency the default install avoids.

Comparison

FeaturePgQuePgQPGMQRiverQuepg-boss
Snapshot-based batching (no row locks)
Zero bloat under sustained load
No external daemon or worker binary
Pure SQL install, managed Postgres ready
Language-agnostic SQL API
Multiple independent consumers (fan-out)
Built-in retry with backoff⚠️
Built-in dead letter queue⚠️⚠️
Legend: ✅ yes · ❌ no · ⚠️ partial / indirect Notes:
  • PgQ is the Skype-era queue engine (~2007) PgQue is derived from. Same snapshot/rotation architecture, but requires C extensions and an external daemon (pgqd) — unavailable on managed Postgres. PgQue removes both constraints.
  • No external daemon: PgQue uses pg_cron (or your own scheduler) for ticking; PGMQ uses visibility timeouts. River, Que, and pg-boss require a Go / Ruby / Node.js worker binary.
  • Que uses advisory locks — no dead tuples from claiming, but completed jobs are still DELETEd. Ruby-only.
  • PGMQ retry is visibility-timeout re-delivery (read_ct tracking) with no configurable backoff or max attempts.
  • pg-boss fan-out is copy-per-queue publish()/subscribe(), not a shared event log with independent cursors.
  • Category: River, Que, pg-boss (and Oban, graphile-worker, solid_queue, good_job) are job queue frameworks. PgQue is an event/message queue optimized for high-throughput streaming with fan-out.

When to use PgQue vs a job queue

PgQue’s model is closer to Kafka (a shared event log with per-consumer cursors) than to ActiveMQ or RabbitMQ (task queues where each job goes to exactly one worker).
  • Choose PgQue when you want event-driven fan-out, zero bloat without tuning, and a language-agnostic SQL API, and you do not need per-job priorities or a worker framework.
  • Choose a job queue (River, pg-boss, Oban, Que, graphile-worker) when you need per-job lifecycle tracking, sub-3 ms latency, priority queues, cron scheduling, unique jobs, or deep language-specific ecosystem integration.

Latency trade-off

PgQue trades per-message dispatch latency for zero bloat and stable throughput. The send and receive functions themselves execute in the microsecond range. End-to-end delivery — the gap between send committing and a consumer seeing the event — depends on the tick period. The default is 100 ms (10 ticks/sec), giving roughly 50 ms median end-to-end latency. The tick rate is tunable from 1 ms to 1,000 ms via pgque.set_tick_period_ms(ms).
If your top priority is single-digit-millisecond dispatch, PgQue is the wrong tool. If your priority is stability under load without bloat, that is where PgQue fits.

Project status

PgQue is early-stage as a product and API layer. The underlying PgQ engine has run at Skype scale for over a decade. What is new here is the packaging, Postgres 14+ modernization, managed-Postgres compatibility, and the modern send/receive/ack/nack API built on top of that proven core. The default install stays small; additional APIs live under sql/experimental/ until they are ready to promote.

Install PgQue

Requirements, install command, ticker setup, role grants, and uninstall.

Quickstart

Hands-on walkthrough: create a queue, send a message, receive and ack a batch.

Build docs developers (and LLMs) love