---
title: "Compared to Kafka, RabbitMQ, SQS and pgmq"
description: "Architectural differences only: how each system gets per-entity ordering, and what that costs. No performance comparisons."
---

> Documentation Index
> Fetch the complete documentation index at: https://queenmq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Compared to Kafka, RabbitMQ, SQS and pgmq

This page compares designs, not speeds. There are no throughput or latency
comparisons here, and that is a deliberate omission: every such number in Queen's
older documentation was measured against a C++ broker that no longer exists, on a
storage engine that no longer exists. Republishing them would be inventing data.
Queen's own measurements, with the configuration that produced each one, are in
[Benchmarks](/benchmarks).

What is worth comparing is structure, because the systems below make genuinely
different bets. One question separates them more sharply than any other: **how do
you get strict ordering per entity, when there are many entities, the set changes
constantly, and one slow entity must not delay the others?**

Queen's answer, for reference: a partition is a row in a PostgreSQL table,
created by the first push that names it, and ordering inside it comes from taking
that row's lock before allocating an offset. Consumption state is one cursor per
(partition, consumer group). A new entity is a new row. Nothing is preallocated,
nothing is assigned, and nothing rebalances.

## Kafka

**The shape.** A topic is split into a fixed number of physical partitions. Each
partition is an append-only log with its own files and its own replica set on the
brokers. A producer chooses a partition, and by default it hashes the record key
modulo the partition count. Within a consumer group, at most one consumer reads a
given partition; membership and assignment are negotiated through a group
coordinator, and adding or removing a consumer triggers a rebalance. Offsets are
committed back into the cluster. You operate the cluster: brokers, a metadata
quorum, and a JVM.

**Where it diverges from Queen.**

- **Entities are hash-modded onto shards.** Because the partition count is fixed
  and much smaller than the number of entities, many entities share a partition.
  A consumer stuck on one entity stalls every entity it happens to collide with,
  which is exactly the head-of-line problem Queen exists to avoid. Queen's
  partitions are logical, so an entity gets its own lane and a stall is contained
  to it.
- **The partition count is a capacity decision made in advance.** You can add
  partitions, but that changes the key-to-partition mapping, so ordering across
  the change is not preserved for existing keys. In Queen the number of lanes is
  whatever your data produced; there is nothing to resize.
- **Rebalancing is a protocol you wait out.** Queen has no assignment at all: a
  pop is a claim on a span, recorded as a lease with an expiry. A worker that dies
  does not trigger a group-wide reassignment; its lease lapses.
- **Kafka replicates itself; Queen does not.** Kafka's durability and failover are
  its own. Queen's are PostgreSQL's, which is an advantage if you already run
  PostgreSQL well and a limitation if you wanted the message broker to be
  independently replicated. One PostgreSQL is one failure domain.
- **Ecosystem.** Kafka has connectors, stream-processing frameworks and a very
  large operational literature. Queen has five language SDKs, an operator CLI and
  an HTTP API.

## RabbitMQ

**The shape.** Producers publish to exchanges; bindings route messages into
queues; consumers take messages from queues with per-message acknowledgement,
negative acknowledgement and dead-letter exchanges. Order is a property of a
queue, and it holds when that queue is drained by a single consumer. Competing
consumers on one queue interleave, and requeues reorder. Queues are live
server-side objects with their own state on the broker.

**Where it diverges from Queen.**

- **Per-entity ordering means a queue per entity.** That is the only construction
  that gives you ordering per key, and it makes the number of broker-side objects
  scale with entity count. Sharding plugins that hash keys onto a fixed set of
  queues reintroduce Kafka's collision problem. Queen's per-entity lane is a row.
- **No replay.** A message that has been consumed and acknowledged is gone.
  Queen's consumption state is a cursor, so a second consumer group can read the
  same history from the start, and a group can be moved back to a timestamp.
- **Routing is RabbitMQ's real strength, and Queen has none of it.** There are no
  exchanges, no bindings, no topic patterns and no header matching. The producer
  names the queue and the partition. The nearest thing is consumer-*side*
  selection: queue names carry a dotted `namespace.task` convention, and
  `GET /api/v1/pop?namespace=…&task=…` pops across every queue that matches. If
  you need content-based routing, Queen will not give it to you.
- **Where Queen borrowed from RabbitMQ.** Leases with expiry, explicit
  `completed` / `failed` / `retry` / `dlq` outcomes, a retry budget and a real
  dead-letter table are all recognisably from this side of the family, with one
  difference that matters more than it sounds: Queen's ack is an offset commit,
  not a per-message receipt, so acking a message implicitly completes every
  earlier unacked message in that partition for that group.

## Amazon SQS

