Zero bloat by design
Snapshot-based batching and TRUNCATE-based rotation instead of per-row DELETE. No dead tuples in the hot path, immune to xmin-horizon pinning. It does not get slower because it has been running for months.
PgQ, universal edition
One SQL file to install.
A durable event stream that lives inside Postgres — closer to Kafka, a shared log with independent per-consumer cursors, than to a task-message broker. Pure SQL and PL/pgSQL on any modern Postgres, managed or self-hosted. No sidecar daemon, no C extension.
\i sql/pgque.sql then select pgque.start();
SKIP LOCKED queue under sustained
load — the failure mode PgQue avoids by construction.
SKIP LOCKED. Not row claiming.
Most Postgres queues rely on SKIP LOCKED plus
DELETE and UPDATE. That holds up in toy
examples, then turns into dead tuples, VACUUM pressure, index bloat,
and performance drift under load.
PgQue uses snapshot-based batching and TRUNCATE-based rotation instead. Events become visible in batches between ticks. The hot path stays predictable, and the queue does not get slower the longer it runs.
The trade-off is end-to-end delivery latency — about a 50 ms median at the default 100 ms tick rate (p50 ≈ 52 ms), tunable down by lowering the tick period. If you need single-digit-millisecond dispatch, PgQue is the wrong tool. If you need stability under load without a bloat tax, this is where it fits.
-- one file, any Postgres 14+, managed or self-hosted\i sql/pgque.sqlselect pgque.start(); -- pg_cron ticks 10x/sec
select pgque.create_queue('orders');select pgque.subscribe('orders', 'fulfilment');select pgque.send('orders', '{"id": 42}'); -- enqueue
-- a consumer reads the current batch, then acks itselect * from pgque.receive('orders', 'fulfilment');select pgque.ack(:batch_id);Snapshot-based batching and TRUNCATE-based rotation instead of per-row DELETE. No dead tuples in the hot path, immune to xmin-horizon pinning. It does not get slower because it has been running for months.
Pure SQL and PL/pgSQL. No C extension, no shared_preload_libraries, no daemon, no restart. Installs with \i pgque.sql on RDS, Aurora, Cloud SQL, AlloyDB, Supabase, Neon, and Crunchy Bridge.
Every consumer keeps its own cursor on one shared event log and independently receives all events. No data duplication, atomic batch boundaries, late subscribers catch up. Closer to Kafka topics than to a job queue.
ACID transactions, transactional enqueue and consume, WAL, backups, replication, SQL visibility. Wrap receive + business writes + ack in one transaction for exactly-once — a property external brokers cannot offer.
The same engine PgQ ran at Skype for messaging across hundreds of millions of users, for over a decade. PgQue rebuilds it for the heavy-load regime it was designed for.
Built-in retry with configurable backoff and a dead letter queue. nack with a delay to reschedule; messages route to the DLQ after max_retries. Inspect and replay from SQL.
PgQue is an event/message queue optimized for high-throughput streaming with fan-out — not a job-queue framework. The full table and notes live in the README.
| Feature | PgQue | PgQ | PGMQ | River | Que | pg-boss |
|---|---|---|---|---|---|---|
| Snapshot batching (no row locks) | yes | yes | no | no | no | no |
| Zero bloat under sustained load | yes | yes | no | no | no | no |
| No external daemon or worker binary | yes | no | yes | no | no | no |
| Pure SQL, managed-Postgres ready | yes | no | yes | yes | yes | yes |
| Multiple independent consumers (fan-out) | yes | yes | no | no | no | partial |
| Built-in dead letter queue | yes | no | partial | partial | no | yes |
PgQue runs on any Postgres 14+. These platforms ship pg_cron, so the automated ticker works out of the box.
Not listed? PgQue runs on any Postgres that has pg_cron 1.5 or newer.
No pg_cron at all? PgQue still works — drive the tick from any external scheduler instead.
Use the SQL API directly, or a typed client. The API is language-agnostic — everything is a function call.
Install in one transaction. Tick with pg_cron. Read the ten-minute tutorial.