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] Key-dependent cache entry options #367

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

gleb-osokin
Copy link

Hi, I was not sure about the exact flow for the feature request/PR, so I decided to just create the PR directly. Let me know, if I need to create a corresponding issue and link it to this PR 😄

Problem

I want to be able to configure individual cache entry options for a given subset of keys (e.g., all keys, starting with "foo-") in a generic manner, that is, via standard IOptions mechanism, provided by Microsoft out of the box. This is beneficial for:

  • storing all the configuration in one place
  • overriding the configuration via common means (i.e., environment variables, appsettings.*.json files, AWS parameter store, etc.)
  • cache API calls simplification: cache consumers don't need to know, how things are configured.

Currently, only the default entry options are exposed, which means I can only setup them for all entries. However, if I want to setup entry options in a more granular manner, I need to resort to adjusting the code at the call site, providing modified options argument.

Solution

Provide new FusionCacheOptions.KeyDependentEntryOptions property, that applies options per cache key template:

{
    "DefaultEntryOptions": { ... },
    "KeyDependentEntryOptions": [
        { "KeyTemplate": "foo-", "Options": { ... },
        { "KeyTemplate": "bar-", "Options": { ... },
    ]
}

Also, provide a public IKeyedFusionCacheEntryOptionsProvider interface, so that custom key lookup strategy can be used.

By default, the KeyPrefixBasedEntryOptionsProvider is used to select the corresponding options. It returns the entry options for the longest matching prefix for the given cache key. If no options are found, DefaultEntryOptions are returned.

On every cache operation that involves the cache key, get the entry options from the following sources, until one is found:
expliticly provided entry options -> key-dependent entry options -> default entry options

Public API updates

  1. FusionCacheOptions has a new KeyDependentEntryOptions (get/set) accessor.
  2. FusionCache constructor now accepts an additional optional IKeyedFusionCacheEntryOptionsProvider parameter. This is the only place, that might cause backward compatibility issues in some very specific circumstances, If this is not acceptable, then we can think of a different approach of setting the provider on cache (i.e. via a property).
    Two built-in implementations for the interface are provided: KeyPrefixBasedEntryOptionsProvider and NullKeyedEntryOptionsProvider.
  3. new overload FusionCacheEntryOptions FusionCache.CreateEntryOptions(string key, Action<FusionCacheEntryOptions>? setupAction = null, TimeSpan? duration = null) has been added as an addition to the existing one, that doesn't accept the key. I'm not sure if it makes sense to mark the existing one as Obsolete, since it is no longer used. I kept it as is for now.
  4. IFusionCacheBuilder now has a new IFusionCacheBuilder WithKeyDependentEntryOptionsProvider(this IFusionCacheBuilder builder, Action<CustomServiceRegistration<IKeyedFusionCacheEntryOptionsProvider>> config) extension method to setup custom IKeyedFusionCacheEntryOptionsProvider provider.

Implementation details and limitations

The current implementation of KeyPrefixBasedEntryOptionsProvider is instantiated upon FusionCache creation, which means later updates to the KeyPrefixBasedEntryOptions members will have only limited effect. That is, changes to the entry options themselves will be applied, however it is not possible to add or remove mappings, or adjust key templates for mappings after cache creation.

Default KeyPrefixBasedEntryOptionsProvider does not support multiple configurations for the same prefix: the internal .ToDictionary() call will throw on multiple entries with the same KeyTemplate. This is done on purpose, to avoid unexpected behavior and hard-to-track bugs later at runtime.

Current prefix search algorithm is a simple binary search among the pre-sorted array of prefixes. Assuming the amount of mappings will stay reasonably small, it shouldn't give too much of an overhead. Otherwise a better data structure, e.g., prefix tree can be assumed. Also, when key-dependent options are not set (that is the case for all existing setups), the only overhead is an additional length + null check, which should be negligible.

IMPORTANT: keys are considered exactly as they are passed in to the public APIs, that is before applying the CacheKeyPrefix adjustment. The reason for this is that CacheKeyPrefix is set up only once for the entire cache, so it is expected to stay the same for all keys.


PS The PR is pretty big, sorry for that. Luckily, the majority of changes are to the XML comments 😸
Also, the project itself is already very nicely structured, so incorporating the options selection fits in perfectly in the existing APIs, without affecting any lower-level caching logic.

And thank you once again for the awesome library! 🚀

@jodydonetti
Copy link
Collaborator

jodydonetti commented Jan 28, 2025

Hi @gleb-osokin , I'm reading your description - and will finish asap - but I was wondering if you already knew about multiple named caches: it already supports multiple caches each configured in a totally different way, with their own DefaultEntryOptions, the standard named options pattern, keyed services and all the rest.

Will update when I have finished reading, but wanted to share that immediately.

Let me know, thanks!

@gleb-osokin
Copy link
Author

Hi @jodydonetti , thank you so much for taking a look at this!

I see that named caches is a very powerful feature indeed. What I wanted to avoid is having to inject multiple different caches into the consumer - they shouldn't really care, what kind of cache it is, they know only the cache key and the data, while all the rest can be governed through the configuration/setup. Ideally I'd want to specify in appsettings.json only those properties of key-dependent entry options, that are different from the default, whereas all the rest would come from DefaultEntryOptions. This is achievable on the configuration stage, even before the FusionCache setup.

In reality, I think, the most common setting that is going to be set up in those key-dependent options is the cache duration. At least that's the case for my setup.

Let me know if all that makes sense 😅

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

Successfully merging this pull request may close these issues.

2 participants