Open jodydonetti opened 4 days ago
Sounds terrific!
My only thought was, should there be an option to opt-out of using MemoryCache.Clear()
?
We do resolve IMemoryCache
from DI in a couple of places and do things with it directly for mostly legacy reasons, and I would not want that to disappear if clearing via FusionCache - I would expect to only clear stuff I have set via FusionCache.
Or does FusionCache use its own instance of MemoryCache so this is not a problem?
Sounds terrific!
Great 😬
My only thought was, should there be an option to opt-out of using
MemoryCache.Clear()
?
I thought about this, but cannot see a concrete reason why. An actual underlying Clear() is what would be desirable, if not for all the other complications.
We do resolve
IMemoryCache
from DI in a couple of places and do things with it directly for mostly legacy reasons, and I would not want that to disappear if clearing via FusionCache - I would expect to only clear stuff I have set via FusionCache.
That is exactly what will happen! One of the criteria I listed is "if the underlying MemoryCache is owned", meaning FusionCache created it, so that check solves this problem.
Or does FusionCache use its own instance of MemoryCache so this is not a problem?
Both via DI and directly (eg: via new()) you can pass FusionCache a MemoryCache instance or not (and in that case it will create its own, which is the default behavior): if you don't pass anything it means it's totally owned by FusionCache, and that is when the actual underlying Clear() kicks in.
Thoughts?
Yes that addresses my concern, we don't specify a MemoryCache instance for FusionCache so then it will create and own it and work as you described. Perfect!
Problem
One of the most requested features for FusionCache has always been a
Clear()
mechanism.The reason why it has always been hard to do it is that we are not talking about a simple memory cache: that would've been quite easy.
Instead we need to consider all of these configurations:
Then multiply all of these for these scenarios:
Finally, as a cherry on top, everything should automatically handle transient errors and work with features like fail-safe, soft timeouts, auto-recovery and more.
And... that is a lot!
So how is it possible to do achieve all of this?
Solution
Now that Tagging is finally coming along, I think we have our solution.
By simply picking a "special" tag like
"*"
we can use Tagging to make a properClear()
mechanism work (for a detail of Tagging works underneath, please refer to that issue).Here's an example:
Damn if that is nice 😬
Performance
On one hand, using Tagging to achieve clear is for sure a great design choice: all the plumbing available in FusionCache is used to achieve and empower Tagging, and in turn all the Tagging plumbing is used to achieve and empower
Clear()
support.Nice, really.
On the other hand, we can go one step further: since the special tag used for clear is one and one only, we may special-case it and do some things to make it even better.
This is why I'm also saving the expiration timestamp for the special clear tag directly in memory (eg: in a normal variable), so that FusionCache will keep it there forever and every
Clear()
call would also update it: in this way the speed of checking it would be even greater than checking the cache entry for the special tag.But wait, in a multi-node scenario a
Clear()
may happen on another node, and we may receive a backplane notification from that other node!Correct, and that is why when receiving a backplane notification FusionCache also checks to see if it is for the special tag and, if so, do what is needed so that the expiration timestamp (and the dedicated variable) is updated, automatically.
And what happens in case of transient issues while sending that backplane notification?
No big deal,we are already covered thanks to Auto-Recovery.
Again, really really nice, even if I say so myself.
Raw Clear()
From some time now the standard
MemoryCache
(currently used as the L1) actually supports a "real"Clear()
method that does what I call a "raw clear", meaning a full one like you do with aDictionary<TKey, TValue>
instead of the "simulated" one done thanks for the client-assisted approach of the Tagging feature.So, can't just FusionCache use it?
Actually no, for the reasons exposed at the beginning, meaning:
MemoryCache
instance is used with different FusionCache instances (usually also with cache-key prefix), aClear()
on theMemoryCache
instance would effectively clear all the FusionCache instances that use the same underlyingMemoryCache
instanceBut a lot of users use FusionCache without L2 and/or a backplane, and without sharing a
MemoryCache
instance between multiple FusionCache instances, so... can't FusionCache do a "raw clear" in those cases?Yes, yes it can!
This is in fact what FusionCache will do. So, if:
MemoryCache
supports a raw clearMemoryCache
is not shared (between different FusionCache instances)then when
Clear()
is invoked FusionCache will actually callClear()
on the underlyingMemoryCache
, and immediately wipe out the entire cache.Can I say, again, nice 🙂 ?