Kafka producers, consumers and admin tools can use Queen as they are: change bootstrap.servers, and nothing else. A topic is a Queen queue and a Kafka consumer group is a Queen consumer group, so Kafka clients and Queen clients read and write the same data.
Turn it on
docker run -d --name queen -p 6632:6632 -p 127.0.0.1:9092:9092 \
-v /var/lib/queen:/var/lib/queen/raft \
-e QUEEN_KAFKA_EMBEDDED=true \
-e QUEEN_KAFKA_ADVERTISED_ADDR=127.0.0.1:9092 \
ghcr.io/queen-mq/queen:2.6.0QUEEN_KAFKA_ADVERTISED_ADDR is the address Kafka clients come back to after their first request, so they must be able to reach it. It has no default: without it the node refuses to start. Send three keyed records with kcat, and read them back:
kcat -P -b 127.0.0.1:9092 -t orders -K: <<'EOF'
customer-123:{"orderId":8891,"total":42}
customer-456:{"orderId":8892,"total":17}
customer-123:{"orderId":8893,"total":5}
EOF
kcat -C -b 127.0.0.1:9092 -t orders -o beginning -e -q -f '%t [%p] offset %o key=%k %s\n'
orders [307] offset 0 key=customer-123 {"orderId":8891,"total":42}
orders [307] offset 1 key=customer-123 {"orderId":8893,"total":5}
orders [400] offset 0 key=customer-456 {"orderId":8892,"total":17}Nobody created orders: kcat’s first request did, 1,024 partitions wide. kcat hashed each key onto a partition, and both orders of customer-123 landed in partition 307, in order. The HTTP API reads the same records. A Kafka key and value are bytes, so each message carries them in base64 (k, v), with the producer’s timestamp (t):
curl -s 'localhost:6632/api/v1/pop/queue/orders/partition/307?batch=10' \
| jq -c '.messages[].data'
{"k":"Y3VzdG9tZXItMTIz","t":1792837397814,"v":"eyJvcmRlcklkIjo4ODkxLCJ0b3RhbCI6NDJ9"}
{"k":"Y3VzdG9tZXItMTIz","t":1792837397814,"v":"eyJvcmRlcklkIjo4ODkzLCJ0b3RhbCI6NX0="}One consumer group for both
Run a Kafka group consumer, and stop it once it has printed the three records:
kcat -b 127.0.0.1:9092 -G billing -o beginning -q -f 'billing [%p] offset %o key=%k\n' orders
billing [400] offset 0 key=customer-456
billing [307] offset 0 key=customer-123
billing [307] offset 1 key=customer-123billing is a Queen consumer group now, with its lag in the dashboard (/api/v1/consumer-groups lists it with "kind":"kafka"). A Queen consumer of billing starts where the Kafka consumer stopped:
curl -s -X POST localhost:6632/api/v1/push -H 'content-type: application/json' -d '{"items":[
{"queue":"orders","partition":"307","payload":{"orderId":8894,"total":99}}]}' >/dev/null
Q=localhost:6632/api/v1/pop/queue/orders/partition/307
curl -s "$Q?consumerGroup=billing&batch=10" | jq -c '.messages[] | {offset, data}'
{"offset":2,"data":{"orderId":8894,"total":99}}And a Kafka consumer reads what a Queen client pushed: a record with no key, whose value is the JSON.
How Kafka maps onto Queen
| Kafka | Queen |
|---|---|
| Topic | A queue of the same name, created by CreateTopics or by the first request that names it |
Partition n |
The partition named "n" |
| Partition count | What CreateTopics asked for, else 1,024. A topic never gets narrower |
| Offset, committed offset | The partition’s offset; the group’s position on that partition |
acks=1, acks=all |
One path: answered when the push is committed in the raft log |
| Replication factor | The raft voters: every voter holds every partition |
| Idempotent producer | Queen’s dedup: producer id, epoch and sequence are the transactionId |
| Transaction | Kept in the leader’s memory, committed with its offsets as one raft entry |
retention.ms |
The queue’s retention, set when the topic is created |
cleanup.policy=compact |
Retention off: every record is kept |
On a cluster every node runs the Kafka port, and together they are one Kafka cluster. The raft leader leads every partition and coordinates every group; a client sent to another node is told to go to the leader. Give each node its own QUEEN_KAFKA_ADVERTISED_ADDR, and never put the nodes behind one load-balanced address: a client sent to the leader would land on another node, and loop. A load balancer is fine in bootstrap.servers, which a client uses once.
Five things to know
- Kafka consumers take no lease. Queen’s retries and dead-letter queue do not apply to them: a Kafka consumer handles its own failures, as on Kafka. Kafka and Queen consumers share a group’s position, not its work, so hand a group from one kind to the other, and do not run both at once.
- Committed offsets never expire. Kafka forgets an empty group after seven days; here a group keeps its position until you delete it.
- Transactions live in the leader’s memory, up to 8 MiB, 50,000 records and 1,000 partitions each. A leader change aborts the transactions that were open.
- The Kafka port has no TLS in 2.6, and SASL passwords travel in clear: keep it on a private network.
- Advertise an address the client can reach, as it will try it. On macOS,
localhostis tried as::1first, so a port published on127.0.0.1only fails withlocalhost: use127.0.0.1.
See also
- Kafka frameworks: Streams, Connect and Flink on the Kafka port.
- Kafka API reference: every Kafka API and version the port answers.
- Kafka clients benchmark: Kafka clients through the port, measured.