Cloud Parts

pgmq

An SQS-style message queue made of SQL functions and tables inside Postgres.

What it is
Message queues
Type
Open source
Made by
pgmq community (started at Tembo)
License
PostgreSQL licence
Facts checked
6 October 2026

What it is

pgmq is a set of SQL functions and tables that lives inside Postgres, installed as an extension or from a plain SQL file, with no background worker. Each queue is a table, pgmq.q_<name>, and each message a JSON row. pgmq.send inserts a row. pgmq.read takes the oldest visible rows with FOR UPDATE SKIP LOCKED and moves their visibility timeout (vt) into the future, so other readers skip them. If the consumer doesn’t call pgmq.delete or pgmq.archive before the timeout, the message becomes visible again and its read_ct goes up. archive moves the row to a matching pgmq.a_<name> table for audit or replay. The API copies Amazon SQS on purpose: delayed sends, batch sends and reads, pop, set_vt, and long polling with read_with_poll.

Because a queue is an ordinary table, a message can be sent in the same transaction as the data it describes, and backups, replicas and SQL tools cover it too. pgmq is free under the PostgreSQL licence and supports Postgres 14 to 18. Tembo built it in 2023; since May 2025 it has lived in its own pgmq organization on GitHub. Supabase Queues is pgmq with a dashboard and API permissions, at no extra cost. Newer versions add FIFO reads by message group, topic routing with * and # wildcards, and partitioned queues whose old partitions pg_partman drops.

Why teams pick it

  • Nothing new to run: queues live in the Postgres you already back up and monitor, and the plain-SQL install works where you can’t add extensions
  • Send a message in the same transaction as your data, so no message goes out for a row that rolled back
  • SQS-style visibility timeouts recover from crashed consumers without a watcher process
  • Any language with a Postgres driver can use it; there are official Rust and Python clients and about twenty community ones
  • Archive tables keep processed messages for audit or replay

Watch out for

  • Every read is an UPDATE and every delete leaves a dead row, so busy queues depend on VACUUM, which can’t clean up rows that a long-running transaction might still see
  • No retry policy or dead-letter queue: a failing message reappears after every timeout until you check read_ct and archive or move it yourself
  • Each message goes to one reader; topic routing copies it into every matching queue instead of sharing one log
  • Partitioned queues need pg_partman and its background worker in shared_preload_libraries, and if traffic outruns the pre-created partitions, maintenance stops for that queue
  • Every send, read and delete is a write on the primary, competing with your application for the same database

Alternatives

What all message queues have in common →

How it fits with other technologies

Send, read with a 30-second visibility timeout, then archive · sql

CREATE EXTENSION pgmq;
SELECT pgmq.create('receipts');

-- enqueue in the same transaction as the order
BEGIN;
INSERT INTO orders (id, status) VALUES (4812, 'paid');
SELECT pgmq.send('receipts', '{"order_id": 4812}');
COMMIT;

-- worker: take up to 10 messages, hidden from other workers for 30 s
SELECT msg_id, read_ct, message FROM pgmq.read('receipts', 30, 10);

-- done: move it to pgmq.a_receipts (or pgmq.delete to drop it)
SELECT pgmq.archive('receipts', 1);

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 pgmq yet. Drop it into the playground and wire it up yourself.Open playground →
Where it came from2023–2026

By 2023 many teams ran job queues on Postgres tables with SELECT … FOR UPDATE SKIP LOCKED, each with its own schema and, often, a watcher process to rescue jobs stuck “in progress”. Tembo, a startup selling managed Postgres, needed a queue between the control plane and data plane of its cloud: the control plane publishes tasks such as “create postgres cluster”. Rather than add Kafka, RabbitMQ or SQS to its stack, it built a Postgres extension and shared it.

Founding engineer Adam Hendel announced PGMQ on 3 August 2023. It borrowed the visibility timeout from SQS and RSMQ, so a crashed consumer’s message simply reappears and no background worker is needed, and added optional archive tables for retention. Version 1.0 followed in November 2023. In December 2024 Supabase launched Supabase Queues on top of it, noting that Tembo had licensed the extension under the OSI-approved PostgreSQL licence.

Tembo’s website now sells cloud coding agents rather than managed Postgres. In May 2025 pgmq moved to its own GitHub organization, where releases continue: FIFO queues arrived in 1.9 and AMQP-style topic routing in 1.11, both in early 2026.

Why it’s built this way

A visibility timeout instead of an “in progress” flag
Marking rows as in progress needs an outside process to find jobs stuck after a crash. With a timeout set on read, an unfinished message becomes visible again on its own, so the queue regulates itself.
SQL functions as the whole interface
Tembo wanted a familiar SQL interface with API parity to SQS and RSMQ, so any language with a Postgres driver can send and read without a new protocol or broker.
One table per queue, plus an archive table
Messages can be inspected with plain SQL, and archiving instead of deleting gives opt-in retention for debugging and replay.
FOR UPDATE SKIP LOCKED on read
FOR UPDATE stops two consumers getting the same message, and SKIP LOCKED lets many consumers share a queue without waiting on each other’s locks.

Timeline

  1. 2023

    Tembo announces PGMQ in August; 1.0 follows in November

  2. 2024

    Supabase launches Supabase Queues on pgmq

  3. 2025

    Project moves to the independent pgmq GitHub organization

  4. 2026

    FIFO queues (1.9) and topic routing (1.11) added

Sources18 · facts checked 6 October 2026

Improve this page on GitHub