Quick Start

 

Products & Services

 

Training Dates

US - San Francisco
Oct 6-7, 2010
Nov 11-12, 2010
Dec 2-3, 2010
Feb 4-5, 2011

India - Delhi
Dec 2-3, 2010

See training details.

 

Blogs & Tweets

Distributed Caching With Terracotta

Terracotta has been integrated with Ehcache since Ehcache 1.4.

From version 1.7 Ehcache has been seamlessly integrated with Terracotta 3.1.1 and takes just a few lines of config in ehcache.xml to get up and running.

New feature in Ehcache 2.0 In Ehcache 2.0 additional configuration options have been added which provide finer grained control.

Distribution with the Terracotta Server Array ("TSA") is the preferred distribution mechanism. It provides coherency, JTA, HA, scale and high performance. It is available as open source or with additional features in the Ehcache EX and FX product editions.

Architecture

Ehcache distributed with TSA is different to the other distribution mechanisms. They all replicate data, with 100% of data held in each node. Scaling is thus limited to how much can be comfortably held in each node. Replication is also not JTA transactional or guaranteed coherent.

With TSA the data is split between an Ehcache node, which is the L1 Cache and the TSA, which is the L2 Cache. As with the other replication mechanisms the L1 can hold as much data as is comfortable. All the rest lies in the L2. In Ehcache EX, each CacheManager can have only one logical TSA (there can be multiple redundant TSAs for HA). In Ehcache FX, the TSAs are striped for unlimited scale.

Data is held in-process in the Ehcache L1 for rapid access, however the data is also always in the TSA. So the cache is unaffected by termination of an Ehcache node. When the node comes back up it reconnects to the TSA L2 and as it uses data fills its local L1. There is thus no notion of a bootstrap as there is with the other distribution mechanisms.

Worked Example

As this example shows, running Ehcache with Terracotta clustering is no different from normal programmatic use.

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class TerracottaExample {
    CacheManager cacheManager = new CacheManager();

    public TerracottaExample() {
        Cache cache = cacheManager.getCache("sampleTerracottaCache");
        int cacheSize = cache.getKeys().size();
        cache.put(new Element("" + cacheSize, cacheSize));
        for (Object key : cache.getKeys()) {
            System.out.println("Key:" + key);
        }
    }

    public static void main(String[] args) throws Exception {
        new TerracottaExample();
    }
}

The above example looks for sampleTerracottaCache.

In ehcache.xml, we need to uncomment or add the following line:

    <terracottaConfig url="localhost:9510"/>

which tells Ehcache to load the Terracotta server config from localhost port 9510. Note: You must have a Terracotta 3.1.1 or higher server running locally for this example.

Next we want to enable Terracotta clustering for the cache named sampleTerracottaCache. Uncomment or add the following in ehcache.xml.

   <cache name="sampleTerracottaCache"
          maxElementsInMemory="1000"
          eternal="false"
          timeToIdleSeconds="3600"
          timeToLiveSeconds="1800"
          overflowToDisk="false">
       <terracotta/>
   </cache>

That's it!

Terracotta Configuration

Terracotta configuration in ehcache.xml is in three parts:

  • CacheManager Configuration
  • Terracotta Server Configuration
  • Enabling Terracotta clustering per cache

CacheManager Configuration

The attributes of ehcache are:

  • name

    an optional name for the CacheManager. The name is optional and primarily used for documentation or to distinguish Terracotta clustered cache state. With Terracotta clustered caches, a combination of CacheManager name and cache name uniquely identify a particular cache store in the Terracotta clustered memory.

    The name will show up in the Developer Consoles.

  • updateCheck

    an optional boolean flag specifying whether this CacheManager should check for new versions of Ehcache over the Internet. If not specified, updateCheck="true".

  • monitoring

    an optional setting that determines whether the CacheManager should automatically register the SampledCacheMBean with the system MBean server. Currently, this monitoring is only useful when using Terracotta and thus the "autodetect" value will detect the presence of Terracotta and register the MBean. Other allowed values are "on" and "off". The default is "autodetect".

    <Ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="ehcache.xsd"
                 updateCheck="true" monitoring="autodetect">
    

Terracotta Server Configuration

Note: You need to install and run one or more Terracotta servers to use Terracotta clustering.

