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
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
● if Cache doesn’t exist and we want to configure it programmatically:
CacheManager cacheManager = Caching.getCachingProvider().getCacheManager(); MutableConfiguration
● 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
// 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 database 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
<
AlarmCacheImpl
AbstractCache BasicCache Facade structure
On AlarmCache example
<
<
AbstractFacade Existing modules
cache-dbaccess cache-loading cache cache-persistence
Responsible for storing the data into database. Modules restructuration
cache-loader
cache-api 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
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.