Ask HN: What's your go-to message queue in 2025?

66 points by enether ↗ HN
The space is confusing to say the least.

Message queues are usually a core part of any distributed architecture, and the options are endless: Kafka, RabbitMQ, NATS, Redis Streams, SQS, ZeroMQ... and then there's the “just use Postgres” camp for simpler use cases.

I’m trying to make sense of the tradeoffs between:

- async fire-and-forget pub/sub vs. sync RPC-like point to point communication

- simple FIFO vs. priority queues and delay queues

- intelligent brokers (e.g. RabbitMQ, NATS with filters) vs. minimal brokers (e.g. Kafka’s client-driven model)

There's also a fair amount of ideology/emotional attachment - some folks root for underdogs written in their favorite programming language, others reflexively dismiss anything that's not "enterprise-grade". And of course, vendors are always in the mix trying to steer the conversation toward their own solution.

If you’ve built a production system in the last few years:

1. What queue did you choose?

2. What didn't work out?

3. Where did you regret adding complexity?

4. And if you stuck with a DB-based queue — did it scale?

I’d love to hear war stories, regrets, and opinions.

98 comments

[ 3.0 ms ] story [ 157 ms ] thread
Kafka for communication between microservices, and MQTT (VerneMQ) for IOT devices
What are your thoughts on Apache Pulsar vs Kafka?
I'm hesitating with EMQx, have you tried it? why did you choose VerneMQ?
Sidekiq, Sidekiq, Sidekiq (or just Postgres if Im dealing with something trivial)
NATS
I use NATS too! It has worked very well for me, using it to collect data from IoT devices. I don't really like all the other bits they tacked on like jetstream and object store, that seems beyond its scope. Subject authorization is also painful to implement. But runtime behaviour has been flawless for me.
SQS. For Ruby apps I use Shoryuken with SQS.
Surprised no body is mentioning ActiveMQ!
I have so far gotten by plenty well writing my own queue systems to fit the needs of the consuming application. Normally the only place where I need queue systems is in distributed systems with rapid fire transmissions to ensure messages hit the network in time sequence order. The additional benefit is that network traffic is saved in order when the current network socket fails so that nothing is lost but time.
I played with most message queues and I go with RabbitMQ in production.

Mostly because it has been very reliable for years in production at a previous company, and doesn’t require babysitting. Its recent versions also has new features that make it is a descent alternative to Kafka if you don’t need to scale to the moon.

And the logo is a rabbit.

Datadog too. i often wonder how come more companies dont pick cute mascots. gives a logo, makes everyone have warm fuzzies immediately, creates pun opportunities.

inb4 "oh but you wont be taken seriously" well... datadog.

Hugging face clearly shares the same philosophy
but usually I only see the namw "huggingface" written, and I think of headcrabs from half-life instead
For large applications in a service-oriented architecture, I leverage Kafka 100% of the time. With Confluent Cloud and Amazon MSK, infra is relatively trivial to maintain. There's really no reason to use anything else for this.

For smaller projects of "job queues," I tend to use Amazon SQS or RabbitMQ.

But just for clarity, Kafka is not really a message queue -- it's a persistent structured log that can be used as a message queue. More specifically, you can replay messages by resetting the offset. In a queue, the idea is once you pop an item off the queue, it's no longer in the queue and therefore is gone once it's consumed, but with Kafka, you're leaving the message where it is and moving an offset instead. This means, for example, that you can have many many clients read from the same topic without issue.

SQS and other MQs don't have that persistence -- once you consume the message and ack, the message disappears and you can't "replay it" via the queue system. You have to re-submit the message to process it. This means you can really only have one client per topic, because once the message is consumed, it's no longer available to anyone else.

There are pros and cons to either mechanism, and there's significant overlap in the usage of the two systems, but they are designed to serve different purposes.

The analogy I tend to use is that Kafka is like reading a book. You read a page, you turn the page. But if you get confused, you can flip back and reread a previous page. An MQ like RabbitMQ or Sidekiq is more like the line at the grocery store: once the customer pays, they walk out and they're gone. You can't go back and re-process their cart.

Again, pros and cons to both approaches.

"What didn't work out?" -- I've learned in my career that, in general, I really like replayability, so Kafka is typically my first choice, unless I know that re-creating the messages are trivial, in which case I am more inclined to lean toward RabbitMQ or SQS. I've been bitten several times by MQs where I can't easily recreate the queue, and I lose critical messages.

"Where did you regret adding complexity?" -- Again, smaller systems that are just "job queues" (versus service-to-service async communication) don't need a whole lot of complexity. So I've learned that if it's a small system, go with an MQ first (any of them are fine), and go with Kafka only if you start scaling beyond a single simple system.