See http://www.terracotta.org/web/display/orgsite/Download.

With a server/servers up and running you need to specify the location of the servers.

Configuration can be specified in two main ways: by reference to a source of configuration or by use of an embedded Terracotta configuration file.

Specification of a source of configuration

To specify a reference to a source (or sources) of configuration, use the url attribute. The url attribute must contain a comma-separated list of:

  • path to the Terracotta configuration file (usually named tc-config.xml)

    Example using a path to Terracotta configuration file:

        <terracottaConfig url="/app/config/tc-config.xml"/>
  • URL to the Terracotta configuration file

    Example using a URL to a Terracotta configuration file:

        <terracottaConfig url="http://internal/ehcache/app/tc-config.xml"/>
  • server host:port of a running Terracotta Server instance

    Example pointing to a Terracotta server installed on localhost:

        <terracottaConfig url="localhost:9510"/>

    Example using multiple Terracotta server instance URLs (for fault tolerance):

        <terracottaConfig url="host1:9510,host2:9510,host3:9510"/>
Specification using embedded tc-config

To embed a Terracotta configuration file within the Ehcache configuration, simply place the usual Terracotta XML config within the terracottaConfig element.

In this example we have two Terracotta servers running on server1 and server2.

    <terracottaConfig>
        <tc-config>
            <servers>
                <server host="server1" name="s1"/>
                <server host="server2" name="s2"/>
            </servers>
            <clients>
                <logs>app/logs-%i</logs>
            </clients>
        </tc-config>
    </terracottaConfig>

Enabling Terracotta clustering per cache

Cache elements can also contain information about whether the cache can be clustered with Terracotta.

The terracotta sub-element has the following attributes:

  • clustered=true|false

    Indicates whether this cache should be clustered with Terracotta. By default, if the terracotta element is included, clustered=true.

  • valueMode=serialization|identity

    Indicates whether this cache should be clustered with serialized copies of the values or using Terracotta identity mode. By default, values will be cached in serialization mode which is similar to other replicated Ehcache modes. The identity mode is only available in certain Terracotta deployment scenarios and will maintain actual object identity of the keys and values across the cluster. In this case, all users of a value retrieved from the cache are using the same clustered value and must provide appropriate locking for any changes made to the value (or objects referred to by the value).

  • coherentReads=true|false

    Indicates whether this cache should have coherent reads with guaranteed consistency across the cluster. By default, this setting is true. If you set this property to false, reads are allowed to check the local value without locking, possibly seeing stale values.

    This is a performance optimization with weaker concurrency guarantees and should generally be used with caches that contain read-only data or where the application can tolerate reading stale data.

    Note that from Ehcache 2.0, this setting has the same affect as coherent.

  • copyOnRead=true|false New feature in Ehcache 2.0

    Indicates whether cache values are deserialized on every read or if the materialized cache value can be re-used between get() calls.

    This setting is useful if a cache is being shared by callers with disparate classloaders or to prevent local drift if keys/values are mutated locally w/o putting back to the cache. i.e. if set to true then each thread has its own copy and cannot affect other threads.

    NOTE: This setting is only relevant for caches with valueMode=serialization

  • coherent=true|false New feature in Ehcache 2.0

    Indicates whether this cache should have coherent reads and writes with guaranteed consistency across the cluster. By default, its value is true. If this attribute is set to false (or "incoherent" mode), values from the cache are read without locking, possibly yielding stale data. Writes to a cache in incoherent mode are batched and applied without acquiring cluster-wide locks, possibly creating inconsistent values across cluster. Incoherent mode is a performance optimization with weaker concurrency guarantees and should generally be used for bulk-loading caches, for loading a read-only cache, or where the application that can tolerate reading stale data.

    This setting overrides coherentReads, which is deprecated. For backward compatibility any configurations setting a value for coherentReads will apply to coherent.

  • synchronousWrites=true|false New feature in Ehcache 2.0

    When set to true, clustered caches use Terracotta SYNCHRONOUS WRITE locks. Asynchronous writes(synchronousWrites="false") maximize performance byallowing clients to proceed without waiting for a "transaction received" acknowledgement from the server. Synchronous writes (synchronousWrites="true") maximizes data safety by requiring that a client receive server acknowledgement of a transaction before that client can proceed. If coherence mode is disabled using configuration (coherent="false") or through the coherence API, only asynchronous writes can occur (synchronousWrites="true" is ignored). By default this value is false (i.e. clustered caches use normal Terracotta WRITE locks).

    The simplest way to enable clustering is to add:

            <terracotta/>

    To indicate the cache should not be clustered (or remove the terracotta element altogether):

            <terracotta clustered="false"/>

    To indicate the cache should be clustered using identity mode:

            <terracotta clustered="true" valueMode="identity"/>

    Following is an example Terracotta clustered cache named sampleTerracottaCache.

        <cache name="sampleTerracottaCache"
               maxElementsInMemory="1000"
               eternal="false"
               timeToIdleSeconds="3600"
               timeToLiveSeconds="1800"
               overflowToDisk="false">
            <terracotta/>
        </cache>

