Last updatedApr 23, 2019

Atlassian Cache 2 overview

Introduction

Atlassian Cache 2 is a version of the Atlassian Cache API (atlassian-cache-api-2.x) that should be used within Atlassian products (initially Jira 6.2 and Confluence 5.5). The API is designed to be backwardly compatible with atlassian-cache-api-0.1.

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

  • Cache – represents a cache.
  • CacheLoader – used with cache to provide read-through semantics (see Cache Population later on this page).
  • CachedReference – represents a resettable reference that is backed by a cache.
  • Supplier – used with a CachedReference to generate the referenced object.
  • CacheFactory – responsible for creating cache and CachedReference   instances that may be configured using CacheSettings.

Cache types

Cache API supports three distinct types of cache:

  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 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).

For more information on how to specify the type of cache, see cache creation later on this page.

Cache population

For populating the data in 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.

Any implementation of CacheLoader that is associated with a nonlocal cache must not make calls to any other nonlocal  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 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 followed. The user of the cache should be prepared to handle their values being evicted at any time.

Cache creation

When creating cache, you can provide CacheSettings to control the configuration of cache, which are created using the CacheSettingsBuilder. See CacheSettingsBuilder for all the settings that can 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
falsefalseLocal
falsetrueLocal
truefalseHybrid
truetrueCluster

When creating a hybrid cache, pay extra attention to the following points:

  • Make sure to associate a CacheLoader with the cache using getCache(String, CacheLoader) or getCache(String, CacheLoader, CacheSettings) methods when obtaining the cache. If 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 cache is created in a way described previously, the returned cache object should be retained for future use. If the returned cache object is not retained for reuse, 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 cache. Sharing can be between:

  • Different plugins.
  • Different versions of a plugin (after an upgrade or 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, use only the classes provided by one of the following:

  • Java JDK (e.g. java.lang.String).
  • 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:

  1. Keys should follow the guidelines in Cluster Cache data.
  2. Values should follow the guidelines in Local Cache data.

Cache code examples

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

1
2
3
4
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.

1
2
3
4
5
6
7
8
9
10
11
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.

1
2
3
4
5
6
7
8
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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