Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] 🧼 Clear #331

Open
jodydonetti opened this issue Nov 17, 2024 · 4 comments
Open

[FEATURE] 🧼 Clear #331

jodydonetti opened this issue Nov 17, 2024 · 4 comments
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@jodydonetti
Copy link
Collaborator

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 EMPTY

Damn 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 MemoryCache instance is used with different FusionCache instances (usually also with cache-key prefix), a Clear() on the MemoryCache instance would effectively clear all the FusionCache instances that use the same underlying MemoryCache instance

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 MemoryCache supports a raw clear
  • the underlying MemoryCache is 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 🙂 ?

@jodydonetti jodydonetti self-assigned this Nov 17, 2024
@jodydonetti jodydonetti added the enhancement New feature or request label Nov 17, 2024
@jodydonetti jodydonetti added this to the v2.0.0 milestone Nov 17, 2024
@angularsen
Copy link

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?

@jodydonetti jodydonetti pinned this issue Nov 18, 2024
@jodydonetti
Copy link
Collaborator Author

jodydonetti commented Nov 18, 2024

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?

@angularsen
Copy link

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!

@jodydonetti jodydonetti changed the title [FEATURE] 🧼 Clear() [FEATURE] 🧼 Clear Dec 2, 2024
@jodydonetti
Copy link
Collaborator Author

Hi all, v2.0.0-preview-3 is out 🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants