5 Scaling TopLink Applications in Clusters

This chapter provides instructions for configuring TopLink applications to ensure scalability in clustered application server environments. The instructions are generic and can be applied to any clustered application server environment; however, additional content is provided for WebLogic server and GlassFish server. Consult your vendor's documentation as required.

This chapter contains the following sections:

5.1 Understanding Scaling TopLink Applications in Clusters

TopLink applications that are deployed to clustered application server environments benefit from cluster scalability, load balancing, and failover. These capabilities ensure that TopLink applications are highly available and can scale as application demand increases. TopLink applications are deployed the same way in clustered server environments as they are in standalone server environments. However, TopLink applications must consider cache consistency in clustered environments.

TopLink utilizes a shared (L2) object cache that avoids database access for objects and their relationships. The cache is enabled by default and enhances application performance. In clustered environments, caching can result in consistency issues (such as stale data) as changes made on one server are not reflected on objects cached in other servers. Cache consistency is only problematic for objects that are frequently updated. Read-only objects are not affected by cache consistency. See the EclipseLink documentation for detailed information on caching:

http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Caching/Caching_Overview

Various options are available for addressing cache consistency:

  • Use distributed caching. TopLink includes an integration with Oracle Coherence that addresses many cache consistency issues that result from operating in a distributed environment. The integration is beyond the scope of this documentation. See Oracle Coherence Integration Guide for Oracle TopLink with Coherence Grid for additional details.

  • Use cache coordination to broadcast changes between the servers in the cluster to update or invalidate changed objects.

  • Use optimistic locking to prevent updates to stale objects and trigger the objects to be invalidated in the cache.

  • Use object/query refreshing when fresh data is required

  • Disable the shared cache or only cache read-only objects

5.2 Main Tasks

The tasks in this section provide general instructions for ensuring that a TopLink application can scale in an application server cluster environment. These tasks must be completed prior to deploying an application.

This section contains the following tasks:

5.2.1 Task 1: Configure Cache Consistency

This task includes different configuration options that mitigate the possibility or chance that an application might use stale data when deployed to an application server cluster environment. The cache coordination option is specifically designed for clustered applications; however, evaluate all the options and use them together (if applicable) to create a solution that results in the best application performance. Properly configuring a cache can, in some cases, eliminate the need to use cache coordination. For additional details on these options, see:

http://wiki.eclipse.org/Introduction_to_Cache_%28ELUG%29#Handling_Stale_Data

The following topics are included in this section:

Note:

Oracle provides a TopLink and Coherence integration that allows TopLink to use Coherence as the L2 cache. The integration is beyond the scope of this documentation. See Oracle Coherence Integration Guide for Oracle TopLink with Coherence Grid for additional details.

5.2.1.1 Disabling the Shared Cache

Cache consistency can be avoided by disabling the shared cache if an application does not require shared caching. To disable the shared cache for all objects, use the <shared-cache-mode> element in the persistence.xml file. For example:

<shared-cache-mode>NONE</shared-cache-mode>

To selectively enable or disable the shared cache, use the shared attribute of the @Cache annotation when defining an entity. For example:

...
@Entity
@Cache(shared=false)
public class Employee {
  ...
}

5.2.1.2 Refreshing the Cache

Refreshing a cache reloads the cache from the database to ensure that an application is using current data. This section describes different ways to refresh a cache.

The @cache annotation provides the alwaysRefresh and refreshOnlyIfNewer attributes which force all queries that go to the database to refresh the cache:

...
@Entity
@Cache(
   alwaysRefresh=true, 
   refreshOnlyIfNewer=true)
public class Employee {
  ...
}

The org.eclipse.persistence.jpa.JpaCache interface includes several methods that remove stale objects if the cache is out of date:

  • The evictAll method invalidates all of the objects in the cache. For example:

    em.getEntityManagerFactory().getCache().evictAll();
    
  • Use the evict method to invalidate specific classes.

    em.getEntityManagerFactory().getCache().evict(MyClass);
    
  • The clear method also refreshes a cache; however, clearing the cache can cause object identity issues if any of the cached objects are in use. Use this method only if the application knows that it no longer has references to objects held in the cache.

