The 'Reluctant Cache' Pattern

Caching is one of the greatest strategies for improving the performance of our applications. Operations such as database access and web service calls can take time, require network hops and consume valuable server resources such as processor cycles.

A common caching pattern is as follows:

   1:  public static List<Customer> GetCustomers() {
   2:      string cacheKey = "Customers";
   3:      int cacheDurationInSeconds = 5; //an artificially low number for demonstration
   4:   
   5:      object customers = HttpRuntime.Cache[cacheKey] as List<Customer>;
   6:   
   7:      if (customers == null) {
   8:          customers = CustomerDao.GetCustomers();
   9:   
  10:          HttpRuntime.Cache.Insert(cacheKey, customers, null, DateTime.Now.AddSeconds(cacheDurationInSeconds), System.Web.Caching.Cache.NoSlidingExpiration);
  11:      }
  12:   
  13:      return (List<Customer>)customers;
  14:  }

If the item is not present in the cache, we fetch it from the data access layer and insert it into the cache for quick access next time (assuming that there is a next time). This works well, but gives us little control as everything will be cached, regardless of how often it is accessed. Imagine how a cache will grow as google indexes our website - the majority of cached items will be thrown away after a period of time without ever been accessed a second time.

The following pattern provides a simple solution for caching the most frequently accessed items, while ignoring items that are seldomly accessed.

The Reluctant Cache Pattern

   1:  public static List<Customer> GetCustomers() {
   2:      string cacheKey = "Customers";
   3:      int cacheDurationInSeconds = 5; //an artificially low number for demonstration
   4:   
   5:      object customers = HttpRuntime.Cache[cacheKey] as List<Customer>;
   6:   
   7:      if (customers == null) {
   8:          customers = CustomerDao.GetCustomers();
   9:   
  10:          if (new ReluctantCacheHelper(cacheKey, cacheDurationInSeconds, 2).ThresholdHasBeenReached) {
  11:              HttpRuntime.Cache.Insert(cacheKey, customers, null, DateTime.Now.AddSeconds(cacheDurationInSeconds), System.Web.Caching.Cache.NoSlidingExpiration);
  12:          }
  13:      }
  14:   
  15:      return (List<Customer>)customers;
  16:  }

As you can see, it only adds one extra line to our cache helper class. The ReluctantCacheHelper class keeps a count of any requests for a particular item over a period of time. If this request count reaches a configurable threshold, it inserts the item into the cache.

A simple demonstration of this illustrates the point. If the list of customers are requested more than once in a five second period, they are placed in the cache. Source code is also available.

The ReluctantCacheHelper class listing is as follows:

   1:  public class ReluctantCacheHelper {
   2:      private short _requestCountThreshold;
   3:      private ReluctantCacheRequestToken _cacheRequestToken;
   4:   
   5:      public ReluctantCacheHelper(string cacheKey) : this(cacheKey, 60) {}
   6:      public ReluctantCacheHelper(string cacheKey, int cacheDurationInSeconds) : this(cacheKey, cacheDurationInSeconds, 2) {}
   7:      public ReluctantCacheHelper(string cacheKey, int cacheDurationInSeconds, short requestCountThreshold) {
   8:          this._requestCountThreshold = requestCountThreshold;
   9:   
  10:          //get the token
  11:          string cacheRequestTokenKey = cacheKey + "_token";
  12:          this._cacheRequestToken = (ReluctantCacheRequestToken)HttpRuntime.Cache[cacheRequestTokenKey];
  13:   
  14:          if (this._cacheRequestToken == null) {
  15:              //create and insert a new token
  16:              this._cacheRequestToken = new ReluctantCacheRequestToken();
  17:              HttpRuntime.Cache.Insert(cacheRequestTokenKey, this._cacheRequestToken, null, DateTime.Now.AddSeconds(cacheDurationInSeconds), System.Web.Caching.Cache.NoSlidingExpiration);
  18:          } else {
  19:              //increment the request token
  20:              this._cacheRequestToken.Increment();
  21:          }
  22:      }
  23:      
  24:      public bool ThresholdHasBeenReached {
  25:          get {
  26:              if (this._cacheRequestToken.RequestCount >= this._requestCountThreshold) {
  27:                  return true;
  28:              } else {
  29:                  return false;
  30:              }
  31:          }
  32:      }
  33:  }

It stores a light-weight CacheRequestToken in the cache and increments it as each request for the item is made. If the count reaches the threshold level, the item will be inserted in the cache for quick retrieval in subsequent requests.

The CacheRequestToken code listing is as follows:

   1:  public class ReluctantCacheRequestToken {
   2:      private short _requestCount = 1;
   3:      public short RequestCount { 
   4:          get { 
   5:              return this._requestCount;
   6:          } 
   7:      }
   8:   
   9:      public void Increment() {
  10:          this._requestCount++;
  11:      }
  12:  }

View Demo - Download Source -


21 Comments

Comments have been disabled for this content.