Skip to content

Connection Pool V2 Performance Results

Malcolm Daigle edited this page Sep 16, 2026 · 7 revisions

Connection Pool V2 performance results

Home / Feature guides

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.

Highlighted Result: Cold-start connection ramp

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

Synchronous cold-start benchmark comparing the legacy pool and Pool V2 on Linux and Windows at 10, 25, 50, and 100 concurrent callers.

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

Asynchronous cold-start benchmark comparing the legacy pool and Pool V2 on Linux and Windows at 10, 25, 50, and 100 concurrent callers.

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.

Highlighted Result: Rapid-fire open and close

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.

Synchronous callers

Synchronous rapid-fire open and close benchmark comparing the legacy pool and Pool V2 on Linux and Windows.

Asynchronous callers

Asynchronous rapid-fire open and close benchmark comparing the legacy pool and Pool V2 on Linux and Windows.

Full benchmark results

The complete result set contains 68 matching benchmark and parameter rows for Linux and Windows. Click the chart to open its full-resolution SVG.

Complete ordered connection-pool benchmark results comparing the legacy pool and Pool V2 on Linux and Windows.

Methodology and test coverage

Comparison method

  • This report compares paired benchmark runs configured with UseConnectionPoolV2=false for the legacy WaitHandleDbConnectionPool and true for 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 interleaved mode, 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 legacy sequential mode 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.

Perf lab environment

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

Measurement configuration

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.

What each pool benchmark measures

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.

Clone this wiki locally