ZiggyCreatures / FusionCache

FusionCache is an easy to use, fast and robust hybrid cache with advanced resiliency features.
MIT License
1.87k stars 96 forks source link

[FEATURE] ♊ Auto-Clone #279

Closed jodydonetti closed 3 months ago

jodydonetti commented 3 months ago

Preface

In general is never a good idea to mutate data retrieved from the cache, it should always be considered immutable/readonly.

To see why, the typical example of such behaviour is an update flow: get something, change it, and then save it back to the database, seemingly easy peasy. The problem in this case is that a cache is a cache, meaning the value may not be the very last version from the database, even by just 1s or less: since we are updating it, we want to have the very last version before changing it and saving it back, otherwise we would loose some changes made after the last time cached it.

Although optimistic concurrency, with a variation of etags/last modified, surely help and helps avoid critical issues like loosing some changes, by enlarging the temporal window where something may be stale we are increasing the chance of optimistic concurrency errors.

Therefore in these scenarios (like the update flow mentioned above) the best practice is to bypass the cache entirely and just get it from the database while using the cache for the remaining reads which, in the vast majority of real-world use cases, comprise 99%+ of the overall reads (typically systems are more read-heavy than write-heavy), so we can keep the incredible perf boost with caching.

Having said that, keep reading...

Problem

It came up multiple times a desire to be able to get something from the cache and safely change it, since this may not necessarily be related to an update flow or because, simply, users may have a particular scenario in mind, and ideally they should be abe to just do that in an easy (and optimized!) way.

Currently a common solution is to create a wrapper implementation of IMemoryCache which is passed to FusionCache and would act on an underlying IMemoryCache instance by serialize + deserialize an entry at each get operation.

This technically works, but it has a couple of issues:

Solution

Since I collected enough evidence that this is something needed by the community (even though probably not by a huge amount of people), I decided to finally tackle this with a new option called Auto-Cloning that:

That's cool, but how?

Well FusionCache always had the ability to specify a serializer (type IFusionCacheSerializer) to be able to work with the distributed cache: it will use the same serializer to (de)serialize values, easy peasy.

To avoid being forced to also specify A new method SetupSerializer(serializer) is being created, while also being able to do the same via dependency injection, as always. The option ReThrowOriginalExceptions will also be respected.

There will be a new entry option, namely bool EnableAutoClone that will trigger the mechanism (default: false). Regarding performance: instead of serializing + deserializing at every get call (which would be a waste), FusionCache will keep track of an internal buffer on each memory entry and, only if and when it will be requried, it will serialize it (once), so that the binary payload will be available to deserialize at every get call.

Extra care will be put into avoiding any synchronization/double-serialization issue related to multithreading/high-load scenario.

Finally, since the feature create the clear expectation to be able to get something from the cache and freely modify it without repercussions, an exception will be thrown in these cases:

Of course DefaultEntryOptions are always at our disposal to do the usual default + granular change flow, so we can both enable it granulary per-call or just once in the DefaultEntryOptions and forget about it (but remember: auto-cloning has a cost, so use it with care).

Alternatives

The wrapper class mentioned above, with all the issues it does have.

Additional context

The upcoming HybridCache from Microsoft will seemingly have a similar feature, meaning it will clone cached values before returning them, so feature-wise this would be aligned with that.

jodydonetti commented 3 months ago

Hi all, v1.3.0 is out 🥳