Call us: +1-415-738-4000
Enterprise Ehcache includes a rich API for extending your application’s capabilities.
Enterprise Ehcache Search is a powerful search API for querying clustered caches in a Terracotta cluster. Designed to be easy to integrate with existing projects, the Ehcache Search API can be implemented with configuration or programmatically. The following is an example from an Ehcache configuration file:
<cache name="myCache" maxElementsInMemory="0" eternal="true"
overflowToDisk="false">
<searchable>
<searchAttribute name="age" />
<searchAttribute name="first_name" expression="value.getFirstName()" />
<searchAttribute name="last_name" expression="value.getLastName()" />
<searchAttribute name="zip_code" expression="value.getZipCode()" />
</searchable>
<terracotta />
</cache>
The following example assumes there is a Person class that serves as the value for elements in myCache. With the exception of "age" (which is bean style), each expression attribute in searchAttribute is set to use an accessor method on the cache element's value. The Person class must have accessor methods to match the configured expressions. In addition, assume that there is code that populates the cache. Here is an example of search code based on these assumptions:
// After CacheManager and Cache created, create a query for myCache:
Query query = myCache.createQuery();
// Create the Attribute objects.
Attribute<String> last_name = myCache.getSearchAttribute("last_name");
Attribute<Integer> zip_code = myCache.getSearchAttribute("zip_code");
Attribute<Integer> age = myCache.getSearchAttribute("age");
// Specify the type of content for the result set.
// Executing the query without specifying desired results
// returns no results even if there are hits.
query.includeKeys(); // Return the keys for values that are hits.
// Define the search criteria.
// This following uses Criteria.and() to set criteria to find adults
// with the last name "Marley" whose address has the zip code "94102".
query.addCriteria(last_name.eq("Marley").and(zip_code.eq(94102)));
// Execute the query, putting the result set
// (keys to element that meet the search criteria) in Results object.
Results results = query.execute();
// Find the number of results -- the number of hits.
int size = results.size();
// Discard the results when done to free up cache resources.
results.discard();
// Using an aggregator in a query to get an average age of adults:
Query averageAgeOfAdultsQuery = myCache.createQuery();
averageAgeOfAdultsQuery.addCriteria(age.ge(18));
averageAgeOfAdultsQuery.includeAggregator(age.average());
Results averageAgeOfAdults = averageAgeOfAdultsQuery.execute();
If (averageAgeOfAdults.size() > 0) {
List aggregateResults = averageAgeOfAdults.all().iterator().next().getAggregatorResults();
double averageAge = (Double) aggregateResults.get(0);
}
The following example shows how to programmatically create the cache configuration, with search attributes.
Configuration cacheManagerConfig = new Configuration();
CacheConfiguration cacheConfig = new CacheConfiguration("myCache", 0).eternal(true);
Searchable searchable = new Searchable();
cacheConfig.addSearchable(searchable);
// Create attributes to use in queries.
searchable.addSearchAttribute(new SearchAttribute().name("age"));
// Use an expression for accessing values.
searchable.addSearchAttribute(new SearchAttribute()
.name("first_name")
.expression("value.getFirstName()"));
searchable.addSearchAttribute(new SearchAttribute().name("last_name").expression("value.getLastName()"));
searchable.addSearchAttribute(new SearchAttribute().name("zip_code").expression("value.getZipCode()"));
cacheManager = new CacheManager(cacheManagerConfig);
cacheManager.addCache(new Cache(cacheConfig));
Ehcache myCache = cacheManager.getEhcache("myCache");
// Now create the attributes and queries, then execute.
...
To learn more about the Ehcache Search API, see the net.sf.ehcache.search* packages in this Javadoc.
Searches occur on indexes held by the Terracotta server. By default, index files are stored in /index under the server's data directory. However, you can specify a different path using the <index> element:
...
<server>
<data>%(user.home)/terracotta/server-data</data>
<index>%(user.home)/terracotta/index</index>
<logs>%(user.home)/terracotta/server-logs</logs>
<statistics>%(user.home)/terracotta/server-statistics</statistics>
...
</server>
...
To enhance performance, it is recommended that you store server data and search indexes on different disks.
This section contains information on avoiding potential issues with using the Ehcache Search API.
If your query returns a result set containing a very large amount of data, performance degradation or errors (such as OOME) could occur due to network and resource limitations. You can manage the size of result sets by following these best practices:
Query.maxResults(int number_of_hits).Results.range(int start_index, int number_of_hits).net.sf.ehcache.search.aggregator package in this Javadoc).Ehcache Search guarantees that all local changes made before a query is executed are available to search results. However, to maintain a high level of performance, cluster locks are not checked. This eventual synchronization of clustered caches and search indexes means that remote changes may not be available to queries until the changes are applied cluster wide (or, for transactional caches, commit() is called). Thus it is possible to get results that include removed elements, inconsistent data from the same query executed at different times, and calculated values that are no longer accurate (such as those returned by aggregators).
You can take certain precautions to prevent these types of problems. For example, if your search uses aggregators, add all aggregators to the same query to get consistent data. If your code attempts to get values using keys returned by a query, use null guards.
Search strings leading with the wildcard character asterisk ("*") or question mark ("?") can be very slow to return results.
Failure to return is handled by a CacheException. If an exception occurs when the value extractor (either expression-based or custom) executes, the affected attribute value is omitted from the search index.
The Enterprise Ehcache cluster events API provides access to Terracotta cluster events and cluster topology.
The interface net.sf.ehcache.cluster.CacheCluster provides methods for obtaining topology information for a Terracotta cluster. The following table lists these methods.
| Method | Definition |
|---|---|
String getScheme() |
Returns a scheme name for the cluster information. Currently TERRACOTTA is the only scheme supported. The scheme name is used by CacheManager.getCluster() to return cluster information (see Events API Example Code).
|
Collection<ClusterNode> getNodes()
|
Returns information on all the nodes in the cluster, including ID, hostname, and IP address. |
boolean addTopologyListener(ClusterTopologyListener listener)
|
Adds a cluster-events listener. Returns true if the listener is already active. |
boolean removeTopologyListener(ClusterTopologyListener)
|
Removes a cluster-events listener. Returns true if the listener is already inactive. |
The interface net.sf.ehcache.cluster.ClusterNode provides methods for obtaining information on specific Terracotta nodes in the cluster. The following table lists these methods.
| Method | Definition |
|---|---|
getId()
|
Returns the unique ID assigned to the node. |
getHostname()
|
Return the hostname on which the node is running. |
getIp()
|
Return the IP address on which the node is running. |

