Skip to content

Releases: ZiggyCreatures/FusionCache

v0.23.0

02 Aug 00:02
564a948
Compare
Choose a tag to compare

📢 Better Backplane (docs)

The Backplane is a powerful component in FusionCache and is already a pretty good feature that works really well.

Now some more scenarios and edge cases are handled even better:

  • active detection and processing of re-connection
  • better handling of connection/subscription errors
  • changed the defualt BackplaneAutoRecoveryMaxItems to unlimited
  • added support for fake connection strings in MemoryBackplane for better concurrency
  • more control on circuit breaker manual closing (eg: when receiving a message)

See here for more.

↩️ Better Auto-Recovery (docs)

The auto-recovery feature of the Backplane is already working pretty well, but sometimes there are situations where it could do a little more, a little better.

Now these scenarios are covered way better:

  • better backpressure handling, with automatic re-sync of the distributed cache state (EnableDistributedExpireOnBackplaneAutoRecovery option)
  • automatically delayed processing on reconnect (BackplaneAutoRecoveryReconnectDelay option)
  • better background queue processing
  • remove duplicate items on publish

See here for more.

📜 Better log messages (docs)

Log messages have been fine tuned even more, with greater details where needed, some extra log messages removed when they were not needed, and finally better wording in certain circumstances, which should help clarify the timing of some events eve more.

See here for more.

😶 More Null Object Pattern support

In FusionCache v0.22.0 the NullFusionCache has been introduced, which is an implementation of the Null Object Pattern.

Now these implementations are also available:

  • NullSerializer (impl of IFusionCacheSerializer)
  • NullDistributedCache (impl of IDistributedCache)
  • NullBackplane (impl of IFusionCacheBackplane)
  • NullPlugin (impl of IFusionCachePlugin)

There's also a new, specific namespace (ZiggyCreatures.Caching.Fusion.NullObjects) for these classes, to avoid polluting the root namespace.

See here for more.

Warning

The existing NullFusionCache class has been moved in the new namespace, and this is a (small) breaking change.

🐵 Added ChaosPlugin

FusionCache already has a set of chaos-engineering related classes like ChaosBackplane, ChaosDistributedCache and more, to ease testing how the system reacts to malfunctioning components, but in a controlled way.

A ChaosPlugin was missing: now not anymore.

See here for more.

v0.22.0

09 Jul 22:46
a8b1ce7
Compare
Choose a tag to compare

🗓️ Added Expire() method (docs)

FusionCache now supports expiring an entry explicitly.
It can behave in 2 different ways, based on the fact that fail-safe is enabled or not:

  • if fail-safe is enabled: the entry will be marked as logically expired, but will still be available as a fallback value in case of future problems
  • if fail-safe is disabled: the entry will be effectively removed

In this way the intent is clear, while still having space for some additional optimizations and support for problematic scenarios.

Here's an example:

await cache.ExpireAsync("foo");

Thanks to the community member @th3nu11 for getting started with the idea.

See here for the original issue.

🔀 Added SkipMemoryCache option (docs)

Although very rare, sometimes it may be useful to skip the memory cache: now it is easily possible thanks to the new SkipMemoryCache option.

See here for the original issue.

📜 Better log messages (docs)

FusionCache now includes the CacheName in each log message, which is now important since the introduction of Named Caches in v0.20.0.

On top of that, when logging about backplane messages, it now also include the CacheInstanceId as an extra help when going detective mode.

See here for the original issue.

😶 Added NullFusionCache

Thanks to some input from community member @Sorenz , FusionCache now provides a native implementation of the Null Object Pattern for when it's needed.

See here for the original issue.

⏲ Better Timestamping

FusionCache now keeps better track of the timestamp of when a cached value has been originally created.

Long story short: this will better track times, and allow for more sophisticated decisions regarding which cache entry version to use in some circumstances.

See here for the original issue.

⛔ Handle Zero

Thanks to community member @CM2Walki 's request, durations equals to (or less than) TimeSpan.Zero are now natively supported, with optimized code paths.

It's not as simple as doing nothing, and it's possible to read more in the original issue's description.

