-
Notifications
You must be signed in to change notification settings - Fork 340
Connection Pool V2 Performance Results
Connection Pool V2 is available in Microsoft.Data.SqlClient 7.1.0. Set the switch at application startup, before opening any database connections:
AppContext.SetSwitch(
"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2",
true);Pool V2 remains opt-in throughout the 7.x release band. Set the switch to false and restart the application to return to the legacy pool.
The cold-start tests clear the pool, then time N concurrent callers that open and hold exactly N physical connections until every open completes. The measured time includes caller creation, connection creation, synchronization, and returning connections to the pool; pool clearing and setup are excluded. Tests cover 10, 25, 50, and 100 concurrent callers with Max Pool Size=200.
Synchronous callers use dedicated threads and Open(). At 100 callers on Linux, the mean time fell from 630.1 ms to 117.5 ms, a 5.4x speedup.
Asynchronous callers use tasks and OpenAsync(). At 100 callers on Linux, the mean time fell from 604.1 ms to 92.0 ms, a 6.6x speedup.
The rapid-fire tests use a fully pre-warmed pool and repeatedly check out and immediately return connections. They measure a zero-hold-time case for the hot acquire, return, and waiter-handoff paths.
The complete result set contains 68 matching benchmark and parameter rows for Linux and Windows. Click the chart to open its full-resolution SVG.
- This report compares paired benchmark runs configured with
UseConnectionPoolV2=falsefor the legacyWaitHandleDbConnectionPoolandtruefor Pool V2. Pool implementation selection is cached when the first pool is created, so the two configurations must execute in separate benchmark processes. - The perf pipeline is a configurable baseline/current comparison harness. It does not automatically create a Pool V2 A/B comparison. Its normal baseline can be a released NuGet package or another source ref; the Pool V2 comparison uses the same harness with the two pool-switch configurations.
- In the default
interleavedmode, the harness builds each variant once, then executes one benchmark unit at a time, baseline first and candidate second, before proceeding to the next unit. The legacysequentialmode instead completes the full baseline suite before the full candidate suite. - A first-pass slowdown above the configured 10% threshold causes only the affected benchmark unit to be re-run for the remaining best-of-three passes. The pipeline marks a regression confirmed only when a strict majority of those passes exceed the threshold; this is a reproducibility check, not a statistical-significance test.
- Speedup is calculated within each platform as
legacy mean / Pool V2 mean. A value above 1.0x means Pool V2 is faster. Linux and Windows timings are separate machine runs and must not be compared directly.
The following values were recorded in the perf-results diagnostics artifacts for the paired experiment runs. The client and SQL Server ran on the same VM in each run.
| Setting | Linux: build 170091 | Windows: build 170477 |
|---|---|---|
| Host lifecycle |
InternalDriverTools/PerfTest provisions a Perf Test Lab VM on Azure Dedicated Hosts, copies the source tree, runs the Linux script over SSH, collects artifacts, and tears the VM down. |
InternalDriverTools/PerfTest provisions a Perf Test Lab VM on Azure Dedicated Hosts, copies the source tree, runs the Windows script over SSH, collects artifacts, and tears the VM down. |
| Client OS | Ubuntu 22.04.5 LTS (Jammy Jellyfish), x64 | Windows Server 2022 Datacenter Azure Edition, version 10.0.20348.5499, x64 |
| Processor | Intel Xeon Platinum 8168 at 2.70 GHz. One socket, 16 physical cores, 32 logical processors, two threads per core, one NUMA node. | Intel Xeon Platinum 8168 at 2.70 GHz. One socket, 16 physical cores, 32 logical processors, two threads per core. |
| Virtualization | Microsoft full hypervisor virtualization | Microsoft Hyper-V virtualization |
| Runtime | .NET 9.0.19, x64 RyuJIT, Release; .NET SDK 10.0.300; BenchmarkDotNet 0.15.8 | .NET 9.0.19, x64 RyuJIT, Release; .NET SDK 10.0.300; BenchmarkDotNet 0.15.8 |
| SQL Server | SQL Server 2022 Developer Edition, RTM-CU26, 16.0.4265.3, x64 | SQL Server 2025 Enterprise Evaluation Edition, RTM, 17.0.1000.7, x64 |
| SQL Server OS | Ubuntu 22.04.5 LTS, x64 | Windows Server 2022 Datacenter Azure Edition, version 10.0 build 20348, x64 |
| SQL engine settings |
MAXDOP=8; cost threshold for parallelism 50; min/max server memory 16/38,575 MB; affinity mask 65535; I/O affinity 0; eight tempdb data files |
MAXDOP=8; cost threshold for parallelism 50; min/max server memory 16/49,148 MB; affinity mask 65535; I/O affinity 0; eight tempdb data files |
| Database and preflight | The script creates sqlclient-perf-db if necessary, records SQL configuration, verifies SELECT 1, and warms the database buffer pool and plan cache before benchmarks. |
The script creates sqlclient-perf-db if necessary, records SQL configuration, verifies SELECT 1, and warms the database buffer pool and plan cache before benchmarks. |
| CPU isolation | The template assigns disjoint client and SQL Server CPU sets. The script pins the client to PERF_CLIENT_CPUS with taskset; it warns and continues if pinning is unavailable. |
The template assigns disjoint client and SQL Server CPU sets. The script pins the client to PERF_CLIENT_CPUS through ProcessorAffinity; it warns and continues if pinning is unavailable. |
| Switch and run configuration |
UseConnectionPoolV2 was the switch under test; UseOptimizedAsyncBehaviour=true; interleaved mode; 10% regression threshold; three confirmation runs |
UseConnectionPoolV2 was the switch under test; UseOptimizedAsyncBehaviour=true; UseManagedSniOnWindows=false; interleaved mode; 10% regression threshold; three confirmation runs |
| Artifacts | BenchmarkDotNet full JSON and GitHub Markdown reports, host CPU topology, SQL configuration, and per-pass CPU-clock and thermal telemetry | BenchmarkDotNet full JSON and GitHub Markdown reports, host CPU topology, SQL configuration, and per-pass CPU-clock and thermal telemetry |
The test runner copies the checked-in runnerconfig.jsonc, replaces only its connection string with the VM connection, and applies explicitly requested behavior-flag overrides. BenchmarkDotNet runs in-process because the AppContext switches must apply to the process executing the benchmarks. The configuration uses MediumRun, Server GC, MemoryDiagnoser, ThreadingDiagnoser, one unroll factor, and runner-specific launch, warm-up, iteration, and invocation counts.
The pool-focused runners use one launch and one warm-up. Connection-pool contention and churn benchmarks use 15 measured iterations and one invocation per iteration; connection-pool stress benchmarks use 20 measured iterations and one invocation per iteration. The full-results chart selects the 68 matching connection-pool benchmark and parameter rows from the raw BenchmarkDotNet reports.
| Benchmark | Parameters in this run | Measured workload |
|---|---|---|
ColdStartRamp / ColdStartRampAsync
|
Connections 10/25/50/100; max pool 200 | Clears the pool, opens the requested number of physical connections, and holds each until all callers connect. Measures cold startup and scale-out; sync uses dedicated threads and async uses tasks. |
RapidFireOpenCloseSync / Async
|
Parallelism 10/50; max pool 50/100 | Uses a fully pre-warmed pool and repeatedly checks connections out and returns them immediately. This is a zero-hold-time worst case for hot acquire/return and waiter-handoff paths. |
RapidOpenCloseSingleThread / Async
|
1,000 operations; pool depths 1/10/100 | Runs repeated checkout/return on one caller with no contention. Isolates per-operation pool overhead and reuse-locality differences between LIFO and FIFO idle-connection ordering. |
SteadyStateOpenQueryClose variants |
Parallelism 50; max pool 10/25/50/100/200; 20 operations per worker | Uses a pre-warmed pool for open, SELECT 1, and close loops. Pool sizes above, equal to, and below demand separate hot-path throughput from waiter back-pressure; variants use sync, async, or dedicated threads. |
SaturatedSyncOpenOnThreadPool |
Parallelism 50; pool 10; worker floors 4/16/32/128 | Runs blocking Open calls on thread-pool threads against a saturated pool. Sweeping the minimum worker count exposes continuation starvation and thread-injection sensitivity. |
RandomizedHoldAndQuery |
Parallelism 10/50; max pool 50/100 | Holds each checkout for a random 0-50 ms and executes a small query about half the time. Represents mixed application hold times. |
MixedSyncAsyncContention |
Parallelism 10/50; max pool 50/100 | Splits workers between sync and async open/query/close loops to exercise both checkout paths against the same pool. |
MultiCommandReuse |
Parallelism 10/50; max pool 50/100 | Checks out once per worker and executes 5-15 sequential commands before returning the connection. Models multi-step ORM or unit-of-work usage. |
PoolExhaustionRecovery |
Parallelism 10/50; max pool 50/100 | Starts at least twice as many tasks as the pool can serve, runs a query, and holds connections for 10-100 ms. Measures queued-waiter back-pressure and recovery. |
BurstyTrafficPattern |
Parallelism 10/50; max pool 50/100 | Runs five waves of concurrent checkouts, each executing 1-5 queries, with a short pause between waves. Models clustered request traffic. |
OpenConnectionsConcurrently |
Concurrent opens 10/50/100; pooling enabled | Starts many OpenAsync calls together and disposes each connection after it opens. Measures concurrent connection acquisition with pooling enabled. |
OpenConnection / OpenAsyncConnection
|
Pooling enabled; MARS on/off | Measures the basic sync and async pooled-open API path for a single connection, with and without Multiple Active Result Sets. |
Home | ADO.NET driver docs | Release notes | Support lifecycle
Report security vulnerabilities privately to MSRC. Never share credentials or access tokens in public reports.