TL;DR: Always call defer rows.Close() after querying your database.
Recently, I ran into a silent but critical problem in my Go application backed by PostgreSQL. Everything worked fine on the surface — requests were being handled, responses came back fast… until one day, I hit a wall.
New requests started hanging unexpectedly, and I noticed some routes weren’t returning data at all. But one route continued to work: the one fetching data from the cache instead of hitting the database.
Something wasn’t right.
🔍 The Clue: pg_stat_activity
I ran the following on my Postgres server:
SHOW max_connections; SELECT COUNT(*) FROM pg_stat_activity; And here was the result:
- max_connections was set to 100
- Active connections in pg_stat_activity? 67 😳
That number seemed way too high. On the client side (Go), I had:
db.SetMaxOpenConns(50) So what was holding these connections open?
🧠 Aha Moment: I Forgot defer rows.Close()
After inspecting my codebase, I realized I had this kind of pattern:
rows, err := db.Query("SELECT * FROM rows_fetcher($1)", query_key) if err != nil { return nil, err } for rows.Next() { // scan logic... } But no defer rows.Close(). 😱
Without explicitly closing the result set, the connection remains open and eventually floods the pool.
🔬 Digging Deeper: Seeing the Idle State
I ran:
SELECT pid, state, query, backend_start, xact_start, query_start FROM pg_stat_activity WHERE state = 'idle'; I noticed many of these had timestamps from weeks or even months ago!
🧹 Cleaning Up
I decided to take a few actions:
- Update all database queries in my Go code to include:
defer rows.Close() - Use pg_terminate_backend(pid) to clean up old, stale processes manually.
- Add SetConnMaxIdleTime to my DB setup:
db.SetConnMaxIdleTime(5 * time.Minute) - Consider setting PostgreSQL server config options like:
idle_in_transaction_session_timeout = 60000 # 1 min idle_session_timeout = 300000 # 5 min ✅ The Result After 24 Hours
I checked back the next day and ran:
SELECT COUNT(*) FROM pg_stat_activity; To my surprise and relief, the number was down to 1.
One. From 67.
💡 Takeaways
- Always close your rows after querying:
- Use pg_stat_activity to spot stale connections.
- Monitor timestamps, old queries might still be holding connections open.
- Use connection pooling wisely (SetMaxOpenConns, SetConnMaxIdleTime).
- Set database level timeouts to catch idle sessions that your app misses.
Top comments (0)