ZiggyCreatures / FusionCache

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

[FEATURE] Fall back Defaults for adaptive caching #239

Closed sabbadino closed 2 months ago

sabbadino commented 2 months ago

Problem

I am playing with adaptive caching. Looking at the code in this page https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/AdaptiveCaching.md I understand that i can set Duration in the Factory body I understand that if i don't set duration and other properties in the factory the "global" FusionCacheEntryOptions is used. I understand that I can change the properties of the "global" FusionCacheEntryOptions in Action<FusionCacheEntryOptions>

In my scenario I have a specific set of values for FusionCacheEntryOptions that I want to apply in the Action<FusionCacheEntryOptions> . Since it's an Action I cannot replace the provided FusionCacheEntryOptions .

Do I need to copy manually the settings or there is something like a FusionCacheEntryOptions (instance) .CopyFrom method I can call in the Action ? Something like

 var ret = await _fusionCache.GetOrSetAsync<T>(key, factory,
     opt => {
         opt.CopyFrom(myOpt);
         });

Or anyway, how can use a "custom" FusionCacheEntryOptions in adaptive sampling , and not the global one ? All overloads that takes a FusionCacheFactoryExecutionContext accepts a Action and not simply options

thanks Enrico

jodydonetti commented 2 months ago

Hi @sabbadino , I would suggest not to try and mix together different features and scenarios, let's try to tackle each part by itself.

Entry Options

Basically all methods in FusionCache that act on cache entries require some options per each entry, called entry options.

Whether you specify them or not, they are there (this is important).

DefaultEntryOptions

It is possible to define a "global" DefaultEntryOptions, which act as a default value in case that's needed.

Calling A Method

While calling each method you can specify them in different ways:

Basically the DefaultEntryOptions are used either when you call a method and don't specify anything, or as a "starting point" for when using the lambda (eg: opt => opt.SetDuration(...) etc).

Adaptive Caching

Even when using Adaptive Caching (via a normal GetOrSet call) some entry options are used anyway by one onf the above ways (eg: if you don't pass anything the DefaultEntryOptions will be used, etc): these options are the ones you'll find inside the FusionCacheFactoryExecutionContext object in the Options property.

Now, when inside of the factory you can either change some properties on the FusionCacheFactoryExecutionContext.Options property like this:

// INSIDE THE FACTORY
ctx.Options.Duration = TimeSpan.FromSeconds(10);

or even completely overwrite it, like this:

// INSIDE THE FACTORY
ctx.Options = new FusionCacheEntryOptions() {
  // ...
};

Duplicate

In case you need it, there's a Duplicate() method on the FusionCacheEntryOptions object that, well, duplicate it to a new one (and, spoiler, it's the one used internally by FusionCache when using the lambda opt => opt etc).

Don't Worry

You may be thinking that when changing some options with Adaptive Caching, you may be inadvertently changing the original options or the DefaultEntryOptions (eg: if you haven't passed anything), thereby breaking something: but don't worry because FusionCache will protect you from this by automatically duplicating the entry options behind the scenes to avoid changing a shared options object.

Also, it will do this both automatically (no manual intervention needed) and in an optimized way, meaning it will do that only when you are actually trying to modify something that was not marked as being "modifiable".

Recap

So, to recap:

Let me know if you need more.

Hope this helps.

jodydonetti commented 2 months ago

I'll add:

I understand that I can change the properties of the "global" FusionCacheEntryOptions in Action<FusionCacheEntryOptions>

Not 100%: as said, you'll be changing a copy of the "global" ones, not directly them, so everything is safe.

In my scenario I have a specific set of values for FusionCacheEntryOptions that I want to apply in the Action<FusionCacheEntryOptions> . Since it's an Action I cannot replace the provided FusionCacheEntryOptions .

Why can't you pass them directly when calling the method?

Do I need to copy manually the settings or there is something like a FusionCacheEntryOptions (instance) .CopyFrom method I can call in the Action ? Something like

 var ret = await _fusionCache.GetOrSetAsync<T>(key, factory,
     opt => {
         opt.CopyFrom(myOpt);
         });

Just do:

var ret = await _fusionCache.GetOrSetAsync<T>(key, factory, myOpt);

Or am I missing something?

sabbadino commented 2 months ago

Hi, sorry my fault, I did a mistake when looking at overloads of GetOrSetAsync taking Func<FusionCacheFactoryExecutionContext<TValue>, CancellationToken, Task<TValue>> factory I missed the one taking a FusionCacheEntryOptions .. I only saw the ones taking an Action<FusionCacheEntryOptions> that's was the reason why I opend the 'Issue' .. sorry wasting your time :) Enrico

jodydonetti commented 2 months ago

No worries!

joshbartley commented 3 weeks ago

Can the docs be updated for this? I ran into the same issue since the full options object was what intellisense picked and spent some time figuring out why failback stopped working with my global defaults. The Step by Step directions have a code sample though don't explicitly call out the two ways and none of the built in logging mentions overriding the defaults either in a way to preserve the global defaults.

jodydonetti commented 3 weeks ago

Can the docs be updated for this?

Good idea!

To be clear you mean a more detailed explanation of the whole defaults -> customization via lamba thing, so that defaults are preserved, right?

joshbartley commented 3 weeks ago

Something to make it more obvious at first pass that you're not setting a single override.

Example from the Step by Step

We can do this very easily, either by specifying it per-call, maybe using the available fluent api, like this:

var product = _cache.GetOrSet<Product>(
    $"product:{id}",
    _ => GetProductFromDb(id),
    options => options.SetFailSafe(true, TimeSpan.FromHours(2), TimeSpan.FromSeconds(30))
);

or, as before, by setting the DefaultEntryOptions object during the registration:

services.AddFusionCache()
    .WithDefaultEntryOptions(new FusionCacheEntryOptions {
        Duration = TimeSpan.FromMinutes(1),

        // FAIL-SAFE OPTIONS
        IsFailSafeEnabled = true,
        FailSafeMaxDuration = TimeSpan.FromHours(2),
        FailSafeThrottleDuration = TimeSpan.FromSeconds(30)
    })
;

or, pass in a FusionCacheEntryOptions to override all defaults per-call

var product = _cache.GetOrSet<Product>(
    $"product:{id}",
    _ => GetProductFromDb(id),
   new FusionCacheEntryOptions {
          // OVERRIDE DEFAULTS
        Duration = TimeSpan.FromSeconds(30),      
        IsFailSafeEnabled = false,
    })
;
jodydonetti commented 2 weeks ago

Hi all, I just updated the Options docs: looks clearer now?