Anyway, long story short: it just works 😊

See here for the original issue.

😴 Lazy instantiations of Named Caches (docs)

When working with Named Caches FusionCache internally works with the DI container to have all the registered FusionCache instances to be provided via the new IFusionCacheProvider interface.

The way it worked until today is that it simply instantiated all the registered ones, immediately.

From now on a lazy handling has been introduced, and cpu and memory will be saved, automatically.

See here for the original issue.

🧽 Better cleanup

While disposing a FusionCache instance, distributed caches and backplanes are now automatically removed and/or unsubscribed from.
Not that it was a huge problem, but still: better cleanup is better, amirite?

See here for the original issue.

📢 Better backplane notifications (docs)

Backplane notifications handling is now even better: the order of some internal operations is more streamlined, some hot paths have been optimized and less memory is being allocated.

🔒 More resilient lock release strategies (docs)

There were a couple of very rare edge cases where, at least theoretically, it could've been possible to not release one of the internal locking primitives used to protect us from Cache Stampede.

Even if they never actually happened, now they cannot happen even theoretically.

🧙‍♂️ Better Adaptive Caching (docs)

More cases and more options are now supported while using adaptive caching: nothing specific to do, they now just work.

To see which options are supported with Adaptive Caching, just take a look at the Options docs and look for the 🧙‍♂️ icon.

🦅 Better Eager Refresh (docs)

When using Eager Refresh the distributed cache will now also be checked, before executing the factory: this is useful in case some fresh version of the data is arrived there in the meantime.

Again, nothing extra to do here.

✅ Automated snapshot testing for binary payload backward compatibility

There are now tests that automatically verify that old binary payloads, generated by old versions of the available serializers, can be deserialized correctly by the current version of the serializers.

This is important to guarantee that when updating a live system that uses a distributed cache that is already filled with some data, it will be possible to do so safely without having to do any extra migration activity or data loss.

See here for the original issue.

⚡ Performance boost

Some general performance optimizations regarding cpu usage, memory allocations and more.

⚠ More [Obsolete] usage

Some members that have been marked with the [Obsolete] attribute from some time, are now also marked with the error flag: there will still be helpful error messages on how to update existing code, but now that that code update must be completed.

This is important for the evolution of FusionCache, and for the upcoming v1.0.

v0.21.0

28 May 19:34
c7aedd2
Compare
Choose a tag to compare

🔂 Conditional Refresh (docs)

FusionCache now fully supports conditional refresh, which is conceptually similar to conditional requests in HTTP.

Basically it is a way to cache and re-use either an ETag, a LastModified date or both, so that it is possible to save resources by not fully getting a remote resource (think: a remote http service response) in case it was not changed since the last time we got it.

Thanks to the community member @aKzenT for the invaluable help with the design discussion.

See here for the original issue.

🦅 Eager Refresh (docs)

It is now possible to tell FusionCache to eagerly start refreshing a value in the cache even before it's expired, all automatically and in the background (without blocking).

For example we can tell FusionCache to cache something for 10 min but eagerly start refreshing it after 90% of that (9 min) in the background, as soon as a GetOrSet<T>(...) call is made after the specified threshold: in this way fresh data will be ready earlier, and without having to wait for the refresh to complete when it would've expired.

Both eager refresh and the usual normal refresh + factory timeouts can be combined, without any problems.

See here for the original issue.

📞 Added EagerRefresh event

A new event has been added, for when eager refresh actually occurs.

📜 Added WithoutLogger() builder method (docs)

With the introduction of the Builder in the v0.20.0 release, something was missing: a way to tell FusionCache to not use a logger, at all: now this is fixed.

Thanks to community member @sherman89 for pointing that out.

See here for the original issue.

⚠ Breaking Change

The type for the factory execution context has changed, from FusionCacheFactoryExecutionContext to FusionCacheFactoryExecutionContext<TValue>: this has been done to support conditional refresh and have access to the stale value already cached, which requires knowing the type upfront.

Most existing code should be fine: the only noticeable difference is that in some situations the C# compiler is, for some reasons, not able to do type inference correctly.

