Enumerator and .Contains() missing; need a way to retrieve Cache Keys
Opened this issue · 9 comments
In .NET 4.7.2, the MemoryCache /ObjectCache classes have an IEnumerator and a .Contains(string key, string regionName = null) method that provide a way to access entries as well as their keys and determine if a key exists within the cache.
Cache can be used to store state, and without the ability to access if keys and thus a state, exist in cache, I am forced to reproduce the entirety of Redis and Memory cache code with these enhancements.
I believe in Caching/MemoryCache.cs, the EntriesCollection property should be marked at least
protected OR an Enumerator provided.
Furthermore, a Contains method should be added that returns true if the specified key exists within the entities collection.
Finally, the IMemoryCache and perhaps even IDistributedMemoryCache should take into account that developers will want to access Keys and determine if Keys exist.
There may be more to this, but this would make life much easier for those of us who are porting from legacy code, and/or are creating stateful systems that rely on a fast cache to store that exipry state.
I also wanted to add, this isn't just an issue with MemoryCache. It's systemic across all the distributed cache interfaces including but not limited to Memory, Redis, and SqlServer cache interfaces.
Again, I'd rather not have to branch and maintain this entire library just to expose the Keys, something that I think is expected to be available IMO.
There's been lots of past discussion as to why this doesn't exist:
- How would one inspect the contents of the cache? #149
- Will there be GetEnumerator() for MemoryCache ? https://github.com/aspnet/Caching/issues/155
- Do you or are you going to support keys Enumerator or indexer #93
- Key export / bulk remove / clear for MemoryCache #187
Thanks for your fast response @Eilon . Apologies as I had searched for "keys" and no results came back so I must have left "open" on the criteria. I'm not a heavy github user.
Read through the links. It sounds like you have a strong opinion toward not implementing this capability. If your philosophical view is fueled by the concern that the cache keys may be changing while enumerating the results back, can't that be avoided by the simple case that it's locked in a ConcurrentDictionary holding those values? If not, couldn't that be added in the XML comments/remarks as a "warning" to developers, rather than removing that ability altogether?
How does Redis do it? Don't they allow for a query of keys with SCAN and less desirable KEYS... however because of this lack of interfaces, I forced to write a custom distributed cache handler for Redis to use those commands.
Here's the scenario that is really holding me up:
We are writing a license server web API. The API will be pinged every 60 seconds by an application while in use. The Web API needs to check to see if the user has a license for the specific application id and seat type in order to ACK or NAK the use of the software.
We look in cache first for a dynamically built key userid_softwareid_seattype for license usage and if not found query the database for the license information and then store it in cache. That cache value will expire in roughly 90 seconds. It is a sliding window expiration. It basically is the grant for that user or unnamed user to a 90 second license to that software seat type.
The next request that comes into the web API for that same user id, software id, and seat type combination, will check the cache for that info. If that license is "in use" meaning it's in cache, then we can determine the user is using 1 of n total licenses for that software.
We can do this by simply iterating through the available keys that match a pattern *_softwareid_seattype to sum the number of "in use" licenses for that softwareid and seat type combination.
We don't have a business requirement to care if the cache state changes seconds later or not. We want to know what the state of license usage (what are the cache keys right now) at the point and time the request/query was made.
We do have a business requirement to have this license server scale out. So it needs to use Redis as our cache server.
What it comes down to, is I cannot use the built in caching mechanisms in .NET Core because of a philosophy that developers will not know what to do with a key that has expired out of cache.
Would a solution to this worry be that a key that doesn't exist simply return a null, or a TryGet be used and return false if the key doesn't exist?
Also, we do use Redis, but this became an issue as we try and mock up cache interfaces for unit testing. If there's an alternative to fake the distributed cache Redis implementation or the StackOverflow.Redis client library for unit testing to use In memory (again, probably can't due to the lack of Key lookup support) I'm all ears and open to refactoring our code.
I'm not sure the particular pattern you describe is an expected use case for this type of cache. The cache can be cleared at any time for a variety of reasons, so wouldn't that mess up the app's license checks?
if the cache doesn't exist, it would automatically query the database and put it in the cache. At which time it would recalc the sum of all keys matching the pattern to determine the total licenses (keys) used in the cache.
I was trying to avoid storing seat usage in a slower mechanism like Sql Server or hdd. Cache makes sense because of the access we had to keys for license usage reference.
Also, faster than putting the reference inside the cache data, and having to pull every object out of cache to analyze the reference info.
That recalc step would be unreliable as the cache could be cleared part way through. These caches are not appropriate for aggregate operations, only individual operations.
BTW also as far as Redis goes, it is more fair to think of it as a database, not as a cache. It's just very popular for use in cache scenarios.
But, as @Tratcher also mentions, this just isn't an expected use case of this type of cache.
Thanks guys.
I think at this point, the plan is to forego using the built in distributed cache code and create an interface abstraction for the StackOverflow.Redis client library. Then I can create a fake in memory implementation (using a simple ConcurrentDictionary to hold the data) for unit testing which should solve my main issue.
@Eilon , You can close this issue out as well.