Chapter 17. Flow Control

Flow control is used to limit the flow of data between a client and server, or a server and another server. It does this in order to prevent the client or server being overwhelmed with data.

17.1. Consumer Flow Control

This controls the flow of data between the server and the client as the client consumes messages. For performance reasons clients normally buffer messages before delivering to the consumer via the receive() method or asynchronously via a message listener. Messages can build up if the consumer cannot process messages as fast as they are being delivered and installed on the internal buffer. This can potentially lead to a lack of memory on the client if they cannot be processed in time.

17.1.1. Window-Based Flow Control

HornetQ consumers improve performance by buffering a certain number of messages in a client-side buffer before passing them to the client to consume.
To prevent a network round trip for every message, HornetQ pre-fetches messages into a buffer on each consumer. The total maximum size of messages (in bytes) that will be buffered on each consumer is determined by the consumer-window-size parameter.
By default, the consumer-window-size is set to 1 MiB (1024 * 1024 bytes).
The value can be:
  • -1 for an unbound buffer
  • 0 to not buffer any messages.
  • >0 for a buffer with the given maximum size in bytes.
Setting the consumer window size can considerably improve performance depending on the messaging use case. As an example, consider the two extremes:
Fast consumers
Fast consumers can process messages as fast as they consume them.
To allow fast consumers, set the consumer-window-size to -1. This will allow unbound message buffering on the client side.
Use this setting with caution: it can overflow the client memory if the consumer is not able to process messages as fast as it receives them.
Slow consumers
Slow consumers take significant time to process each message and it is desirable to prevent buffering messages on the client side so that they can be delivered to another consumer instead.
Consider a situation where a queue has two consumers; one of which is very slow. Messages are delivered in a circular fashion to both consumers; the fast consumer processes all of its messages very quickly until its buffer is empty. At this point there are still messages waiting to be processed in the buffer of the slow consumer which prevents them being processed by the fast consumer. The fast consumer is therefore sitting idle when it could be processing the other messages.
To allow slow consumers, set the consumer-window-size to 0 (for no buffer at all). This will prevent the slow consumer from buffering any messages on the client side. Messages will remain on the server side ready to be consumed by other consumers.
Setting this to 0 can give deterministic distribution between multiple consumers on a queue.
Most of the consumers cannot be clearly identified as fast or slow consumers but are in-between. In that case, setting the value of consumer-window-size to optimize performance depends on the messaging use case and requires benchmarks to find the optimal value, but a value of 1MiB is fine in most cases.

17.1.1.1. Using Core API

If HornetQ Core API is used, the consumer window size is specified by ClientSessionFactory.setConsumerWindowSize() method and some of the ClientSession.createConsumer() methods.

17.1.1.2. Using JMS

If JNDI is used to look up the connection factory, the consumer window size is configured in JBOSS_DIST/jboss-as/server/<PROFILE>/deploy/hornetq/hornetq-jms.xml:
<connection-factory name="ConnectionFactory">
   <connectors>
      <connector-ref connector-name="netty-connector"/>
   </connectors>
   <entries>
      <entry name="/ConnectionFactory"/>       
   </entries>
      
   <!-- Set the consumer window size to 0 to have *no* buffer on the client side -->
   <consumer-window-size>0</consumer-window-size>
</connection-factory>
If the connection factory is directly instantiated, the consumer window size is specified by HornetQConnectionFactory.setConsumerWindowSize() method.

17.1.2. Rate limited flow control

It is also possible to control the rate at which a consumer can consume messages. This can be used to make sure that a consumer never consumes messages at a rate faster than the rate specified.
The rate must be a positive integer to enable this functionality and is the maximum desired message consumption rate specified in units of messages per second. Setting this to -1 disables rate limited flow control. The default value is -1.

17.1.2.1. Using Core API

If the HornetQ core API is being used, the rate can be set via the ClientSessionFactory.setConsumerMaxRate(int consumerMaxRate) method or alternatively via some of the ClientSession.createConsumer() methods.

17.1.2.2. Using JMS

If JNDI is used to look up the connection factory, the max rate can be configured in JBOSS_DIST/jboss-as/server/<PROFILE>/deploy/hornetq/hornetq-jms.xml:
<connection-factory name="NettyConnectionFactory">
   <connectors>
      <connector-ref connector-name="netty-connector"/>
   </connectors>
   <entries>
      <entry name="/ConnectionFactory"/>       
   </entries>
<!-- We limit consumers created on this connection factory to consume messages at a maximum rate of 10 messages per sec -->
   <consumer-max-rate>10</consumer-max-rate>
</connection-factory>
If the connection factory is directly instantiated, the max rate size can be set via the HornetQConnectionFactory.setConsumerMaxRate(int consumerMaxRate) method.

Note

Rate limited flow control can be used in conjunction with window based flow control. Rate limited flow control only effects how many messages a client can consume in a second and not how many messages are in its buffer. If you had a slow rate limit and a high window based limit the internal buffer in the client would soon fill up with messages.