Because of this, in some instances a code like this:

var product = cache.GetOrSet(...);

must be updated to explicitly add the TValue type, like this:

var product = cache.GetOrSet<Product>(...);

v0.21.0-preview2

21 May 12:21
Compare
Choose a tag to compare
v0.21.0-preview2 Pre-release
Pre-release

⚠ PRE-RELEASE

This is a pre-release of FusionCache, so please be aware that although it contains some shiny new stuff (see below), it is also subject to change before the final release.

Having said that, you should also know that this version is already very polished, the api design surface area well modeled, performance-tuned, with full xml-docs and it has all the bells and whistles of a normal release.

🔂 Conditional Refresh

FusionCache now fully supports conditional refresh, which is conceptually similar to conditional requests in HTTP.

Basically it is a way to cache and re-use either an ETag, a LastModified date or both, so that it is possible to save resources by not fully getting a remote resource (think: a remote http service response) in case it was not changed since the last time we got it.

Thanks to the community member @aKzenT for the invaluable help with the design discussion.

See here for the issue.

🦅 Eager Refresh

It is now possible to tell FusionCache to eagerly start refreshing a value in the cache even before it's expired, all automatically and in the background (without blocking).

For example we can tell FusionCache to cache something for 10 min but eagerly start refreshing it after 90% of that (9 min) in the background, as soon as a GetOrSet(...) call is made after the specified threshold: in this way the fresh data will be ready earlier, and without having to wait for the refresh to complete when it would've expired.

Both eager refresh and the usual normal refresh + factory timeouts can be combined, without any problems.

See here for the issue.

📞 Added EagerRefresh event

A new event has been added, for when eager refresh actually occurs.

📜 Added WithoutLogger() builder method

With the introduction of the Builder in the v0.20.0 release, something was missing: a way to tell FusionCache to not use a logger, at all: now this is fixed.

Thanks to community member @sherman89 for pointing that out.

See here for the issue.

v0.21.0-preview1

01 May 16:38
Compare
Choose a tag to compare
v0.21.0-preview1 Pre-release
Pre-release

⚠ PRE-RELEASE

This is a pre-release of FusionCache, so please be aware that although it contains some shiny new stuff (see below), it is also subject to change before the final release.

Having said that, you should also know that this version is already very polished, the api design surface area well modeled, performance-tuned, with full xml-docs and it has all the bells and whistles of a normal release.

🔂 Conditional Refresh

FusionCache now fully supports conditional refresh, which is conceptually similar to conditional requests in HTTP.

Basically it is a way to cache and re-use either an ETag, a LastModified date or both, so that it is possible to save resources by not fully getting a remote resource (think: a remote http service response) in case it was not changed since the last time we got it.

Thanks to the community member @aKzenT for the invaluable help with the design discussion.

See here for the issue.

🦅 Eager Refresh

It is now possible to tell FusionCache to eagerly start refreshing a value in the cache even before it is expired.

For example we can tell FusionCache to cache something for 10min but eagerly start refreshing it after 9min in the background, so that it will be ready earlier without waiting for the refresh to end.

Although it's basically the same as caching something directly for 9min + enabling fail-safe + setting a very low factory soft timeout like 10ms, some community members requested it because it felt more intuitive this way, while others felt the other way around: therefore the addition, to support both approaches and be more welcoming.

Also, both approaches can be combined without problems.

See here for the issue.

📜 Added WithoutLogger() builder method

With the introduction of the Builder in the v0.20.0 release, something was missing: a way to tell FusionCache to not use a logger, at all: now this is fixed.

Thanks to community member @sherman89 for pointing that out.

See here for the issue.

v0.20.0

08 Apr 17:52
Compare
Choose a tag to compare

ℹ Update Notes

If you are updating from a previous version, please read here.

👷‍♂️ Added Builder support (docs)

There's now a really easy to use and flexible fluent API that supports the builder pattern.
This allows a nice modular setup of FusionCache when using dependency injection, with the ability to add components and configure them with a couple of keystrokes, like:

