Show HN: An SQS Alternative on Postgres

Overall purpose & positioning

  • PGMQ is a message queue implemented as a Postgres extension, marketed as an SQS-style queue with SQL semantics and client libraries.
  • It’s compared to tools like River (Go-only), RabbitMQ, Kafka, Graphile Worker, Faktory, and various homegrown Postgres queues; it sits in the “simple, DB-backed job queue” space.

Advantages of using Postgres as a queue

  • Any language that can talk to Postgres can produce/consume messages; SQL is the API, so it’s naturally language-agnostic.
  • A major benefit is transactional integration: enqueueing jobs in the same DB transaction as business data simplifies failure modes and eliminates many “event lost / event out of sync” bugs.
  • Operational simplicity: many teams already run Postgres, so they avoid introducing and operating a separate MQ system.
  • Debuggability: it’s easy to inspect, query, and manually adjust queue state with SQL.

Limitations, scale, and when other MQs win

  • RabbitMQ/Kafka provide richer semantics: fan-out / consumer groups, advanced routing, integrations, and large-scale throughput.
  • PGMQ doesn’t natively push the same message to multiple consumers the way Kafka consumer groups do; you’d need multiple queues and transactional fan-out.
  • Concerns are raised about using a shared DB as a queue in high-load or microservice-heavy environments; dedicated queue services may scale and isolate better.
  • Some see SQS as more robust and “not really comparable” to a DB extension, especially at larger scale.

Delivery semantics and correctness debate

  • The README’s “exactly once” wording is heavily criticized; several commenters stress that in distributed systems you only realistically get at-least-once or at-most-once delivery, and must design for idempotent processing.
  • Visibility timeouts and FOR UPDATE SKIP LOCKED can reduce duplicate processing in the steady state, but do not solve failures (disk loss, partitions, consumer crashes).

Retries, DLQs, and features

  • Retries are supported via messages becoming visible again after a timeout.
  • There’s no built-in dead-letter queue yet; current practice is to check a read-count field and move “poison” messages to a separate queue, typically archiving successfully processed ones.
  • Batch reads/writes and long-poll reads exist; push-style delivery and HTTP interfaces are discussed but not yet designed.

Trend: DB as queue

  • Some see “DB as queue” as a bad old idea; others argue that for most non-“Google scale” apps Postgres queues are perfectly adequate and dramatically reduce architectural complexity.