You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Entra ID access token is acquired on every connect(), even on a pool hit #659
With Entra ID authentication (e.g. Authentication=ActiveDirectoryDefault), Connection.__init__ acquires an access token on everyconnect(), before the native connection pool is consulted. On a pool hit the freshly acquired token is never used: the pooled physical connection is already authenticated, and the pool keys only on the sanitized connection string, so the token in attrs_before is not reapplied. The token acquisition and struct packing are therefore wasted work on every reused connection.
This partially defeats the purpose of pooling for token-auth workloads: pooling is enabled to avoid per-connection cost, yet a token is still materialized for each connection.
Where (v1.10.0)
mssql_python/connection.py, Connection.__init__:
# token acquired unconditionally, before the pool is consultedsanitized=remove_sensitive_params(parsed_params)
self.connection_str=_ConnectionStringBuilder(sanitized).build()
token=get_auth_token(auth_type, credential_kwargs) # <-- always runsiftoken:
self._attrs_before[ConstantsDDBC.SQL_COPT_SS_ACCESS_TOKEN.value] =token# ... later ...# pool checkout happens here, in the C layer, keyed on connection_strifnotPoolingManager.is_initialized():
PoolingManager.enable()
self._pooling=PoolingManager.is_enabled()
self._conn=ddbc_bindings.Connection(
self.connection_str, self._pooling, self._attrs_before
)
get_auth_token -> AADAuth._acquire_token reuses a cached credential instance, but still calls credential.get_token("https://database.windows.net/.default") and get_token_struct() (UTF-16-LE encode + struct.pack) on every call. azure-identity serves the token from its own in-memory cache while it is still valid, so this is not a full network round-trip each time, but it is per-connection CPU work whose result is discarded on a pool hit.
Evidence
A dlt pipeline loading many tables to a Fabric Warehouse with Authentication=ActiveDirectoryDefault and native pooling enabled:
The native pool is active and reusing connections: SQL_ATTR_RESET_CONNECTION (pool checkout reset) is logged ~212 times, with a single real cold login (~2.9s) followed by a uniform ~0.28s per open.
Yet get_token: Azure AD token acquired successfully is logged once per connect() (146 token acquisitions for 146 opens, exactly 1:1), i.e. a token is produced even for the reused connections.
Impact
Wasted CPU per connection (token struct packing + credential.get_token bookkeeping) exactly in the high-frequency, short-connection scenario that pooling is meant to optimize.
Makes it harder to reason about pooling from the caller side: every open still performs an auth step, so a pooled checkout is indistinguishable from a fresh login by wall-clock.
Possible direction
Consult the pool before acquiring/materializing the token, and only acquire when a new physical connection will actually be opened (pool miss). This depends on the pool-key/identity work tracked in #651: the pool currently cannot tell the caller whether a checkout will reuse or open a connection, and it cannot safely reuse a token-auth connection across identities. If the pool became identity-aware (per #651), a pool hit for the same identity could skip token acquisition entirely.
Thanks Sam Debruyn (@sdebruyn) — accurate diagnosis, and the 1:1 token-to-connect() ratio despite ~200 reuses nails it.
Confirmed in the code: Connection.init acquires and packs the token into attrs_before before the native pool is consulted, so on a pool hit the reused connection is already authenticated and that token is discarded — wasted work on every connect().
Coupling to #651: today the pool keys only on the connection string, so it can't safely skip acquisition or reuse token-auth connections across identities. Once the pool is identity-aware, acquisition becomes lazy / pool-key-first — compute the pool key without a token where the identity is known (Managed Identity, Service Principal, Interactive/Device-code), consult the pool, and acquire only on a miss or a near-expiry refresh.
One caveat: for auth types where the token is the identity key (DefaultAzureCredential, raw token, token_provider) we still need it materialized — raw tokens are caller-supplied anyway, so no acquisition cost there.
Net: acquisitions will scale with pool misses, not total connect() calls. Tracking this as the performance half of #651 — thanks for the detailed report and profiling.
Activity
github-actions commentedon Jul 2, 2026
Hi Sam Debruyn (@sdebruyn), thank you for opening this issue!
Our team will review it shortly. We aim to triage all new issues within 24-48 hours and get back to you.
If you have additional information to share, please feel free to update the issue.
Thank you for your patience!
jahnvi480 commentedon Jul 3, 2026
Thanks Sam Debruyn (@sdebruyn) — accurate diagnosis, and the 1:1 token-to-connect() ratio despite ~200 reuses nails it.
Confirmed in the code: Connection.init acquires and packs the token into attrs_before before the native pool is consulted, so on a pool hit the reused connection is already authenticated and that token is discarded — wasted work on every connect().
Coupling to #651: today the pool keys only on the connection string, so it can't safely skip acquisition or reuse token-auth connections across identities. Once the pool is identity-aware, acquisition becomes lazy / pool-key-first — compute the pool key without a token where the identity is known (Managed Identity, Service Principal, Interactive/Device-code), consult the pool, and acquire only on a miss or a near-expiry refresh.
One caveat: for auth types where the token is the identity key (DefaultAzureCredential, raw token, token_provider) we still need it materialized — raw tokens are caller-supplied anyway, so no acquisition cost there.
Net: acquisitions will scale with pool misses, not total connect() calls. Tracking this as the performance half of #651 — thanks for the detailed report and profiling.
feat(pooling): defer token acquisition to a lazy factory on pool miss (…
test: cover lazy token-factory edge cases (#659)
FEAT: token-expiry capture and identity-aware pool key (#660)
jahnvi480 commentedon Aug 5, 2026
Sam Debruyn (@sdebruyn) The PR has been merged and it will be available to use in our next release. Thank you for helping us make our driver better.
RELEASE:1.13.0 (#707)