FusionCache is an easy to use, fast and robust hybrid cache with advanced resiliency features.
MIT License
Bot releases are hidden (Show)
Published by jodydonetti almost 2 years ago
SkipDistributedCache
optionAfter some requests by the community, a new entry option has been added to skip the distributed cache only in specific calls, in a very granular way.
See here for more.
SkipDistributedCacheReadWhenStale
optionIt is now possible to skip distributed cache reads when the memory entry was stale: this will lead to a perf boost when the 2nd layer is not actually a distributed cache but just an out-of-process cache, like in mobile apps, standalone apps, games and more.
See here for more.
Thanks to an issue opened by @RMcD , it has been discovered that MemoryPack has a quite strange and non-standard way to handle transitive dependencies + code generation regarding .NET Standard 2.1 and .NET 7.
After having opened an issue on its repo + some back and forth with its author (thanks to @neuecc for your openness and support!), a solution has been found. The issue there remains, but at least now the integration between FusionCache and MemoryPack is good to go, even on .NET 7!
See here for more.
EnableBackplaneNotifications
option to (inverted) SkipBackplaneNotifications
A small rename so that now everything is more aligned and intuitive.
See here for more.
Augmented some online docs and inline XML docs, for a better experience while coding.
Published by jodydonetti almost 2 years ago
A new serializer is now available in its own package, to support the new MemoryPack serialization format.
It is based on the amazing work by @neuecc , and this new serializer seems to be already the fastest serializer in the .NET space.
Thanks for your hard work Yoshifumi!
After a couple of releases where it was disabled by default, the initial response in real-world projects has been positive: therefore I decided to enable the auto-recovery feature by default for everyone ๐
PS: of course you can always turn it off in case you are having problems.
Published by jodydonetti almost 2 years ago
A new serializer is now available in its own package, to support the Protobuf serialization format.
It is based on of the most used Protobuf serializers on .NET: protobuf-net (repo here) by @mgravell : thanks for your hard work!
[DebuggerDisplay]
usagesSome class have been decorated with the [DebuggerDisplay]
attribute, for a better debugging experience.
Some methods and properties have been decorated even better with nullability annotations, so that static flow analysis can help us catch even more potential NREs (null reference exceptions).
I never noticed that and, since it is only used internally, nobody really paid any attention to that but the community user @luizhtm was spot on in noticing a typo in the Fusio(n)CacheChaosUtils
utility class: a missing "n"!
Now there's the correct one with the right name, but temporarily the old one also remains: the entire class and every method inside has been marked as [Obsolete]
with an explanation of the issue and how to correct any reference.
Thanks again @luizhtm !
Added a couple more helpful descriptions.
Published by jodydonetti almost 2 years ago
A new serializer is now available in its own package, to support the MessagePack serialization format.
It is based on the most used one on .NET: MessagePack (repo here) by @neuecc : thanks for your hard work!
ReThrowSerializationExceptions
A new option has been added to better control de/serialization exceptions, and the internal flow has been made better around those potential exceptions.
NOTE: contrary to the already existing ReThrowDistributedCacheExceptions
which is disabled by default, this option is enabled by default. The rationale is that, while an error while talking to the distributed cache may be transient (because the distributed cache is temporarily down, there's a network error, etc), de/serialization should either always work or not, and typically any error is because of an unsupported data type or a missing configuration, and that is something that we should know as soon as possible and not pretend like it can magically go away later with a retry (like, instead, a transient distributed cache error instead).
ChaosSerializer
A new ChaosSerializer
has been added (in the ZiggyCreatures.FusionCache.Chaos package) to better handle fail scenarios during de/serialization.
Fixed some typos and added a couple more helpful descriptions.
Published by jodydonetti about 2 years ago
FusionCache now has a backplane auto-recovery feature!
It has been added to automatically recover from transient errors in the backplane by keeping a local queue of the notifications that have not been sent successfully. These notifications will be automatically retried as soon as the backplane will become available again, without having to do anything.
It also handles common edge cases, like out of order notifications, and it can also be limited in size to avoid too consuming too much memory, with a sensible heuristic about which notifications to keep.
๐งช NOTE: for now the feature is experimental, and must be manually enabled via the new EnableBackplaneAutoRecovery
option.
More info here.
The chaos-related utilities and components, like ChaosDistributedCache
and ChaosBackplane
, have been moved to a separate project (ZiggyCreatures.FusionCache.Chaos).
This should also slightly reduce dependencies and code size of the main package.
Added links to the related online documentation in the xml docs for most options, and fixed some typos.
Added a page about Logging.
Added a chapter about the lock timeout option in the Cache Stampede page.
Added a chapter about notifications behaviour in the Backplane page.
Added a chapter about auto-recovery in the Backplane page.
Some members marked as [Obsolete]
from a very very long time have been finally removed to clean up the code.
Published by jodydonetti about 2 years ago
A member of the community (@yzhoholiev , thanks!) noticed that the local (memory cache) expiration, when coming from the distributed cache, was not good enough: with this fix the inferred memory cache expiration in those situations is more precise.
In some scenario a little bit more memory may be used with the distributed cache (eg: when not using fail-safe, otherwise it stays the same) but that should not be very frequent and the additional memory should reasonably not be a problem.
See here for more.
Published by jodydonetti over 2 years ago
DistributedCacheDuration
Added a new entry option DistributedCacheDuration
(as a nullable TimeSpan
).
This serves as an optional specific duration for the distributed cache (if you are using one) to allow custom overrides of the "main" duration: in this way it is now possible for example to specify a duration in the memory cache of 1 min
and a duration in the distributed cache of, say, 1 hour
.
In theory this is useless if you are using a backplane but, in case you prefer not to or cannot for some reason, this would mitigate the synchronization problems you may encounter in a multi-node scenario.
See here for more.
FusionCache now supports SourceLink, which should make your debugging experience easier.
See here for more.
FusionCache now supports Deterministic Builds, which is a nice addition.
See here for more.
Duplicate()
method overrideThe override removed has been marked [Obsolete]
for a very very long time and hopefully nobody should've been using it.
Published by jodydonetti over 2 years ago
failSafeDefaultValue
providedA decision has been made to temporarily rollback a change made in v0.11.0.
To see why take a look at this comment.
The main reason is to avoid unwanted surprises for existing users, who could have suddenly started to receive sub-optimal values during a first call.
Discussion are ongoing on how to better handle this scenario.
Published by jodydonetti over 2 years ago
This release includes a lot of small features, changes and fixes.
In a previous release there has been a small regression in the events emitted during a GetOrSet
call: now it has been fixed.
See https://github.com/jodydonetti/ZiggyCreatures.FusionCache/issues/49
failSafeDefaultValue
providedHistorically the soft timeout, if specified, has been used only when there was a fallback value in the form of an already expired cache entry.
Sadly, after having introduced the failSafeDefaultValue
param, that has not been used to enable the soft timeout.
Now this is resolved thanks to @alexmaek , who also made their first contribution!
See https://github.com/jodydonetti/ZiggyCreatures.FusionCache/pull/67
Note
This feature has been rolled back in v0.11.1 release because it was not the right move. See the pull request above for more!
Following a community request, it is now possible to enable re-throwing of distributed cache exceptions, if you feel like it.
This is done via the new ReThrowDistributedCacheExceptions
option on the FusionCacheEntryOptions
object.
See https://github.com/jodydonetti/ZiggyCreatures.FusionCache/issues/34
Now synthetic timeouts are handled in a more specific way, and there's a new BackplaneSyntheticTimeoutsLogLevel
option in the FusionCacheEntryOptions
object.
The internal flow on backplane exceptions is now better, to let the (optional) circuit breaker do its job more easily.
Published by jodydonetti over 2 years ago
FusionCache now supports adaptive caching!
This means having new GetOrSet
overloads where it is possible to change some options inside the factory, to be able to adapt (hence the name of the feature) some of these options to the object being cached. A typical example is caching a fresh news article for a small Duration
, and an old one for a higher one.
More examples and explanations in the related documentation.
There's a new optimization that kicks in automatically during a GetOrSet
call if:
IsFailSafeEnabled
)FactorySoftTimeout
has been specifiedLockTimeout
has NOT been specified (pretty common)In this case it may happen that a during multiple concurrent GetOrSet
calls for the same key, the first one acquired the lock and start executing the factory. During this time the other GetOrSet
calls for the same cache key are put on hold (to prevent a cache stampede, see here). But if the factory takes too much time it doesn't make sense to wait for it for the other callers, if they allow fail-safe and there's stale data to use as a fallback.
So with this optimization this is exactly what happens, maybe granting a perf boost in thee situations.
NOTE: these early-returning GetOrSet
calls btw will NOT temporarily re-save the stale data in the cache, because the currently running factory will do so nonetheless, and because doing so does not incur in extra factory calls, so we are still protected from the cache stampede problem.
Thanks to a suggestion by @JoeShook , FusionCache now emit events for cache hit with isStale
set to true
not only when a fail-safe is being activated, but in all subsequent cache hits until the entry is replaced by a successful factory execution, which frankly makes more sense.
HasDistributedCache
A new bool
property has been added to IFusionCache
to let you know if there is a distributed cache configured (like the already existing HasBackplane
).
Added a warning log when setting up a backplane in this scenario:
DefaultEntryOptions.EnableBackplaneNotifications
is set to true
This is important to prevent shooting yourself in the foot without knowing it, and that may happen because without a distributed cache there is no shared state between nodes, and if by default the backplane notifications are enabled it means that every time something is put into the cache in a node, all the other nodes will wipe their copy, creating a never ending update-remove-update-remove-etc loop.
You can read more about a scenario like this in the backplane documentation.
Published by jodydonetti over 2 years ago
FusionCache now supports adaptive caching!
This means having new GetOrSet
overloads where it is possible to change some options inside the factory, to be able to adapt (hence the name of the feature) some of these options to the object being cached. A typical example is caching a fresh news article for a small Duration
, and an old one for a higher one.
More examples and explanations in the related documentation.
There's a new optimization that kicks in automatically during a GetOrSet
call if:
IsFailSafeEnabled
)FactorySoftTimeout
has been specifiedLockTimeout
has NOT been specified (pretty common)In this case it may happen that a during multiple concurrent GetOrSet
calls for the same key, the first one acquired the lock and start executing the factory. During this time the other GetOrSet
calls for the same cache key are put on hold (to prevent a cache stampede, see here). But if the factory takes too much time it doesn't make sense to wait for it for the other callers, if they allow fail-safe and there's stale data to use as a fallback.
So with this optimization this is exactly what happens, maybe granting a perf boost in thee situations.
NOTE: these early-returning GetOrSet
calls btw will NOT temporarily re-save the stale data in the cache, because the currently running factory will do so nonetheless, and because doing so does not incur in extra factory calls, so we are still protected from the cache stampede problem.
HasDistributedCache
A new bool
property has been added to IFusionCache
to let you know if there is a distributed cache configured (like the already existing HasBackplane
).
Added a warning log when setting up a backplane in this scenario:
DefaultEntryOptions.EnableBackplaneNotifications
is set to true
This is important to prevent shooting yourself in the foot without knowing it, and that may happen because without a distributed cache there is no shared state between nodes, and if by default the backplane notifications are enabled it means that every time something is put into the cache in a node, all the other nodes will wipe their copy, creating a never ending update-remove-update-remove-etc loop.
You can read more about a scenario like this in the backplane documentation.
Please try it out and let me know so I can push the final version out.
Published by jodydonetti over 2 years ago
FusionCache now has a fully functioning backplane, to ease synchronization between nodes in a multi-node scenario.
It's very easy to setup and requires no additional work: it just works.
There are currently 2 implementations: one memory-only (mainly for testing) and one for Redis.
The backplane, like the 2nd level cache, is fully featured including events and logging and, since it talks to a separate system (eg: a Redis or Memcached instance), it natively support a simple circuit-breaker to better handle transient errors, again like the 2nd level cache.
Since the v0.9.0 is a big release with a great new feature, I took the chance and tried to improve the docs.
Of course there's the new part about the backplane and how to use it in various situations (both with and without the distributed cache).
I also collected all the IDistributedCache
implementations available on Nuget, which is something I hope can be useful to the community as they can serve as the secondary cache layer.
Finally some typos have been corrected and a lot of small parts in general have been added where I felt they were missing.
Thanks to a tip by the community (see here https://github.com/jodydonetti/ZiggyCreatures.FusionCache/issues/38), I changed a log level used when no fallback entry was available for a fail-safe activation, since it seemed more correct this way.
CacheKeyPrefix
optionThe FusionCacheOptions.CacheKeyPrefix
option is now fully obsolete: it has been marked with the [Obsolete]
attribute including the additional error
flag, and is hidden via the [EditorBrowsable]
attribute so its use will not compile anymore (see #33 for more).
Evict
methodThe Evict
method was just something theoretically used to create the backplane, nothing to be used in the normal FusionCache usage.
Since in the end the final backplane design did not need this method, it has been removed to keep a more streamlined API.
Published by jodydonetti over 2 years ago
This is the third and last BETA release containing the new Backplane #11 ๐.
It contains everything that was already in alpha1, alpha2, beta1 and beta2 plus:
In general this is the final polishing pass of everything.
The new docs are finished, particularly the ones about the new backplane and how to use it in various situations (both with and without the distributed cache). I also collected all the IDistributedCache
implementations available on Nuget, which is something I hope can be useful to the community as they can serve as the secondary cache layer. Finally some typos have been corrected and more parts in general have been added where I felt they were missing.
The tentative new Publish[Async] method was experimentally introduced a little while ago: it served its purpose, and since it was just an experiment and realliy only used internally, I decided to remove it before the next official version, so to keep a clean a slim api.
The tentative new Evict method was experimentally introduced a little while ago: it served its purpose, and since it was just an experiment and realliy only used internally, I decided to remove it before the next official version, so to keep a clean a slim api.
Please try it and let me know so I can push the final version out!
Published by jodydonetti over 2 years ago
This is the second BETA release containing the new Backplane #11 ๐.
It contains everything that was already in alpha1, alpha2 and beta1 plus:
Because of some local code explorations + git reverts, I haven't in fact pushed the code for the backplane auto-setup in DI: this means that in a DI scenario we were not, in fact, using the backplane, even if properly configured.
Yep, I'm that dumb ๐
Better initial backplane subscription handling, with a design that is more open for future extensibility.
Please try it and let me know so I can push the final version out!
Published by jodydonetti over 2 years ago
This is the first BETA release containing the new Backplane #11 ๐.
It contains everything that was already in alpha1 and alpha2 plus:
In preparation for the big official release I've changed a couple of names to make them prettier and shorter. For example I went from SendBackplaneNotification
to Publish
, etc
Now backplane notifications are more granular, differentiating between entry SET and REMOVE. Also added the instant in which a notification has been generated, for a better synchronization.
Way better peformance for serialization of backplane messages (RedisBackplane
).
Added backplane options to the log string for FusionCacheEntryOptions
.
HasBackplane
A new bool
property has been added to IFusionCache
to let you know if there is a backplane.
Please try it and let me know so I can push the final version out!
Published by jodydonetti over 2 years ago
This is the second alpha release containing the new Backplane #11 ๐.
It contains:
For both message sent/received.
Added logging about the backplane.
Also, thanks to a tip by the community (see here #38), I changed a log level used when no fallback entry was available for a fail-safe activation, since it seemed more correct this way.
Please try it and let me know so I can push the final version out!
Of course, everything in the alpha1 is still included.
Published by jodydonetti over 2 years ago
Finally, this is the first (alpha) release containing the Backplane #11 ๐.
FusionCache finally has a fully functioning backplane: see here for more.
CacheKeyPrefix
The FusionCacheOptions.CacheKeyPrefix
option is now fully obsolete: it has been marked with the [Obsolete]
attribute including the additional error
flag, so its use will not compile anymore (see #33 for more).
Please try it and let me know so I can push the final version out!
Published by jodydonetti almost 3 years ago
This is a small release, in preparation of the big one where the Backplane #11 will be added ๐.
CacheName
It is now possible to specify a cache name, namely a logical name that identify a cache.
It can be used for identification, and in a multi-node scenario it is typically shared between nodes to create a logical association that may be useful to have them communicate together (ref Backplane).
It is possible to specify it in the FusionCacheOptions
, and is acessible as a read-only prop directly on any FusionCache instance.
InstanceId
Each time a new FusionCache instance is created (eg: via a new FusionCache(...)
directly or via DI) a new id will be generated, globally and uniquely identifying that specific instance.
It can be useful to uniquely identify a single instance in a multi-node scenario, for example to precisely route notifications to each one (ref Backplane).
It is acessible as a read-only prop directly on any FusionCache instance.
Evict
methodA new Evict
method has been added: this method evicts an entry from the cache, like the Remove
method, but with 2 very important differencs:
Again, this is useful in the Backplane that is coming.
It is only available in a sync fashion and not in an async one, since it acts only locally and there's nothing to do asynchronously.
[Obsolete]
for future removalThe CacheKeyPrefix option is now obsolete: it still works, but will be kinda removed in the next version.
See here for more info.
Added a couple of small perf optimizations here and there.
Published by jodydonetti about 3 years ago
A new FusionCacheOptions.DistributedCacheKeyModifierMode
option has been added, so that you can now control how the cache key will be modified to be used in the distributed cache (Prefix
, Suffix
or None
).
The default value is Prefix
but, if for example you are having problems with Redis ACLs (which are prefix-based), you can change this to Suffix
(or even None
) to solve them.
Thanks to RogerSep for the hint about Redis ACLs, I hope this will solve your problem!
Published by jodydonetti about 3 years ago
Task<T>
to ValueTask<T>
The async part of the api surface area has been migrated from using the Task<T>
type to the ValueTask<T>
type.
This allows saving a good amount of memory allocations, making our apps more performant.
Is this a breaking change? In short, no.
To expand on it a little bit more, it could be if you directly used a Task<T>
returned from one of the async methods, instead of await
ing it normally. In that case simply add .AsTask()
to turn the ValueTask<T>
into a Task<T>
and everything will be fine.
In all other normal usage scenarios, just await
ing on one of the async methods would work absolutely the same, while also allocating less memory ๐
I've finally removed the TryGetResult<T>
type, marked as [Obsolete]
from a lot of time now.
It has been replaced a long ago with the MaybeValue<T>
type, which is better designed and used also as an input, and not just an output.
For more information about the change please read the "Breaking change" section of the v0.1.3 release.
The same goes for the Success
prop in the MaybeValue<T>
type, also marked as [Obsolete]
since the beginning and added just to allow a more pleasant transition from the old type to the new one.
Thanks to the the great Marc Gravell and Stephen Toub for their writings on this subject, in particular this and this which helped clear my mind on the subject.