Showing posts with label Caching. Show all posts
Showing posts with label Caching. Show all posts

Saturday, June 18, 2016

Client Side Caching for jQuery

Updates: 6/26/16

  • Fixed bug where triple equals null check would miss.
  • Added support for data driven cache key.
  • Removed let and const statements (some minifiers were having a hard time with them)

Original:

There is great question on Stack Overflow about caching a jquery ajax response in javascript/browser. Unfortunately, even thought it was a good solution, it did not do quite what I needed it to.

The application I was trying to optimize sometimes made redundant parallel requests, and I needed my caching solution to include a queuing system to prevent duplicate fetches.

Below is a simple solution that uses jQuery.ajaxPrefilter to check a local cache prior to making GET requests. Additionally, it will queue the callback if the request is already in flight. The cache stores the queue in both memory and local storage, ensuring that the cache will persist across page loads.

Implementation

(function ($) {
  "use strict";
 
  var timeout = 60000;
  var cache = {};
 
  $.ajaxPrefilter(onPrefilter);
 
  function onPrefilter(options, originalOptions) {
    if (options.cache !== true) {
      return;
    }
 
    var callback = originalOptions.complete || $.noop;
    var cacheKey = getCacheKey(originalOptions);
 
    options.cache = false;
    options.beforeSend = onBeforeSend;
    options.complete = onComplete;
 
    function onBeforeSend() {
      var cachedItem = tryGet(cacheKey);
 
      if (!!cachedItem) {
        if (cachedItem.data === null) {
          cachedItem.queue.push(callback);
        } else {
          setTimeout(onCacheHit, 0);
        }
 
        return false;
      }
 
      cachedItem = createCachedItem();
      cachedItem.queue.push(callback);
      setCache(cacheKey, cachedItem);
      return true;
 
      function onCacheHit() {
        invoke(callback, cachedItem);
      }
    }
 
    function onComplete(data, textStatus) {
      var cachedItem = tryGet(cacheKey);
 
      if (!!cachedItem) {
        cachedItem.data = data;
        cachedItem.status = textStatus;
        setCache(cacheKey, cachedItem);
 
        var queuedCallback;
        while (!!(queuedCallback = cachedItem.queue.pop())) {
          invoke(queuedCallback, cachedItem);
        }
 
        return;
      }
 
      cachedItem = createCachedItem(data, textStatus);
      setCache(cacheKey, cachedItem);
      invoke(callback, cachedItem);
    }
  }
 
  function tryGet(cacheKey) {
    var cachedItem = cache[cacheKey];
 
    if (!!cachedItem) {
      var diff = new Date().getTime() - cachedItem.created;
 
      if (diff < timeout) {
        return cachedItem;
      }
    }
 
    var item = localStorage.getItem(cacheKey);
 
    if (!!item) {
      cachedItem = JSON.parse(item);
 
      var diff = new Date().getTime() - cachedItem.created;
 
      if (diff < timeout) {
        return cachedItem;
      }
 
      localStorage.removeItem(cacheKey);
    }
 
    return null;
  }
 
  function setCache(cacheKey, cachedItem) {
    cache[cacheKey] = cachedItem;
 
    var clone = createCachedItem(cachedItem.data, cachedItem.status, cachedItem.created);
    var json = JSON.stringify(clone);
    localStorage.setItem(cacheKey, json);
  }
 
  function createCachedItem(data, status, created) {
    return {
      data: data || null,
      status: status,
      created: created || new Date().getTime(),
      queue: []
    };
  }
 
  function invoke(callback, cachedItem) {
    if ($.isFunction(callback)) {
      callback(cachedItem.data, cachedItem.status);
    }
  }
  
  function getCacheKey(originalOptions) {
    if (!!originalOptions.data) {
      return originalOptions.url + "?" + JSON.stringify(originalOptions.data);
    }
 
    return originalOptions.url;
  }
 
})(jQuery);

Enjoy,
Tom

