メモリ 32GB を使い切って停止していた API サーバーの正常化
背景
入社して4か月ほど経った頃のことでした。マシンごとのメモリ使用量が、不自然なほどばらばらでした。あるマシンは20GB 以上を使い、あるマシンは1GB ほどしか使っていませんでした。そして、ほぼすべてのマシンが4〜5日ほど経つと強制終了され、再起動していました。
原因を探す
最初は、再起動のルールがあるのだと思っていました。他チームのバックエンドサーバーは、実際に週に一度マシンを再起動していると聞きました。しかし、インスタンスがメモリを20GB も使うのは、再起動のルールとは関係なく、何かがおかしい状態でした。
数日間モニタリングした結果、API サーバーのどこかでメモリリークが起きてインスタンスのメモリを使い切り、最後に OOM で強制終了されていることがわかりました。ところが Sentry にも AWS のモニタリングツールにも原因になりそうな痕跡が残っておらず、デバッグは難航しました。
メモリダンプを取る
チームリーダーから、本番サーバーのメモリダンプを取って調べてみようというアイデアをもらいました。正直、気が進みませんでした。ダンプを取っている間にサービスに障害が出るかもしれず、ダンプを持ってきても読み方がわからないので、意味のある結果を見つけるのは難しそうでした。それでも他に方法がなく、ひとまずやってみることにしました。
調べてみると、メモリダンプのツールがいくつかありました。チームリーダーに共有し、本番サーバーに直接アクセスして、2つのツールでメモリをスキャンしました。1つ目のツールでは何も得られませんでしたが、2つ目のツールで、同じエラーが何度も発生したと思われるダンプのログを見つけました。
原因と解決
原因は、アプリのプッシュ送信を並列化したコードにありました。並列処理のためにスレッドを作るのですが、スレッドの中で例外が発生すると、そのスレッドが回収されずにそのまま残り、メモリリークが積み重なっていました。スレッド内部の処理ロジックに try/catch を付けてデプロイすると、それ以降すべてのインスタンスが1GB ほどしか使わない正常な状態に戻りました。
最初は解決策がまったく思い浮かばず、ストレスを感じていました。結局、自分で開いて中をのぞいてみれば、答えは思ったより近くにあるということを学んだ作業でした。