Interview question
Diagnose connection-pool pressure from a leaking code path
Pairs a bounded isolated reproduction with telemetry and a disposal fix for leaked database connections.
TL;DR
Pairs a bounded isolated reproduction with telemetry and a disposal fix for leaked database connections.
Connection lifetime, pool saturation, disposal, concurrency reproduction, metrics, and mitigation versus root cause.
Practice the problem like a real interview: restate, reason, implement, and test.
The endpoint manually opens a database connection and returns early on one validation branch without closing it. In an isolated test database, configure a small pool, repeat that branch, and show a corrected await using scope. Do not stress the developer or production database.
Query duration remains low, but connection acquisition time and timeout count rise until the pool is exhausted. Fixing disposal restores steady active/idle connection behavior.
I separate evidence: fast database commands plus slow connection acquisition points to pool pressure rather than query execution. The code fix makes ownership explicit with await using, including early returns and exceptions.
[Fact]
public async Task Leaked_branch_exhausts_a_small_pool_but_disposal_recovers()
{
await using var database = await PostgresFixture.StartAsync(
maxPoolSize: 2,
connectionTimeoutSeconds: 1);
var leaked = new List<DbConnection>();
try
{
leaked.Add(await OpenAndReturnEarlyAsync(database.DataSource));
leaked.Add(await OpenAndReturnEarlyAsync(database.DataSource));
await Assert.ThrowsAsync<NpgsqlException>(async () =>
await database.DataSource.OpenConnectionAsync());
}
finally
{
foreach (var connection in leaked)
await connection.DisposeAsync();
}
var correctedCalls = Enumerable.Range(0, 20)
.Select(_ => ExecuteHealthyBranchAsync(database.DataSource));
await Task.WhenAll(correctedCalls).WaitAsync(TimeSpan.FromSeconds(10));
Assert.Equal(0, database.CheckedOutConnections);
}
// Deliberately broken reproduction: an early return transfers no ownership
// and leaves the opened connection checked out.
private static async Task<DbConnection> OpenAndReturnEarlyAsync(
DbDataSource dataSource) =>
await dataSource.OpenConnectionAsync();
private static async Task ExecuteHealthyBranchAsync(DbDataSource dataSource)
{
await using var connection = await dataSource.OpenConnectionAsync();
await using var command = connection.CreateCommand();
command.CommandText = "SELECT 1";
await command.ExecuteScalarAsync();
}
The fixture must use a disposable PostgreSQL instance and expose pool metrics only to the test. The failing half proves the acquisition timeout; the corrected half proves that deterministic disposal restores the pool under repeated use.
The production evidence chain is request concurrency, connection acquisition duration, active/idle pool counts, database sessions, and recent code changes. A focused isolated load test can prove the fix; increasing pool size alone only delays exhaustion.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.