Copy On Read

The copyOnRead setting is most easily explained by first examining what it does when not enabled and exploring the potential problems that can arise.

For a cache for which copyOnRead is NOT enabled, the following reference comparsion will always be true (NOTE: assuming no other thread changes the cache mapping between the get()'s)

    Object obj1 = c.get("key").getValue();
    Object obj2 = c.get("key").getValue();

    if (obj1 == obj2) {
      System.err.println("Same value objects!");
    }

The fact that the same object reference is returned accross multiple get()'s implies that the cache is storing a direct reference to cache value. When copyOnRead is enabled the object references will be fresh and unique upon every get().

This default behavior (copyOnRead=false) is usually what you want although there are at least two scenarios in which this is problematic: (1) Caches shared between classloaders and (2) Mutable value objects

Imagine two web applications that both have access to the same Cache instance (this implies that the core ehcache classes are in a common classloader). Imagine further that the classes for value types in the cache are duplicated in the web application (ie. they are not present in the common loader). In this scenario you would get ClassCastExceptions when one web application accessed a value previously read by the other application. One solution to this problem is obviously to move the value types to the common loader, but another is to enable copyOnRead so that thread context loader of the caller will be used to materialize the cache values on each get(). This feature has utility in OSGi environments as well where a common cache service might be shared between bundles

Another subtle issue concerns mutable value objects in a clustered cache. Consider this simple code which shows a Cache that contains a mutable value type (Foo):

    class Foo {
      int field;
    }

    Foo foo = (Foo) c.get("key").getValue();
    foo.field++;

    // foo instance is never re-put() to the cache

    // ...

If the Foo instance is never re-put() to the Cache your local cache is no longer consistent with the cluster (it is locally modified only). Enabling copyOnRead eliminates this possibility since the only way to affect cache values is to call mutator methods on the Cache.

It is worth noting that there is a performance penalty to copyOnRead since values are deserialized on every get().

Behaviour differences with CacheEventListeners when using Terracotta Clustering

Terracotta clustering works by clustering the MemoryStore, unlike the replication mechanisms which use the CacheEventListener infrastructure.

This results in a simpler programming contract than with the replication mechanisms.

Things to note:

Cache listeners

Cache listeners listen for changes, including replicated cluster changes, made through the Cache API. Because Terracotta cluster changes happen transparently directly to the MemoryStore a listener will not be invoked when an event occurs out on the cluster. If it occurs locally, then it must have occurred through the Cache API, so a local event will be detected by a local listener.

A common use of listeners is to trigger a reload of a just invalidated Element. In Terracotta clustering this is avoided as a change in one node is always coherent to the other nodes.

To send out cache change events across the cluster, you need to set up a local listener on the relevant cache so that these events can be propagated to other nodes. This is done by adding the following cacheEventListenerFactory tag to the cache:

    <cacheEventListenerFactory class="net.sf.ehcache.event.TerracottaCacheEventReplicationFactory"/>

Overflow to Disk

Overflow to Disk is ignored when using Terracotta Clustering. However it is not needed because the Terracotta server has its own overflow to disk. Once the maxElementsInMemory limit is reached Elements will be evicted to the cluster.

Cluster Topology New feature in Ehcache 2.0">

The interface net.sf.ehcache.cluster.CacheCluster provides methods for obtaining topology information for a Terracotta cluster.

The following methods are available:

  • String getScheme() Returns a scheme name for the cluster information. Currently TERRACOTTA is the only scheme supported.
  • 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 cluster nodes.

        public interface ClusterNode {
    
        /**
         * Get a unique (per cluster) identifier for this node.
         *
         * @return Unique per cluster identifier
         */
        String getId();
    
        /**
         * Get the host name of the node
         *
         * @return Host name of node
         */
        String getHostname();
    
        /**
         * Get the IP address of the node
         *
         * @return IP address of node
         */
        String getIp();
    
     }

Terracotta Cluster Events New feature in Ehcache 2.0">

Terracotta has a Terracotta Cluster event notification mechanism. From Ehcache 2.0, Ehcache can receive these events.

This is not cache event notification, rather it is cluster event notification.

The Terracotta Distributed Ehcache cluster events API provides access to Terracotta cluster events and cluster topology.

The interface net.sf.ehcache.cluster.ClusterTopologyListener provides methods for detecting the following cluster events:

public interface ClusterTopologyListener {

    /**
     * A node has joined the cluster
     *
     * @param node The joining node
     */
    void nodeJoined(ClusterNode node);

    /**
     * A node has left the cluster
     *
     * @param node The departing node
     */
    void nodeLeft(ClusterNode node);

    /**
     * This node has established contact with the cluster and can execute clustered operations.
     *
     * @param node The current node
     */
    void clusterOnline(ClusterNode node);

    /**
     * This node has lost contact (possibly temporarily) with the cluster and cannot execute
     * clustered operations
     *
     * @param node The current node
     */
    void clusterOffline(ClusterNode node);

}

Example Code

This example prints out the cluster nodes and then registers a ClusterTopologyListener which prints out events as they happen.

    CacheManager mgr = ...

    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"); }

    });