Sunday, February 21, 2016

Async Cache Repository v2

Three years ago (wow, time flies) I wrote a generic Cache Repository that has become one of my more popular open source projects. It is 2016, so was definitely time to create an async version of that cache repository! This new implementation has all of the same features as the original, only now it is completely async from top to bottom.

CacheRepository.Web

Features

  • Thread Safe GetOrSet
  • Configurable Expiration Enums
  • Transparent Cache Key Management By Type
  • A Web Implementation

NuGet Package and Source

Enjoy,
Tom

Thursday, April 30, 2015

Persistent Cache Repository using SQLite

A while back I blogged about an ICacheRepository. I still really like that concept, although admittedly there a few updates that I should make to the implementation.

The basic implementation that I shared before was for an in memory cache. There are plenty of document databases that I would recommend for you to use as a persistent cache mechanism, and they would also have the added benefit of being distributed across services.

What do you do if you need a persistent cache, but you don't have any of those document databases available? Just use files! By which I mean SQLite.

Would this stand up under load? Probably not.
Would I recommend that production systems use this? No.
Is it super simple to setup? Yes, yes it is!

Enjoy,
Tom

Sunday, November 24, 2013

Generic Enum Attribute Caching

Attributes are wonderful for decorating your Enums with additional markup and meta data. However, looking up attributes via reflection is not always a fast operation in terms of performance. Additionally, no one likes typing that code over and over again.

Well not to worry, just use the following extension methods to help cache your Enum attribute look ups, and increase your application's performance! But how much faster is this? Good question...

Iterations Average Elapsed Ticks Difference
No Cache With Cache
2 1182.00 3934.00 4x Slower
10 20.10 7.10 3x Faster
100 13.07 1.37 10x Faster
1,000 13.27 1.40 10x Faster
10,000 13.56 1.45 10x Faster
100,000 13.02 1.33 10x Faster

Sunday, February 17, 2013

Cache Repository for MVC

First and foremost, the CacheRepository is NOT web specific!

The CacheRepository library contains the abstract CacheRepositoryBase class, which can be implemented by any caching system that you choose. The CacheRepository.Web NuGet package includes a WebCacheRepository implementation that leverages System.Web.Caching.Cache

CacheRepository.Web

Get all of the source code, unit tests, and a complete MVC4 sample application (preconfigured to include dependency injection with Unity) from GitHub. Or to just jump in and start using it, grab the CacheRepository.Web package from NuGet.

ICacheRepository

The ICacheRepository interface has all the standard Get, Set, Remove and Clear methods. Additionally, these methods have been expanded to include enum parameters to group manage cache expiration.

Best of all, it includes GetOrSet methods. These methods will try to get the value of the specified key, and when it can not find that value it will then be loaded via the passed in Func. A key feature here is the fact that the setter of the GetOrSet will lock on load to prevent redundant data loads. (More details about this below.)

public interface ICacheRepository
{
    object Get(string key);
    T Get<T>(string key);
 
    T GetOrSet<T>(string key, Func<T> loader);
    T GetOrSet<T>(string key, Func<T> loader, DateTime expiration);
    T GetOrSet<T>(string key, Func<T> loader, TimeSpan sliding);
    T GetOrSet<T>(string key, Func<T> loader, CacheExpiration expiration);
    T GetOrSet<T>(string key, Func<T> loader, CacheSliding sliding);
 
    void Set<T>(string key, T value);
    void Set<T>(string key, T value, DateTime expiration);
    void Set<T>(string key, T value, TimeSpan sliding);
    void Set<T>(string key, T value, CacheExpiration expiration);
    void Set<T>(string key, T value, CacheSliding sliding);
 
    void Remove(string key);
    void ClearAll();
}

ThreadSafe GetOrSet

As any good cache should, the CacheRepository is thread safe and can be treated as a singleton.