The interface net.sf.ehcache.cluster.ClusterTopologyListener provides methods for detecting the following cluster events:
// Get cluster data
CacheManager mgr = new CacheManager(); // Local ehcache.xml exists, with at least one cache configured with Terracotta clustering.
CacheCluster cluster = mgr.getCluster("TERRACOTTA");
// Get current nodes
Collection<ClusterNode> nodes = cluster.getNodes();
for(ClusterNode node : nodes) {
System.out.println(node.getId() + " " + node.getHostname() + " " + node.getIp());
}
// Register listener
cluster.addTopologyListener(new ClusterTopologyListener() {
public void nodeJoined(ClusterNode node) { System.out.println(node + " joined"); }
public void nodeLeft(ClusterNode node) { System.out.println(node + " left"); }
public void clusterOnline(ClusterNode node) { System.out.println(node + " enabled"); }
public void clusterOffline(ClusterNode node) { System.out.println(node + " disabled"); }
});
If a CacheManager instance is created and configured programmatically (without an ehcache.xml or other external configuration resource), getCluster("TERRACOTTA") may return null even if a Terracotta cluster exists. To ensure that cluster information is returned in this case, get a cache that is clustered with Terracotta:
|
Since the current node joins the cluster before code adding the topology listener runs, the current node may never receive the nodeJoined event. You can detect if the current node is in the cluster by checking if the cluster is online:
cluster.addTopologyListener(cacheListener);
if(cluster.isClusterOnline()) {
cacheListener.clusterOnline(cluster.getCurrentNode());
}
The Enterprise Ehcache bulk-load API can optimize bulk-loading of caches by removing the requirement for locks and adding transaction batching. The bulk-load API also allows applications to discover whether a cache is in bulk-load mode and to block based on that mode.
The initial consistency mode of a cache is set by configuration and cannot be changed programmatically (see the attribute "consistency" in <terracotta>). The bulk-load API should be used for temporarily suspending the configured consistency mode to allow for bulk-load operations.
|
The following table lists the bulk-load API methods that are available in org.terracotta.modules.ehcache.Cache.
| Method | Definition |
|---|---|
public boolean isClusterBulkLoadEnabled()
|
Returns true if a cache is in bulk-load mode (is not consistent) throughout the cluster. Returns false if the cache is not in bulk-load mode (is consistent) anywhere in the cluster. |
public boolean isNodeBulkLoadEnabled()
|
Returns true if a cache is in bulk-load mode (is not consistent) on the current node. Returns false if the cache is not in bulk-load mode (is consistent) on the current node. |
public void setNodeBulkLoadEnabled(boolean)
|
Sets a cache’s consistency mode to the configured mode (false) or to bulk load (true) on the local node. There is no operation if the cache is already in the mode specified by setNodeBulkLoadEnabled(). When using this method on a nonstop cache, a multiple of the nonstop cache’s timeout value applies. The bulk-load operation must complete within that timeout multiple to prevent the configured nonstop behavior from taking effect. For more information on tuning nonstop timeouts, see Tuning Nonstop Timeouts and Behaviors.
|
public void waitUntilBulkLoadComplete()
|
Waits until a cache is consistent before returning. Changes are automatically batched and the cache is updated throughout the cluster. Returns immediately if a cache is consistent throughout the cluster. |
Note the following about using bulk-load mode:
isClusterBulkLoadEnabled() can return false in one node just before another node calls setNodeBulkLoadEnabled(true) on the same cache. Understanding exactly how your application uses the bulk-load API is crucial to effectively managing the integrity of cached data.get() methods that fail with ObjectNotFound return null.The following example code shows how a clustered application with Enterprise Ehcache can use the bulk-load API to optimize a bulk-load operation:
import net.sf.ehcache.Cache;
public class MyBulkLoader {
CacheManager cacheManager = new CacheManager(); // Assumes local ehcache.xml.
Cache cache = cacheManager.getEhcache("myCache"); // myCache defined in ehcache.xml.
cache.setNodeBulkLoadEnabled(true); // myCache is now in bulk mode.
// Load data into myCache.
cache.setNodeBulkLoadEnabled(false); // Done, now set myCache back to its configured consistency mode.
}
On another node, application code that intends to touch myCache can run or wait, based on whether myCache is consistent or not:
...
if (!cache.isClusterBulkLoadEnabled()) {
// Do some work.
}
else {
cache.waitUntilBulkLoadComplete()
// Do the work when waitUntilBulkLoadComplete() returns.
}
...
Waiting may not be necessary if the code can handle potentially stale data:
...
if (!cache.isClusterBulkLoadEnabled()) {
// Do some work.
}
else {
// Do some work knowing that data in myCache may be stale.
}
...
Certain environments require consistent cached data while also needing to provide optimized reads of that data. For example, a financial application may need to display account data as a result of a large number of requests from web clients. The performance impact of these requests can be reduced by allowing unlocked reads of an otherwise locked cache.
In cases where there is tolerance for getting potentially stale data, an unlocked (inconsistent) reads view can be created for Cache types using the UnlockedReadsView decorator. UnlockedReadsView requires Ehcache 2.1 or higher. The underlying cache must have Terracotta clustering and use the strong consistency mode. For example, the following cache can be decorated with UnlockedReadsView:
<cache name="myCache"
maxElementsInMemory="500"
eternal="false"
overflowToDisk="false"
<terracotta clustered="true" consistency="strong" />
</cache>
You can create an unlocked view of myCache programmatically:
Cache cache = cacheManager.getEhcache("myCache");
UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");
The following table lists the API methods available with the decorator net.sf.ehcache.constructs.unlockedreadsview.UnlockedReadsView.
| Method | Definition |
|---|---|
public String getName()
|
Returns the name of the unlocked cache view. |
public Element get(final Object key)
|
Returns the data under the given key. Returns null if data has expired. |
public Element getQuiet(final Object key)
|
Returns the data under the given key without updating cache statistics. Returns null if data has expired. |
By default, caches have the following attributes set as shown:
<cache ... copyOnRead="true" ... >
...
<terracotta ... consistency="strong" ... />
...
</cache>
Default settings are designed to make distributed caches more efficient and consistent in most use cases.
The explicit locking methods for Enterprise Ehcache provide simple key-based locking that preserves concurrency while also imposing cluster-wide consistency. If certain operations on cache elements must be locked, use the explicit locking methods available in the Cache type.
The explicit locking methods are listed in the following table:
public void acquireReadLockOnKey(Object key)
|
Set a read lock on the element specified by the argument (key). |
public void acquireWriteLockOnKey(Object key)
|
Set a write lock on the element specified by the argument (key). |
public void releaseReadLockOnKey(Object key)
|
Remove a read lock from the element specified by the argument (key). |
public void releaseReadLockOnKey(Object key)
|
Remove a write lock from the element specified by the argument (key). |
public boolean isReadLockedByCurrentThread(Object key)
|
Returns true if the current thread holds a read lock on the element specified by the argument (key). |
public boolean isWriteLockedByCurrentThread(Object key)
|
Returns true if the current thread holds a write lock on the element specified by the argument (key). |
The following example shows how to use explicit locking methods:
String key1 = "123";
Foo val1 = new Foo();
cache.acquireWriteLockOnKey(key1);
try {
cache.put(new Element(key1, val1));
} finally {
cache.releaseWriteLockOnKey(key1);
}
// Now safely read val1.
cache.acquireReadLockOnKey(key1);
try {
Object cachedVal1 = cache.get(key1).getValue();
} finally {
cache.releaseReadLockOnKey(key);
}
For locking available through the Terracotta Toolkit API, see Locks.
You can configure clustered CacheManagers and caches using the fluent interface as follows:
...
Configuration configuration =
new Configuration().terracotta(newTerracottaClientConfiguration()
.url("localhost:9510"))
// == <terracottaConfig url="localhost:9510 />
.defaultCache(new CacheConfiguration("defaultCache", 100))
// == <defaultCache maxElementsInMemory="100" ... />
.cache(new CacheConfiguration("example", 100)
// == <cache name="example" maxElementsInMemory="100" ... />
.timeToIdleSeconds(5)
.timeToLiveSeconds(120)
// added these TTI and TTL attributes to the cache "example"
.terracotta(new TerracottaConfiguration()));
// added <terracotta /> element in the cache "example"
// Pass the configuration to the CacheManager.
this.cacheManager = new CacheManager(configuration);
...
If your application uses the write-behind API with Ehcache and you cluster Ehcache with Terracotta, the write-behind queue automatically becomes a clustered write-behind queue. The clustered write-behind queue features the following characteristics:
The write-behind queue is enabled for a cache with the <cacheWriter /> element. For example:
<cache name="myCache" eternal="false" maxElementsInMemory="1000" overflowToDisk="false">
<cacheWriter writeMode="write_behind" maxWriteDelay="8" rateLimitPerSecond="5" writeCoalescing="true" writeBatching="true" writeBatchSize="20" writeBehindMaxQueueSize="500" retryAttempts="2" retryAttemptDelaySeconds="2">
<cacheWriterFactory class="com.company.MyCacheWriterFactory"
properties="just.some.property=test; another.property=test2" propertySeparator=";"/>
</cacheWriter>
</cache>
Values for <cacheWriter /> attributes can also be set programmatically. For example, the value for writeBehindMaxQueueSize, which sets the maximum number of pending writes (the maximum number of elements that can be waiting in the queue for processing), can be set with net.sf.ehcache.config.CacheWriterConfiguration.setWriteBehindMaxQueueSize().
See the Ehcache documentation for more information on the write-behind API and on using synchronous write-through caching.