Uses for Cluster Events

If Ehcache got disconnected from the Terracotta Server Array say due to a network issue, then in Ehcache 2.0 each cache operation will block indefinitely. In other words it is configured for fail-fast to protect the ACIDity of the cluster.

However this approach will also cause processing of requests to the cache to stop likely causing the outage to cascade. In some cases graceful degradation may be more appropriate. When the clusterOffline events fires you could call Cache.setDisabled() which will cause puts and gets to bypass the cache. Your application would then degrade to operating without a cache, but might be able to do useful work.

You could also take the whole application off-line.

When connectivity is restored you could then reverse the action, taking the cache back online or the application back on line as the case may be.

We expect to add some of these common behaviours into configuration in the next release:

  • blocking - block until connectivity is restored
  • disable cache - cache cleared, puts ignored, gets return null
  • local mode - continue but use local data only. This last one presents problems on reconnect because local changes will not be consistent with the cluster.

More Information

Please see Terracotta Documentation for much more information.

Development Setup

The extra thing at development time is having a local Terracotta server running for integration and/or interactive testing.

Maven

Terracotta has a Maven plugin available which makes this very simple.

Setting up for Integration Testing
    <pluginRepositories>
        <pluginRepository>
            <id>terracotta-snapshots</id>
            <url>http://www.terracotta.org/download/reflector/maven2</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <plugin>
        <groupId>org.terracotta.maven.plugins</groupId>
        <artifactId>tc-maven-plugin</artifactId>
        <version>1.5.1</version>
        <executions>
            <execution>
                <id>run-integration</id>
                <phase>pre-integration-test</phase>
                <goals>
                    <goal>run-integration</goal>
                </goals>
            </execution>
            <execution>
                <id>terminate-integration</id>
                <phase>post-integration-test</phase>
                <goals>
                    <goal>terminate-integration</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
Interactive Testing

To start Terracotta:

    mvn tc:start

To stop Terracotta:

    mvn tc:stop

See http://forge.terracotta.org/releases/projects/tc-maven-plugin/ for a complete reference.

FAQ

I get a net.sf.ehcache.CacheException: Terracotta cache classes are not available, you are missing jar(s) most likely

You need to include the ehcache-terracotta jar in your classpath.

When I start ehcache I get "WARN - Can't connect to server[localhost:9510:s1]. Retrying..."

You have not configured a Terracotta server for Ehcache to connect to.

Is Expiry the same in Terracotta?

timeToIdle and timeToLive work as usual. Ehcache 1.7 introduced a less fine grained age recording in Element which rounds up to the nearest second. Some APIs may be sensitive to this change.

