Search code examples
c#.netcachingcachemanager

CacheManager - Refresh cache every x minutes or on expiration


Using CacheManager, how would you best go about implementing this scenario, where the cache instance contains data, that can take a long time to get from a slow source?

I never want the users to wait for the cache to populate (I am not concerned about first load)

I can think of two ways, but not sure if they are possible with CacheManager:

"Scheduled" refresh

  • Set the cache instance to expire after 60 minutes
  • Every 15 minutes, schedule something to refresh the cache instance

Refresh on expiry

  • When cache instance expires, fire an event that refreshes the data. While the data is refreshing (or if it fails to refresh), the cache instance still returns the "stale" data.

What is technically possible with Cachemanager, and which approach works best - if at all?


Solution

  • Regarding your second option, "Refresh on expiry", this doesn't work because CacheManager doesn't return the cached value if the item expired. And, while you "refresh" the cache, you'd eventually get a lot of cache misses from other callers.

    My suggestions:

    If you have only a small amount of keys cached for 60 minutes and they all "work the same", just spin up a background task which runs async to your process and refreshes the cache every 15 minutes.

    If you have many keys which can vary a lot in terms of expiration, you could cache the data with 60 minutes expiration and store a secondary key (a fake key with no actual value) with a 15 minutes expiration. Then, if the fake key expires, refresh the actual key/value... As key for the fake one, use a prefix+actual key for example and then listen to OnRemove event for example.

    Quick example program

    internal class Program
    {
        private static ICacheManager<object> _cache;
    
        private static void Main(string[] args)
        {
            _cache = CacheFactory.Build(c => c.WithDictionaryHandle());
    
            _cache.OnRemoveByHandle += Cache_OnRemoveByHandle;
    
            for (var i = 0; i < 10; i++)
            {
                _cache.Add("key" + i, "data" + i);
                AddFakeKey("key" + i);
                Thread.Sleep(1000);
            }
    
            Console.ReadKey();
        }
    
        private static void AddFakeKey(string forKey)
        {
            _cache.Add(new CacheItem<object>("trigger_" + forKey, "n/a", ExpirationMode.Absolute, TimeSpan.FromSeconds(1)));
        }
    
        private static void Cache_OnRemoveByHandle(object sender, CacheItemRemovedEventArgs e)
        {
            // Remark: you might get this event triggered for each level of the cache e.Level can be checked to react only on the lowest level...
    
            Console.WriteLine("OnRemoveByHandle " + e.ToString());
            if (e.Key.StartsWith("trigger_") && e.Reason == CacheItemRemovedReason.Expired)
            {
                var key = e.Key.Substring(8);
                Console.WriteLine("Updating key " + key);
    
                // updating the key
                _cache.Update(key, _ => "new value");
    
                // add a new fake key for another round
                AddFakeKey(key);
            }
        }
    }
    

    This even works with Redis if keyspace notifications are enabled.