{"id":42821995,"url":"https://github.com/perfectsquircle/pg_mq","last_synced_at":"2026-01-30T06:59:35.118Z","repository":{"id":87365876,"uuid":"588658914","full_name":"perfectsquircle/pg_mq","owner":"perfectsquircle","description":"A simple asynchronous message queue for PostgreSQL","archived":false,"fork":false,"pushed_at":"2024-01-05T22:51:41.000Z","size":49,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-17T04:01:09.058Z","etag":null,"topics":["asynchronous","message-queue","postgres","postgresql","publish-subscribe","pubsub","queue","rabbitmq","sql","workqueue"],"latest_commit_sha":null,"homepage":"https://worlds-slowest.dev/posts/postgresql-message-queue/","language":"PLpgSQL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/perfectsquircle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-01-13T17:04:22.000Z","updated_at":"2024-02-19T16:20:03.000Z","dependencies_parsed_at":"2023-10-11T21:40:12.376Z","dependency_job_id":null,"html_url":"https://github.com/perfectsquircle/pg_mq","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/perfectsquircle/pg_mq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfectsquircle%2Fpg_mq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfectsquircle%2Fpg_mq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfectsquircle%2Fpg_mq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfectsquircle%2Fpg_mq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/perfectsquircle","download_url":"https://codeload.github.com/perfectsquircle/pg_mq/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/perfectsquircle%2Fpg_mq/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28907106,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T06:42:00.998Z","status":"ssl_error","status_checked_at":"2026-01-30T06:41:58.659Z","response_time":66,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","message-queue","postgres","postgresql","publish-subscribe","pubsub","queue","rabbitmq","sql","workqueue"],"created_at":"2026-01-30T06:59:34.602Z","updated_at":"2026-01-30T06:59:35.108Z","avatar_url":"https://github.com/perfectsquircle.png","language":"PLpgSQL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pg_mq\n\n\u003e A simple asynchronous message queue for PostgreSQL\n\n## Features\n\n- No polling!\n  - Messages are pushed to listening consumers using [PostgreSQL NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html).\n- Pure PostgreSQL\n  - No external vendors or plugins are needed.\n- Exchanges\n  - Isolate messaging concerns into distinct exchanges per installation.\n- Queues\n  - Declare queues that select messages based on routing patterns.\n- Work queue semantics\n  - Messages are delivered to a single listening consumer per queue.\n- Pub/Sub semantics\n  - Make queues 1-to-1 per consumer for publish/subscribe.\n- Rate Limiting\n  - Consumers can define how many messages they can handle simultaneously.\n\n## Usage\n\n### Create an exchange\n\nCreate a new exchange to publish messages to.\n\n```sql\nCALL mq.create_exchange(exchange_name=\u003e'My Exchange');\n```\n\n### Create a queue\n\nQueues belong to an exchange. They must define a routing pattern to receive messages. The routing pattern is a regular expression which is executed against all incoming routing keys.\n\n```sql\nCALL mq.create_queue(exchange_name=\u003e'My Exchange', queue_name=\u003e'My Queue', routing_key_pattern=\u003e'^My Key$');\n```\n\n### Publish a message\n\nMessages are comprised of a routing key, a JSON body, and headers. They can be published to an exchange.\n\n```sql\nCALL mq.publish(exchange_name=\u003e'My Exchange', routing_key=\u003e'My Key', body=\u003e'{ \"hello\": \"world\" }', headers=\u003e'foo=\u003ebar');\n```\n\n### Consume a message\n\nA consumer can listen to a queue. It will receive all messages in the queue if it's the sole consumer. It will receive some fraction of messages in the queue if there are multiple consumers.\n\n```sql\nCALL mq.open_channel(queue_name=\u003e'My Queue');\n```\n\nMessages are delivered with NOTIFY in JSON format. They have a shape like so:\n\n```json\n{\n  \"delivery_id\": 1,\n  \"routing_key\": \"My Key\",\n  \"body\": { \"hello\": \"world\" },\n  \"headers\": { \"foo\": \"bar\" }\n}\n```\n\n### Acknowledge a message\n\nA consumer can acknowledge a message using the `delivery_id`. An acknowledged message is removed from its queue and discarded.\n\n```sql\nCALL mq.ack(delivery_id=\u003e1);\n```\n\nA negative acknowledgement will put the message back in the queue.\n\n```sql\nCALL mq.nack(delivery_id=\u003e1);\n```\n\n### Experimental Features\n\nAn optional delay can be added to a negative acknowledgement. The message won't be delivered again until after the interval.\n\n```sql\nCALL mq.nack(delivery_id=\u003e1, retry_after=\u003e'5 minutes');\n```\n\n\u003e [!WARNING]  \n\u003e To use the above, you need a seperate process. Read below.\n\nThis has a caveat however. Since pg_mq is built off triggers, messages only flow through if there's external input (e.g. new message published, new channel opened.) If messages stop getting published for some period of time, nor do any consumers connect, a message that is held waiting for a retry will not get processed.\n\nPostgreSQL doesn't have timers built in, nor is there a way to spawn a separate thread. So as a workaround, there needs to be an external temporal actor to periodically check if any messages are stuck waiting.\n\n```sql\nCALL mq.sweep_waiting_message(queue_name=\u003e'My Queue');\n```\n\nThis could be triggered by a cron task (e.g. [pg_cron](https://github.com/citusdata/pg_cron)) or by a timer loop in your application. See [Program.cs](examples/net6.0/Program.cs).\n\n## Installation\n\n1. Create a database\n2. Execute `.sql` files in [src](./src/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperfectsquircle%2Fpg_mq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fperfectsquircle%2Fpg_mq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fperfectsquircle%2Fpg_mq/lists"}