Rails 5.2以降のRails 5系でDBに都度接続している場合においてはdatabase.ymlで `reaping_frequency: null` を設定する
Rails 5.2以降でDBに都度接続している場合においてはdatabase.ymlで reaping_frequency: null
を設定しないと、スレッドが枯渇してサーバーにsshすらできなくなりサーバーの再起動が必要になります。
sshはこんなエラーになります。
shell request failed on channel 0
またサーバーに入っていると下記のようなエラーがでて何もできなくなります。
-bash: fork: retry: 子プロセスがありません -bash: fork: リソースが一時的に利用できません
スレッドが枯渇しているサーバーは外部からみると、CPU、メモリなどの負荷がかかっているようには見えず、再起動して入ってみてもOOM-Killerなどが発動しておらず、何が起こっているかわかりにくいのが特徴です。
1プロセスあたりのファイルディスクリプタ数の上限も十分にあり、tcp周りの設定( net.ipv4.tcp_tw_reuse
とか) もしてあり、file descriptorの枯渇もみられなかったので、原因がわからず焦りました。
ちなみに tcp_tw_recycle
は廃止されたそうです。
原因
下記のPRにより、 reaping_frequency
が設定されていないとデフォルトの60秒が設定されるようになりました。
これはReaperがDBへの接続がハングしているか死んでいるかをチェックする間隔になります。
Reaperは別スレッドを立てて定期的に起き上がって確認する実装になっています。
Reaperは、establish_connectionが実行され、コネクションプールが生成されるたびにスレッドを作成します。
がこのスレッドは特にkillもされずに残る実装になっていそうです。
2020/08/31時点のedgeを見ると、ちゃんと止める実装も入っている気がします。
で、activerecord-refresh_connectionなどを使って都度接続などを行っていると、コネクションを貼るたびにスレッドが作られ増え続けます。
スレッドが増える様子は pstree
コマンドで確認できます。(下記の中で数字がスレッド数です)
# pstree sh---bundle-+-bundle---73*[{bundle}] |-bundle---49*[{bundle}] |-bundle---178*[{bundle}] |-bundle---106*[{bundle}] |-bundle---122*[{bundle}] |-bundle---98*[{bundle}] |-bundle---130*[{bundle}] |-bundle---138*[{bundle}] `-74*[{bundle}] // アクセスする # pstree sh---bundle-+-bundle---73*[{bundle}] |-bundle---49*[{bundle}] |-bundle---186*[{bundle}] |-bundle---106*[{bundle}] |-bundle---122*[{bundle}] |-bundle---98*[{bundle}] |-bundle---130*[{bundle}] |-bundle---138*[{bundle}] `-74*[{bundle}]
そうすると、スレッド数がもりもり増えていき、thread-maxに到達して、サーバーにアクセスできなくなります。
対応方法
reaping_frequency
を明示的に null ないしは 0で指定すればそもそもReaperはスレッドを作らなくなります。
さらに都度接続しているのであれば、そのリクエスト内でコネクションがおかしくなったらエラーになりますが、次のリクエストを処理するときは再接続するのでエラーが出続けることはないので、上記の設定でも問題ないと考えています。
参照
thread-maxの設定