services.AddFusionCache()
  .WithSerializer(
    new FusionCacheSystemTextJsonSerializer()
  )
  .WithDistributedCache(
    new RedisCache(new RedisCacheOptions()
    {
      Configuration = "..."
    })
  )
  .WithBackplane(
    new RedisBackplane(new RedisBackplaneOptions()
    {
      Configuration = "..."
    })
  )
;

Thanks to the community member @aKzenT for the invaluable help with the design discussion.

See here for the original issue.

📛 Added Named Caches support (docs)

FusionCache now natively support multiple named caches via dependency injection, thanks to the new IFusionCacheProvider interface/service.
This makes it easy to register and retrieve different caches, potentially with different underlying caching storage (but not necessarily) and different configurations.
This is even easier thanks to the aforementioned Builder Pattern support, like this:

// USERS CACHE
services.AddFusionCache("UsersCache")
  .WithDefaultEntryOptions(opt => opt
    .SetDuration(TimeSpan.FromSeconds(10))
  )
;

// PRODUCTS CACHE
services.AddFusionCache("ProductsCache")
  .WithDefaultEntryOptions(opt => opt
    .SetDuration(TimeSpan.FromSeconds(30))
    .SetFailSafe(true, TimeSpan.FromMinutes(10))
  )
;

Then you can depend on an IFusionCacheProvider service and easily ask to it for a certain cache with a GetCache("MyCache") call.
For example in an mvc controller you can go like this:

public class HomeController : Controller
{
  IFusionCache _usersCache;
  IFusionCache _productsCache;

  public HomeController(IFusionCacheProvider cacheProvider)
  {
    _productsCache = cacheProvider.GetCache("ProductsCache");
    _usersCache = cacheProvider.GetCache("UsersCache");
  }

  public IActionResult Product(int id)
  {
    _productsCache.GetOrDefault<Product>($"product:{id}");
    // ...
  }
}

And again, thanks to the community member @aKzenT for the invaluable help with the design discussion here, too.

See here for the original issue.

🆕 Added CacheKeyPrefix option (docs)

Since there's now native support for multiple named caches, I'm currently playing with the idea of adding support for a CacheKeyPrefix option, that would be added in front of any cache key you specify with a certain FusionCache instance. This can be helpful to avoid cache key collisions when working with multiple named caches all sharing the same underlying caching storage mechanism (eg: memory, Redis, MongoDB, etc).

♾ Handle Infinity

There's now native support for infinite expirations, with specific optimizations in the code to avoid exceptions and for better perf.

See here for more.

📜 Custom Plugins Log Levels (docs)

Two new options have been added to control the level for plugins info/error log entries.

See here for more.

v0.20.0-preview2

29 Mar 22:22
134051f
Compare
Choose a tag to compare
v0.20.0-preview2 Pre-release
Pre-release

⚠ PRE-RELEASE

This is a pre-release of FusionCache, so please be aware that although it contains some shiny new stuff (see below), it is also subject to change before the final release.

Having said that, you should also know that this version is already very polished, the api design surface area well modeled, performance-tuned, with full xml-docs and it has all the bells and whistles of a normal release.

ℹ NOTE: if you are updating from a previous version, please read here.

Ok, enough with the warnings: let's talk about the features 🎉

👷‍♂️ Added Builder Pattern support

There's now a really easy to use and flexible fluent API that supports the builder pattern.
This allows a nice modular setup of FusionCache when using dependency injection, with the ability to add components and configure them with a couple of keystrokes, like:

services.AddFusionCache()
  .WithSerializer(
    new FusionCacheSystemTextJsonSerializer()
  )
  .WithDistributedCache(
    new RedisCache(new RedisCacheOptions()
    {
      Configuration = "..."
    })
  )
  .WithBackplane(
    new RedisBackplane(new RedisBackplaneOptions()
    {
      Configuration = "..."
    })
  )
;

NOTE: The api surface has changed a little bit from v0.20.0-preview1.

Thanks to the community member @aKzenT for the invaluable help with the design discussion.

See here for the issue.
See here for the docs.

