Perf: Fast Cache Approach - Exploring Possible Optimisations in the Token Acquisition Process - #2982
Open
Divyansh-jain2 wants to merge 5 commits into
Open
Conversation
In getSqlFedAuthTokenPrincipal, attempt acquireTokenSilently (an MSAL cache-only read that never contacts Entra ID) before taking the global token semaphore. A cache hit returns immediately without contending for the gate, eliminating head-of-line blocking where in-memory token reads queued behind threads refreshing tokens. A cache miss falls through to the unchanged gated slow path, preserving the existing AAD-stampede protection. The silent read is extracted into a package-private helper acquireTokenSilentlyOrNull for unit testing. Adds SQLServerMSAL4JUtilsTest coverage for the cache-hit (returns token, no AAD call) and cache-miss (returns null, falls through) paths using Mockito.
…silent fast-path Re-extract the ActiveDirectoryServicePrincipal silent cache read into the package-private helper acquireTokenSilentlyOrNull so the cache-hit and cache-miss paths can be unit-tested in isolation. Behavior is unchanged: a hit returns the cached token without taking the semaphore; a miss returns null and falls through to the gated slow path. Adds SQLServerMSAL4JUtilsTest coverage: testAcquireTokenSilentlyReturnsCachedTokenOnHit (cache hit returns the token and never calls acquireToken) and testAcquireTokenSilentlyReturnsNullOnMiss (cache miss returns null), using Mockito to mock the MSAL client.
The reference benchmark set connectRetryCount=0 in the URL but did not mention INTERMITTENT_TLS_MAX_RETRY=0, a hard-coded constant that cannot be set from the connection string. Add a Retry configuration note to the header (and an inline comment) so anyone reproducing the numbers knows to patch that constant in both the baseline and the change build.
Author
@microsoft-github-policy-service agree company="Microsoft" |
Divyansh-jain2
marked this pull request as ready for review
July 12, 2026 06:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When connecting with
authentication=ActiveDirectoryServicePrincipal(and the other AAD principal flows that go throughSQLServerMSAL4JUtils.getSqlFedAuthTokenPrincipal), every token acquisition — including cache hits — is currently serialized behind a globalSemaphore(1)keyed on the principal. The semaphore exists to stop a burst of cache misses from stampeding the Entra ID token endpoint, but it also forces threads that only need to read an already-cached token to queue one-at-a-time behind whoever currently holds the permit.Under load where many connections share the same service principal, this turns a cheap in-memory cache read into a serialization point: N concurrent connections that could all be satisfied from the MSAL cache instead drain through a single permit.
This change adds a lock-free silent fast-path that attempts a cache-only token read before taking the semaphore. Cache hits return immediately and in parallel; only cache misses fall through to the unchanged, semaphore-gated slow path that protects the token endpoint.
How it works
acquireTokenSilently(...)is MSAL's cache-only lookup — it checks the client application's in-memory token cache (rehydrated here from the persistent cache aspect) for a still-valid token for the requested scopes and returns it without contacting Entra ID. If nothing valid is cached, its future fails with anExecutionException, which we treat as a miss and fall through to the existing gated path.The gated slow path is unchanged and remains safe: the first caller through the gate refreshes the token and populates the shared cache; callers that were waiting on the gate then get that freshly-cached token because
acquireToken(...)does its own cache lookup first (skipCachedefaults tofalse), so they don't make a redundant AAD call.Net effect: cache hits are served concurrently; the token endpoint keeps the same stampede protection for misses.
Testing
Validated with a standalone single-service-principal Token Acquisiton benchmark, included for reference (not part of the build) at
docs/performance/TokenAcquisitionTest.java.What the harness does
SELECT 1, close it, and repeat in a tight loop for T seconds. Sharing one credential across all threads is the worst-case head-of-line / token-cache contention scenario, which isolates exactly what this change affects.CountDownLatchso they contend at once rather than ramping up.coldmode is also available to measure the cache-miss stampede.PerformanceLogCallbackthat captures the driver's internalTOKEN_ACQUISITIONtiming for every connection and reports count / avg / max, accumulated lock-free so the measurement adds negligible overhead.How it's run
The same command is used twice, changing only which driver jar is on the classpath (before vs. after this change). The
driver-org/fast-cacheargument is just a label printed in the output.The file will not compile inside this repo by design — it depends on a small, local, git-ignored
Credshelper that the reader supplies with their own Azure SQL server FQDN, database name and service-principal id/secret. No credentials are embedded; it is kept purely so reviewers can see exactly how the numbers below were produced.Results — token acquisition average latency (warm cache; lower is better):
The improvement reflects cache hits no longer queuing behind the single semaphore permit. Cache-miss behavior (the AAD refresh path) is unchanged.
Guidelines