PgQ, universal edition

Zero-bloat Postgres queue.

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();
event-table dead tuples under a held xmin horizon
Throughput of a SKIP LOCKED queue collapsing under sustained load — the failure mode PgQue avoids by construction
The death spiral of a SKIP LOCKED queue under sustained load — the failure mode PgQue avoids by construction.

Not 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.

quickstart.sql
-- one file, any Postgres 14+, managed or self-hosted
\i sql/pgque.sql
select 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 it
select * from pgque.receive('orders', 'fulfilment');
select pgque.ack(:batch_id);

#Why PgQue

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.

The anti-extension

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.

Native fan-out

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.

Real Postgres guarantees

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.

Built for sustained load

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.

Retry, backoff, and DLQ

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.

#How it compares

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 PgQuePgQPGMQRiverQuepg-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
  • yes
  • no
  • partial / indirect

#Runs on

PgQue runs on any Postgres 14+. These platforms ship pg_cron, so the automated ticker works out of the box.

Managed cloud

Amazon RDS
Google Cloud SQL
Azure Database
Supabase
Neon
Aiven
DigitalOcean
PlanetScale
Crunchy Bridge
Tiger Data
ClickHouse Cloud
IBM Cloud
Oracle OCI
Ubicloud

Postgres-compatible

Amazon Aurora
Google AlloyDB
YugabyteDB

Kubernetes operators

StackGres
CloudNativePG
Percona Operator
Zalando

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.

#Client libraries

Use the SQL API directly, or a typed client. The API is language-agnostic — everything is a function call.

A queue that does not rot.

Install in one transaction. Tick with pg_cron. Read the ten-minute tutorial.