Delivery acknowledgement ≠ business outcome

  1. 01PublishProducer → broker
  2. 02PersistRecord durability
  3. 03ProcessBusiness change
  4. 04AcknowledgeRetries can occur
Guarantees depend on the whole path, including an external database or API.

There is no reliable=true switch on a message broker. Reliability comes from the producer settings, broker storage, consumer acknowledgements, and the way the application commits its business result.

Here is a revised comparison of RabbitMQ, ActiveMQ Classic, and Kafka without the blanket promise of “exactly once.”

Three different models

RabbitMQ routes messages through exchanges into queues. One of the competing consumers normally handles each message in a queue. If several systems need the same event, each system typically gets its own queue bound to the exchange.

ActiveMQ Classic is closely associated with JMS. It supports queues, topics, durable subscriptions, and several wire protocols, including OpenWire and AMQP. Persistence, session type, and acknowledgement mode all affect its failure behaviour.

Kafka stores records in a partitioned log. Reading a record does not remove it; consumers track their position with an offset and can replay retained data. In a classic consumer group, each partition is assigned to one consumer at a time; this does not describe the separate share-group mechanism.

A work queue, pub/sub, and an event log may look similar on an architecture diagram. Their routing, replay, and scaling models are not interchangeable.

Delivery semantics in plain terms

  • At most once: processing may be lost, but the system does not plan to redeliver it. This can happen when a consumer acknowledges before completing its work.
  • At least once: failures can cause redelivery. This is a common choice for important events, provided consumers tolerate duplicates.
  • Exactly once: reading, producing the business effect, and recording progress must share an atomic boundary, or the receiver must deduplicate. A network hop alone cannot provide that end-to-end result.

At-least-once assumes a defined failure and retention model. TTL, retention limits, queue deletion, overflow and DLQ policies can interrupt the path to successful business processing. Redelivery is not indefinite preservation of every message.

Whenever a product claims “exactly-once support,” ask where the guarantee begins and ends. An external database, payment gateway, or HTTP API may sit outside it.

RabbitMQ: acknowledgements still allow duplicates

RabbitMQ has two separate confirmation paths:

  1. Publisher confirms tell a producer that the broker has handled a publication. They neither confirm consumer processing nor independently guarantee that a message reached the intended queue.
  2. Consumer acknowledgements tell the broker that a delivery can be removed from the queue.

For at-least-once processing, the consumer acknowledges only after the business operation succeeds. If it commits a database change and then loses its connection before sending the acknowledgement, RabbitMQ will redeliver the message. The next attempt sees a duplicate by design.

A successful confirm is also possible when an exchange cannot route the message to any queue. With AMQP 0-9-1, use mandatory and handle basic.return to detect that case. Waiting for a confirm without checking routing can leave a satisfied producer and an empty queue.

Consumers therefore need idempotent operations or a durable record of processed event IDs. The redelivered flag is useful evidence, but it is not a substitute for safe processing logic.

Durable queues, persistent messages, replication, and publisher confirms protect different parts of the path. No single option replaces the others.

ActiveMQ Classic: familiar trade-offs in a JMS stack

With ActiveMQ Classic, persistent delivery, acknowledgement mode, and transacted JMS sessions shape the result. A rollback, connection loss, or unfinished transaction can trigger redelivery; messages that exceed the retry policy usually end up in a dead-letter queue.

A broker transaction is valuable, but it does not automatically include an unrelated database. Applications still need an explicit consistency design for external side effects.

Kafka: ordering is per partition

Kafka preserves record order within a partition, not across an entire topic. Events that require per-customer ordering normally use the same key so that they land in the same partition.

The same-key rule assumes a consistent partitioning strategy. Increasing the partition count can change where a key lands. Log order also does not guarantee completion order if a consumer processes records concurrently.

Offset timing determines what a failure can do:

  • commit the offset before the business operation, and processing may be skipped;
  • complete the operation before committing the offset, and processing may repeat;
  • store the result and offset atomically in one system, and the guarantee is stronger inside that boundary.

Kafka’s idempotent producer prevents a class of duplicate writes caused by producer retries. Kafka transactions can atomically publish output records and consumer offsets in a Kafka-to-Kafka pipeline. For that pipeline, output records and offsets must commit in the same transaction, automatic offset commits must be disabled, and downstream readers use isolation.level=read_committed to exclude open and aborted transactions. Aborts, consumer-position recovery and partition ownership changes also need correct handling. Transactions do not automatically make a write to an external database or API exactly once.

Choosing a broker

The useful question is not “which broker is fastest?” but “which failure model matches the system?”

  • RabbitMQ works well for work queues, flexible routing, and separate subscriber queues.
  • ActiveMQ Classic remains a natural fit for established JMS-based integration.
  • Kafka is strong when retention, replay, stream processing, and partition-based scaling matter.

Before choosing, I write down seven answers:

  1. May a message be lost?
  2. May it be processed more than once?
  3. How does the consumer recognise a duplicate?
  4. Where is publication confirmed, and where is processing confirmed?
  5. What ordering is required, and for which key?
  6. Where does a poison message go after repeated failures?
  7. How will we replay the flow and verify the result after an incident?

Without those answers, the broker name will not rescue the design.

Primary references


Originally published on my Telegram channel @it_underside.

Yours, DPUPP

Related Articles