The preceding methods are passive and only refresh objects the next time the cache is accessed. To actively refresh an object, use the EntityManager.refresh method. The method refreshes a single object at a time.

Any of the following APIs also refresh a cache:

  • Session.refreshObject

  • DatabaseSession and UnitOfWork: refreshAndLockObject methods

  • ObjectLevelReadQuery: refreshIdentityMapResult and refreshRemoteIdentityMapResult methods

The ClassDescriptor class also provides methods that refresh a cache:

  • setShouldAlwaysRefreshCache

  • setShouldAlwaysRefreshCacheOnRemote

  • setShouldDisableCacheHits

  • setShouldDisableCacheHitsOnRemote

  • setShouldOnlyRefreshCacheIfNewerVersion

Use these methods in a descriptor amendment method. For example:

public void addToDescriptor(ClassDescriptor descriptor) {
    descriptor.setShouldRefreshCacheOnRemote(true);
    descriptor.setShouldDisableCacheHitsOnRemote(true);
}

Lastly, a query hint triggers a query to refresh the cache. For example:

Query query = em.createQuery("Select e from Employee e");
query.setHint("javax.persistence.cache.storeMode", "REFRESH");

5.2.1.3 Setting Cache Expiration

Cache expiration makes a cached object instance invalid after a specified amount of time. Any attempt to use the object causes the most up-to-date version of the object to be reloaded from the data source. Expiration can help ensure that an application is always using the most recent data. This section describes different ways to set expiration.

The @cache annotation provides the expiry and expiryTimeOfDay attributes which remove cache instances after a specific amount of time. The expiry attribute is entered in milliseconds. The default value if no value is specified is -1 which indicates that expiry is disabled. The expiryTimeOfDay attribute is an instance of the org.eclipse.persistence.annotations.TimeOfDay interface. The following example sets the object to expire after 5 minutes:

...
@Entity
@Cache(expiry=300000)
public class Employee {
  ...
}

At the descriptor level, use the ClassDescriptor.setCacheInvalidationPolicy method to set a CacheInvalidationPolicy instance. The following invalidation policies are available:

  • DailyCacheInvalidationPolicy: the object is automatically flagged as invalid at a specified time of day.

  • NoExpiryCacheInvalidationPolicy: the object can only be flagged as invalid by explicitly calling IdentityMapAccessor.invalidateObject method.

  • TimeToLiveCacheInvalidationPolicy: the object is automatically flagged as invalid after a specified time period has elapsed since the object was read.

5.2.1.4 Setting Optimistic Locking

Optimistic locking prevents one user from writing over another user's work. Locking is important when multiple servers or multiple applications access the same data and is relevant in both single-server and multiple-server environments. In a multiple-server environment, locking is still required if an application uses cache refreshing or cache coordination. This section describes different ways to set optimistic locking.

The @OptimisticLocking annotation specifies the type of optimistic locking to use when updating or deleting entities. Optimistic locking is supported on an @Entity or @MappedSuperclass annotation. The following attributes are available:

  • ALL_COLUMNS: This policy compares every field in the table in the WHERE clause when doing an update or a delete.

  • CHANGED_COLUMNS: This policy compares only the changed fields in the WHERE clause when doing an update. A delete operation will only compare the primary key.

  • SELECTED_COLUMNS: This policy compares selected fields in the WHERE clause when doing an update or a delete. The fields specified must be mapped and not be primary keys.

  • VERSION_COLUMN: (default) This policy allows a single version number to be used for optimistic locking. The version field must be mapped and not be the primary key. To automatically force a version field update on a parent object when its privately owned child object's version field changes, use the cascaded method set to true. The method is set to false by default.

