How to configure distributed EJB Timers

Do you need to scale your EJB Timers at its bests ? WildFly 27 supports a new mechanism for storing your Timers in a cluster: distributed EJB Timers. In this article we will learn how to configure it in no time.

EJB Timers overview

The default Timer implementation of EJB Timers before WildFly 27 relies on a File Data Store which serializes Timer data in the application server data folder:

<timer-service thread-pool-name="default" default-data-store="default-file-store">
    <data-stores>
        <file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
    </data-stores>
</timer-service>

On the other hand, in a clustered environment, you need to use a distributed approach. One option is to store your Timers in the Database. This article discusses how to configure them: Creating persistent clustered EJB 3 Timers

Using a Database as shared store, however, can limit the scalability of applications. WildFly 27 provide a persistent timer service implementation which both highly-available and scalable. This feature relies on Infinispan’s cache consistent hash to determine which timers a given cluster member should manage.

Enabling EJB distributed Timers

By default, WildFly 27 HA configuration uses the distributable timer implementation for both persistent timers (via an Infinispan distributed cache) and non-persistent timers (via a local cache configured with file passivation).

<timer-service default-persistent-timer-management="persistent" default-transient-timer-management="transient">
    <data-stores>
        <file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
    </data-stores>
</timer-service>

To enable the distributable timer service in other configurations, you need to modify the default timer-service configuration of the ejb3 subsystem as follows:

/subsystem=ejb3/service=timer-service:undefine-attribute(name=default-data-store)
/subsystem=ejb3/service=timer-service:write-attribute(name=default-persistent-timer-management, value=persistent)
# reload changes
reload

The persistent cache references the infinispan-timer-management element from the new distributable-ejb subsystem:

<subsystem xmlns="urn:jboss:domain:distributable-ejb:1.0" default-bean-management="default">
        <infinispan-bean-management name="default" cache-container="ejb" max-active-beans="10000"/>
        <infinispan-client-mappings-registry cache-container="ejb" cache="client-mappings"/>
        <infinispan-timer-management name="persistent" cache-container="ejb" cache="persistent" max-active-timers="10000"/>
        <infinispan-timer-management name="transient" cache-container="ejb" cache="transient" max-active-timers="10000"/>
</subsystem>

As you can see, for each EJB Timer, it is possible to switch between distributed timer management or existing in-memory implementation for management of transient timers.
This has the advantage of supporting passivation of timer instances for applications with a fairly large number of transient timers above some configured maximum.

Here is the list of attributes for the persistent timer-management:

/subsystem=distributable-ejb/infinispan-timer-management=persistent:read-resource
{
    "outcome" => "success",
    "result" => {
        "cache" => "persistent",
        "cache-container" => "ejb",
        "marshaller" => "JBOSS",
        "max-active-timers" => 10000
    }
}

Check these attributes:

  • marshaller: Indicates the marshalling implementation used for serializing the timeout context of a timer. The default is “JBOSS”. You can also opt for PROTOSTREAM marshaller. Please note that the marshallers serializes the Timer “info” object with which a given Timer was created, and is made available to timeout callbacks.
  • max-active-timers: The maximum number of active timers to retain in memory before triggering passivation

The EJB Distributed Cache in action

Let’s deploy an example application which uses EJB Timers, such as the EJB Timer quickstart: https://github.com/wildfly/quickstart/tree/main/ejb-timer

Next, let’s inspect from JConsole the MBeans to see the available Infinispan Cache and their size:

distributed ejb timers

Finally, you can also access each of the above Caches also programmatically. For example:

@Resource(name = "java:jboss/infinispan/container/ejb")
CacheContainer cacheContainer;


@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
       Cache<String, String> c = cacheContainer.getCache("ejb-timer-persistent.war.ScheduleExample.PERSISTENT");
}

Conclusion

This article is a walk-through the configuration of Distributed EJB Timers to scale applications using EJB Timers. Thanks to Paul Ferraro for his valuable hints in writing this article.

Found the article helpful? if so please follow us on Socials