Cache invalidation
Cache invalidation is a framework that allows you to invalidate (or evict) EJB entity instances from its container's instance cache by passing a set of their primary keys.
You can invalidate both local cache and a cache on a remote machine in the cluster.
Why is this useful? Use-cases?!
Suppose, we have data that is more often read than updated, i.e. read-mostly data. We would like it to be cached between transactions instead of loading the same data on each request from the database. Ok, we can do it by using commit option A. Since we have a cache that is accessed concurrently by transactions, we have to use pessimistic locking for cached instances. But in our case, since the data is mostly read-only, it's not efficient, i.e. there is no sense to obtain an exclusive lock for read operation. Ok, we can set <read-only>true</read-only> in jboss.xml to avoid transaction-long pessimistic locking (exclusive lock will still be obtained for the duration of the invocation which is needed when we load the data from the database). But then we can't update the data when we really need to.
One of the solutions to this problem is to have two containers for the same data: one is for read-only access with caching between transactions (RO) and the other one is for read and write operations (RW) (with or without caching). Then, when we modify the data with the RW container, we will notify the RO container that the data is updated and should be reloaded from the database.
Or, suppose, we have a cluster. And we want to use containers with commit option A on each machine in the cluster. But if an instance is updated on one machine (and consequently in the database) the other machines in the cluster will still have the old data in the cache. These other machines should be notified that the instance is updated and should be reloaded form the database.
Cache invalidation is the mechanism to send these update notifications to RO containers when RW containers commit changes.
Configuration
Let's look at how the containers are configured with cache invalidation.
So, we have:
containers that receive notifications
containers that send notifications
and for sending containers we should provide some kind of an address to send notifications to
Note, a container can be both: sending and receiving notifications. For example, an RW container with commit option A in a cluster would be interested to receive a notification that some other container in the cluster which provides access to the same persistent data updated the data in the database.
The part of the container that receives the notification is its cache. Specifically, the cache implementation that receives cache invalidation events is
org.jboss.ejb.plugins.InvalidableEntityInstanceCache
The invalidation event is triggered by transaction commit (actually it is sent from an implementation of javax.transaction.Synchronization from afterCompletion method). All we need to do is to give it the primary keys of the instances that were modified. And this is done by the following interceptor:
org.jboss.cache.invalidation.triggers.EntityBeanCacheBatchInvalidatorInterceptor
The address of notifications is an invalidation group. Containers that represent the same persistent data are organized into the same invalidation group. When one of the containers updates a set of instances, it sends a notification to its invalidation group and the other containers invalidate (evict) the instances from their caches.
Container configurations based on 'Standard CMP 2.x EntityBean'
For example we have an entity bean A, we have a cluster and want to use commit option A on each machine in the cluster. The jboss.xml deployment descriptor will look like this:
<jboss> <enterprise-beans> <entity> <ejb-name>A</ejb-name> <local-jndi-name>ALocal</local-jndi-name> <configuration-name>Standard CMP 2.x EntityBean with cache invalidation</configuration-name> <cache-invalidation>true</cache-invalidation> <cache-invalidation-config> <invalidation-group-name>AGroup</invalidation-group-name> </cache-invalidation-config> </entity> <!-- other entity and session beans ... --> </enterprise-beans> </jboss>
The 'Standard CMP 2.x EntityBean with cache invalidation' is the predefined container configuration in standardjboss.xml which already has invalidable instance cache and the cache invalidation interceptor. The only thing you need to set explicitly is cache-invalidation (true) and invalidation-group-name (AGroup in our case). You will also need to configure JMS cache invalidation bridge in the cache-invalidation-service.xml to send notifications across the cluster (see the comments in cache-invalidation-service.xml).
That's it! But if you don't have a cluster but have just RO and RW containers on one machine you can further optimize the containers.
<container-configuration> <container-name>RO CMP2.x EntityBean for cache invalidation</container-name> <call-logging>false</call-logging> <invoker-proxy-binding-name>entity-rmi-invoker</invoker-proxy-binding-name> <container-interceptors> <interceptor>org.jboss.ejb.plugins.ProxyFactoryFinderInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.TxInterceptorCMT</interceptor> <interceptor>org.jboss.ejb.plugins.EntityCreationInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityLockInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityInstanceInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityReentranceInterceptor</interceptor> <interceptor>org.jboss.resource.connectionmanager.CachedConnectionInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntitySynchronizationInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.cmp.jdbc.JDBCRelationInterceptor</interceptor> </container-interceptors> <instance-pool>org.jboss.ejb.plugins.EntityInstancePool</instance-pool> <instance-cache>org.jboss.ejb.plugins.InvalidableEntityInstanceCache</instance-cache> <persistence-manager>org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager</persistence-manager> <locking-policy>org.jboss.ejb.plugins.lock.QueuedPessimisticEJBLock</locking-policy> <container-cache-conf> <cache-policy>org.jboss.ejb.plugins.LRUEnterpriseContextCachePolicy</cache-policy> <cache-policy-conf> <min-capacity>50</min-capacity> <max-capacity>1000000</max-capacity> <overager-period>300</overager-period> <max-bean-age>600</max-bean-age> <resizer-period>400</resizer-period> <max-cache-miss-period>60</max-cache-miss-period> <min-cache-miss-period>1</min-cache-miss-period> <cache-load-factor>0.75</cache-load-factor> </cache-policy-conf> </container-cache-conf> <container-pool-conf> <MaximumSize>100</MaximumSize> </container-pool-conf> <commit-option>A</commit-option> </container-configuration>
The read-only container is not supposed to modify data, so we can remove the cache invalidation interceptor from the stack and leave just the invalidable cache.
<container-configuration> <container-name>RW CMP 2.x EntityBean for cache invalidation</container-name> <call-logging>false</call-logging> <invoker-proxy-binding-name>entity-rmi-invoker</invoker-proxy-binding-name> <container-interceptors> <interceptor>org.jboss.ejb.plugins.ProxyFactoryFinderInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.TxInterceptorCMT</interceptor> <interceptor>org.jboss.ejb.plugins.EntityCreationInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityLockInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityInstanceInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityReentranceInterceptor</interceptor> <interceptor>org.jboss.resource.connectionmanager.CachedConnectionInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntitySynchronizationInterceptor</interceptor> <interceptor>org.jboss.cache.invalidation.triggers.EntityBeanCacheBatchInvalidatorInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.cmp.jdbc.JDBCRelationInterceptor</interceptor> </container-interceptors> <instance-pool>org.jboss.ejb.plugins.EntityInstancePool</instance-pool> <instance-cache>org.jboss.ejb.plugins.EntityInstanceCache</instance-cache> <persistence-manager>org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager</persistence-manager> <locking-policy>org.jboss.ejb.plugins.lock.QueuedPessimisticEJBLock</locking-policy> <container-cache-conf> <cache-policy>org.jboss.ejb.plugins.LRUEnterpriseContextCachePolicy</cache-policy> <cache-policy-conf> <min-capacity>50</min-capacity> <max-capacity>1000000</max-capacity> <overager-period>300</overager-period> <max-bean-age>600</max-bean-age> <resizer-period>400</resizer-period> <max-cache-miss-period>60</max-cache-miss-period> <min-cache-miss-period>1</min-cache-miss-period> <cache-load-factor>0.75</cache-load-factor> </cache-policy-conf> </container-cache-conf> <container-pool-conf> <MaximumSize>100</MaximumSize> </container-pool-conf> <commit-option>A</commit-option> </container-configuration>
The read/write container is supposed to modify the data, so the interceptor is in the stack. But we would not like to invalidate the container's cache, so we use the default instance cache. Alternatively, we could base the RW container on 'Instance Per Transaction CMP 2.x EntityBean' container by just adding the cache invalidation interceptor to its stack.
The jboss.xml now would look like this:
<jboss> <enterprise-beans> <entity> <ejb-name>ARO</ejb-name> <local-jndi-name>AROLocal</local-jndi-name> <configuration-name>RO CMP2.x EntityBean for cache invalidation</configuration-name> <cache-invalidation>true</cache-invalidation> <cache-invalidation-config> <invalidation-group-name>AGroup</invalidation-group-name> </cache-invalidation-config> </entity> <entity> <ejb-name>ARW</ejb-name> <local-jndi-name>ARWLocal</local-jndi-name> <configuration-name>RW CMP 2.x EntityBean for cache invalidation</configuration-name> <cache-invalidation>true</cache-invalidation> <cache-invalidation-config> <invalidation-group-name>AGroup</invalidation-group-name> </cache-invalidation-config> </entity> </enterprise-beans> <container-configurations> <!-- container configurations defined above ... --> </container-configurations> </jboss>
Container configurations based on 'cmp2.x jdbc2 pm' (since 3.2.6)
The entity elements in jboss.xml will look exactly the same. So below is just an equivalent to 'Standard CMP 2.x EntityBean with cache invalidation':
<container-configuration> <container-name>cmp2.x jdbc2 pm with cache invalidation</container-name> <call-logging>false</call-logging> <invoker-proxy-binding-name>entity-rmi-invoker</invoker-proxy-binding-name> <sync-on-commit-only>false</sync-on-commit-only> <insert-after-ejb-post-create>true</insert-after-ejb-post-create> <call-ejb-store-on-clean>true</call-ejb-store-on-clean> <container-interceptors> <interceptor>org.jboss.ejb.plugins.ProxyFactoryFinderInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.TxInterceptorCMT</interceptor> <interceptor>org.jboss.ejb.plugins.EntityCreationInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityInstanceInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntityReentranceInterceptor</interceptor> <interceptor>org.jboss.resource.connectionmanager.CachedConnectionInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.EntitySynchronizationInterceptor</interceptor> <interceptor>org.jboss.cache.invalidation.triggers.EntityBeanCacheBatchInvalidatorInterceptor</interceptor> <interceptor>org.jboss.ejb.plugins.cmp.jdbc2.RelationInterceptor</interceptor> </container-interceptors> <instance-pool>org.jboss.ejb.plugins.EntityInstancePool</instance-pool> <instance-cache>org.jboss.ejb.plugins.PerTxEntityInstanceCache</instance-cache> <persistence-manager>org.jboss.ejb.plugins.cmp.jdbc2.JDBCStoreManager2</persistence-manager> <locking-policy>org.jboss.ejb.plugins.lock.NoLock</locking-policy> <container-cache-conf> <cache-policy-conf> <min-capacity>500</min-capacity> <max-capacity>10000</max-capacity> </cache-policy-conf> <cache-policy-conf-other> <partitions>10</partitions> <!-- uncomment to use JDBC java.sql.Statement.executeBatch() <batch-commit-strategy></batch-commit-strategy> --> <invalidable></invalidable> </cache-policy-conf-other> </container-cache-conf> <container-pool-conf> <MaximumSize>100</MaximumSize> </container-pool-conf> <commit-option>C</commit-option> <!-- don't change, irrelevant, use container-cache-conf --> </container-configuration>
The changes are the cache invalidation interceptor in the stack and <invalidable/> tag in the cache-policy-conf-other.
Remove the cache invalidation interceptor to get an equivalent of 'RO CMP2.x EntityBean for cache invalidation'.
Remove the <invalidable/> and you will get an equivalent of 'RW CMP 2.x EntityBean for cache invalidation'.
Custom Primary Keys in Clustered Environments
The use of custom primary key classes for your entities can cause problems if the Cache Invalidation Framework is used in a clustered environment. Clustered invalidations work by sending a message around the cluster; this message includes the serialized primary key of the bean to invalidate. The other nodes in the cluster need to be able to deserialize this message, and in order to do this they must have access to the primary key's class. This class will only be available to the CIF if the custom primary key class is located in the server/.../lib directory. If the primary key is loaded from the deploy directory (i.e. it is included in a deployed ear or jar), the class will not be visible to the CIF and clustered invalidation will fail.
If you place your custom primary key classes in server/.../lib, be sure not to leave a copy packaged with your deployment in deploy. If the class is located in both places, type-mismatches can occur, where the primary key of a cached bean does not match that of a replicated invalidation message solely because the two objects' class were loaded by different classloaders. In this situation, invalidations will not work properly.
Comments