C2mon Cache refactoring

issue-39 1. What is caching? Agenda 2. JCache overview 3. Providers overview 4. C2MON Cache What is caching? What is caching?

Cache: A high-performance, low-latency datastructure in which an application places a temporary copy of information that is likely to be used more than once Benefits of using cache

● Performance ● Scale up – get the most out of one machine ● Scale out – add more capacity with more machines ● Usually very fast and easy to apply When to use caching ● When applications use the same data more than once ● When cost (time / resources) of making an initial copy is lower than fetching or producing the data again or when it isfaster to request from a Cache JCache overview What is JCache?

● A common mechanism for performing CRUD operations from Caches

● Provides core caching concepts and API

● Provides application portability between various Caching solutions JCache: Main features ● Read/Write Integration support ● Cache Event Listeners ● Type-safety - fully generic ConcurrentMap like API ● Statistics ● Store-By-Value/Store-By-Reference ● Annotations support ● Support for local and distributed caching ● Topology Agnostic - Topologies not defined or restricted by the specification Store-By-Value and Store-By-Reference

JSR-107 storing mechanisms:

● Store-By-Reference (optional)

Object key = ... Object value = ... cache.put(key, value); assert cache.get(key) == value; assert cache.iterator().next().getKey() == key;

● Store-By-Value(default) Method Default Consistency

boolean containsKey(K key) last value

Cache Consistency V get(K key) happen-before

Consistency refers to the behavior of V getAndPut(K key, V value) happen-before caches and the guarantees that exist when concurrent cache mutation occur Iterator> last value iterator() together with the visibility of the mutations when multiple threads are void put(K key, V value) happen-before accessing a cache. boolean putIfAbsent(K key, V happen-before plus compare and value) swap An implementation may provide support for different consistency boolean remove(K key) happen-before plus compare and swap models in addition to the required Default Consistency model. T invoke(K key, happen-before EntryProcessor entryProcessor, Object... arguments); Cache Topologies Embedded approach Client/Server approach Cache Types

replicated cache partitioned cache JCache Core interfaces JCache API CacheManager

● Configures and manages named Caches ○ caches may be created dynamically or predefined ● provides Cache infrastructure ● provides Cache scopes(for clusters) ● provides important for Store-By-Value Cache ClassLoaders ● Provides Cache lifecycle management Cache

A Cache is a Map-like data-structure that permits the temporary storage of Key-based Values, some what like java.util.Map data-structure. A Cache is owned by a single CacheManager. Entry

An Entry is a single key-value pair stored by a Cache. ExpiryPolicy ● Eternal – Cache entries will never expire. ● Access – Cache entries will expire if they have not been accessed within a configured time interval. ● Creation – Cache entries will expire after a configured time interval from when they were added to the cache. ● Update – Cache entries will expire if they have not been modified within a configured time interval. JCache Runtime Structure JCache API example

● if Cache already exists, or it’s configured in XML:

CachingProvider provider = Caching.getCachingProvider(); CacheManager manager = provider.getCacheManager();

Cache personCache = manager.getCache(“personCache”, Long.class, Person.class); personCache.put(1L, new Person()); Person person = personCache.get(1L);

● if Cache doesn’t exist and we want to configure it programmatically:

CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(); MutableConfiguration config = new MutableConfiguration<>(); config.setTypes(Long.class, Person.class); config.setStoreByValue(true); // value is serialized cacheManager.createCache("personCache", config); Atomic Operations: EntryProcessor

● allows develop Lock-Free API ● eliminates round-trips in distributed systems ● Needs Serialization (if distributed) “An invocable function that allows applications to perform compound operations on a Cache.Entry atomically, according the defined* consistency of a Cache. Any Cache.Entry mutations will not take effect until after the EntryProcessor.process(MutableEntry, Object...) method has completed execution. Implementations may execute EntryProcessor}s in situ, thus avoiding locking, round-trips and expensive network transfers.”

JCache specification EntryProcessor usage example

