Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
DatabasesJune 23, 20264 min read

Database performance: Stop Connection Pool Exhaustion with Timeouts

Database performance depends on protecting your connection pool. Learn how to use query timeouts and statement limits to kill runaway queries before they crash.

databasespostgresqlperformanceengineeringbackendreliabilityMySQLRedisDatabase

Last month, our primary billing service started throwing 500 errors during peak traffic. It wasn't a spike in requests, but a single, unindexed join that went rogue and held onto database connections for 45 seconds each. By the time we identified the culprit, our entire connection pool was exhausted, effectively taking down the rest of our infrastructure.

When you're fighting to maintain database performance, the most dangerous thing you can do is let a single slow query dictate the health of your entire system. If you aren't enforcing strict query timeouts, you’re essentially leaving your application’s availability to chance.

Why you need database-level limits

We usually think about timeouts at the application layer—the connect_timeout or read_timeout in our ORM or database driver. But those are often unreliable. If the connection hangs deep within the database engine or if the application server is under heavy load, those client-side settings might never trigger.

If you rely on your ORM to kill a query, you're betting that the application can still communicate with the database. If the connection pool is saturated, that communication is already dead.

Implementing statement limits effectively

To gain control, you need to shift your connection pooling defense to the database engine itself. In PostgreSQL, for example, you have a few options.

1. The global approach: statement_timeout

You can set a global limit in your postgresql.conf or at the user level. We started by setting a hard limit for our web-user role:

SQL
ALTER ROLE web_app_user SET statement_timeout = '5s';

This ensures that no query—regardless of how it’s triggered—can run longer than five seconds. This was a "nuclear" option for us. It immediately stopped the pool exhaustion, but it also broke a few long-running report exports that were actually legitimate.

2. The surgical approach: Per-transaction settings

Instead of a global hammer, we moved to setting the timeout per transaction. Most drivers allow you to execute a command immediately after opening a connection:

SQL
BEGIN;
SET LOCAL statement_timeout = '2s';
-- Run your critical query here
COMMIT;

Using SET LOCAL is the secret sauce here. It only affects the current transaction, meaning you don't need to worry about cleaning up after yourself. If the transaction completes, the setting disappears. If it hangs, the database kills it after two seconds, and the connection returns to the pool immediately.

The trade-offs of aggressive timeouts

We first tried setting a blanket 500ms timeout for everything. It was a disaster. We saw a spike in 500 errors because our primary search query, which involves a complex full-text index, occasionally hits 600ms under high load.

When you implement query optimization through timeouts, you have to accept that you're trading off "perfect" results for "reliable" system health. We ended up with a tiered approach:

  • Standard API endpoints: 2-second limit.
  • Background worker queues: 30-second limit.
  • Reporting/Analytics: No automatic limit, but routed to a read-replica.

Managing runaway queries in production

Even with timeouts, you need visibility. We use pg_stat_activity to monitor what’s currently holding up our connections. If you notice a specific query consistently hitting your statement limits, you don't just increase the timeout—you fix the index.

SQL
SELECT pid, query, state, wait_event_type 
FROM pg_stat_activity 
WHERE state = 'active' 
AND query_start < NOW() - INTERVAL '1 second';

This query shows you everything running for longer than a second. If you see your top-tier queries here, you've got an indexing problem, not a timeout problem.

Frequently Asked Questions

Does statement_timeout affect the entire connection? No, it affects the duration of a single statement. If you set it to 5s, each individual query in a transaction has 5s to complete. If your transaction has 10 queries, the total time could technically be 50s.

Will setting a low timeout kill my database performance? Actually, it usually improves it. By killing runaway queries, you prevent "query storms" where multiple slow queries pile up, consume all available memory, and force the database into disk-swapping hell.

What happens if I use a connection pooler like PgBouncer? PgBouncer is great for managing connections, but it doesn't know what happens inside the query. You still need the database-level timeout to ensure the query itself doesn't hang the backend process.

I'm still tinkering with whether we should move these limits into our database proxy layer instead of the application. Managing these settings across 50 different microservices is a headache, and I suspect a central configuration via ProxySQL or a similar tool would be cleaner. For now, the SET LOCAL pattern is doing the heavy lifting, and our connection pool exhaustion issues have dropped to zero.

Back to Blog

Similar Posts

DatabasesJune 22, 20264 min read

Database queueing with SELECT FOR UPDATE SKIP LOCKED

Database queueing with SELECT FOR UPDATE SKIP LOCKED is the secret to high-concurrency task processing. Learn to eliminate race conditions in your workers.

Read more
DatabasesJune 22, 20265 min read

Covering indexes: Speed up read queries by eliminating bookmark lookups

Master covering indexes to eliminate bookmark lookups. Improve database query performance by keeping data retrieval inside the B-tree index structure.

Read more
Close-up of a smartphone showing Python code on the display, showcasing coding and technology.
DatabasesJune 20, 20264 min read

Indexing Strategy for App Developers: Stop Slow Queries

Master an indexing strategy for app developers to fix slow production queries. Learn how to read EXPLAIN plans, pick the right columns, and avoid overhead.

Read more