Self-hosted service
pgmq/pgmq avatar
pgmq/pgmq

PGMQ: a Postgres message queue with no background worker

A lightweight message queue. Like AWS SQS and RSMQ but on Postgres.

5,304 stars149 forksRustPostgreSQL

At a glance

What is it?
PGMQ puts queue tables and SQL functions inside Postgres itself, so a consumer only needs a database connection. This review covers the extension and SQL-only install paths, the visibility timeout model, and where a broker on top of Postgres stops being the right answer.
Who is it for?
PGMQ fits teams that already operate Postgres and want queue semantics without running a second piece of infrastructure, and it is a poor fit when you need throughput beyond what a single Postgres instance can absorb or a delivery guarantee stronger than at-least-once. Before adopting it, create a queue and send a delayed message in a scratch database, read it back with a short visibility timeout, and confirm that the message reappears after the timeout expires.
Can I use it commercially?
Yes. PostgreSQL is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What PGMQ solves, and who ends up using it

Most teams that reach for a message queue already run Postgres, and adding a broker means a second system to deploy, secure, back up and monitor. PGMQ takes the opposite route: the queue lives in the database you already have. The README describes it as "a lightweight message queue. Like AWS SQS and RSMQ but on Postgres", and the feature list says there is no background worker and no external dependency, only Postgres SQL objects. That last point is the whole pitch. A producer is an INSERT through a function call, and a consumer is a SELECT through another function call.

The intended audience is fairly narrow. If your application already talks to Postgres, a queue inside it removes a network hop and a credential set. If your application does not use Postgres at all, PGMQ gives you no advantage, because you would be adopting a database to get a queue. The README's own framing points at SQS and RSMQ users, which is to say people who want a small set of queue primitives rather than a streaming platform.

One design decision worth noting early: every queue is its own table in the pgmq schema, named with a q_ prefix. A queue called my_queue is the table pgmq.q_my_queue. That is not an implementation detail you can ignore, because it means queue count maps directly onto table count in a single schema, and anything you do to tables in Postgres applies here.

How the visibility timeout and the archive actually work

The mechanism is a visibility timeout, usually shortened to vt. When a consumer reads messages, it passes a vt in seconds. The README's read example takes two messages and makes them invisible for 30 seconds, and states that if the messages are not deleted or archived within 30 seconds they become visible again and another consumer can read them. The row returned by read carries msg_id, read_ct, enqueued_at, last_read_at, vt, message and headers. read_ct is the delivery counter, so a consumer can detect a message that keeps coming back.

That is at-least-once delivery in practice. The feature list claims "exactly once" delivery within a visibility timeout, which is accurate as far as it goes: while the timeout holds, only one consumer sees the message. The claim does not extend past the timeout, and the README is explicit that an unacknowledged message returns to the queue. Any handler that is not idempotent needs to think about what happens when read_ct is greater than one.

Retention is the other half of the model. Messages stay in the queue until explicitly removed, and they can be archived instead of deleted. Archiving is what makes replay possible without keeping a separate log, since the message row moves rather than disappears. Send also accepts a delay parameter, so a message can sit on the queue unconsumable for a fixed number of seconds. Between delay, vt and archive, the primitives cover most of what a small job system needs.

Installing PGMQ with Docker and sending your first message

The README calls Docker the fastest way to get started, because the image ships Postgres with PGMQ pre-installed as an extension. Note the image tag in the README example is v1.10.0, which is older than the current release, so check the tag you actually pull.

bash
docker run -d --name pgmq-postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 ghcr.io/pgmq/pg18-pgmq:v1.10.0

Connect with psql against the connection string the README uses, then enable the extension in the pgmq schema.

bash
psql postgres://postgres:postgres@localhost:5432/postgres
sql
CREATE EXTENSION pgmq;

Create a queue and send one message. The send function returns the message id, so you should see a single row with the value 1.

sql
SELECT pgmq.create('my_queue');

SELECT * from pgmq.send(
  queue_name => 'my_queue',
  msg        => '{"foo": "bar1"}'
);

To see the visibility timeout in action, send a second message with a delay, then read both with a 30 second vt. The delayed message is on the queue but cannot be consumed until the delay elapses.

sql
SELECT * from pgmq.send(
  queue_name => 'my_queue',
  msg        => '{"foo": "bar2"}',
  delay      => 5
);

SELECT * FROM pgmq.read(
  queue_name => 'my_queue',
  vt         => 30,
  qty        => 2
);

If the queue is empty, or every message is currently invisible, the read returns no rows, which the README states directly. That is the behaviour to expect right after a read with a long vt. The SQL-only path exists for environments that cannot install the extension: clone the repository and run psql -f pgmq-extension/sql/pgmq.sql against your database. INSTALLATION.md compares the two approaches and should be read before choosing.

FIFO groups and topic routing are the parts with real constraints

The feature list includes FIFO queues with message group keys for ordered processing, and topic-based routing with wildcard patterns for publish-subscribe. Both are documented in the docs directory rather than the README, and both change the shape of what you are building.

Ordering with group keys is a scoping decision, not a global one. Messages that share a group key are processed in order; messages in different groups are not ordered relative to each other. If your workload needs a single global order across all messages, group keys do not give you that, and you should treat the queue as unordered. The examples directory contains grouped_rr_example.sql and grouped_sqs_style_comparison.sql, which is where the intended usage pattern lives.

