4.6. Message Driven Beans

Message driven beans (MDB) are an EJB 3 bean type that is invoked from a message, specifically from a JMS message. There are three areas of concern when tuning for message driven beans. The first is the number of sessions to be run in parallel. The second is the pool size for the beans themselves. This pool is similar to the pool for stateless session beans, but is related to the session parameter. The third is thread pooling, discussion of which is deferred to the JCA chapter.
The number of sessions is the number of actual message driven beans which will be waiting on the same queue and/or topic. Assume a queue is defined, and ten messages are to be processed from that queue at the same time, requiring ten sessions. In the EJB 3 world, this can be achieved through an annotation on the bean implementation itself. In fact, there are two parameters within the annotation: maxSessions and minSessions. The annotation itself is the standard EJB 3 MDB of: @ActivationConfigProperty(propertyName, propertyValue)
So, within the MDB, the minimum and maximum number of sessions can be specified, giving the opportunity to control how many instances will process messages from a given queue at one time. Refer to the following example:
@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName="destinationType"
        , propertyValue="javax.jms.Queue"),
        @ActivationConfigProperty(propertyName="destination"
        , propertyValue="queue/replenish")
        @ActivationConfigProperty(propertyName=”minSessions”
        , propertyValue=”25”)
        @ActivationConfigProperty(propertyName=”maxSessions”
        , propertyValue=”50”)
})
In this example two activation configuration properties are defined, specifying the minimum and maximum number of sessions. The minimum defines that there should always be that number of instances of the MDB in the pool, in the method ready state (ready to receive messages). The maximum specifies there can be up to that many processing messages, so if there are messages waiting and all of the instances are busy, the pool can grow from the minimum to the maximum. In this example there can be up to fifty instances of the MDB in the pool, processing messages. The “pool” parameter mentioned here is highly interrelated to the minimum and maximum number of sessions.
Just as there are pools for stateless sessions beans, there is also an instance pool for message driven beans. In this case though, there is really only one pool type that can be used: StrictMaxPool. The reason why StrictMaxPool is the only option for message driven beans is simply the nature of the bean type. Having an unbounded number of message driven beans processing messages could present problems. Many systems that use messaging process millions upon millions of messages, and it's not scalable to keep increasing the number of instances to process everything in parallel. Also it's really not possible because the default for maxSessions parameter is 15.
There's a relationship between the pool and the sessions parameters: maxSessions and minSessions. Assume that the number of maxSessions is set to a number larger than the default of 15, perhaps 50, but the default pool size to hold the instances of the MDB is only 15. Only 15 instances will process messages at once, not the 50 as might be expected. The reason for only getting 15 is that the default pool size (as defined in the configuration file) is 15, like the default maxSessions. So changing the maxSessions does not actually help unless the pool's configuration is changed to match. The pool's configuration is specified in the same file as for stateless sessions beans: ejb3-interceptors-aop.xml, in the directory JBOSS_EAP_DIST/jboss-as/server/PROFILE/deploy. Note that the minimal configuration does not contain the EJB 3 container.
Below is an example of this file:
<domain name="Message Driven Bean" extends="Intercepted Bean" inheritBindings="true">
          <bind pointcut="execution(public * *->*(..))">
                 <interceptor-ref name="org.jboss.ejb3.security.AuthenticationInterceptorFactory"/>
                 <interceptor-ref name="org.jboss.ejb3.security.RunAsSecurityInterceptorFactory"/>
          </bind>
          <bind pointcut="execution(public * *->*(..))">
                 <interceptor-ref name="org.jboss.ejb3.tx.CMTTxInterceptorFactory"/>
                 <interceptor-ref name="org.jboss.ejb3.stateless.StatelessInstanceInterceptor"/>
                 <interceptor-ref name="org.jboss.ejb3.tx.BMTTxInterceptorFactory"/>
                 <interceptor-ref name="org.jboss.ejb3.AllowedOperationsInterceptor"/>
                 <interceptor-ref name="org.jboss.ejb3.entity.TransactionScopedEntityManagerInterceptor"/>
                 <!-- interceptor-ref name="org.jboss.ejb3.interceptor.EJB3InterceptorsFactory"/ -->
                 <stack-ref name="EJBInterceptors"/>
          </bind>
          <annotation expr="class(*) AND !class(@org.jboss.ejb3.annotation.Pool)">
                 @org.jboss.ejb3.annotation.Pool (value="StrictMaxPool", maxSize=15, timeout=10000)
          </annotation>
   </domain>
  <domain name="Consumer Bean" extends="Intercepted Bean" inheritBindings="true">
       <bind pointcut="execution(public * *->*(..))">
              <interceptor-ref name="org.jboss.ejb3.security.RunAsSecurityInterceptorFactory"/>
       </bind>
       <bind pointcut="execution(public * *->*(..))">
              <interceptor-ref name="org.jboss.ejb3.tx.CMTTxInterceptorFactory"/>
              <interceptor-ref name="org.jboss.ejb3.stateless.StatelessInstanceInterceptor"/>
              <interceptor-ref name="org.jboss.ejb3.tx.BMTTxInterceptorFactory"/>
              <interceptor-ref name="org.jboss.ejb3.AllowedOperationsInterceptor"/>
              <interceptor-ref name="org.jboss.ejb3.entity.TransactionScopedEntityManagerInterceptor"/>
       </bind>
       <bind pointcut="execution(public * *->*(..)) AND (has(* *->@org.jboss.ejb3.annotation.CurrentMessage(..)) OR hasfield(* *->@org.jboss.ejb3.annotation.CurrentMessage))">
              <interceptor-ref name="org.jboss.ejb3.mdb.CurrentMessageInjectorInterceptorFactory"/>
       </bind>
       <bind pointcut="execution(public * *->*(..))">
              <!-- interceptor-ref name="org.jboss.ejb3.interceptor.EJB3InterceptorsFactory"/ -->
              <stack-ref name="EJBInterceptors"/>
       </bind>
       <annotation expr="class(*) AND !class(@org.jboss.ejb3.annotation.Pool)">
              @org.jboss.ejb3.annotation.Pool (value="StrictMaxPool", maxSize=15, timeout=10000)
       </annotation>