At the descriptor level, configure optimistic locking by using the ClassDescriptor.setOptimisticLockingPolicy method to set an optimistic FieldsLockingPolicy instance. As with the annotation, the following policies are included:

  • AllFieldsLockingPolicy: This policy compares every field in the table in the WHERE clause when doing an update or a delete.

  • ChangedFieldsLockingPolicy: This policy compares only the changed fields in the WHERE clause when doing an update. A delete operation will only compare the primary key.

  • SelectedFieldsLockingPolicy: This policy compares selected fields in the WHERE clause when doing an update or a delete. The fields specified must be mapped and not be primary keys.

  • VersionLockingPolicy: This policy is used to allow a single version number to be used for optimistic locking. To automatically force a version field update on a parent object when its privately owned child object's version field changes, use the VersionLockingPolicy.setIsCascaded method set to true.

  • TimestampLockingPolicy: This policy is used to allow a single version timestamp to be used for optimistic locking.

5.2.1.5 Using Cache Coordination

Cache coordination synchronizes changes among distributed sessions. Cache coordination is most useful in application server clusters where the need to maintain consistent data for all applications can be challenging. Moreover, cache consistency becomes increasingly more difficult as the number of servers within an environment increases.

Cache coordination works by broadcasting notifications of transactional object changes among sessions (ServerSession or persistence unit) in the cluster. Cache coordination is most useful for application that are primarily read-based and when changes are performed by the same application operating with multiple, distributed sessions.

Cache coordination significantly minimizes stale data, but does not completely eliminate the possibility that stale data might occur. In addition, cache coordination reduces the number of optimistic lock exceptions encountered in a distributed architecture, and decreases the number of failed or repeated transactions in an application. However, cache coordination in no way eliminates the need for an effective locking policy. To ensure the most current data, use cache coordination with optimistic or pessimistic locking; optimistic locking is preferred.

Cache coordination is supported over RMI and JMS and can be configured either declaratively by using persistence properties in a persistence.xml file or by using the cache coordination API. System properties that match the persistence properties are available as well.

For additional details on cache coordination see:

http://wiki.eclipse.org/Introduction_to_Cache_%28ELUG%29#Cache_Coordination_2

Configuring JMS Cache Coordination Using Persistence Properties

The following example demonstrates how to configure cache coordination in the persistence.xml file and uses JMS for broadcast notification. For JMS, provide a JMS topic JNDI name and topic connection factory JNDI name in addition to the protocol. The JMS topic should not be JTA enabled and should not have persistent messages.

<property name="eclipselink.cache.coordination.protocol" value="jms" />
<property name="eclipselink.cache.coordination.jms.topic" 
   value="jms/EmployeeTopic" />
<property name="eclipselink.cache.coordination.jms.factory"
   value="jms/EmployeeTopicConnectionFactory" />

Applications that run in a cluster generally do not require a URL as the topic is enough to locate and use the resource. For applications that run outside the cluster, a URL is required. The following example is a URL for a WebLogic server cluster:

<property name="eclipselink.cache.coordination.jms.host"
   value="t3://myserver:7001/" />

A user name and password for accessing the servers can also be set if required. For example:

<property name="eclipselink.cache.coordination.jndi.user" value="user" />
<property name="eclipselink.cache.coordination.jndi.password" value="password" />

Configuring RMI Cache Coordination Using Persistence Properties

The following example demonstrates how to configure cache coordination in the persistence.xml file and uses RMI for broadcast notification.

<property name="eclipselink.cache.coordination.protocol" value="rmi" />

Applications that run in a cluster generally do not require a URL because JNDI is replicated and each server can look up each others listener. If an application runs outside of a cluster, or if JNDI is not replicated, then each server must provide its URL. This could be done through the persistence.xml file; however, different persistence.xml files (thus JAR or EAR) for each server is required, which is normally not desirable. A second option is to set the URL programmatically using the cache coordination API. See "Configuring Cache Coordination Using the Cache Coordination API". The final option is to set the URL as a system property on each application server. The following example sets the URL for a WebLogic server cluster using a system property:

-Declipselink.cache.coordination.jms.host=t3://myserver:7001/

A user name and password for accessing the servers can also be set if required; for example:

<property name="eclipselink.cache.coordination.jndi.user" value="user" /><property name="eclipselink.cache.coordination.jndi.password" value="password" />

