CacheAdapter 4.0 released with Redis support.

My 'Glav.CacheAdapter' package has recently had a major release and is now at version 4.0. If you are not familiar with what this package does, you can look at previous posts on the subject here and here. In a nutshell, you can program against a cache interface, and via configuration, switch between ASP.Net web cache, memory cache, Windows Azure Appfabric, memcached and now redis.

The nuget packages for the core component and the package with all configuration and code examples are:

Glav.CacheAdapter.Core - binary only

Glav.CacheAdapter - binary, configuration examples, code examples.

Bitbucket code repository - complete source code for the CacheAdapter

The major new feature in this release is that we now fully support the Redis data store as a cache engine. Redis is an excellent data store mechanism and is used as a cache store in many circumstances. Windows Azure has recently adopted redis as their preferred cache mechanism, and deprecating the Windows Azure Appfabric cache mechanism they previously supported. 

In addition, there are significant performance improvements when managing cache dependencies, some squashed bugs and a method addition to the interface. Read on for the details.

Redis support

On premise and Azure hosted redis support is the major feature of this release. You can enable this in configuration and instantly be using redis as your cache implementation. Redis has a much richer storage and query mechanism than memcached does, and this enables the library to use redis specific functionality to ensure cache dependency management is taking advantage of this enhanced feature set.

To use redis, simply change the following values in the configuration:

<add key="Cache.CacheToUse" value="redis"/>

Obviously, you also need to point the cache provider to the redis instance to use. In the example below, I am pointing to my test instance in windows azure, and specifying the port number (which happens to be the SSL port used):

<add key="Cache.DistributedCacheServers" value="glavtest.redis.cache.windows.net:6380"/>

There are some redis specific settings we need to set before connecting to an azure redis instance. We need set whether we are using SSL (in this case yes) and also we need to set the password. If you are using a local instance of redis you may not be required to set the password. For redis in azure, the password is one of your access keys which you can get from the azure portal. Additionally, it is recommended that 'Abort on connect' flag is set to false. In this example, I have also set the connection timeout to 15 seconds:

<add key="Cache.CacheSpecificData" value="ssl=true;abortConnect=false;connecttimeout=15000;password={your-azure-redis-access-key}"/>

Behind the scenes, the cache adapter relies on the StackExchange.Redis client so any configuration options it supports are supported here. However, do not specify the host in the CacheSpecificData section as they will be ignored.

Cache Dependency Management

The cache adapter supports a dependency management feature that is available to all cache engines supported. This was previously a generic management component, that ensured this feature worked across all cache types. You can read more about the functionality here. With the introduction of redis support, the cache library utilises a redis specific cache dependency management component to ensure that the rich feature set of redis is taken advantage of for a better performing cache dependency management solution.

Typically, if using dependency management, you may have a line like this in your configuration:

<add key="Cache.DependencyManagerToUse" value="default"/>

If you are using redis cache, the redis specific cache dependency manager will be used.

When using redis, this is equivalent to specifying:

<add key="Cache.DependencyManagerToUse" value="redis"/>

 If using any other cache type, then the generic cache dependency manager will be used. If (for whatever reason) you want to change this behaviour for redis, you can specify that the generic mechanism is used instead.

<add key="Cache.DependencyManagerToUse" value="generic"/>

In addition to these changes, performance for the generic cache dependency manager has been vastly improved resulting in significant speed gains. The redis specific dependency manager is still faster when using redis due to its usage of redis specific queries.

New interface method - InvalidateCacheItems

This interface method takes a list of cache keys and performs a bulk invalidation.  

void InvalidateCacheItems(IEnumerable<string> cacheKeys)

Where possible, cache specific features are used to ensure this process is much more efficient than simply cycling through each key and invalidating each one separately.

Bugs and Fixes

There are a few bugs addressed in this release.

