テック技報最新号へ →
コード

Go: database/sql の接続は既定で無期限に使い回される。SetConnMaxLifetime で寿命を付ける

database/sql のコネクションプールは、ConnMaxLifetime が既定で 0(無期限)なので、一度張った接続をずっと使い続ける。DNS の切り替えやプロキシの振り分け変更があっても古い接続は新しい接続先へ移らない。

@amitsinha__07 · 投稿 2026-10-27
#go#database#postgres#infra

困る場面

  • DB のフェイルオーバーで DNS が新しいプライマリを指しても、アプリは古いホストへの接続を持ち続ける。
  • RDS Proxy や PgBouncer、ロードバランサーを増やしても、既存の接続は古いノードに張り付いたままで負荷が偏る。
  • 途中の NAT やファイアウォールがアイドル接続を黙って切り、次のクエリで broken pipe や connection reset が出る。

やり方

go
db, err := sql.Open("pgx", dsn)
if err != nil {
    return err
}
db.SetConnMaxLifetime(5 * time.Minute) // 張ってから5分経った接続は使い終わったら閉じる
db.SetConnMaxIdleTime(time.Minute)     // 1分使われなかった接続は閉じる
db.SetMaxOpenConns(20)                 // 上限も併せて決めておく
db.SetMaxIdleConns(20)
  • SetConnMaxLifetime は「作られてからの経過時間」、SetConnMaxIdleTime(Go 1.15+)は「最後に使われてからの経過時間」で接続を閉じる。
  • 寿命を迎えた接続は使用中に切られるわけではなく、プールに戻ったときに閉じられる。進行中のクエリやトランザクションは壊れない。

注意点

  • 値は DB 側やネットワーク機器のアイドルタイムアウトより短くする(例: 途中の機器が 350 秒でアイドル接続を切るなら、それより短く)。
  • 短くしすぎると接続を張り直す回数が増える。TLS 接続では張り直しのコストが大きいので、数分程度が目安。
  • 全インスタンスが同時に起動すると寿命も同時に切れるが、database/sql は接続ごとに作成時刻が違うので、普段はばらける。

出典

X の元の投稿を開く