How to Diagnose Intermittent Connection Pool Exhaustion in GitHub Actions?
0 reputation · 09 Nov 2025, 00:13 UTC
0 reputation · 09 Nov 2025, 00:13 UTC
I run a CI workflow that connects to a PostgreSQL database during integration tests. Occasionally the workflow fails with a connection pool exhaustion error, even though the number of parallel jobs is below the database's max pool size.
The workflow executes on GitHub‑hosted Ubuntu runners, which are virtual machines in Azure with a shared network stack. I suspect the issue could be due to transient network latency, runner resource limits, or the way the job manages database connections across steps.
I would like to understand how to diagnose whether the exhaustion is caused by the runner environment or by the application logic, and what mitigation steps are available.
Should I switch to a self‑hosted runner or a larger runner with a custom image? How can I capture detailed connection logs within the workflow? What configuration changes can reduce the chance of pool exhaustion?
35275 reputation · 09 Nov 2025, 09:17 UTC
Intermittent exhaustion is almost always a symptom of a temporary spike in connection demand that outpaces the pool’s configured limits or a leak that only becomes apparent under load. The most common culprits are:
maxPoolSize for the number of concurrent jobs or threads the workflow spawns.Most JDBC drivers expose a debug level that prints pool state changes. For HikariCP, add the following to your application.yml or set the environment variable before the test step:
export HIKARI_DEBUG=true
# or in Java
System.setProperty("com.zaxxer.hikari.pool.PoolBase", "DEBUG");
GitHub Actions will capture the log output automatically. Look for lines such as HikariPool-1 - Created new connection and HikariPool-1 - Connection returned to see how many connections are in use.
After each step that opens a connection, run a lightweight query against pg_stat_activity to count active sessions:
echo "SELECT count(*) FROM pg_stat_activity WHERE datname = 'your_db';" | psql -h $DB_HOST -U $DB_USER -d $DB_NAME -t
Store the output in a file and compare a successful run to a failing one.
Insert a sleep 10 step after the test job to give the pool time to recycle connections before the next job starts. If the failure rate drops, a timing issue is likely.
Set maxPoolSize to a value well above the expected peak (e.g., 200) and rerun the workflow. If the exhaustion events disappear, the pool size was the bottleneck.
GitHub‑hosted runners expose CPU and memory limits. Use cat /proc/meminfo and cat /proc/cpuinfo to verify that the runner isn’t hitting its quota during the test run. If the runner is throttled, consider a larger runner size (e.g., ubuntu-22.04-large) or a self‑hosted runner in a dedicated environment.
maxPoolSize to at least the sum of connections needed by all parallel jobs plus a safety margin.idleTimeout and maxLifetime to ensure idle connections are closed and refreshed regularly.DataSource ds = ...; ds.close();connectionTestQuery or validationTimeout so the driver can detect broken connections early.Only move to a self‑hosted or larger runner if:
ping or traceroute tests).Otherwise, tuning the pool and ensuring proper cleanup is usually enough.
What is the maxPoolSize (or equivalent) configured in your application? Knowing the exact value will let us decide whether the pool is truly undersized or if the issue is elsewhere.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 09 Nov 2025, 04:45 UTC
To build on the diagnostic steps, it is worth investigating if "zombie" connections are contributing to the exhaustion. In GitHub-hosted environments, transient network interruptions can cause the runner to lose connectivity without sending a proper TCP FIN packet to PostgreSQL.
If the database is not configured to detect these dead sessions, they remain in an active or idle state until the database's internal timeout is reached, effectively leaking pool slots despite the application logic being correct.
pg_stat_activity query, specifically check the state_change column to see how long sessions have been idle.tcp_keepalives_idle and tcp_keepalives_interval to prune dead connections more aggressively.maxLifetime slightly shorter than the database's own connection timeout to force a clean refresh of connections.