• Fix for minor performance issue when checking the dependency management (Issue #33 - https://bitbucket.org/glav/cacheadapter/issue/33/call-to)

• Fixed a bug where a new configuration was not properly applied, if applied after the first initialisation.

Feel free to register bugs, suggestions and feedback on the issue register here. I do look at all the issues and attempt to get as much done in the little spare time I have.

Don’t forget, if you have already installed Glav.CacheAdatper package, you only really need to update the Glav.CacheAdapter.Core component to get the new functionality.

5 Comments

  • Is is possible to use async/await for Func<T> of ICacheProvider.Get<T>(...) ? For example, a cache item is coming from a web request, then waiting in the function for the http result will be a bad practice.

    Also, what happens if the Func<T> returns NULL, does it get added to the cache or ignored?

  • I got the code and added the following 2 new ASYNC methods to the ICacheProvider with implementation in CacheProvider (Note: I had to change to use .NET 4.5 and hence remove the Microsoft.Bcl.* from the dependent packages). Would you please add them to the package and create a new release?

    ```
    public Task<T> GetAsync<T>(string cacheKey, DateTime expiryDate, Func<Task<T>> getData, string parentKey = null, CacheDependencyAction actionForDependency = CacheDependencyAction.ClearDependentItems) where T : class
    {
    return GetAndAddIfNecessaryAsync(cacheKey,
    data =>
    {
    _cache.Add(cacheKey, expiryDate, data);
    _logger.WriteInfoMessage(string.Format("Adding item [{0}] to cache with expiry date/time of [{1}].", cacheKey,
    expiryDate.ToString("dd/MM/yyyy hh:mm:ss")));
    },
    getData,
    parentKey,
    actionForDependency
    );
    }

    public Task<T> GetAsync<T>(string cacheKey, TimeSpan slidingExpiryWindow, Func<Task<T>> getData, string parentKey = null, CacheDependencyAction actionForDependency = CacheDependencyAction.ClearDependentItems) where T : class
    {
    return GetAndAddIfNecessaryAsync(cacheKey,
    data =>
    {
    _cache.Add(cacheKey, slidingExpiryWindow, data);
    _logger.WriteInfoMessage(
    string.Format("Adding item [{0}] to cache with sliding sliding expiry window in seconds [{1}].", cacheKey,
    slidingExpiryWindow.TotalSeconds));
    },
    getData,
    parentKey,
    actionForDependency
    );
    }

    private async Task<T> GetAndAddIfNecessaryAsync<T>(string cacheKey, Action<T> addData, Func<Task<T>> getData, string parentKey = null, CacheDependencyAction actionForDependency = CacheDependencyAction.ClearDependentItems) where T : class
    {
    if (!_config.IsCacheEnabled)
    return await getData();

    //Get data from cache
    T data = _cache.Get<T>(cacheKey);

    // check to see if we need to get data from the source
    if (data == null)
    {
    //get data from source
    data = await getData();

    //only add non null data to the cache.
    if (data != null)
    {
    addData(data);
    ManageCacheDependenciesForCacheItem(data, cacheKey, parentKey, actionForDependency);
    }
    }
    else
    {
    _logger.WriteInfoMessage(string.Format("Retrieving item [{0}] from cache.", cacheKey));
    }

    return data;
    }

    ```

  • Is there a way to get all dependency children based on a parent key?

  • Nevermind. I found this:

    cacheProvider.InnerDependencyManager.GetDependentCacheKeysForParent(masterKey);

    Your cache utility is great by the way!

  • Hi,
    I'm trying out your CacheAdapter and I've been through most of your blog posts and code to ensure I'm using it correctly. Problem I have is I'm running a unit test which calls a repository (which is using the cache) and the test makes the same call twice - I expect my logger to only log once but it always logs twice. Are there any specific configuration issues when running from unit tests which need to be taken into account? (I have copied the config settings into the Unit test project's App.config file.
    I'm using Visual Studio 2015.

    Thanks,
    Regards.

Add a Comment

As it will appear on the website

Not displayed

Your website