Umbraco IAppPolicyCache in Load Balanced Environments
a
Hello all, We use IAppPolicyCache for caching certain content, and in this instance are load balancing 2 servers. An issue that we are noticing is that if we clear an IAppPolicyCache entry on one server, it doesn't occur on the other server. What is the best way to send a signal to clear the cache on all sites? I saw an article on ICacheRefresher, but I wasn't sure how to use that in the real world. https://docs.umbraco.com/umbraco-cms/fundamentals/setup/server-setup/load-balancing
Adding more context. Here is a current sample call to get cached content: private readonly IAppPolicyCache _runtimeCache; ... var errorPageNodeId = _runtimeCache.GetCacheItem(cacheKey, async () => { }, new TimeSpan(0, 15, 0)); and clear it due to some event: _runtimeCache.ClearByKey(cacheKey);
I'm a little confused, as this example uses **IAppPolicyCache**: https://docs.umbraco.com/umbraco-cms/reference/cache/examples/tags but this page says to seemingly reference a different **DistributedCache **: https://docs.umbraco.com/umbraco-cms/reference/cache
This is the best example I’ve found so far, is this a good model to use? The person’s code isn’t working apparently but it makes sense to me. https://our.umbraco.com/forum/using-umbraco-and-getting-started/112171-use-distributedcache-to-send-icacherefresher-to-subscriber-server-not-working
d
Hi Aaron! I can confirm that the Cache refresher is the right tool for this job. I've never done this myself unfortunately, but from my understanding, it's very similar to notifications. If I remember correctly, you use the
DistributedCache
to send caching notifications across all your instances. It doesn't actually do the caching itself. You can implement
ICacheRefresher
to have it clear your
IAppPolicyCache
. Then you use
DistributedCache
to fire a notification that makes all your instances execute their
ICacheRefresher
. Does that make sense?
a
yes, thanks. Any idea how to fire the refresh from DistributedCache? It looks like I need to get an instance of DistributedCache, but there is no IDistributedCache to inject
d
Have you tried
DistributedCache
without the
I
?
It might not have an interface
a
I get the classic "CS0120: An object reference is required for the non-static field, method, or property "DistributedCache.RefreshAll(Guid)"
d
Try injecting the class 😉
a
nice, that hadn't even occurred to me. Seems to build, thanks.
hmm, close but no cigar. When the cache refresher is executed, I am getting this: One or more errors occurred. (A suitable constructor for type 'Umbraco.Cms.Core.Notifications.CacheRefresherNotification' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided.) it seems the example I am trying to follow is a bit off.
d
Ah, you might need to implement your own cache refresher notification
a
yeah, just realized that, thanks
d
You might not need to actually implement the ICacheRefresher. I believe you can just use the
INotificationHandler
interface, according to this page in the docs: https://docs.umbraco.com/umbraco-cms/reference/notifications/cacherefresher-notifications
a
Ok. I actually got it to work with a custom notification class, which was not in the example post I was following: using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Sync; namespace Marathon.Core.Core.CacheRefresher { public class MyCacheRefresherNotification : CacheRefresherNotification { public MyCacheRefresherNotification(object messageObject, MessageType messageType): base(messageObject, messageType) { MessageObject = messageObject ?? throw new ArgumentNullException(nameof(messageObject)); MessageType = messageType; } public object MessageObject { get; } public MessageType MessageType { get; } } }
My only fear with using Umbraco's INotificationHandler and its events is that I am caching custom content, not umbraco content, idk if that would cause any issues. Probably not?
d
It depends. The MessageObject is saved into the database iirc, so whatever you put into the DistributedCache, I expect it needs to be serializable in some way
Usually you'd just stuff some sort of ID into the MessageObject and then use the ID to empty the caches. You probably don't need the cached value in order to invalidate the cache
a
Cool, thanks for the explanation!
Ok so if I am implementing RefreshAll() like so:
Copy code
public override void RefreshAll()
    {
        _logger.LogInformation("Refresh RunTime Cache in the machine name {machineName}", Environment.MachineName);
        AppCaches.RuntimeCache.Clear();
        base.RefreshAll();
    }
I'm thinking I don't need to manually clear the cache when calling this:
Copy code
_runtimeCache.ClearByKey(cacheKey); // <- unnecessary
_distributedCache.RefreshAll(MyRuntimeCacheLoadBalancerRefresher.UniqueId); //clear cache across all servers
d
Sounds about right 👍
a
To anyone else discovering this for help in the future, I just discovered IPayloadCacheRefresher. You can create an implementation of this and clear cache by payload - meaning for example, by cache key instead of the whole runtime cache. Here is an example. MyPayloadCacheRefresher.cs: public static class MyPayloadCacheSettings { public static Guid UniqueId = Guid.Parse("e3ef58g4-eafc-41b7-9c31-e23923711653"); } public class MyPayloadCacheRefresher : IPayloadCacheRefresher { private readonly ILogger _logger; private readonly AppCaches _appCaches; Guid ICacheRefresher.RefresherUniqueId => MyPayloadCacheSettings.UniqueId; string ICacheRefresher.Name => "My Payload Runtime Cache Load Balancer Refresher"; public MyPayloadCacheRefresher(AppCaches appCaches, ILogger logger) { _logger = logger; _appCaches = appCaches; } void IPayloadCacheRefresher.Refresh(string[] payload) { //_logger.LogInformation("Fired Marathon Refresh Payload Cache in the machine name {machineName}", Environment.MachineName); foreach (var cacheKey in payload) { _appCaches.RuntimeCache.ClearByKey(cacheKey); } } void IJsonCacheRefresher.Refresh(string json) { throw new NotImplementedException(); } void ICacheRefresher.RefreshAll() { //throw new NotImplementedException(); _logger.LogInformation("Fired Marathon Refresh ALL Payload Cache in the machine name {machineName}", Environment.MachineName); _appCaches.RuntimeCache.Clear(); } public void Refresh(int id) { throw new NotImplementedException(); } public void Remove(int id) { throw new NotImplementedException(); } public void Refresh(Guid id) { throw new NotImplementedException(); } }
and you can call it in your code like _distributedCache.RefreshByPayload(MyPayloadCacheSettings.UniqueId, new List() { "my-cache-key" });
this way you aren't refreshing the entire server runtime cache of all servers
27 Views