View source | Discuss this page | Page history | Printable version   

Draft:How To Cache Data

Contents

Overview

Storing data in memory, known as data caching, increases application performance by providing a less expensive means of feeding data into the application logic and by reducing the number of queries executed by the database, saving those resources for other activities.

However, using data a cache introduces the risk of executing business logic with stale data. This can result in errors or unexpected application behaviour. To prevent this, the cache must be invalidated whenever the underlying data changes.

Because of this invalidations, frequently modified data is not a good candidate for storage in the application cache. Consider carefully all the use cases that could case invalidations before adding a new data cache.

Bulbgraph.png   Starting from 3.0PR20Q2, the CacheManagement infrastructure is available for the implementation of data caches

How the CacheManagement Works

The CacheManagement implementation provides two main benefits:

To control when a data cache needs to be invalidated, a new table AD_Cache stores a single record for each registered cache and holds the timestamp of the last modification of the underlying data in the database.

A background thread, implemented by CacheInvalidationBackgroundManager queries this table at a configurable frequency and informs the individual CacheManager instances if the cache they are holding needs to be refreshed.

How to Implement a New Data Cache

For the sake of this explanation, let's consider the case of the organizational structure of the clients in Openbravo. The organization tree is used very frequently in the application logic to determine the data that should be visible in a given context. On the other hand, the organizational structure changes very seldom, That makes it a good candidate for a cache.

The steps involved in implementing the cache are:

  1. Register the cache in the application dictionary
  2. Implement the CacheManager that will hold the cached data
  3. Create the database trigger(s) to inform the CacheManager when data needs to be refreshed

Registering the cache in the application dictionary

The first step in implementing this new cache is registering it in the Cache Invalidation Control window

CacheInvalidationControlWindow.png

The record's Search Key will work as the link between the invalidation record and the CacheManager implementing the cache.

The module in which the cache is implemented needs to be in development. More about this is explained in the Modularity page.

Implementing the CacheManager

The second step is to implement the class that will hold the cache, extending the CacheManager class.

 
public class OSPCacheManager
    extends CacheManager<String, Map<String, OrganizationStructureProvider.OrgNode>> {
 
  private static final String CACHE_IDENTIFIER = "OrganizationStructure";
 
  @Override
  public String getCacheIdentifier() {
    return CACHE_IDENTIFIER;
  }
 
}

In this case, for each key (the client id), the cache will hold a Map that contains the nodes in the organizational structure of the client.

The cache identifier, returned by the getCacheIdentifier must return the same value used in the Search Key in the previous step.

Cache Manager descendants are automatically injected by Weld to the CacheInvalidationBackgroundManager.

Creating the database trigger that notifies the need to invalidate

Whenever the organizational structure changes, this database trigger must update the last_invalidation field in the AD_Cache table. This will make the CacheInvalidationBackgroundManager request the appropriate cache to invalidate its records.

 
CREATE TRIGGER ad_org_cache_trg
AFTER INSERT OR UPDATE OR DELETE ON ad_org
FOR EACH ROW EXECUTE PROCEDURE ad_org_cache_trg();
 
CREATE OR REPLACE FUNCTION public.ad_org_cache_trg()
 RETURNS TRIGGER
 LANGUAGE plpgsql
 AS $function$
BEGIN
 
  IF AD_isTriggerEnabled()='N' THEN
    IF TG_OP = 'DELETE' THEN
      RETURN OLD;
    ELSE
      RETURN NEW;
    END IF;
  END IF;
 
  IF TG_OP = 'DELETE' OR TG_OP = 'INSERT'
      OR coalesce(new.ad_businessunit_org_id,'NULL') != coalesce(old.ad_businessunit_org_id,'NULL')
      OR coalesce(new.ad_calendarowner_org_id,'NULL') != coalesce(old.ad_calendarowner_org_id,'NULL')
      OR coalesce(new.ad_legalentity_org_id,'NULL') != coalesce(old.ad_legalentity_org_id,'NULL')
      OR coalesce(new.ad_periodcontrolallowed_org_id,'NULL') != coalesce(old.ad_periodcontrolallowed_org_id,'NULL') THEN
    UPDATE ad_cache
      SET last_invalidation = now()
      WHERE value = 'OrganizationStructure';
  END IF;
 
  IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
 
END;
 
$function$;

In most scenarios, any modification to a database table should invalidate the cache. However, the trigger can check for any specific conditions before forcing cache data to be refreshed. In this case, for example, the cache is only invalidated when the fields controlling the organizational structure are updated.

Additional Information

The CacheInvalidationBackgroundManager is a daemon thread will check for updated invalidation records at a frequency defined by the Preference identified with the property CacheInvalidationControlPeriod. If the preference does not exist, the records will be checked every 10 seconds by default.

The CacheInvalidationBackgroundManager implements a JMX MBean interface. This makes it possible to use a JMX client to start and stop the daemon as well as to force the invalidation of an individual cache by invoking the invalidateCache operation with the appropriate Search Key as argument.

CacheInvalidationMBean.png

Retrieved from "http://wiki.openbravo.com/wiki/Draft:How_To_Cache_Data"

This page has been accessed 1,159 times. This page was last modified on 7 December 2019, at 11:26. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.