📛 Added Named Caches support

FusionCache now natively support multiple named caches via dependency injection, thanks to the new IFusionCacheProvider interface/service.
This makes it easy to register and retrieve different caches, potentially with different underlying caching storage (but not necessarily) and different configurations.
This is even easier thanks to the aforementioned Builder Pattern support, like this:

// USERS CACHE
services.AddFusionCache("UsersCache")
  .WithDefaultEntryOptions(opt => opt
    .SetDuration(TimeSpan.FromSeconds(10))
  )
;

// PRODUCTS CACHE
services.AddFusionCache("ProductsCache")
  .WithDefaultEntryOptions(opt => opt
    .SetDuration(TimeSpan.FromSeconds(30))
    .SetFailSafe(true, TimeSpan.FromMinutes(10))
  )
;

Then you can depend on an IFusionCacheProvider service and easily ask to it for a certain cache with a GetCache("MyCache") call.
For example in an mvc controller you can go like this:

public class HomeController : Controller
{
  IFusionCache _usersCache;
  IFusionCache _productsCache;

  public HomeController(IFusionCacheProvider cacheProvider)
  {
    _productsCache = cacheProvider.GetCache("ProductsCache");
    _usersCache = cacheProvider.GetCache("UsersCache");
  }

  public IActionResult Product(int id)
  {
    _productsCache.GetOrDefault<Product>($"product:{id}");
    // ...
  }
}

And again, thanks to the community member @aKzenT for the invaluable help with the design discussion here, too.

See here for the issue.
See here for the docs.

🆕 Added CacheKeyPrefix option

Since there's now native support for multiple named caches, I'm currently playing with the idea of adding support for a CacheKeyPrefix option, that would be added in front of any cache key you specify with a certain FusionCache instance. This can be helpful to avoid cache key collisions when working with multiple named caches all sharing the same underlying caching storage mechanism (eg: memory, Redis, MongoDB, etc).

♾ Handle Infinity

There's now native support for infinite expirations, with specific optimizations in the code to avoid exceptions and for better perf.

See here for more.

📜 Custom Plugins Log Levels

Two new options have been added to control the level for plugins info/error log entries.

See here for more.

v0.20.0-preview1

03 Mar 22:12
Compare
Choose a tag to compare
v0.20.0-preview1 Pre-release
Pre-release

⚠ PRE-RELEASE

This is a pre-release of FusionCache, so please be aware that although it contains some shiny new stuff (see below), it is also subject to change before the final release.

Having said that, you should also know that this version is already very polished, the api design surface area well modeled, performance-tuned, with full xml-docs and it has all the bells and whistles of a normal release.

Ok, enough with the warnings: let's talk about the features 🎉

👷‍♂️ Added Builder Pattern support

There's now a really easy to use and flexible fluent API that supports the builder pattern.
This allows a nice modular setup of FusionCache when using dependency injection, with the ability to add components and configure them with a couple of keystrokes, like:

services.AddFusionCache(b => b
  .WithSerializer(
    new FusionCacheSystemTextJsonSerializer()
  )
  .WithDistributedCache(
    new RedisCache(new RedisCacheOptions()
    {
      Configuration = "..."
    })
  )
  .WithBackplane(
    new RedisBackplane(new RedisBackplaneOptions()
    {
      Configuration = "..."
    })
  )
);

Thanks to the community member @aKzenT for the invaluable help with the design discussion.

See here for more.

📛 Added Named Caches support

FusionCache now natively support multiple named caches via dependency injection, thanks to the new IFusionCacheProvider interface/service.
This makes it easy to register and retrieve different caches, potentially with different underlying caching storage (but not necessarily) and different configurations.
This is even easier thanks to the aforementioned Builder Pattern support, like this:

// USERS CACHE
services.AddFusionCache("UsersCache", b => b
  .WithDefaultEntryOptions(opt => opt
    .SetDuration(TimeSpan.FromSeconds(10))
  )
);

// PRODUCTS CACHE
services.AddFusionCache("ProductsCache", b => b
  .WithDefaultEntryOptions(opt => opt
    .SetDuration(TimeSpan.FromSeconds(30))
    .SetFailSafe(true, TimeSpan.FromMinutes(10))
  )
);