Topic routing moves PGMQ toward publish-subscribe. Wildcard patterns mean a subscriber matches on a subject rather than reading a fixed queue name, and examples/topics.sql shows the shape of it. The trade-off is that routing logic now lives in SQL patterns that you maintain, and a pattern that is too broad quietly delivers more than intended. Neither feature is exotic, but both deserve a test against your real message subjects before you commit to them.

Where PGMQ is the wrong tool

The honest limitation is that PGMQ inherits every property of the database underneath it. A queue table in the same Postgres instance as your application data competes for the same connections, the same CPU and the same disk. A slow or expensive consumer query holds a connection that your web tier also wants. There is no separate broker to scale out, because the broker is the database.

Long visibility timeouts are the second failure mode. A consumer that reads a batch and then crashes leaves those messages invisible until the vt expires. The README's own example uses 30 seconds, and that number is a policy you have to pick: too short and slow handlers cause duplicate work, too long and a crash stalls the queue. read_ct is your only signal that this is happening, and the README does not describe an automatic dead-letter mechanism, so a message that fails repeatedly will keep cycling unless your application deletes or archives it.

The third case is scale. PGMQ is described as lightweight and as having API parity with SQS and RSMQ. That is a statement about the API surface, not about throughput. If you need fan-out to many independent consumers, long retention with replay across a cluster, or partitioning beyond one Postgres instance, a dedicated log or broker is the better fit. Choosing PGMQ because it avoids a deployment is reasonable; choosing it because you expect it to behave like a distributed log is not.

PGMQ compared with Redis and Kafka

Against Redis, the difference is durability and where the data lives. A Redis list or stream keeps the queue in memory, with persistence as a configured extra. PGMQ keeps messages as rows in a table, so they survive a restart by default and can be queried with ordinary SQL. The cost is that every queue operation is a database round trip with transactional overhead, which is heavier than an in-memory pop.

Against Kafka, the difference is the delivery model. Kafka is a partitioned log where consumers track offsets and messages are retained by policy regardless of consumption. PGMQ is a queue where a read makes a message invisible for a timeout and the message stays until someone deletes or archives it. Kafka gives you replay across many consumers by design; PGMQ gives you replay by archiving individual messages. Kafka also requires its own cluster. If you already run Postgres and your volume fits, PGMQ removes an entire system from your operational surface. If you need partitioned ordering at high volume, it does not replace Kafka.

There is also a middle option worth naming: writing your own queue table with SELECT ... FOR UPDATE SKIP LOCKED. PGMQ is essentially that pattern with a defined API, a visibility timeout, an archive and client libraries. The value is in not maintaining those functions yourself, and in the Rust and Python client libraries listed in the README. The community list is long (Go, Java, Ruby, Elixir, PHP, several TypeScript variants), but those are community-maintained, not part of the core repository, so their release cadence is not tied to PGMQ's.

Maintenance, licensing and upgrade cost

The repository is not archived, and the last push was on 2026-09-18. Releases are frequent: v1.13.0 on 2026-09-07, v1.12.0 on 2026-07-14, v1.11.1 on 2026-04-19. That cadence matters less than the fact that PGMQ is installed inside your database, so an upgrade is a database change, not a container swap. The README points to UPDATING.md in pgmq-extension for version updates, and the SQL-only install is exactly the path where upgrades need care, because you applied the objects by hand and will have to reapply them.

The licence is PostgreSQL, the same permissive licence as the database itself. That is permissive enough for commercial use, but the usual caveat applies: this is not legal advice, and if you redistribute PGMQ inside a product you should read the LICENSE file rather than a summary of it. The extension is also published on PGXN, according to the README badge.

Supported versions are Postgres 14 through 18 in the feature list, while the badge at the top of the README lists 13 through 18. That discrepancy is small but real, and it is the kind of thing to confirm against your own server version before planning an upgrade window.

Editorial conclusion

PGMQ fits teams that already operate Postgres and want queue semantics without running a second piece of infrastructure, and it is a poor fit when you need throughput beyond what a single Postgres instance can absorb or a delivery guarantee stronger than at-least-once. Before adopting it, create a queue and send a delayed message in a scratch database, read it back with a short visibility timeout, and confirm that the message reappears after the timeout expires. Then check INSTALLATION.md's considerations section to decide between the extension and the SQL-only install, because that choice determines how you upgrade later.

Frequently asked questions

How do you use Postgres as a queue with PGMQ?

You enable the extension with CREATE EXTENSION pgmq, create a queue with pgmq.create, send JSON messages with pgmq.send, and consume them with pgmq.read, which makes messages invisible for a visibility timeout. Each queue is a table in the pgmq schema named with a q_ prefix.

Can PostgreSQL be used as a message broker with PGMQ?

PGMQ is built on exactly that premise: the README describes it as a lightweight message queue on Postgres with no background worker and no external dependencies, only SQL objects. The limits come from the database itself, since consumers share its connections and resources.

What happens if a PGMQ message is not deleted within the visibility timeout?

It becomes visible again and can be read by another consumer. The read function returns read_ct, the delivery counter, so a handler can tell that a message has been redelivered.

Which Postgres versions does PGMQ support?

The feature list states Postgres 14 through 18, while the badge at the top of the README lists 13 through 18. Confirm the range against your server version before upgrading.

How do you install PGMQ without the Postgres extension?

The README's SQL-only path clones the repository and runs psql -f pgmq-extension/sql/pgmq.sql against your database, which installs the objects into the pgmq schema. INSTALLATION.md compares this with the extension install and should be read first.

Official sources

  1. License: PostgreSQL
  2. pgmq/pgmq on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pgmq-pgmq.svg)](https://hysenlabs.com/projects/pgmq-pgmq)