Thanks for reaching out about this connection pooling issue! This is a classic case where the error message can be a bit misleading. Let’s break this down systematically and get to the bottom of it.
First, let’s confirm your active connections with a more detailed query:
SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state;
SELECT datname, usename, state, wait_event_type, wait_event
FROM pg_stat_activity
WHERE state IS NOT NULL;
The “too many clients” error despite only seeing three active connections in pg_stat_activity usually points to one of these scenarios:
- Connection Pool Lifecycle Issues:
- Connections might not be properly released back to the pool
- The pool might be configured with a size that’s too small for your workload
Let’s check your Npgsql connection string settings. You mentioned you’re using defaults, which means:
// Default Npgsql connection string
"Host=localhost;Database=mydb;Username=myuser;Password=mypass;Maximum Pool Size=100;Minimum Pool Size=1"
To better manage this, I’d recommend explicitly setting these parameters:
"Host=localhost;Database=mydb;Username=myuser;Password=mypass;Maximum Pool Size=20;Minimum Pool Size=1;Connection Idle Lifetime=300;Connection Pruning Interval=10"
- Diagnostic Queries
Here’s a query to monitor connection pool usage:
SELECT
now() - backend_start as connection_age,
now() - xact_start as transaction_age,
usename,
application_name,
state,
query
FROM pg_stat_activity
WHERE datname = current_database()
ORDER BY connection_age DESC;
To track connection churn:
SELECT count(*), usename, application_name
FROM pg_stat_activity
GROUP BY usename, application_name;
Recommendations:
- Implement connection pool monitoring in your application:
using (var conn = await dataSource.OpenConnectionAsync())
{
// Log pool statistics
var poolStats = dataSource.Statistics;
logger.LogInformation($"Total: {poolStats.Total}, Idle: {poolStats.Idle}, Busy: {poolStats.Busy}");
}
- Consider implementing a circuit breaker pattern for database connections:
using Polly;
var retryPolicy = Policy
.Handle<NpgsqlException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
- Review your transaction management - ensure you’re properly disposing connections:
await using (var conn = await dataSource.OpenConnectionAsync())
await using (var tx = await conn.BeginTransactionAsync())
{
// Your database operations goes here
await tx.CommitAsync(); // commit the transaction after insert/delete/update
}
With the queries output you can understand if it’s a connection pool configuration issue or if there might be some connection leaks in the application code.
Also, for time-series workloads in TimescaleDB, we typically recommend starting with a smaller connection pool, you can reduce from 100 connections to 20 connections and scaling up based on metrics, rather than defaulting to larger pools. This helps prevent connection thrashing while still maintaining good performance for most time-series write patterns.