Skip to content

Vulkan vs. RabbitMQ & SQS

RabbitMQ and SQS are the workhorses of the queue world, and Vulkan borrows their best ideas shamelessly: acks, retries, dead letters, visibility timeouts, routing exchanges, FIFO groups. The disagreement is about one default: what happens to a message after it’s consumed.

They delete it. Vulkan keeps it. Almost every difference below falls out of that.

RabbitMQSQSVulkan
Ack / retry / DLQ
Delayed deliveryplugin✅ (15 min cap)✅ arbitrary run_at
Routing✅ exchanges (the gold standard)❌ (SNS bolt-on)✅ topic + header bindings
FIFOper-queueFIFO queues (300 msg/s cap)✅ per-key, opt-in per message
Replay history❌ consumed = gone❌ consumed = gone
Fan-out to N consumersexchange→N queues, declared in advanceSNS→N queues, declared in advanceany group, anytime, incl. retroactively
Message visibilityopaque broker stateopaque, 14-day maxrows; retention you choose
Transactional enqueue
InfrastructureErlang cluster to runnone (but AWS-only)the Postgres you already run

Both systems can fan out — if you declared the topology before the messages flowed. An exchange with three bound queues delivers to three consumers; the fourth queue you bind next quarter receives only what’s published after it exists. The past is gone; it was deleted on consume.

This sounds like an edge case until you live it: every new service that “wishes it had been listening from the start” becomes a custom backfill project. In Vulkan, it’s vulkan.FromOffset(0)replay is a one-liner, because nothing was deleted.

When a message goes missing in RabbitMQ, the investigation involves the management UI, rabbitmqctl, and educated guessing about which queue, which exchange, which binding. In SQS, you have CloudWatch metrics and a console that shows you counts but fights you on contents.

In Vulkan, the message is a row, its delivery history is rows, and the question “what happened to order ord_8431, everywhere?” is:

SELECT d.consumer_group, d.status, d.attempts, d.last_error
FROM vulkan.events e
JOIN vulkan.deliveries d ON d.event_offset = e."offset"
WHERE e.payload->>'order_id' = 'ord_8431';
  • SQS when you’re all-in on AWS and want Lambda event-source mappings and IAM-native everything with zero libraries — and you don’t need replay, routing, or transactions.
  • RabbitMQ when you need its protocol reach (AMQP/MQTT/STOMP clients in every language since 2009) or sub-millisecond broker latencies in fire-and-forget mode.
  • Either, when your messaging volume dwarfs your database capacity and you want the isolation of a separate system.

For the standard case — a product whose background work, events, and routing currently sprawl across one of these plus cron jobs plus an outbox — one Postgres-native platform is less to run, more to query, and strictly more capable.