Atlassian Cache 2 Overview

Introduction

Atlassian is rolling out a new version of the Atlassian Cache API (atlassian-cache-api-2.x) that will be used within Atlassian products (initially JIRA 6.2 and Confluence 5.5). The API has been designed to be backwardly compatible with atlassian-cache-api-0.1.

The main interfaces in the API from an end-user perspective are:

Cache types

The Cache API supports three distinct types of caches:

  1. Local - the data in a local cache is held on the JVM. The keys and values are stored by reference.
  2. Cluster - the data in a cluster cache is replicated across the cluster. The keys and values are stored by value, and hence must be Serializable (this is only enforced at run-time).
  3. Hybrid - the data in a hybrid cache is stored locally on each node in a cluster, and is kept synchronized via invalidation messages. The keys and values are stored by reference, however the keys must be Serializable (this is only enforced at run-time).

See Cache Creation for how to specify the type of cache.

Cache population

For populating the data in a cache, the Cache API supports two styles:

  1. Read Through Cache semantics where values for cache misses are retrieved using a supplied CacheLoader when the cache is created. This is the preferred style for new development.
  2. Traditional put-style semantics where values are put into the cache using the put(K, V) and putIfAbsent(K, V) methods. This is supported for backwards compatibility.

There is a limitation that any implementation of CacheLoader that is associated with a non-local Cache must not make calls to any other non-local Cache or CachedReference.

This limitation also applies to any implementation of Supplier that is associated with a non-local CachedReference.

Cache eviction

The Cache API supports two rules for eviction of values from a cache, which are specified in the CacheSettings:

  1. expireAfterAccess(long, java.util.concurrent.TimeUnit) - evicts entries that have not been accessed for a specified duration.
  2. expireAfterWrite(long, java.util.concurrent.TimeUnit) - evicts entries that have not been updated since a specified duration.

To be precise, the rules are actually hints to the underlying cache provider that may or may not be observed. The user of the cache should be prepared to handle their values being evicted at anytime.

Cache creation

When creating a cache, it is possible to provide a CacheSettings to control the configuration of a cache, which are created using the CacheSettingsBuilder. See CacheSettingsBuilder for all the settings that may be specified.

When settings are not explicitly specified, the product specific default will be used. See each product's documentation for detailed information on how defaults are determined.

The following table describes the how the different cache types are created based on the two properties remote() and replicateViaCopy().

remote() replicateViaCopy() Cache Type Created
false false Local
false true Local
true false Hybrid
true true Cluster

When creating a hybrid cache, be sure to associate a CacheLoader with the cache by using either of the getCache(String, CacheLoader) or getCache(String, CacheLoader, CacheSettings) methods when obtaining the cache. If a CacheLoader is not associated, and the put(K, V) or putIfAbsent(K, V) are used, then the cache will not operate correctly in a cluster, as each put operation will invalidate local entries in all other nodes in the cluster.

Whenever a cache is created with an associated CacheLoader by using either of the getCache(String, CacheLoader) or getCache(String, CacheLoader, CacheSettings) methods, then the returned Cache object should be retained for future use. If the returned Cache object is not retained for re-use, then every subsequent call to obtain a new Cache object will result in the current contents of the underlying cache to be evicted.

Cache data sharing guidelines

This section provides guidelines for developers wanting to share data stored in a cache. Sharing can be between:

  • Different plugins.

  • Different versions of a plugin (after an upgrade/downgrade).

Local cache data

Local cache keys and values are stored by reference. Therefore, to ensure that the data can be accessed between plugins and plugin restarts, only use classes provided by:

  • Java JDK (e.g. java.lang.String); OR

  • The product core (such as Confluence core classes).

Note that a plugin can serialize custom classes to a portable format that is stored as a java.lang.String or byte[].

Cluster cache data

Cluster cache keys and values are stored by value, which means they are serialized on insertion to the cache, and deserialized on extraction.

Therefore, to ensure that the data can be shared, both product and plugin developers must ensure that the objects placed into a cluster cache implement serialization with support for versioning. See Versioning of Serializable Objects.

It's recommended that plugin developers use classes from the Java JDK and product core. If custom classes are required to be shared between plugins, then a common shared library should be used for defining them.

Hybrid cache data

Hybrid cache keys are stored by value, and values are stored by reference.

Therefore:

Cache code examples

The following example shows how to obtain a cache called "old-style" using defaults.

Cache<String, String> cache = cacheManager.getCache("old-style");

// Returns null
cache.get("money");

The following example shows how to obtain a cache called "with-loader" that uses a CacheLoader.

Cache<String, String> cache =
    cacheManager.getCache(
    "with-loader",
    new CacheLoader<String, String>() {
        public String load(String key) {
            return "value for " + key;
        }
    });

// Returns "value for money"
cache.get("money");

The following example shows how to obtain a hybrid cache called "adam" that expires values after 60 seconds of no access.

CacheSettings required =
    new CacheSettingsBuilder().
        remote().
        expireAfterAccess(60, TimeUnit.SECONDS).
        build();

Cache<String, String> c =
    cacheManager.getCache("adam", someCacheLoader, required);

CachedReference code examples

The following example shows how to use a CachedReference.

Supplier<Date> generateTheAnswer = new Supplier<>()
{
    public Date get()
    {
        return new Date();
    }
}

CachedReference<Date> cr = 
    factory.getCachedReference(
        "mycache", generateTheAnswer);
cr.get(); // will call generateTheAnswer
cr.get(); // will get from the cache
cr.reset();
cr.get(); // will call generateTheAnswer
Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport