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.

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:

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.

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:

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:

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.

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.

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.

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:

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:

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.