String name = “Foo”; cache.invoke(K key, (entry, arguments) -> { V value = entry.getValue(); value.setName(name); entry.setValue(value); return null; });

public class CustomEntryProcessor implements EntryProcessor { @Override public T process(MutableEntry entry, Object... arguments) throws EntryProcessorException { V value = entry.getValue(); value.setName(arguments[0]); entry.setValue(value); return null; } }

// Usage cache.invoke(K key, new CustomEntryProcessor(), T… arguments); Cache Annotations The JCache covers the most common cache operations:

● @CacheDefaults ● @CacheKey ● @CacheResult ● @CacheValue ● @CachePut ● @CacheRemove ● @CacheRemoveAll JCache annotations example

@CacheDefaults(cacheName = “personCache”) public class PersonRepository {

@CachePut public void put(@CacheKey Long id, @CacheValue Person person) {...}

@CacheResult public Person get(@CacheKey Long id) { Person person = // get object from DB return person; }

@CacheRemove public void invalidate(@CacheKey Long id) {}

@CacheRemoveAll public void invalidateAll() {} } JCache supporting frameworks Cache Providers with JCache Support

● Ehcache

● Ignite

● Coherence

● Infinispan Ehcache

● Terracotta as one of the companies supporting actively JSR107 ● Full JSR107 support ● Flexible ● Scalable ● minimal dependencies ● Open Source Licensing - Apache 2.0 license Hazelcast

Full TCK Compliant implementation for:

● Embedded Members

● Very fast and highly scalable

JCache Implementation

● Dead simple

● Near Cache support Apache Ignite

● Near Cache support

● fully JCache TCK compliant

● Distributed Queries

● Apache 2 license

● Web Console

● Docker&Kubernetes support C2MON use case evaluation Exisitng C2MON Cache module Runs on Ehcache + Terracotta

Ehcache dependent

Cluster based on Terracotta(license required)

Limited amount of clients

Forced Store-By-Value very complex clustered locking system Goals

Support for JCache standard Easier cluster-locking mechanism

Fully Open Source No limitations on cache clients

Clusterable eventual replacement for JMS messaging Support of jUnit tests Nice to have: Continuous query Auto detections of nodes in the cluster Summary + lesson learned

JCache vs vendor specific features

Which is my favoured cache?

Best documentation, material, resources C2mon Cache Motivations

● Simplifying codebase ● Responsibility Separation ● Provider independency SOC & SRP

● removal of heavy Facades ● introduction of single responsibility services, grouped by operations ● easier to use specified services Existing modules

cache-dbaccess cache-loading cache cache-persistence Existing modules

cache-dbaccess cache-loading cache cache-persistence

This module is responsible for connecting to using MyBatis for mapping models Existing modules

cache-dbaccess cache-loading cache cache-persistence

Here DAO objects are created and all Cache loaders are initialized.

Basic interfaces:

● C2monCacheLoader ● SimpleCacheLoader Existing modules

cache-dbaccess cache-loading cache cache-persistence

Main module for cache operations. Provides functionality for caching as well as for data manipulating and C2MON logic for tags.

All caches have three related classes: Configuration, Cache and Facade. Cache structure

On AlarmCache example

<> <> <> AlarmCache C2monCacheWithListeners C2monCache

AlarmCacheImpl

AbstractCache BasicCache Facade structure

On AlarmCache example

<> AlarmFacade

<> AlarmFacadeImpl ConfigurableCacheFacade

AbstractFacade Existing modules

cache-dbaccess cache-loading cache cache-persistence

Responsible for storing the data into database. Modules restructuration

cache-loader

cache- cache cache-persistence

cache-impl Modules restructuration

cache-loader

cache-api cache cache-persistence

cache-impl

In this module whole Cache API interfaces and classes are localized. In addition, there are also packages responsible for notifications.

Main class is Cache. Service Provider Interface

● Easy to implement Separation of Concerns ● Spring IoC support Modules restructuration

cache-loader

cache-api cache cache-persistence

cache-impl

Former cache-loading module. Adjusted to the new Cache class. Modules restructuration

cache-loader

cache-api cache cache-persistence

cache-impl

Implementations of cache-api modules(IgniteCache, IgniteCacheFactory etc.) and vendor specific configurations. Modules restructuration

cache-loader

cache-api cache cache-persistence

cache-impl

Configurations of all caches.

Former Facade classes divided into smaller services, according to Separation of Concerns and Single Responsibility principles. Modules restructuration

cache-loader

cache-api cache cache-persistence

cache-impl

Existing cache-persistence module, should be adjusted to newest api module.

Not finished yet. Caches

C2mon cache layer is containing currently 13 ➔ Alarm different caches, divided by tag they are storing. ➔ AliveTimer ➔ Cluster ➔ CommandTag ➔ Commfault ➔ ControlTag ➔ DataTag ➔ Device ➔ DeviceClass ➔ Equipment ➔ Process ➔ RuleTag ➔ SubEquipment Caches

After refactoring amount of caches will decrease. ➔ Alarm Cluster shouldn’t be required anymore. ➔ AliveTimer ➔ CommandTag ➔ Commfault ➔ ControlTag ➔ DataTag ➔ Device ➔ DeviceClass ➔ Equipment ➔ Process ➔ RuleTag ➔ SubEquipment Caches

After refactoring amount of caches will decrease. ➔ Alarm Cluster shouldn’t be required anymore. ➔ AliveTimer ➔ CommandTag ➔ Commfault Additionally, RuleTag, ControlTag and DataTag an ➔ ControlTag be stored in the same cache - ➔ DataTag TagCache TagLocationService is not needed(purpose of ➔ Device ➔ that service was to locate tag by its id in one of DeviceClass ➔ Equipment these three caches). ➔ Process ➔ RuleTag ➔ SubEquipment C2MON Cache API

● Basic Cache API ● Cache Loader API ● Transactions API ● Cache and Cache Objects Factory API C2monCache API

C2monCache class contains all methods from former BasicCache.

Instead of having all listening related methods in ApplicationObjectSupport ModificationListener child class, now there is delegation of them into seperate class.

C2monCache CacheFactory

Cache Factory is responsible for creating Cache and initializing IgniteCache. AbstractCacheFactory

Every Cache has to have following parameters;

● name ● key and value types ● matching DAO bean

All names are stored in CacheName enumerator. IgniteCacheFactory CacheObjectFactory

Another factory provided has responsibility to create, configure and validate CacheObject. CacheObjectFactory

Parameters required for creation:

● id ● properties createCacheObject(id, properties) is a template method which requires providing AlarmCacheObjectFactory implementation of following methods:

● configureCacheObject(..) ● validateConfig(..) Transactions

Instead of locking, Ignite and other providers cache.executeTransaction(() -> { //Task allow to use transactions. Since C2MON should be provider independent, there is a simple return null; transaction API(works only with Java 8). Cache }); class has an abstract method:

Optional executeTransaction(TransactionalCallable callable)

TransactionalCallable is a Optional type = cache.executeTransaction(() -> { @FunctionalInterface. //Task

When calling this method, transactional action return type; }); has to be executed inside of lambda. //Then type has to be handled Cache configuration

● Cache is created as a bean using CacheFactory ● every Cache is created in separate @Configuration class ● every Cache is initialized with: ○ name ○ Key-Value types ○ Cache Loader Service structure

According to the previous slides, SOC and SRP should be used.

Cache is a basic cache class, shouldn’t be extended by cache unrelated methods.

Service should be responsible for ony related operations on data, putting everything inside of a Service should be avoided.

Rule of thumb: if cache not related service is possible, should be achieved. What’s next…?

● Work that I could not finish ○ Add missing services for tags(in attached documentation) ○ continue merging DataTag, RuleTag and ControlTag into single cache ○ Query API(new module with methods for querying) ● Further improvements ○ Simplification of the cache listener/notification mechanism ○ Naming conventions

More details in attached documentation.