</domain>
In this example are four important settings. First is the definition of the message driven bean. Second is the StrictMaxPool configuration, with the default value of 15, the same default value for the maxSessions parameter. The next two are the same, except for something called a consumer bean. Consumer beans are a JBoss-specific implementation, not Java EE compliant, that gives all the functionality of the message driven bean, but does so without having to implement the MessageListener interface, and is a pure POJO model like stateless and stateful session beans. It brings the EJB 3 POJO model to asynchronous processing, without the old EJB 2.x model that was carried over for message driven beans. Of course, if this option is used, the application will not be portable, so use at your own risk.
The maxSize parameter on the StrictMaxPool needs to be the same as the maxSessions set on the bean itself. If they are not set to the same value, strange behavior occurs. When the queue has more messages than either the maxSessions or maxSize value, messages up to the maxSessions' value will be processed, then the number of messages next processed is the difference between maxSessions and maxSize, with this cycle repeating. To illustrate this, maxSessions is set to 25, the pool size left at the default of 15. When messages are being processed the number of instances will alternate between 15 and 10, which is the difference between the maxSessions and the maxSize parameters. So 15 messages will be processed, then 10, then 15, then 10, repeating until the number of messages drops to 15 or below. Keeping these two parameters in sync is important to get the desired behavior.
Determining the "right" value for number of sessions is dependent on the rate of incoming messages, processing time it takes to process each message, and the requirement for how long it should take to process messages. It may be best for some applications to throttle the message processing rate and allow the queue to grow over time, or at least during peak times, allowing the processing to catch up, and eventually drain the queue when the incoming rate drops. For other applications it may be best to match the processing rate to the incoming rate. In either case calculating the right value is based on the number of incoming messages over a given period of time and how long does it take to process each message. Unfortunately there's no strict mathematical formula as there will not be perfectly linear scalability when processing in parallel, and scalability will vary by hardware, OS, persistence mechanism (in the case of persistent messages), JVM version and even the version of EAP, and which messaging provider you would use. As mentioned earlier, EAP 5.1.x has two messaging providers which, although they provide the same JMS API work with the same MDB implementation, are very different under the covers. One uses a database for persistent messages and the other a file journal, which is by far the biggest difference. That difference is huge in regards to scalability, and can have a profound effect on the number of MDB instances required. There is no substitute for load testing to get this parameter right for your environment.