"And if you stuck with a DB-based queue -- did it scale?" -- I've done this in the past. It scales until it doesn't. Given my experience with MQs and Kafka, I feel it's a trivial amount of work to set up an MQ/Kafka, and I don't get anything extra by using a DB-based queue. I personally would avoid these, unless you have a compelling reason to use it (eg, your DB isn't huge, and you can save money).

> This means you can really only have one client per topic, because once the message is consumed, it's no longer available to anyone else.

It depends on your use case (or maybe what you mean by "client"). If I just have a bunch of messages that need to be processed by "some" client, then having the message disappear once a client has processed it is exactly what you want.

Absolutely, if you only ever have one client, SQS or a message queue is perfectly fine!
We build applications very differently. SQS queues with 1000s of clients have been a go to for me for over a decade. And the opposite as well — 1000s of queues (one per client device, they’re free). Zero maintenance, zero cost when unused. Absurd scalability.
Hey I'm curious how the consumers of those queues typically consume their data, is it some job that is polling, another piece of tech that helps scale up for bursts of queue traffic, etc. We're using the google equivalent and I'm finding that there are a lot of compute resources being used on both the publisher and subscriber sides. The use cases here I'm talking about are mostly just systems trying to stay in sync with some data where the source system is the source of record and consumers are using it for read-only purposes of some kind.
On the producer side I’d expect to see change data capture being directed to a queue fairly efficiently, but perhaps you have some intermediary that’s running between the system of record and the queue? The latter works, but yeah it eats compute.

On the consumer side the duty cycle drives design. If it’s a steady flow then a polling listener is easy to right size. If the flow is episodic (long periods of idle with unpredictable spikes of high load) one option is to put a alarm on the queue that triggers when it goes from empty to non-empty, and handle that alarm by starting the processing machinery. That avoids the cost of constantly polling during dead time.

Certainly. There are many paths to victory here.

One thing to consider is whether you _want_ your producers to be aware of the clients or not. If you use SQS, then your producer needs to be aware of where it's sending the message. In event-driven architecture, ideally producers don't care who's listening. They just broadcast a message: "Hey, this thing just happened." And anyone who wants to subscribe can subscribe. The analogy is a radio tower -- the radio broadcaster has no idea who's listening, but thousands and thousands of people can tune in and listen.

Contrast to making a phone call, where you have to know who it is that you're dialing and you can only talk to one person at a time.

There are pros and cons to both, but there's tremendous value in large applications for making the producer responsible for producing, but not having to worry about who is consuming. Particularly in organizations with large teams where coordinating that kind of thing can be a big pain.

But you're absolutely right: queues/topics are basically free, and you can have as many as you want! I've certainly done it the SQS way that you describe many times!

As I mentioned, there are many paths to victory. Mine works really well for me, and it sounds like yours works really well for you. That's fantastic :)

UUCP
People will call me crazy but why not SMTP for message queueing?
Because it's only INSERT not SELECT nor DELETE? Maybe you meant IMAP which does have APPEND <https://www.rfc-editor.org/rfc/rfc3501.html#section-6.3.11> to insert a new message into a folder, and a bazillion SELECT options, plus of course DELETE

I still would call that crazy, because of the mental tax of explaining to every new employee "wait, you're using IMAP for what?" but if it works for you, then great

Does Google Cloud Tasks count?
No one ever seems to use it, but for AMPQ I like Beanstalkd. It’s fast, stable and has not failed me under high RPS.
This is my go-to solution as well. It is great, but utilizes just one CPU core. But if this the problem, then your business is already booming.
Kafka is fairly different from the rest of these — it’s persistent and designed for high read throughput to multiple simultaneous clients at the same time, as some other commenters have pointed out.

We wanted replayability and multiple clients on the same topic, so we evaluated Kafka, but we determined it was too operationally complex for our needs. Persistence was also unnecessary as the data stream already had a separate archiving system and existing clients only needed about 24hr max of context. AWS Kinesis ended up being simpler for our needs and I have nothing but good things to say about it for the most part. Streaming client support in Elixir was not as good as Kafka but writing our own adapter wasn’t too hard.

I am using Beanstalkd, it is small and fast and you just apt-get it on Debian.

However, I have noticed that oftentimes devs are using queues where Workflow Engines would be a better fit.

If your message processing time is in tens of seconds – talk to your local Workflow Engine professional (:

A classic. Not something I personally use these days, but I think just as a piece of software it is an eternally good example of something simple, powerful, well-engineered, pleasant to use, and widely-compatible, all at the same time
In that case, any suggestions if the answer was looking for workflow engines? Ideally something that will work for no-person-in-the-middle workloads in the tens of seconds range as well as person-making-a-decision workflows that can live for anywhere between minutes and months?
Postgres. Doing ~ 70k messages/second average. Nothing huge but don’t need anything dedicated yet.
I'm curious on how people use Postgres as a message queue. Do you rely on libraries or do you run a custom implementation?
We also use Postgres but we don't have many jobs. It's usually 10-20 squedule that creates hourly-monthly jobs and they are mostly independent. Currently a custom made solution but we are going to update it to use skip locked and use Notify/Listen + interval to handle jobs. There is a really good video about it on YouTube called: "Queues in PostgreSQL Citus Con."
You can go an awfully long way with just SELECT … FOR UPDATE … SKIP LOCKED
I've never found a satisfying way to not hold the lock for the full duration of the task that is resilient to workers potentially dying. And postgres isn't happy holding a bunch of locks like that. You end up having to register and track workers with health checks and a cleanup job to prune old workers so you can give jobs exclusivity for a time.
If you go that route you could spawn a parent process , the work is done in a child process ?
I use a visibility timeout.

So when selecting a message that isn’t started. It also looks for in progress ones that have been going longer than the timeout.

The update sets status, start time, and attempt counter.

If attempt counter equals 3 when the update happens, it sets the message to failed. The return looks at the stats sees failed and raises a notification.

Then if it’s a fix like correcting data or something I just reset the state to have it reprocess.

Never needed to track workers or cleanup jobs etc.

Hold the lock and write a row with timestamp at the time you read.

That row indicates you are the one processing the data and no one else should. When reading, abort the read if someone else wrote that row first.

When you are finished processing, hold the lock and update the row you added before to indicate processing is complete.

The timestamp can be used to timeout the request.

Just select for update skipped locked. Table is partitioned to keep unprocessed small.
Built right in using a group of pg functions, or also with a library, or also with a python based tool that happens to use pg for the queue.
Curious what kind of hardware you're using for that 70K/s?
It’s an r8g instance in aws. Can’t remember the size but I think it’s over provisioned because it’s at like 20% utilisation and only spikes to 50.
What’s your batch size?
For my extremely specialized case, I use a SQLite database as a message queue. It absolutely wouldn't scale, but it doesn't need to. It works extremely well for what I need it to do.
Have you written up about it? I'd love to read it if so. Thought of using SQLite several times like this but never mustered the courage to try.
I use SQLite as an offline buffer for telemetry data, basically one thread does INSERT of the payloads and another thread does just SELECT and then DELETE when it has successfully transmitted the payload.
I would join in asking for more details.

I have an idea of a project where even MySql/Maria is too much of admin burden.

There's very little to it, really, just a messages table with a id INTEGER PRIMARY KEY AUTOINCREMENT column (autoincrement to prevent id reuse, which is otherwise legal), a payload TEXT NOT NULL column (which is usually JSON encoded), and, in my case, a TEXT json annotations column, with some computed indexes. A publisher just pushes rows in, and a subscriber takes an exclusive lock to grab the first row matching the annotations that it cares about (I use DELETE RETURNING, but you can make it work however you need).

You can use `PRAGMA data_version` on a dedicated thread to watch for changes and notify other waiters via a condition variable. It's not the nicest solution, because it's just a loop around a query, but it gets the job done.

A req-rep pattern can be done by doing a `INSERT ... RETURNING id` and having the the other side re-push into the same or a different message queue with an annotation referring to that id. Alternatively, you could have a table with a req, rep, and status column to coordinate it all.

It's far from everything you'd need from a complete, robust message broker, but for small single or multi-process message queue with a max of a few dozen readers and writers, it gets the job done nicely. In a single process, you can even replace the data_version loop thread with `sqlite3_commit_hook` on writers to notify readers that something has changed via the condition_variable.

using zeebe/Camunda at work. The system gives you a way of designing and partitioning message-based workflows. It has a very thorough design.
We had a lot of reliability isdues with zeebe/camunda (granted we started using it at version 0.10), and now they also rugulled the free version. So I would never go near that company again.
The US Federal Reserve uses IBM MQ for the FedNow interbank settlement service that went live last year.

Architecture info: https://explore.fednow.org/resources/technical-overview-guid...

Likely implies z/OS is common on both sides. Given the stakes and availability needs not a bad choice.
In the linked doc they say it’s MQ on AIX but I believe it’s interchangeable with Z.
I've used Qless for several years;

For those unfamiliar, it's a Lua library that gets executed in Redis using one of the various language bindings (which are essentially wrappers around calling the Lua methods).

With our multi-node redis setup it seems to be quite reliable.

Maybe start by explaining what you want to use it for?