CubicLouve

Spring_MTの技術ブログ

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 は廃止されたそうです。

qiita.com

git.kernel.org

原因

下記のPRにより、 reaping_frequency が設定されていないとデフォルトの60秒が設定されるようになりました。

github.com

これはReaperがDBへの接続がハングしているか死んでいるかをチェックする間隔になります。

api.rubyonrails.org

Reaperは別スレッドを立てて定期的に起き上がって確認する実装になっています。

Reaperは、establish_connectionが実行され、コネクションプールが生成されるたびにスレッドを作成します。

がこのスレッドは特にkillもされずに残る実装になっていそうです。

https://github.com/rails/rails/blob/1f9e308695556aae16872b54d9a0f0e1d7b3d055/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L359

2020/08/31時点のedgeを見ると、ちゃんと止める実装も入っている気がします。

https://github.com/rails/rails/blob/1f9e308695556aae16872b54d9a0f0e1d7b3d055/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L331-L356

で、activerecord-refresh_connectionなどを使って都度接続などを行っていると、コネクションを貼るたびにスレッドが作られ増え続けます。

github.com

スレッドが増える様子は 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の設定

https://github.com/torvalds/linux/blob/7c2a69f610e64c8dec6a06a66e721f4ce1dd783a/Documentation/admin-guide/sysctl/kernel.rst#threads-max