Closed Blackclaws closed 1 year ago
It seems that this is a fundamental limitation of IDistributedCache:
https://github.com/dotnet/runtime/issues/36402
I wonder whether it wouldn't be time to introduce another interface that is optional for cache implementations to implement that would expose this functionality. You could then check whether a given cache implements that interface and throw a NotImplementedException otherwise.
Hi @Blackclaws , and thanks for using FusionCache!
We have cached entities of significant size and a service that prefills the cache under certain circumstances. When that service prefills the cache it needs to check whether some entries are already in the cache. Right now this means retrieving the whole entity as there is no "ContainsKey" method.
Yep, I confirm.
A function to retrieve the cache metadata for a given cache key, i.e. whether the cache entry exists, whether its stale, what the remaining lifetime is etc.
This is not possible, because a cache entry is composed of metadata + data, and is an atomic unit, so for each cache key you can only get a cache entry as a whole.
Storing a second cache entry with an _exists added to the end of the key that is just a simple entity.
This should work, but I'd like to point out a couple of details about it, because initially I explored this very approach, too. Cache entries can be evicted at different times in different conditions and based on different settings, so by splitting the thing in 2 entries, sometimes you may have false positives or false negatives. Also, to get the entire entry you now would have to make 2 separate requests to the cache, per-entry.
This is more relevant the slower the cache is. But we are downloading data from our backend onto devices with varying storage performance. We want to check the status of the cache on application startup and pre-populate it from the backend if needed. Right now the loading of entities from disk-cache does cost us some performance we'd like to recover :)
Makes total sense. I am assuming you are using something like SQLite as the 2nd layer, right? I'm asking just to have more context.
It seems that this is a fundamental limitation of IDistributedCache:
Yes, exactly! FusionCache has been designed to use the 2 common caching abstractions in .NET: IMemoryCache
and IDistributedCache
, so that any available impl (mostly for IDistributedCache
of course) could've been easily used, without anyone having to explicitly create an implementation of, say, a custom IFusionCacheLayer
or something like that.
Of course this has advantages - like the one mentioned above - but also disadvantages, like being limited to the features defined in those abstractions.
I wonder whether it wouldn't be time to introduce another interface that is optional for cache implementations to implement that would expose this functionality. You could then check whether a given cache implements that interface and throw a NotImplementedException otherwise.
I'm playing with a similar idea from some time now: create something like the aforementioned IFusionCacheLayer
(or similar name) with all the features that could possibly be needed, even for very advanced scenario (think about methods like Exists(id)
, ExistsMulti(ids, ...)
, TryGetMulti(ids, ...)
, RemoveMulti(ids, ...)
and so on).
Of course I would then need to implement that for every needed distributed cache out there (like Redis, MongoDB, etc).
At the same time, I could also create only one ready-made implementation that could work with any available impl of the common IDistributedCache
interface: this would basically follow the adapter pattern. It would throw a NotImplementedException
for the methods that are completely not possible, like you described, or implement when possible the missing ones in a non-optimized way, like a RemoveMulti(ids, ...)
which can be implemented as a normal foreach(var id in ids)
loop + a Remove(id)
call for each id, or something like that.
Now: I am very very near to finally be able to release a feature-complete v1.0, based on my plans. After that I think I'll start putting the pieces together for a v2, which would probably follow the design highlighted above (and then some, I have a lot of things planned 😬).
Hope this helps!
Sorry but just to confirm, the TL;DR here is that checking for key existence is neither supported nor planned, correct?
Hi @tmenier , that's correct.
As said previously:
This is not possible, because a cache entry is composed of metadata + data, and is an atomic unit, so for each cache key you can only get a cache entry as a whole.
So to check for the existence you have to get the entire cache entry.
Hope this helps.
Thanks! It seemed like an almost obvious omission but after re-reading I get it now. Just about any underlying mechanism I can imagine (ConcurrentDictionary
, Redis, etc) natively supports some kind of key existence check, so I suspect it's less about atomic units and more about the limits of those MS interfaces? Kind of a shame they left it out, but I totally get how it blocks you here.
Is your feature request related to a problem? Please describe. We have cached entities of significant size and a service that prefills the cache under certain circumstances. When that service prefills the cache it needs to check whether some entries are already in the cache. Right now this means retrieving the whole entity as there is no "ContainsKey" method.
Describe the solution you'd like A function to retrieve the cache metadata for a given cache key, i.e. whether the cache entry exists, whether its stale, what the remaining lifetime is etc.
Describe alternatives you've considered Storing a second cache entry with an _exists added to the end of the key that is just a simple entity.
Additional context This is more relevant the slower the cache is. But we are downloading data from our backend onto devices with varying storage performance. We want to check the status of the cache on application startup and pre-populate it from the backend if needed. Right now the loading of entities from disk-cache does cost us some performance we'd like to recover :)