Kjensen
Kjensen

Reputation: 12384

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

Refresh on expiry

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

Upvotes: 3

Views: 8275

Answers (2)

MichaC
MichaC

Reputation: 13410

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.

Upvotes: 2

Haytam
Haytam

Reputation: 4733

I didn't quiet get your "Scheduled" refresh idea, why set the cache to expire in 60 minutes and refresh it every 15 minutes?

If I were you, I would of done it like this (somehow like your second idea):

You populate the CacheManager for the first time, then every 60 minutes you create and new CacheManager instance (and populate it) and if everything goes well you replace the older instance (you dispose it too).

I thought about this because in CacheManager, expired items don't get deleted automatically so they will still be there, and if you decide to do something like [Populate-ClearAll-Populate-..] then you might get into some problems, so the safest way is to create a new instance at every refresh so that you're sure your Cache is always working.

Upvotes: 1

Related Questions