In Ehcache Elements can have overridden TTI and TTLs. Terracotta supports this functionality.

What Eviction strategies are supported?

Ehcache supports LRU, LFU and FIFO eviction strategies.

Terracotta supports LRU and LFU eviction from the local node. Not FIFO and not custom evictors.

What Stores are available and how are they configured?

The Terracotta server provides an additional store, generally referred to as the Level 2 or L2 store.

The MemoryStore in JVM in the local node is referred to as the L1 Store.

maxElementsInMemory - the maximum number of elements in the local L1 store.

maxElementsOnDisk - is overridden when using Terracotta to provide the L2 size. The L2 size is effectively the maximum cache size.

overflowToDisk normally controls whether to overflow to the DiskStore. This is ignored when using Terracotta - the DiskStore is never used. When the store gets full, elements will always overflow to the Terracotta L2 Store running on the server. The L2 can be further configured with the tcconfig.

When do Elements overflow?

Two things to cause elements to be flushed from L1 to L2.

  • the L1 store exceeding maxElementsInMemory
  • When the local JVM exceeds 70% of Old Generation. This can be turned off in the TC Config. By default it is on (in 1.7).

How does Element equality work in Serialization mode?

An Element, key and value in Ehcache is guaranteed to .equals() another as it moves between stores.

In the Express install or Serialization mode of Terracotta, which is the default, Terracotta is the same. Elements will not == each other as they move between stores.

How does Element equality work in Identity mode?

An Element in Ehcache is guaranteed to .equals() another as it moves between stores.

However in Identity mode, Terracotta makes a further guarantee that they key and the value ==. This is achieved using extensions to the Java Memory Model.

What is the recommended way to write to a database?

Terracotta' non Ehcache API offers an async writethrough to the database which is guaranteed. It uses the TIM Async module and works by putting the database update in a clustered queue. It guarantees that a node, even if the local node fails, will take it out and process it.

That option is not available with Ehcache although it may get added.

If updates to a database bypass the Terracotta clustered application, then how is that coherent?

It isn't. This is a problem with using a database as an integration point. Integration via a message queue, with a Terracotta clustered application acting as a message queue listener and updating the database avoids this. As would The application receiving a REST or SOAP call and writing to the database.

AQ can have DB trigger put in a poll. Or AQ can push it up.

Do CacheEventListeners work?

A local CacheEventListener will work locally, but other nodes in a Terracotta cluster are not notified unless the TerracottaCacheEventReplicationFactory event listener is registered for the cache.

What are the JMX statistics available in the Terracotta Developer Console?

SampledCache and SampledCacheManager MBeans are made available in the Terracotta Developer Console.

These are time based gauges, based on once per second measurements. These are different to the JMX MBeans available through the ManagementService.

What classloader is used to deserialize clustered objects in valueMode="serialization"?

The following classloaders are tried in this order:

  • Thread.currentThread().getContextClassLoader() (so set this to override)
  • The classloader used to define the Ehcache CacheManager - the one that loaded the ehcache-terracotta.jar

What versions of Ehcache and Terracotta work together?

For full compatibility Ehcache 2.0 should be used with Terracotta Server 3.2.1.

Ehcache 1.7 works with TC 3.1.1.

Ehcache Core 1.7.3 core works with TC 3.2.

What happens when an L1 (i.e. an Ehcache node) disconnects from the L2 (i.e. the Terracotta Server Array)?

  • The L1 receives an operations disabled event. Then spawns a thread with a timer waiting for an operations-enabled event. How long to wait depends on heart beating settings. 65 seconds should work for the default assuming l2.l1.reconnect is enabled with 5s timeout.
  • If in this time there is no Operations-Enabled event, then the L1 can conclude that it is disconnected and flip a Boolean in the application, which instructs incoming requests to not access the Terracotta Shared space.

    Note that existing threads doing I/O against the TC server (whether for data or for locks) are stuck.

  • If the application desires timeouts then you have to employ a proxy collection - e.g. a wrapper around a Queue or Map, (in DSO) - where the Queue APIs are proxied through to a thread pool - so that you can try/join out in "timeout" seconds and throw a timeout to the application. This is however somewhat messy - since the application threads may receive a timeout, but the "terracotta transaction" may still make it through to the L2.