For the complete documentation index, see llms.txt. This page is also available as Markdown.

Non-blocking Batched Delivery

Batched, promise-based message publishing that multiplies throughput while keeping delivery guarantees

When your application publishes messages to a Message Broker, each send is a full network round trip: serialize, write, and block until the Broker confirms. Publish a thousand messages and you pay for a thousand round trips, one after another.

Non-blocking Batched Delivery changes that equation. Messages are fired to the Broker without waiting for individual confirmations, multiple messages are combined into single Broker writes, and confirmations are collected once for the whole set - right before your transaction commits. The result is a multiplier on publishing throughput, without giving up a single delivery guarantee.

You'll know you need this when:

  • A single Command results in many Events, and publishing them one by one dominates the request time

  • You run imports, migrations or ETL jobs that push thousands of messages to a Broker

  • Your Outbox or high-volume workflow is bottlenecked on publishing latency, not on processing

  • You want fire-and-forget publishing speed, yet nothing may be silently lost

How it works

With synchronous publishing, every message follows the pattern: send, wait for Broker confirmation, send the next one. The waiting dominates - the Broker is mostly idle while your application blocks on network latency.

High Throughput Publishing attacks this with two independent mechanisms:

  1. Batch publishing - messages published within the same execution scope are combined and written to the Broker together (a single multi-row insert for DBAL, a batched publish for RabbitMQ, one batch request per 10 messages for SQS, a single script execution for Redis, producer lingering for Kafka)

  2. Non-blocking confirmation - instead of blocking per message, the message is handed to the Broker and its delivery confirmation is collected later

Confirmations are then awaited before commit - all outstanding deliveries are resolved before your transaction commits, so a successful Command execution means every published message is confirmed by the Broker.

What each provider offers

Batching is available everywhere. Non-blocking confirmation requires a transport that can hand off a write and learn its outcome later, which not every store can do:

Provider
Batch publishing
Non-blocking confirmation

RabbitMQ (AMQP)

single batched write - AmqpLib driver only, see below

Yes - confirms awaited at scope end

Kafka

producer lingering

Yes - delivery reports awaited at scope end

Amazon SQS

batch send requests

Yes - responses awaited at scope end

Database (DBAL)

multi-row insert

No - the insert blocks until the database confirms it

Redis

single scripted round trip

No - the round trip blocks until Redis confirms it

DBAL and Redis therefore accept no parameters: batching is the whole of what they can offer, and their configuration says so.

Enabling High Throughput Publishing

Not calling the method leaves publishing unchanged. Calling it enables every mechanism the provider supports:

and for Message Publisher:

Each mechanism can be turned off by name, and the confirmation timeout tuned:

The same method is available on KafkaMessageChannelBuilder, SqsBackedMessageChannelBuilder, RedisBackedMessageChannelBuilder and DbalBackedMessageChannelBuilder, and on every Message Publisher configuration. On DBAL and Redis it takes no arguments.

Publishing from Business Code

The most common scenario requires no API changes at all. When your Command Handler publishes Events to an asynchronously published channel, Ecotone collects them and delivers them as one batch:

All three Events are written to the Broker in a single batched operation, and their confirmations are awaited together before the Command Bus returns. Your business code stays exactly the same - enabling withHighThroughputPublishing() on the channel is the only change.

Publishing with Futures

For explicit control, MessagePublisher exposes publishDeferred, which fires the message and returns a Future:

  1. The message is sent to the Broker immediately, but the confirmation is not awaited

  2. resolve() awaits the delivery confirmation - it throws PublishingFailedException if the Broker rejected the message

This enables pipelining: fire many publishes, let the Broker work on all of them concurrently, then resolve the Futures at the end:

You never risk losing a message by forgetting to resolve a Future. Ecotone awaits all unresolved deliveries before the surrounding transaction commits, and flushes any remaining ones on application shutdown.

Batch Messages

To publish a set of messages as one explicit unit, use BatchMessage:

  1. Each entry carries its own headers

  2. Entries can be individually delayed

  3. Entries can individually expire

The whole batch is delivered to the Broker in a single operation, yet each entry keeps its own metadata, delay and time to live. Sending a BatchMessage requires batch publishing to be enabled - otherwise Ecotone fails at configuration time telling you so.

On providers that support non-blocking confirmation, a batch can be published as a Future instead, so the whole set is confirmed later:

Delivery Guarantees

Speed without safety would be no gain at all. High Throughput Publishing keeps the full set of Ecotone's delivery guarantees:

  • Confirmed before commit - all pending deliveries are awaited before the transaction commits. A Command that finished successfully means every published message is safely stored in the Broker

  • Per-message failure attribution - when part of a batch fails, Ecotone knows exactly which messages failed. Retries redeliver only the failed ones - already delivered messages are never duplicated

  • Error Channel routing - messages that exhaust retries are routed to your Error Channel or Dead Letter individually, each as a separate, replayable message

  • No silent loss - deliveries that were never explicitly resolved are awaited at the transaction boundary and flushed on shutdown, with failures logged and routed

For the details of send-path resiliency, see Resilient Sending.

The Throughput Multiplier

Publishing 1000 messages to a Broker, single synchronous sends vs High Throughput Publishing, measured with Ecotone's benchmark suite on a local Docker setup:

Provider
1000 synchronous sends
High Throughput Publishing
Multiplier

Kafka

292 ms

22 ms

~13x

Amazon SQS

1776 ms

165 ms

~10x

Database (DBAL)

435 ms

48 ms

~9x

RabbitMQ (AMQP)

240 ms

35 ms

~7x

Redis

77 ms

24 ms

~3x

These numbers come from a local network setup, where round trips are cheapest. In production, where the Broker sits behind real network latency, every eliminated round trip is worth more - the multiplier grows with the distance to your Broker and with the volume published per scope.

Materials

Last updated

Was this helpful?