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
UPDATEand 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_ctand 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 →
Amazon SQSAWSAWS’s fully managed message queue: no brokers to run, billed per request.
BullMQOpen sourceThe Node.js job queue on Redis, Bull’s successor, with ports to other languages and, since v6, a Postgres backend.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.
| pgmq | PgQue | PgQueuer | |
|---|---|---|---|
| Model | Message queue: each message goes to one reader | Shared event log: every subscribed consumer sees every event | Job queue: Python workers run a handler per job |
| What you install | SQL objects, as an extension or a plain SQL file | One PL/pgSQL file, plus a scheduler that calls the ticker | A Python package; pgq install creates its tables |
| Languages | Any, through SQL; official Rust and Python clients | Any, through SQL; first-party Python, Go and TypeScript clients | Python 3.10+, async handlers |
| Handing out work | FOR UPDATE SKIP LOCKED, then a visibility timeout | Batches between ticks, found by comparing two transaction snapshots; no row locks | FOR UPDATE SKIP LOCKED, then status picked with a heartbeat |
| Delivery | Exactly once within the visibility timeout, then at least once | At least once, in batches; about 52 ms median wait at the default 100 ms tick | At least once; handlers must be idempotent |
| Retries | The message reappears after the timeout; track read_ct yourself | nack schedules a retry; a dead-letter queue after the limit | RetryRequested with a delay, or hold failed jobs for review |
| Finished messages | Deleted, or moved to an archive table | Stay in the log; ack moves the consumer’s cursor | Deleted from the queue; the outcome is appended to a log table |
| Dead rows and cleanup | Updates and deletes leave dead rows for VACUUM; optional partitions dropped by pg_partman | None on the hot path: three tables rotate and the oldest is emptied with TRUNCATE | Deletes leave dead rows; pgq autovac makes autovacuum aggressive |
| Licence and maturity | PostgreSQL licence; 1.0 in November 2023 | Apache 2.0; 0.x, first release April 2026 | MIT; 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 LOCKEDon readFOR UPDATEstops two consumers getting the same message, andSKIP LOCKEDlets many consumers share a queue without waiting on each other’s locks.
Timeline
- 2023
Tembo announces PGMQ in August; 1.0 follows in November
- 2024
Supabase launches Supabase Queues on pgmq
- 2025
Project moves to the independent pgmq GitHub organization
- 2026
FIFO queues (1.9) and topic routing (1.11) added
- Tembo blog: Introducing PGMQ, Simple Message Queues built on Postgres (Aug 2023) · archived copy, original
- Tembo blog: PGMQ, Lightweight Message Queue on Postgres with No Background Worker (Nov 2023) · archived copy, original
- pgmq v1.0.0 release (Nov 2023)
- Supabase blog: Supabase Queues (Dec 2024)
- pgmq pull request #397: update org reference on readme (May 2025)
- pgmq v1.9.0 and v1.11.0 releases: FIFO queues and topic routing (2026)
- Tembo: Run any coding agent in the cloud
Sources18 · facts checked 6 October 2026
- pgmq README
- pgmq: Installation (extension or SQL only)
- pgmq source: pgmq.sql (read, read_with_poll, pop, set_vt, enable_notify_insert)
- pgmq docs: Partitioned queues
- pgmq docs: Topic-based routing
- pgmq docs: FIFO queues
- pgmq v1.9.0 release: FIFO queues
- pgmq LICENSE (PostgreSQL licence)
- Supabase docs: Queues
- PostgreSQL docs: Routine vacuuming
- Brandur: Postgres job queues and failure by MVCC (2015)
- PgQue README (model, latency, retries)
- PgQue docs: Concepts and heritage (snapshots, rotation, delivery)
- PgQueuer README
- PgQueuer docs: Architecture
- PgQueuer docs: Performance tuning (autovacuum)
- PgQueuer ADR-0004: Delivery is at-least-once
- PgQueuer docs: Database-level retry