2.2. Key JBoss Cache Behaviors

JBoss Cache is a very flexible tool and includes a great number of configuration options. See the JBoss Cache User Guide for an in depth discussion of these options. Here we focus on the main concepts that are most important to the Second Level Cache use case. This discussion will focus on concepts; see Section 3.2, “Configuring JBoss Cache” for details on the actual configurations involved.

2.2.1. Replication vs. Invalidation vs. Local Mode

JBoss Cache provides three different choices for how a node in the cluster should interact with the rest of the cluster when its local state is updated:

  • Replication: The updated cache will send its new state (e.g. the new values for an entity's fields) to the other members of the cluster. This is heavy in terms of the size of network traffic, since the new state needs to be transmitted. It also has the effect forcing the data that is cached in each node in the cluster to be the same, even if the users accessing the various nodes are interested in different data. So, if a user on node A reads an Order entity with primary key 12343439485030 from the database, with replication that entity will be cached on every node in the cluster, even though no other users are interested in that particular Order.

    Because of these downsides, replication is generally not the best choice for entity, collection and query caching. However, in a cluster replication is the only valid choice for the timestamps cache.

  • Invalidation: The updated cache will send a message to the other members of the cluster telling them that a particular piece of data (e.g. a particular entity) has been modified. Upon receipt of this message, the other nodes will remove this data from their local cache, if it is stored there. Invalidation is lighter than replication in terms of network traffic, since only the "id" of the data needs to be transmitted, not the entire new state. The downside to invalidation is that if the invalidated data is needed again, it has to be re-read from the database. However, in most cases data that many nodes in the cluster all want to have in memory is not data that is frequently changed.

    Invalidation makes no sense for query caching; if it is used for a query cache region the Hibernate/JBoss Cache integration will detect this and switch any query cache related calls to Local mode. Invalidation must not be used for timestamp caching; Hibernate will throw an exception during session factory startup if it finds that it is.

  • Local: The updated cache does not even know if there are any other nodes, and will not attempt to update them. If JBoss Cache is used as a Second Level Cache in a non-clustered environment, Local mode should be used. If there is a cluster, Local mode should never be used for entity, collection or timestamp caching. Local mode can be used for query caching in a cluster, since the replicated timestamps cache will ensure that outdated cached queries are not used. Often Local mode is the best choice for query caching, as query results can be large and the cost of replicating them high.

    If the same underlying JBoss Cache instance is used for the query cache and any of the other caches, the SessionFactory can be configured to suppress replication of the queries, essentially making the queries operate in Local mode. This is done by adding the following configuration:

    hibernate.cache.region.jbc2.query.localonly=true

    If the JBoss Cache instance that the query cache is using is configured for invalidation, setting this property isn't even necessary; the Hibernate/JBoss Cache integration will detect this condition and switch any query cache-related calls to Local mode.

2.2.2. Synchronous vs. Asynchronous

In JBoss Cache terms, synchronous vs. asynchronous refers to whether a thread that initiates a cluster-wide message blocks until that message has been received, processed and acknowledged by the other members of the cluster. Synchronous means the thread blocks; asynchronous means the message is sent and the thread immediately returns; more of a "fire and forget". An example of a message would be a set of cache inserts, updates and removes sent out to the cluster as part of a transaction commit.

In almost all cases, the cache should be configured to use synchronous messages, as these are the only way to ensure data consistency across the cluster. JBoss Cache supports programatically overriding the default configured behavior on a per-call basis, so for the special cases where sending a message asynchronously is appropriate, the Hibernate/JBoss Cache integration code will force the call to go asynchronously. So, configure your caches to use synchronous messages.

2.2.3. Locking Scheme

JBoss Cache supports both optimistic and pessimistic locking schemes. See the JBoss Cache User Guide for an in depth discussion of these options. In the Second Level Cache use case, the main benefit of optimistic locking is that updates of cached entities by one transaction do not block reads of the cached entity by other transactions, yet REPEATABLE_READ semantics are preserved.

If you are using optimistic locking and data versioning in Hibernate for your entities, you should use it in the entity cache as well. If you are not using data versioning, pessimistic locking should be a better choice. Optimistic locking has a higher level of runtime overhead than pessimistic locking, and in most Second Level Cache use cases a REPEATABLE_READ semantic from the cache is not required (see Section 2.2.4, “Isolation Level” for details on why not).

2.2.4. Isolation Level

If the PESSIMISTIC node locking scheme is used, JBoss Cache supports a number of different isolation level configurations that specify how different transactions coordinate the locking of nodes in the cache. These are somewhat analogous to database isolation levels; see the JBoss Cache User Guide for an in depth discussion of these options. For the Second Level Cache use case, only two are valid: READ_COMMITTED and REPEATABLE_READ. In both cases, cache reads do not block for other reads. In both cases a transaction that writes to a node in the cache tree will hold an exclusive lock on that node until the transaction commits, causing other transactions that wish to read the node to block. In the REPEATABLE_READ case, the read lock held by an uncommitted transaction that has read a node will cause another transaction wishing to write to that node to block until the read transaction commits. This ensures the reader transaction can read the node again and get the same results, i.e. have a repeatable read.

READ_COMMITTED allows the greatest concurrency, since reads don't block each other and also don't block a write.

If the OPTIMISTIC node locking scheme is used, any isolation level configuration is ignored by the cache. Optimistic locking provides a repeatable read semantic but does not cause writes to block for reads.

In most cases, a REPEATABLE_READ setting on the cache is not needed, even if the application wants repeatable read semantics. This is because the Second Level Cache is just that -- a secondary cache. The primary cache for an entity or collection is the Hibernate Session object itself. Once an entity or collection is read from the second level cache, it is cached in the Session for the life of the transaction. Subsequent reads of that entity/collection will be resolved from the Session cache itself -- there will be no repeated read of the Second Level Cache by that transaction. So, there is no benefit to a REPEATABLE_READ configuration in the Second Level Cache.

The only exception to this is if the application uses Session's evict() or clear() methods to remove data from the Session cache and during the course of the same transaction wants to read that same data again with a repeatable read semantic.

Note that for query and timestamp caches, the behavior of the Hibernate/JBC integration will not allow repeatable read semantics even if JBC is configured for REPEATABLE_READ. A cache read will not result in a read lock in the cache being held for the life of the transaction. So, for these caches there is no benefit to a REPEATABLE_READ configuration.

2.2.5. Initial State Transfer

When a new node joins a running cluster, it can request from an existing member a copy of that member's current cache contents. This process is known as an initial state transfer. Doing an initial state transfer allows the new member to have a "hot cache"; i.e. as user requests come in, data will already be cached, helping avoid an initial set of reads from the database.

However, doing an initial state transfer comes at a cost. The node providing the state needs to lock its entire tree, serialize it and send it over the network. This could be a large amount of data and the transfer could take a considerable period of time to process. While the transfer is ongoing, the state provider is holding locks on its cache, preventing any local or replicated updates from proceeeding. All work around the cache can come to a halt for a period.

Because of this cost, we generally recommend avoiding initial state transfers in the second level cache use case. The exception to this is the timestamps cache. For the timestamps cache, an initial state transfer is required.

2.2.6. Cache Eviction

Eviction refers to the process by which old, relatively unused, or excessively voluminous data can be dropped from the cache, allowing the cache to remain within a memory budget. Generally, applications that use the Second Level Cache should configure eviction. See Chapter 4, Cache Eviction for details.

2.2.7. Buddy Replication and Cache Loading

Buddy replication refers to a JBoss Cache feature whereby each node in the cluster replicates its cached data to a limited number (often one) of "buddies" rather than to all nodes in the cluster. Buddy replication should not be used in a Second Level Cache. It is intended for use cases where one node in the cluster "owns" some data , and just wants to make a backup copy of the data to provide high availability. The data (e.g. a web session) is never meant to be accessed simultaneously on two different nodes in the cluster. Second Level Cache data does not meet this "ownership" criteria; entities are meant to be simultaneously usable by all nodes in the cluster.

Cache Loading refers to a JBoss Cache feature whereby cached data can be persisted to disk. The persisted data either serves as a highly available copy of the data in case of a cluster restart, or as an overflow area for when the amount of cached data exceeds an application's memory budget. Cache loading should not be used in a Second Level Cache. The underlying database from which the cached data comes already serves the same functions; adding a cache loader to the mix is just wasteful.