Cloud Parts

PgQue

Skype’s PgQ engine repackaged as one SQL file: a zero-bloat event log inside any Postgres 14+.

Type
Open source
Made by
Nikolay Samokhvalov (open-source project)
License
Apache 2.0 (includes ISC-licensed PgQ code)
Facts checked
6 October 2026

What it is

PgQue is a queue written only in SQL and PL/pgSQL, installed from one file into any Postgres 14 or newer, managed services included. Producers call pgque.send, which appends an event to the queue’s table. A ticker records ticks, each storing a snapshot of which transactions had committed; by default pg_cron runs it in a one-second slot that ticks every 100 ms, and pg_timetable or your own scheduler also work. A batch is every event whose transaction committed between two ticks, found by comparing the two snapshots, so no rows are locked. Each consumer subscribes and keeps its own cursor: receive returns its next batch and ack moves the cursor on. Several consumers each see every event, more like a Kafka topic than a work queue; experimental cooperative consumers split one consumer’s batches across a pool of workers.

Most Postgres queues update or delete each row after use, leaving dead tuples for VACUUM, which can’t remove them while a long transaction or a stalled replica holds the xmin horizon. PgQue never deletes rows on its hot path. Each queue is three child tables; the ticker writes into one at a time and, once every consumer has read past the oldest, empties it with TRUNCATE, which frees the storage at once and leaves nothing to vacuum. The cost is latency: about 52 ms median from send to receive at the default tick. It’s free under Apache 2.0, and very new: the repository was created in April 2026 and the latest release is 0.2.2.

Why teams pick it

  • No dead tuples in the event tables, even with the xmin horizon pinned: in the project’s benchmark a SKIP LOCKED queue’s dead tuples grew about 14 times and its throughput fell 35%, while PgQue stayed at zero
  • Native fan-out: any number of consumers read the same log at their own pace without copying events
  • Pure SQL: no C extension, no shared_preload_libraries and no daemon, so it installs on RDS, Aurora, Cloud SQL, AlloyDB, Supabase and Neon
  • Retries with nack, a dead-letter queue, and separate reader, writer and admin roles come in the default install
  • The engine underneath, PgQ, has run in production since Skype built it in 2006, with hundreds of queues there by 2009

Watch out for

  • Very new: created in April 2026 and still 0.x; its own README calls the API layer early-stage and marks cooperative consumers experimental
  • Nothing is delivered without a running ticker; with pg_cron it holds a background worker for each one-second slot, and cron.job_run_details grows until you purge it
  • Batching adds latency (about 52 ms median at 10 ticks a second), and send, tick and receive must be separate transactions; not for single-digit-millisecond dispatch
  • A stopped or slow consumer blocks table rotation, so the event tables keep growing until it catches up
  • No per-job priorities, scheduled delivery (send_at) or message TTL yet; for those, the project points to a job-queue framework

Alternatives

What all message queues have in common →

How it fits with other technologies

Two consumers on one queue, each receiving every event · sql

-- install once, from a clone of the repo: \i sql/pgque.sql
select pgque.start();                         -- pg_cron ticker, 10 ticks a second

select pgque.create_queue('orders');
select pgque.subscribe('orders', 'billing');
select pgque.subscribe('orders', 'emails');   -- also sees every event

select pgque.send('orders', '{"order_id": 42, "total": 99.95}'::jsonb);

-- in a later transaction, after the next tick
select * from pgque.receive('orders', 'billing', 100);
select pgque.ack(1);                          -- the batch_id from those rows

pgmq, PgQue and PgQueuer#

All three keep queues in Postgres tables. They differ in what a reader gets, which languages can join in, and how old rows are cleaned up.

