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
Non-blocking Batched Delivery is available as part of Ecotone Enterprise.
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:
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)
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:
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.
RabbitMQ batches writes only on the AmqpLib connection factory (Enqueue\AmqpLib\AmqpConnectionFactory). With the AmqpExt factory each message is written individually, so only nonBlockingConfirmation contributes. Both drivers support non-blocking confirmation.
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:
The message is sent to the Broker immediately, but the confirmation is not awaited
resolve()awaits the delivery confirmation - it throwsPublishingFailedExceptionif 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.
publishDeferred requires non-blocking confirmation, so it is not available on DBAL and Redis Publishers - there is nothing to defer, as the write blocks until the store confirms it. Publish batches there with convertAndSend, shown below.
Batch Messages
To publish a set of messages as one explicit unit, use BatchMessage:
Each entry carries its own headers
Entries can be individually delayed
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:
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
Links
Message Publisher [Documentation]
Delivery Semantics and Guarantees [Documentation]
Last updated
Was this helpful?