RMI cache coordination can use either asynchronous or synchronous broadcasting; asynchronous is the default. Synchronous broadcasting ensures that all of the servers are updated before the request returns. The following example configures synchronous broadcasting.

<property name="eclipselink.cache.coordination.propagate-asynchronously"
   value="false" />

If multiple applications on the same server or network use cache coordination a separate channel can be used for each application. For example:

<property name="eclipselink.cache.coordination.channel" value="EmployeeChannel" />

Lastly, if required, change the default RMI multicast socket address that allows servers to find each other. The following example explicitly configures the multicast settings:

<property name="eclipselink.cache.coordination.rmi.announcement-delay"
   value="1000" />
<property name="eclipselink.cache.coordination.rmi.multicast-group"
   value="239.192.0.0" />
<property name="eclipselink.cache.coordination.rmi.multicast-group.port"
   value="3121" />
<property name="eclipselink.cache.coordination.packet-time-to-live" value="2" />

Configuring Cache Coordination Using the Cache Coordination API

Use the CommandManager interface to programmatically configure cache coordination for a session. The following example configures RMI cache configuration:

Session.getCommandManager().setShouldPropagateAsynchronously(boolean)

Session.getCommandManager().getDiscoveryManager().
    setAnnouncementDelay()
    setMulticastGroupAddress()
    setMulticastPort()
    setPacketTimeToLive()

Session.getCommandManager().getTransportManager().
    setEncryptedPassword()
    setInitialContextFactoryName()
    setLocalContextProperties(Hashtable)
    setNamingServiceType() //passing in one of:
        TransportManager.JNDI_NAMING_SERVICE
        TransportManager.REGISTRY_NAMING_SERVICE
    setPassword()
    setRemoteContextProperties(Hashtable)
    setShouldRemoveConnectionOnError()
    setUserName()

Setting Cache Synchronization

Cache synchronization determines how objects changes are broadcast among session members. The following synchronization modes are available:

  • SEND_OBJECT_CHANGES: (Default) This option sends a list of changed objects including data about the changes. This data is merged into the receiving cache.

  • INVALIDATE_CHANGED_OBJECTS: This option sends a list of the identities of the objects that have changed. The receiving cache invalidates the objects rather than changing any of the data.

  • SEND_NEW_OBJECTS_WITH_CHANGES: This option is the same as the SEND_OBJECT_CHANGES option except it also includes any newly created objects from the transaction.

  • NONE: This option does no cache coordination.

The @cache annotation coordinationType attribute is used to specify the synchronization mode. For example:

...
@Entity
@Cache(CacheCoordinationType.SEND_NEW_OBJECTS_CHANGES)
public class Employee {
  ...
}

The ObjectChangeSet.setCacheSynchronizationType method can also be used to set the synchronization mode. For example

setCacheSynchronizationType() // passing in one of:
    ClassDescriptor.DO_NOT_SEND_CHANGES
    ClassDescriptor.INVALIDATE_CHANGED_OBJECTS
    ClassDescriptor.SEND_NEW_OBJECTS_WITH_CHANGES
    ClassDescriptor.SEND_OBJECT_CHANGES

5.2.2 Task 2: Ensure TopLink is Enabled

Ensure the TopLink JAR files are included on the classpath of each application server in the cluster to which the TopLink application is deployed and configure TopLink as the persistence provider. See Chapter 2, "Using TopLink with WebLogic Server," and Chapter 3, "Using TopLink with GlassFish Server," for detailed instructions on setting up TopLink with WebLogic server and GlassFish, respectively.

5.2.3 Task 3: Ensure All Application Servers are Part of the Cluster

Configure an application server cluster that includes each application server that hosts the TopLink application:

5.3 Additional Resources

The following additional resources are available:

5.3.2 Related JavaDoc

For more information, see the following APIs in Oracle Fusion Middleware Java API Reference for Oracle TopLink.

  • org.eclipse.persistence.annotations.OptimisticLocking

  • org.eclipse.persistence.annotations.Cache

  • org.eclipse.persistence.descriptors.ClassDescriptor

  • org.eclipse.persistence.sessions.coordination