pgmqPgQuePgQueuer
ModelMessage queue: each message goes to one readerShared event log: every subscribed consumer sees every eventJob queue: Python workers run a handler per job
What you installSQL objects, as an extension or a plain SQL fileOne PL/pgSQL file, plus a scheduler that calls the tickerA Python package; pgq install creates its tables
LanguagesAny, through SQL; official Rust and Python clientsAny, through SQL; first-party Python, Go and TypeScript clientsPython 3.10+, async handlers
Handing out workFOR UPDATE SKIP LOCKED, then a visibility timeoutBatches between ticks, found by comparing two transaction snapshots; no row locksFOR UPDATE SKIP LOCKED, then status picked with a heartbeat
DeliveryExactly once within the visibility timeout, then at least onceAt least once, in batches; about 52 ms median wait at the default 100 ms tickAt least once; handlers must be idempotent
RetriesThe message reappears after the timeout; track read_ct yourselfnack schedules a retry; a dead-letter queue after the limitRetryRequested with a delay, or hold failed jobs for review
Finished messagesDeleted, or moved to an archive tableStay in the log; ack moves the consumer’s cursorDeleted from the queue; the outcome is appended to a log table
Dead rows and cleanupUpdates and deletes leave dead rows for VACUUM; optional partitions dropped by pg_partmanNone on the hot path: three tables rotate and the oldest is emptied with TRUNCATEDeletes leave dead rows; pgq autovac makes autovacuum aggressive
Licence and maturityPostgreSQL licence; 1.0 in November 2023Apache 2.0; 0.x, first release April 2026MIT; 1.0 in May 2026
  • PgQue’s latency figure comes from its own committed benchmark.

See it in a real stack

No architecture uses PgQue yet. Drop it into the playground and wire it up yourself.Open playground →
Where it came from2006–2026

In 2006 Skype needed asynchronous processing and replication across its many Postgres databases. Marko Kreen built PgQ, a queue inside Postgres that reused the snapshot-based event handling of the Slony-I replication system. Its first user was Londiste, Skype’s replication tool. Skype open-sourced PgQ in 2007 as part of SkyTools, and by 2009 it ran hundreds of queues and consumers there. Events were served in batches between ticks made by a separate ticker, and the queue’s three tables were rotated rather than deleted from.

PgQ needed a C extension and the pgqd daemon, which managed services such as RDS and Cloud SQL don’t allow. Meanwhile most newer Postgres queues claimed rows with SKIP LOCKED and deleted them when done, and write-ups such as Brandur’s 2015 post about Que at Heroku showed backlogs building as dead tuples outran VACUUM.

In April 2026 Nikolay Samokhvalov, founder of Postgres.ai and co-host of Postgres.fm, released PgQue 0.1. It keeps the PgQ engine and builds on PgQ’s pure PL/pgSQL path, with pg_cron replacing pgqd, 64-bit transaction ids, and a send, receive and ack API on top. Version 0.2 in May added first-party clients and sub-second ticks.

Why it’s built this way

Batches from two snapshots, not row locks
Whether an event belongs to a batch depends only on transaction visibility between two ticks, so delivery needs no row locks or per-row deletes, and every consumer can run the same test against its own cursor.
Rotate three tables and TRUNCATE the oldest
TRUNCATE drops a table’s storage at once with no dead tuples. Table inheritance is used rather than declarative partitioning because a cheap truncate-and-reuse cycle per table is exactly what rotation needs.
Replace the pgqd daemon with pg_cron
Managed Postgres won’t load C extensions or run a custom daemon beside the database, but pg_cron is available on the major providers.
Keep the engine, change the surface
PgQ’s batch, tick, rotation and consumer-tracking core had a decade of production use, so PgQue leaves it unchanged and only modernizes types and adds a friendlier API and roles.

Timeline

  1. 2006

    Marko Kreen starts PgQ at Skype, inspired by Slony-I

  2. 2007

    Open-sourced in SkyTools; Londiste replication is its first user

  3. 2009

    SkyTools 3 alpha adds cascading, cooperative consumers and the pgqd daemon

  4. 2026

    PgQue 0.1 ships in April as a single SQL file

  5. 2026

    0.2 adds Python, Go and TypeScript clients and sub-second ticks

Sources22 · facts checked 6 October 2026

Improve this page on GitHub