-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
[API Proposal]: Operations on partial IDistributedCache key values #76340
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @dotnet/area-extensions-caching Issue DetailsBackground and motivationThere are a great many reasons to cache values across disparate application types. Recently, I've been working to cache "recently used" values, such as those used in searches on a per-user basis. Ideally, each value stored to the cache should be capable of independent expirations so I might save two values for a given user on Monday, with a 5 day expiry, three more on Wednesday and be confident that come Saturday, the values saved Monday will be expired and no longer available in the cache. However, this scenario is frustrated by the current IDistributedCache API for a couple of reasons:
As it stands, I instead have to serialize each of my recent values along with their own local expiry values into a single list identified by a single known key, retrieve and deserialize them all for any search history queries and do a client-side evaluation of which values are still valid. This should ideally all be done within the IDistributedCache implementation to improve performance and eliminate surface area for bugs. API ProposalI would suggest implementing the following using the default interface methods introduced in C# 8.0. That said, I propose it as a discrete interface so as to simplify my example of it and more easily highlight what I'm articulating. namespace Microsoft.Extensions.Caching.Distributed;
public interface IExpressibleDistributedCache// : IDistributedCache //The name could use some additional contemplation
{
/// <summary>
/// Gets the values for any key that matches the predicate.
/// </summary>
/// <param name="keyPredicate">An expression identifying the requested key(s).</param>
/// <returns>The located value or null.</returns>
IReadOnlyDictionary<string, byte[]> Get(Expression<Func<string, bool>> keyPredicate);
/// <summary>
/// Gets the values for any key that matches the predicate.
/// </summary>
/// <param name="keyPredicate">An expression identifying the requested key(s).</param>
/// <param name="token">Optional. The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the located value or null.</returns>
Task<IReadOnlyDictionary<string, byte[]>> GetAsync(Expression<Func<string, bool>> keyPredicate, CancellationToken token = default);
/// <summary>
/// Refreshes values in the cache based on their respective keys, resetting their sliding expiration timeout (if any).
/// </summary>
/// <param name="keyPredicate">A expression identifying the specified key(s).</param>
void Refresh(Expression<Func<string, bool>> keyPredicate);
/// <summary>
/// Refreshes values in the cache based on their respective keys, resetting their sliding expiration timeout (if any).
/// </summary>
/// <param name="keyPredicate">An expression identifying the specified key(s).</param>
/// <param name="token">Optional. The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
Task RefreshAsync(Expression<Func<string, bool>> keyPredicate, CancellationToken token = default);
/// <summary>
/// Removes the value with the given key(s).
/// </summary>
/// <param name="keyPredicate">An expression identifying the requested value.</param>
void Remove(Expression<Func<string, bool>> keyPredicate);
/// <summary>
/// Removes the value with the given key(s).
/// </summary>
/// <param name="keyPredicate">An expression identifying the specified key(s).</param>
/// <param name="token">Optional. The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
Task RemoveAsync(Expression<Func<string, bool>> keyPredicate, CancellationToken token = default);
} The reason to use API UsageIExpressibleDistributedCache cache; //Injected
IExpressibleDistributedCache _cache;
//Get all the cached entries starting with "recentSearchTerms_user"
_cache.GetAsync(key => key.StartsWith("recentSearchTerms_user"));
//Refresh these cached entries
_cache.RefreshAsync(key => key.StartsWith("recentSearchTerms_user"));
//Remove the terms belonging to a specific user
_cache.RemoveAsync(key => key.StartsWith("recentSearchTerms_user123456_")); Alternative DesignsAlternatively, provide a mechanism, per #36402 so clients can list all the keys (available at time of query) and perform the filtering locally before sending follow-up queries. This would be an inferior solution though because it requires at least two round-trips and because #36402 is susceptible to keys no longer being available when the follow-up call is made. Rather, this approach would ensure that the operations were being run against as fresh a set of data can be made available and specifically how that's done (e.g. checkpoints, follower cache, etc.) is left as an implementation detail. RisksWhile it's reported that the Bonsai serializer has been used for several years now within Microsoft, it's only recently been open sourced, so it might be subject to bugs for more elaborate queries in the wild. This is why I kept the expression signature simple in my API example above. If released as IExpressibleDistributedCache (a better name TBD), there's the risk that there are limited implementations of it due to the more commonly used and visible IDistributedCache. Thus my recommendation to instead release via default interface methods on IDistributedCache so that not only can existing implementations continue to work without issue, but a team-recommended approach for demonstrating the relatively new (at least publicly) Bonsai serialization can be provided out of the box.
|
Background and motivation
There are a great many reasons to cache values across disparate application types. Recently, I've been working to cache "recently used" values, such as those used in searches on a per-user basis.
Ideally, each value stored to the cache should be capable of independent expirations so I might save two values for a given user on Monday, with a 5 day expiry, three more on Wednesday and be confident that come Saturday, the values saved Monday will be expired and no longer available in the cache.
However, this scenario is frustrated by the current IDistributedCache API for a couple of reasons:
As it stands, I instead have to serialize each of my recent values along with their own local expiry values into a single list identified by a single known key, retrieve and deserialize them all for any search history queries and do a client-side evaluation of which values are still valid. This should ideally all be done within the IDistributedCache implementation to improve performance and eliminate surface area for bugs.
API Proposal
I would suggest implementing the following using the default interface methods introduced in C# 8.0. That said, I propose it as a discrete interface so as to simplify my example of it and more easily highlight what I'm articulating.
The reason to use
Expression<Func<string, bool>>
is so that the expression remains quotable for expression tree serializers such the recently open-sourced Bonsai (docs) to serialize the predicate across network boundaries as necessary.API Usage
Alternative Designs
Alternatively, provide a mechanism, per #36402 so clients can list all the keys (available at time of query) and perform the filtering locally before sending follow-up queries.
This would be an inferior solution though because it requires at least two round-trips and because #36402 is susceptible to keys no longer being available when the follow-up call is made. Rather, this approach would ensure that the operations were being run against as fresh a set of data can be made available and specifically how that's done (e.g. checkpoints, follower cache, etc.) is left as an implementation detail.
Risks
While it's reported that the Bonsai serializer has been used for several years now within Microsoft, it's only recently been open sourced, so it might be subject to bugs for more elaborate queries in the wild. This is why I kept the expression signature simple in my API example above.
If released as IExpressibleDistributedCache (a better name TBD), there's the risk that there are limited implementations of it due to the more commonly used and visible IDistributedCache. Thus my recommendation to instead release via default interface methods on IDistributedCache so that not only can existing implementations continue to work without issue, but a team-recommended approach for demonstrating the relatively new (at least publicly) Bonsai serialization can be provided out of the box.
The text was updated successfully, but these errors were encountered: