-
Notifications
You must be signed in to change notification settings - Fork 167
Description
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:
- L1
- L2 (optional)
- backplane (optional)
- isolated L1 or shared L1
- isolated L2 or shared L2
- usage of a cache-key prefix
- usage of multiple named caches
Then multiply all of these for these scenarios:
- single node
- multiple nodes
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 proper Clear() mechanism work (for a detail of Tagging works underneath, please refer to that issue).
Here's an example:
cache.Set("foo", 1);
cache.Set("bar", 2);
cache.Set("baz", 3);
// CLEAR
cache.Clear();
// NOW THE CACHE IS EMPTYDamn if that is nice!
Note
In reality, the special tag I currently picked in preview-1 is "__*" so that it would not collide with a nice "*" tag that users may potentially end up using... but I'm trying to understand what HybridCache will use and maybe use the same, so that doing a RemoveByTag("*") in both libraries will get you the same result. I'll take a decision before going GA with v2.
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 a Dictionary<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:
- if there is an L2: if we clear L1, at the first request the data that should've been cleared will come back from L2 (where it is not possible to do an actual clear)
- if the L1 is shared: if the same
MemoryCacheinstance is used with different FusionCache instances (usually also with cache-key prefix), aClear()on theMemoryCacheinstance would effectively clear all the FusionCache instances that use the same underlyingMemoryCacheinstance
But 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:
- there is no L2
- there is no backplane
- the underlying
MemoryCachesupports a raw clear - the underlying
MemoryCacheis not shared (between different FusionCache instances)
then when Clear() is invoked FusionCache will actually call Clear() on the underlying MemoryCache, and immediately wipe out the entire cache.
Can I say, again, nice 🙂 ?