Skip to content

[Prototype] Use IIdentityCache and CompositeCache#3439

Closed
pmaytak wants to merge 11 commits into
mainfrom
pmaytak/caching-v2
Closed

[Prototype] Use IIdentityCache and CompositeCache#3439
pmaytak wants to merge 11 commits into
mainfrom
pmaytak/caching-v2

Conversation

@pmaytak

@pmaytak pmaytak commented Jul 1, 2022

Copy link
Copy Markdown
Contributor

Related to cache improvements #3532.

General problem statement
Currently if user does not specify caching mechanism, MSAL's default cache is in-memory without eviction. If in-memory cache with eviction is desired, the recommendation is use a separate Microsoft.Identity.Web.TokenCaches package, however, which also uses object serialization.

Goal

  • Primary: Need a default performant in-memory cache with eviction and without serialization (without the need to use Microsoft.Identity.Web.TokenCache).
  • Secondary: Need a more user friendly way to provide a custom cache to MSAL. (Currently user has to subscribe to caching event handlers.)

Changes proposed in this request

  • Adds an ability for the user to provide an implementation of IIdentityCache when creating a confidential client app instance via the CacheOptions.
  • Adds a sample default implementation of IIdentityCache using .NET's Memory Cache if user does not set up caching.
  • Adds a sample implementation of ICompositeCache.

Previous prototypes for cache improvements (#3104, #3177) focused on how to implement better cache (namely Wilson's) by replacing current caching code in non-breaking way. The biggest obstacle was the use of GetAccounts method and other GetAllX methods that are used in cache serialization which are unable to provide a partition key. It is an issue because generally cache implementation don't provide GetAll functionality. (Everywhere else we do have a partition key). There was also a challenge of replacing the storage structures themselves (ex. accessors) to work with the new cache - it would require significant refactoring.

This prototype only focuses on CCA. The GetAccounts and other GetAllX methods without partition key will not use the new cache. (GetAccounts is not applicable to confidential apps because we recommend distributed caching there, so there would only ever be one account in the cache at a time.) This prototype also has the new cache instance side-by-side with the current cache serialization which are mutually exclusive.

CodeTour
CodeTour file going over overall end-to-end code changes: IIdentityCache_Prototype.zip
How-to:

  1. Install CodeTour VS Code extension.
  2. Open in VS Code:
    image

… option. Add CompositeCache as an implementation.

internal IdentityCacheWrapper(CacheOptions cacheOptions)
{
// Set (or overwrite) cache to user-specified implementation, otherwise set to default implementation, if not already set.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about default implentation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, definitely it's adding a dependency to .NET Memory Cache. Added it for simplicity to show that it could work E2E. Practically we could replace it with some other memory cache with eviction that doesn't have dependencies (eg MiseCache).

{
internal class IdentityCacheWrapper
{
internal static IIdentityCache s_iIdentityCache { get; set; }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be updated. The lifecycle of user-provided cache should be managed externally to MSAL. Whether the default cache is static or not would be set via CacheOptions.UseSharedCache.

}
}

private async Task<ITokenCacheAccessor> GetOrCreateAccessorAsync(string partitionKey)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the order of precedence of which cache to use should be something like:

  • Use user-provided IIdentityCache implementation, or
  • Use user-specified legacy cache serialization, or
  • Use default in-memory cache based on IIdentityCache.

return Task.FromResult(result);
}

public Task SetAsync<T>(string category, string key, T value, CacheEntryOptions cacheEntryOptions, CancellationToken cancellationToken = default)

@pmaytak pmaytak Jul 7, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T is of type ITokenCacheAccessor. Although we could change it to a simpler data structure.

}
else
{
var cachedAccessor = await IdentityCacheWrapper.GetAsync<ITokenCacheAccessor>(partitionKey).ConfigureAwait(false);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The accessors could be simplified to just a dictionary, since we're partitioning in the cache implementation now.

@pmaytak

pmaytak commented Oct 10, 2022

Copy link
Copy Markdown
Contributor Author

Ran some perf tests comparing current cache and new cache. When EnableCacheSerialization is True, cache serialization is used, so new cache code is bypassed; so that data should be the same. Rows with False is what compares the current and new in-memory caches. The numbers are about the same, with few cases where new cache is much faster. Not sure why; these may be outliers. I ran the tests on my dev machine (since had package restore issues running on the build pipeline). Running on the build pipeline should give more consistent results.

Method CacheSize EnableCacheSerialization Mean (current cache) Mean (new cache) Allocated (current cache) Allocated (new cache)
AcquireTokenForClient (1, 10) False 18.25 μs 18.92 μs 22.14 KB 22.99 KB
AcquireTokenForClient (1, 10) True 183.42 μs 179.11 μs 275.72 KB 274.26 KB
AcquireTokenForClient (1, 1000) False 252.41 μs 233.54 μs 130.89 KB 131.74 KB
AcquireTokenForClient (1, 1000) True 31,911.00 μs 31,179.70 μs 18677.8 KB 18595.69 KB
AcquireTokenForClient (10000, 10) False 43.72 μs 20.53 μs 22.15 KB 22.99 KB
AcquireTokenForClient (10000, 10) True 183.53 μs 186.21 μs 274.2 KB 275.93 KB
AcquireTokenForOBO (1, 10) False 31.81 μs 37.68 μs 35.04 KB 49.66 KB
AcquireTokenForOBO (1, 10) True 255.22 μs 254.58 μs 329.94 KB 330.29 KB
AcquireTokenForOBO (1, 1000) False 282.67 μs 299.44 μs 143.78 KB 158.41 KB
AcquireTokenForOBO (1, 1000) True 37,466.31 μs 35,920.64 μs 21477.27 KB 21419.47 KB
AcquireTokenForOBO (10000, 10) False 58.20 μs 39.01 μs 35.04 KB 49.66 KB
AcquireTokenForOBO (10000, 10) True 263.41 μs 263.01 μs 331.61 KB 331.91 KB

Comment thread src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
Comment thread src/client/Microsoft.Identity.Client/AppConfig/CacheOptions.cs
@pmaytak pmaytak closed this Oct 29, 2022
@bgavrilMS bgavrilMS deleted the pmaytak/caching-v2 branch January 4, 2025 18:04
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.

3 participants