-
Notifications
You must be signed in to change notification settings - Fork 167
Description
Problem
When you have a cache containing items grouped in some way (within the same cache instance), you may have the need to remove/invalidate them as a set and not by every single item key (you also may not have all their keys).
For example, if you are using cache for many elements within a container and you want to invalidate all of them in a single operation knowing the container identifier only.
In this cases you probably compose the cache key using a common prefix (the group id or something similar), but the supported invalidation/removal methods only work on specific key and they don't support a "starts with" logic for example.
Solution
Some other cache libraries support the concept of "region". When you set a cache entry, you can do it using the key only or a key/region pair. In this way all invalidation procedures can be easily done using the single key (obviously) and also using the region identifier, to remove all associated items.
Alternatives
An alternative solution (easier to achive, but more flexible for some aspects) solution could be supporting a startsWith/endsWith/contains (or even RegEx) logic on cache key on invalidation operations. In this way you can use prefixes to identify more then one item.
Example
Both solutions stand on an abstraction to handle region items keys, which need to be saved internally on every provider.
For example, in MemoryCacheAccessor, the SetEntry method should manage something like this:
var (memoryEntryOptions, absoluteExpiration) = options.ToMemoryCacheEntryOptionsOrAbsoluteExpiration(_events, _options, _logger, operationId, key);
if (memoryEntryOptions is not null)
{
entry.PhysicalExpiration = memoryEntryOptions.AbsoluteExpiration!.Value;
_cache.Set<IFusionCacheMemoryEntry>(key, entry, memoryEntryOptions);
if (!string.IsNullOrEmpty(region))
{
// this extension method manages a dictionary regionKey/itemKeys within the same cache
_cache.SetRegionChild(item.Region, key);
}
}
else if (absoluteExpiration is not null)
{
entry.PhysicalExpiration = absoluteExpiration.Value;
_cache.Set<IFusionCacheMemoryEntry>(key, entry, absoluteExpiration.Value);
}
else
{
throw new InvalidOperationException("No MemoryCacheEntryOptions or AbsoluteExpiration was determined: this should not be possible, WTH!?");
}On the other way, the RemoveEntry should be
public void RemoveEntry(string operationId, string key, string region, FusionCacheEntryOptions options)
{
// ACTIVITY
using var activity = Activities.SourceMemoryLevel.StartActivityWithCommonTags(Activities.Names.MemoryRemove, _options.CacheName, _options.InstanceId!, key, operationId, CacheLevelKind.Memory);
if (_logger?.IsEnabled(LogLevel.Debug) ?? false)
_logger.Log(LogLevel.Debug, "FUSION [N={CacheName} I={CacheInstanceId}] (O={CacheOperationId} K={CacheKey}): [MC] removing data (from memory)", _options.CacheName, _options.InstanceId, operationId, key);
_cache.Remove(key);
if (!string.IsNullOrEmpty(region))
{
_cache.RemoveRegionChild(key)
}
// EVENT
_events.OnRemove(operationId, key);
}