Then you can depend on an IFusionCacheProvider service and easily ask to it for a certain cache with a GetCache("MyCache") call.
For example in an mvc controller you can go like this:

public class HomeController : Controller
{
  IFusionCache _usersCache;
  IFusionCache _productsCache;

  public HomeController(IFusionCacheProvider cacheProvider)
  {
    _productsCache = cacheProvider.GetCache("ProductsCache");
    _usersCache = cacheProvider.GetCache("UsersCache");
  }

  public IActionResult Product(int id)
  {
    _productsCache.GetOrDefault<Product>($"product:{id}");
    // ...
  }
}

And again, thanks to the community member @aKzenT for the invaluable help with the design discussion here, too.

See here for more.

🆕 Added CacheKeyPrefix option

Since there's now native support for multiple named caches, I'm currently playing with the idea of adding support for a CacheKeyPrefix option, that would be added in front of any cache key you specify with a certain FusionCache instance. This can be helpful to avoid cache key collisions when working with multiple named caches all sharing the same underlying caching storage mechanism (eg: memory, Redis, MongoDB, etc).

This decision is not final, and I'm interested in getting the opinions from the community.

v0.19.0

12 Feb 21:16
Compare
Choose a tag to compare

🆕 Added DistributedCacheFailSafeMaxDuration option (docs)

After a request by the community member @RMcD a new entry option has been added to be able to specify a fail-safe max duration specific for the distributed cache level.

See here for more.

⚠ Behaviour change with stale data

After talking with community member @fkuhnert , an old thought re-emerged: for readonly methods like TryGet[Async] and GetOrDefault[Async] no stale data throttling should occur. This means that, even with fail-safe enabled, if there's some usable stale data it will be returned, BUT not saved. This makes sense, as a TryGet or a GetOrDefault does not suggest a "save" operation (see: Principle of Least Astonishment).

This should not change any apparent behaviour from the outside: the only thing potentially noticeable is that, if you have a large number of TryGet/GetOrDefault calls done on stale data, you may see some more of them going to the distributed cache, that's all.

🆕 Added ServiceStack JSON support

A new serializer is now available in its own package, to support the ServiceStack JSON serializer.

Thanks @mythz for yout support!

⛔ Added entry options normalization

Thanks to the community member @suhrab for spotting an edge case that could have been already handled.

When a user enable fail-safe and specifies a FailSafeMaxDuration lower than the Duration, the situation can feel messy since the thing being asked is not really in line with how FusionCache operates.

To avoid this, a new normalization phase has been added, to make all the values coherent with the situation.
In case a normalization occurs, FusionCache will also log it with a (configurable) Warning level.

See here for more.

📞 Added FactorySuccess event (docs)

A new event has been added that will trigger when a non-background factory completes successfully, after a request from community member @bartlomiejgawel (thanks for the tip!).
For the background one, keep using the BackgroundFactorySuccess event that already exists.

See here for more.

⚡ Performance Boost

When logging is enabled, FusionCache automatically generates a unique operation id per each operation (eg: each GetOrSet/GetOrDefault/TryGet/etc call) to make detective work easier while looking at the logs.

The generation of these operation ids now consumes 83% less cpu time and 45% less memory.

📕 Better docs

The docs has been expanded and refined: one of these changes is that in the Options now there's a clear indication about which entry options are compatible with adaptive caching, thanks to a chat with @celluj34.

Thanks Joe for the inspiration!

v0.18.0

18 Dec 21:19
Compare
Choose a tag to compare

🆕 Added SkipDistributedCache option

After 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.

🆕 Added SkipDistributedCacheReadWhenStale option

It 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.

🐞 Better MemoryPack support for .NET 7

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.

📢 Changed EnableBackplaneNotifications option to (inverted) SkipBackplaneNotifications

A small rename so that now everything is more aligned and intuitive.

See here for more.

📕 Better docs

Augmented some online docs and inline XML docs, for a better experience while coding.