**The shape.** A fully managed queue. Standard queues are at-least-once with no
ordering guarantee. FIFO queues add ordering *within a message group id*:
messages sharing a group id are delivered in order and a group is consumed
serially, which is structurally the same idea as a Queen partition. A consumer
receives messages under a visibility timeout, deletes them when done, and failures
go to a dead-letter queue via a redrive policy. FIFO queues also deduplicate on a
deduplication id or a content hash, inside a window the service fixes. FIFO comes
with per-queue throughput quotas that standard queues do not have.

**Where it diverges from Queen.**

- **There is no server, and that is the whole point.** Nothing to run, nothing to
  size, nothing to back up. Queen cannot match that: it is a binary plus a
  PostgreSQL you operate. If the appeal is "no infrastructure", stop here.
- **No consumer groups.** Fan-out is a topology you assemble (a topic in front of
  several queues), and each subscriber gets its own copy stored separately. In
  Queen fan-out is a second `consumerGroup` string on the pop, reading the same
  stored messages through its own cursor.
- **No replay.** A deleted message is gone; there is no cursor to rewind and no
  history to re-read. Queen's replay is a `seek` on a group.
- **The deduplication window is the service's, not yours.** Queen's
  `dedupWindowSeconds` is a per-queue setting, keyed on the `transactionId` you
  supply, enforced inside the database before an offset is allocated.
- **You cannot query the backlog.** Queen's queues, partitions, cursors, dead
  letters and lag are tables: you can `SELECT` them with the client you already
  have, and take a `pg_dump` of the whole thing.
- **Ordering domain limits are different in kind.** SQS constrains FIFO
  throughput per queue and per group by quota; Queen constrains it by physics:
  one partition is one writer and one in-flight leased batch per group.

## pgmq

pgmq is the closest comparison, because it makes the same core bet: PostgreSQL is
the right place to keep messages.

**The shape.** A PostgreSQL extension. Creating a queue creates a table (plus an
archive table). Sending inserts a row. Reading updates the row to set a
visibility timeout and returns it; deleting removes it; archiving moves it. There
is no broker process: clients connect to PostgreSQL and call SQL functions.

**Where it diverges from Queen.**

- **Fan-out.** pgmq has no consumer-group concept, so delivering the same stream
  to several independent consumers means a queue per consumer, and one stored copy
  of every message per consumer. Queen stores each message once and gives each
  consumer group its own cursor over it: the group is a string on the pop, and
  adding one writes no additional message data.
- **Ordering per entity.** Rows carry monotone ids, but concurrent readers taking
  rows under visibility timeouts do not preserve per-key order, so per-entity FIFO
  again means a queue per entity. Queen's ordering domain is the partition, and
  partitions are rows inside one queue.
- **Per-message row state versus a cursor.** pgmq tracks delivery per message: an
  `UPDATE` to lease it, a `DELETE` or a move to archive to finish it. Queen keeps
  no per-message state at all: acknowledgement advances one `committed` offset
  per (partition, group). That is why Queen has no per-message delete to do, and
  also why it cannot delete one message on request.
- **Extension versus schema.** pgmq is installed as an extension, which some
  managed PostgreSQL offerings do not allow. Queen needs `CREATE SCHEMA` and **no
  extensions**; it applies its own DDL and stored procedures at boot.
- **A broker tier, for better and worse.** Queen's separate process is what buys
  HTTP clients in any language, long-polling, and the coalescing of many
  concurrent pushes into shared commits. It is also a process to run, a network
  hop that pgmq does not have, and one more thing that can be misconfigured.
  pgmq's answer (no broker) is genuinely simpler when your consumers are all
  happy speaking SQL.

## Choosing something else

Each of these systems is the right answer to a question Queen answers badly.

| Choose | When |
| --- | --- |
| **Kafka** | Your ordering domains are few and stable rather than one-per-entity; you want the broker to replicate itself independently of a database; you need the connector and stream-processing ecosystem; you already operate a JVM cluster well |
| **RabbitMQ** | You need real routing (exchanges, topic patterns, header matching) or protocol breadth beyond HTTP; ordering per queue is enough and you do not need replay |
| **Amazon SQS** | You want no server and no database to operate at all; your fan-out is built from a topic plus several queues; your ordering domains fit FIFO's quotas; you never need to re-read history |
| **pgmq** | You want PostgreSQL-backed queuing with no broker process; your consumers can all speak SQL; a work queue with visibility timeouts is enough, without consumer groups or replay; you can install extensions |
| **Queen** | You need strict FIFO per entity at high and unpredictable cardinality, where one slow entity must not block others; consumer groups with replay over the same stored messages; ack-and-push in one PostgreSQL transaction; one binary and no cluster, next to a PostgreSQL you already run |

Before committing either way, read [Limits and non-goals](/start/limits). It is
the honest inverse of this page, and it will disqualify Queen faster than any
comparison table.

Source: https://queenmq.com/start/compare/index.mdx
