Goのdatabase/sqlのコネクションプールの設定は必ず検討しよう
ケース1:Too many connections
DBにコネクションを張ろうとした時に、DB側でコネクション数が最大に達してしまい、コネクションを張ることができなかったことによるエラーです。

本ケースの原因は、2つ目の分岐「コネクション数が最大コネクション数未満か?」が必ずYesとなり、同時に大量の新規コネクションを作成しようとしたことになります。
この2つ目の分岐の「最大コネクション数」とは、DBインスタンスに設定されるもので、SetMaxOpenConns()で指定できるものです。そしてこの最大コネクション数、指定しない場合のデフォルト値は0、つまり無限であり、指定しなかった結果Too many connectionsが発生してしまいました。
設定する上での注意点として、アプリケーション側で負荷に応じてコンテナを増やす等、オートスケーリングを設定している場合は、コンテナが複数ある場合を想定した設定値とする必要があります。
ケース2:接続試行回数が跳ね上がる
ある時、DBのモニタリング情報を確認していると、1分間当たりのDBの接続試行回数が数百単位で跳ね上がる瞬間があることに気づき、これに対処しました。
事象について調査すると、コネクションの作成と切断を一瞬の内に繰り返していることが分かりました。これはつまり、コネクションをうまく使いまわせていないということになります。
database/sqlにおいて、Query()やExec()でコネクションを使い終わると、putConn()が呼ばれ、その時点でのidleなコネクションの数が上限に達していればコネクションを切断し、そうでなければをidleにするという動きをします。(大まかな流れのみ表現しています。詳細はputConn()を参照してください)

ここで「idleなコネクションの数の上限」とは、DBインスタンスに設定されるもので、SetMaxIdleConns()で設定できるものです。そしてこのデフォルト値は2であり、idleなコネクションは2つまで存在できるということになります。
このデフォルト値で、複数のSQLを実行するようなgoroutineを100個同時に実行したらどうなるでしょうか。それぞれのgoroutineでコネクションの取得と解放を繰り返し、その過程で以下のようにidleなコネクションの数が最大値の2に達し、コネクションの切断が発生し、また切断した分新たにコネクションを作成する、といったことが発生します。これを繰り返した結果、接続試行回数が跳ね上がっていました。
- goroutine①がコネクション開放 → idleなコネクションの数が1に
- goroutine②がコネクション開放 → idleなコネクションの数が2に
- goroutine③がコネクション開放 → idleなコネクションの数が上限に達しているので、コネクションを切断
- goroutine①がコネクション取得 → idleなコネクションを使用 → idleなコネクションの数が1に
- goroutine②がコネクション取得 → idleなコネクションを使用 → idleなコネクションの数が0に
- goroutine③がコネクション取得 → idleなコネクションが無いので新規コネクション作成
本ケースの対処方法としては、まずSetMaxIdleConns()での設定値をケース1のSetMaxOpenConns()での設定値と同値にすることで、idleなコネクションの数が確実に上限を超えないようにしました。但しSetMaxIdleConns()を大きくするだけだと、コネクションを使わない時もidleとして保持し続けることになるのでリソースの浪費に繋がります。これについてはSetConnMaxIdleTime()でコネクションがidleで存在できる時間を制限することで対処しました。
最後に
For the vast majority of programs, you needn’t adjust the connection pool defaults. But for some advanced programs, you might need to tune the connection pool parameters or work with connections explicitly.
直訳:
ほとんどのプログラムでは、sql.DB 接続プールのデフォルトを調整する必要はありません。 ただし、一部の高度なプログラムでは、接続プールのパラメーターを調整したり、接続を明示的に操作したりする必要がある場合があります。
本記事で取り上げた事例に遭遇した身としては、これを読んで「ほとんどのプログラムでデフォルトを調整する必要があるのでは…」という感想を抱いてしまいました(個人の感想です)。
database/sqlを使う方が本記事を読み、コネクションプールを適切に設定するための一助となれば幸いです。