Expand connection pool benchmark coverage for ChannelDbConnectionPool#4451
Expand connection pool benchmark coverage for ChannelDbConnectionPool#4451mdaigle wants to merge 3 commits into
Conversation
… pooling matrix Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The connection pool implementation switch (UseConnectionPoolV2) and the SNI switch are read and cached on first use in LocalAppContextSwitches. Because BenchmarkConfig runs every case in one process via InProcessEmitToolchain, toggling these switches per iteration had no effect and would have produced misleading Legacy/New/Native/Managed rows. Select the pool implementation once per process via a new UseConnectionPoolV2 config flag (set in Program.SetupConfigurations), matching how SNI and async behavior are already configured. Comparing pools is done with two runs. Keep only the genuinely per-iteration dimensions as benchmark parameters: warm/cold pool, sync/async, pooling on/off, and connection count. This aligns the benchmark with issue dotnet#3356's evaluation of the ChannelDbConnectionPool. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Round out connection pool benchmark coverage for evaluating ChannelDbConnectionPool (UseConnectionPoolV2) vs the legacy WaitHandleDbConnectionPool per issue dotnet#3356. - ConnectionPoolContentionRunner: steady-state open->SELECT 1->close across 50 concurrent workers on a warm pool, with MaxPoolSize >= Parallelism (no wait) and MaxPoolSize << Parallelism (back-pressure). Exercises the async waiter wake path and lock contention that most differentiate the two pools under exhaustion (dotnet#601, dotnet#979). - ConnectionPoolChurnRunner: single-threaded rapid open/close on a warm pool. Low- noise measure of raw per-checkout overhead and per-op allocations. Both honor the process-level UseConnectionPoolV2 config flag (compare via two runs) and are wired into Config.Benchmarks, Program.cs, and runnerconfig.jsonc following the existing runner pattern. The existing ConnectionPoolStressRunner covers burst parallel open (cold/warm x sync/async x pooling on/off). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Expands the Microsoft.Data.SqlClient.PerformanceTests benchmark harness to better compare the legacy WaitHandleDbConnectionPool with the new ChannelDbConnectionPool by introducing additional pool-focused runners and making pool selection a process-level config choice (so it aligns with how LocalAppContextSwitches caches the switch).
Changes:
- Adds a process-level
UseConnectionPoolV2flag torunnerconfig.jsoncand applies it once inProgram.SetupConfigurations. - Reworks
ConnectionPoolStressRunnerto focus on parallel open throughput dimensions (cold/warm, sync/async, pooling on/off). - Introduces two new benchmark runners:
ConnectionPoolContentionRunner(steady-state contention/exhaustion) andConnectionPoolChurnRunner(single-thread checkout overhead).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/tests/PerformanceTests/runnerconfig.jsonc | Adds UseConnectionPoolV2 and enables configs for new pool runners. |
| src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs | Applies UseConnectionPoolV2 AppContext switch once per process and wires up new runners. |
| src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs | Adds UseConnectionPoolV2 to the deserialized config surface and new runner config fields. |
| src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolStressRunner.cs | Refactors runner to measure parallel open throughput across key dimensions. |
| src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolContentionRunner.cs | New runner for steady-state pool contention/exhaustion behavior. |
| src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/ConnectionPoolChurnRunner.cs | New runner for low-noise per-checkout overhead measurement. |
| _connections.TryAdd(conn); | ||
|
|
||
| await Task.Delay(rng.Next(10, 101)); | ||
| if (returnToPool) | ||
| { | ||
| conn.Close(); |
| if (_connections.TryTake(out var first)) | ||
| { | ||
| int seed = i; | ||
| tasks[i] = Task.Run(async () => | ||
| { | ||
| var rng = new Random(seed); | ||
| for (int j = 0; j < 10; j++) | ||
| { | ||
| using var conn = new SqlConnection(_connectionString); | ||
| await conn.OpenAsync(); | ||
| first.Close(); | ||
|
|
||
| // ~50% of the time, execute a lightweight query while holding the connection | ||
| if (rng.Next(2) == 0) | ||
| { | ||
| using var cmd = new SqlCommand($"SELECT TOP 1 Val FROM {_tableName}", conn); | ||
| _ = await cmd.ExecuteScalarAsync(); | ||
| } | ||
| while (_connections.TryTake(out var conn)) |
| // Return them all to the pool so subsequent checkouts are served from idle. | ||
| for (int i = 0; i < count; i++) | ||
| { | ||
| conns[i].Close(); | ||
| } |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev/cheena/perf-project #4451 +/- ##
===========================================================
+ Coverage 63.95% 65.01% +1.05%
===========================================================
Files 284 284
Lines 66786 66786
===========================================================
+ Hits 42712 43419 +707
+ Misses 24074 23367 -707
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Summary
Expands the connection pool benchmark coverage introduced in #4447 to more thoroughly evaluate the new
ChannelDbConnectionPool(UseConnectionPoolV2) against the legacyWaitHandleDbConnectionPool, aligned with the design goals in #3356: parallel connection opening, async best practices / low threadpool pressure, and minimal thread/lock contention.Motivation (from #3356 and linked issues)
The new pool targets the slow, serialized connection opening described in #601 and #979. Good performance coverage needs to exercise the dimensions where the two implementations actually diverge: cold vs warm pools, sync vs async callers, and behavior under pool exhaustion (async waiter wake path).
Correctness fix
The pool-implementation switch (
UseConnectionPoolV2) and the SNI switch are read once and cached inLocalAppContextSwitches. BecauseBenchmarkConfigruns every case in a single process viaInProcessEmitToolchain, toggling thoseAppContextswitches per benchmark iteration has no effect — every "Legacy/New" row would silently use whichever value was read first.Fix: the pool implementation is now a process-level config flag (
UseConnectionPoolV2inrunnerconfig.jsonc, applied once inProgram.SetupConfigurations), matching howUseManagedSniOnWindowsandUseOptimizedAsyncBehaviourare already handled. Comparing the two pools is done with two runs (false then true) — which is how the #3356 charts were produced. Each runner logs the active implementation so results are self-describing.Benchmark coverage
ConnectionPoolStressRunner(reworked)ConnectionPoolContentionRunner(new)SELECT 1→close across 50 workers;MaxPoolSize≥ Parallelism (no wait) and ≪ Parallelism (back-pressure); sync/asyncConnectionPoolChurnRunner(new)Rate limiting (#4396), pruning (#4304), and shutdown (#4302) are time/behavior-based and are intentionally left to reliability testing (#3667) rather than microbenchmarks.
Metrics come from the existing
MemoryDiagnoser+ThreadingDiagnoser(Mean, Completed Work Items, Lock Contentions, Allocated/Gen0).Sample results
Validated locally against SQL Server (both pools). Burst-open of 100 pooled connections (
ConnectionPoolStressRunner):Async Completed Work Items also dropped ~1180 → ~324, confirming reduced threadpool pressure.
Absolute numbers above are from a local containerized SQL instance and are illustrative, not production-representative; the harness and relative/threading metrics are the point.
Checklist