Caching objects in Memcached using C#

Last week I was busy with researching the alternatives of implementing a caching solution for an upcoming application. Not surprisingly Redis and Memcached were the ones on the top of the list. Because we don't need those extra features of Redis like durability, master-slave replication and operations on complex values, our pick was Memcached.

For those of you who have not heard about it yet
Memcached is free & open source, high-performance, distributed memory object caching system.
In order to cache your objects in memcached you need a Memcached server and a client library suitable for the programming language of your choice.

Couchbase Server Community Edition comes with a windows installer and it takes only seconds to have an up and running Memcached server in your development environment.

You can find a list of memcached client apis/libraries here. Among the alternatives for .NET Enyim Memcached Client is the one which is still supported and has gained wider acceptance in community.

Enyim Memcached Client Configuration

You can configure your client in runtime or by adding the following lines to your application configuration file :
  
<configsections>
  <sectiongroup name="enyim.com">
      <section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching" />
  </sectiongroup>
</configsections>
<enyim.com>
    <memcached protocol="Binary">
      <servers>
        <add address="127.0.0.1" port="11210" />
      </servers>
    </memcached>
</enyim>

To override the default socket pooling behaviour include also the following :
  
<enyim.com>
      ...
      <socketpool connectiontimeout="00:00:00.500" deadtimeout="00:00:10" 
      maxpoolsize="30" minpoolsize="20" />
</enyim.com>

To learn more about client configuration visit https://github.com/enyim/EnyimMemcached/wiki/MemcachedClient-Configuration

Using the Library

 For most common scenarios Enyim.Caching.MemcachedClient is the only class to interact :
 
MemcachedClient client = new MemcachedClient();
client.Store(StoreMode.Set, "key", value); 
But be careful! Creating a MemcachedClient instance is an expensive operation and it's a bad practice to construct a new MemcachedClient everytime you need. Consider following a "create once use many times" strategy instead:
 
public class CacheManager
{
    private static readonly MemcachedClient cache = new MemcachedClient();
    
    public static object Get(string key)
    {
       return cache.Get(key);
    }

    public static void Add(string key, object value, int duration)
    {
       cache.Store(StoreMode.Set, key, value, DateTime.Now.AddMinutes(duration));         
    }

    public static void Remove(string key)
    {
       cache.Remove(key);
    }
}

Keys given to store objects must be unique and known to callers for later access. Id fields are natural candidates in the first sense but there will be a collision in the cache if objects of two different types has the same id value. In order to provide uniqueness among all types, CacheManager is evolved to :
public static class CacheManager
{
   private static readonly UniqueKeyValuePair<Type, string> storePrefixes = RegisterTypes();

   private static readonly MemcachedClient cache = new MemcachedClient();

   private static UniqueKeyValuePair<Type, string> RegisterTypes()
   {
      UniqueKeyValuePair<Type, string> prefixes = new UniqueKeyValuePair<Type, string>();    
      prefixes.Add(typeof(Staff), "S");
      prefixes.Add(typeof(Company), "C");
      return prefixes;
   }

   public static T Get<T>(string key) where T : class
   {
      string prefix = storePrefixes[typeof(T)];
      if (prefix != null)
      {
         try
         {
            return (T)cache.Get(prefix + key);
         }
         catch
         {
             return null;
         }
      }
      else
      {
         return null;
      }
   }

   public static void Add<T>(string key, T value, int duration) where T : class
   {
      string prefix = storePrefixes[value.GetType()];
      if (prefix != null)
      {
         cache.Store(StoreMode.Set, prefix + key, value, DateTime.Now.AddMinutes(duration));
      }            
   }

   public static void Remove<T>(string key)
   {
      string prefix = storePrefixes[typeof(T)];
      if (prefix != null)
      {
        cache.Remove(prefix + key);
      }
   }
}

You may consider implementing an interface for your cachable objects and limit CacheManager's usage to those types:
public interface ICacheable
{
   string CacheKey
   {
      get;
   }
}

public class CacheManager
{
   ...
 
   public static void Add<T>(T value, int duration) where T : ICacheable
   {
      string prefix = storePrefixes[value.GetType()];
      if (prefix != null)
      {
         cache.Store(StoreMode.Set, prefix + value.CacheKey, value, DateTime.Now.AddMinutes(duration));
      }            
   }
   
  ...
}

Post a Comment

 

Copyright © 2011 BREAKPOINT | Powered by Blogger | Template by 54BLOGGER