Even better, the GetOrSet methods lock on set. This means that 10 threads could be trying to load the same cache value simultaneously, but only one will actually trigger a load and set. This helps redundant resource loads and database calls and keep your application running optimally.

private T GetOrSet<T>(string key, Func<T> loader,
    DateTime? expiration, TimeSpan? sliding)
{
    // Get It
    T value;
    var success = TryGet(key, out value);
 
    // Got It or No Loader
    if (loader == null || success)
        return value;
 
    // Load It
    return LockedInvoke(key, () =>
    {
        // Get It (Again)
        success = TryGet(key, out value);
 
        if (!success)
        {
            // Load It (For Real)
            value = loader();
 
            // Set It
            if (value != null)
                Set(key, value, expiration, sliding);
        }
 
        return value;
    });
}

Configurable Expiration Enums

Instead of being forced to set each of your cache expirations separately, you now have the option to use an enum value to group your cache expirations. Enums are available for both absolute expiration and sliding expiration.

You may override the duration of each cache key in your app config; this is really useful for when you want to uniformly modify your cache expirations in your QA or testing environments. Additionally, you may override the CacheRepositoryBase.GetConfigurationValue method to pull this configuration for anywhere you want, not just the ConfigurationManager.

public enum CacheExpiration
{
    VeryShort = 10,     // Ten Seconds
    Short = 60,         // One Minute
    Medium = 300,       // Five Minutes
    Long = 3600,        // One Hour
    VeryLong = 86400    // One Day
}

<configuration>
  <appSettings>
    <add key="CacheExpiration.VeryShort" value="15" />
    <add key="CacheExpiration.Short" value="90" />

ByType Extensions

Transparent cache key management is offered through a series of ICacheRepository extension methods. This makes caching even easier, as you don't need to worry about cache key collisions.

Using these extension methods, you many cache a Cat object with an Id of 1 and a Dog object with an Id of 1 in the same manner. The extension methods will create a key prefix based on the type and then use the specified identified as the suffix.

[Fact]
public void SameKeyDifferentType()
{
    var setCat = new Cat { Name = "Linq" };
    CacheRepository.SetByType(1, setCat);
 
    var setDog = new Dog { Name = "Taboo" };
    CacheRepository.SetByType(1, setDog);
 
    var getCat = CacheRepository.GetByType<Cat>(1);
    Assert.Equal(setCat.Name, getCat.Name);
 
    var getDog = CacheRepository.GetByType<Dog>(1);
    Assert.Equal(setDog.Name, getDog.Name);
}

Web Implementation & Dependency Injection via Unity

The CacheRepository.Web NuGet package includes a WebCacheRepository implementation that leverages System.Web.Caching.Cache. This is a very simple, but very effective, production ready implementation of the CacheRepositoryBase.

It should also be noted that this implementation is not only useful in ASP.NET, the System.Web.Caching.Cache is located in the System.Web library, but it still available outside of a web context. This means that this implementation can be used in other applications, including but not limited to Windows Services.

public class WebCacheRepository : CacheRepositoryBase
{
    private readonly Cache _cache;
        
    public WebCacheRepository()
    {
        _cache = HttpContext.Current == null
            ? HttpRuntime.Cache
            : HttpContext.Current.Cache;
    }

Wiring the cache repository to be injected into your controllers via Unity is also very easy. Just register the WebCacheRepository for the ICacheRepository interface, and be sure to provide a ContainerControlledLifetimeManager for optimal performance.

To use the implementation below, you will need to include the Unity.MVC3 NuGet Package to gain access to the UnityDependencyResolver.

public static void Initialise()
{
    var container = BuildUnityContainer();
    
    DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
 
private static IUnityContainer BuildUnityContainer()
{
    var container = new UnityContainer();
 
    container.RegisterType<ICacheRepository, WebCacheRepository>(
        new ContainerControlledLifetimeManager());           
 
    return container;
}

If you missed the links above, here they are again:

Shout it

Enjoy,
Tom

Real Time Web Analytics