【ITニュース解説】The OOM killer stopped my acceptance check four times in two days. It was rebuilding the project to read a number.
2026年09月15日に「Dev.to」が公開したITニュース「The OOM killer stopped my acceptance check four times in two days. It was rebuilding the project to read a number.」について初心者にもわかりやすく解説しています。
ITニュース概要
受け入れチェックがメモリ不足で頻繁に停止。原因は、「読み込み」という名の処理がプロジェクトを毎回再ビルドしていたこと。既に記録された情報を無駄に作り直していた。観察するだけの処理が重い場合、既存情報を効率的に活用し、無駄な再生成を避けるべきだ。
ITニュース解説
システム開発の現場では、コードの品質を保つために様々なチェックやテストが自動で行われる。今回、あるプロジェクトで「acceptance check(承認チェック)」という重要なプロセスが、立て続けに停止するという問題が発生した。これは、プロジェクトの各部分(ユニット)がきちんと開発基準を満たしているかを確認する最終段階のステップで、テストの合否、コードのチェック、ドキュメントの更新状況などを確認し、その結果を一行ずつ出力するものだった。
しかし、この承認チェックが2日間で4回も「killed」というメッセージとともに強制終了してしまった。エラーメッセージも、どこでプログラムが壊れたかを示す情報(スタックトレース)も一切なく、ただ突然停止する。これは、システムがメモリ不足になった際に、強制的にプログラムを終了させる「OOM killer(Out Of Memory killer)」と呼ばれる機能が働いたためだった。つまり、コンピュータのメモリが足りなくなり、処理を続けられなくなったのである。著者は当初、メモリの限界に達したことが原因だと考え、並列処理の数を減らすなどの対策を試みたが、状況は改善しなかった。
4回目の強制終了の後、著者はようやく「このステップが一体何をしているのか」を詳しく調べてみた。そして、驚くべき事実が判明する。これまで「read(読み取り)」という名前から、単に既存の結果を集めて表示するだけの軽い処理だと思い込んでいたこのステップが、実際にはプロジェクトの「ビルド(再構築)」を繰り返していたのだ。プロジェクトには15個のユニットがあり、この承認チェックは、それぞれのユニットに対して「ビルド」を行い、そのビルドが成功したかどうかを確認していた。
「ビルド」とは、ソースコードと呼ばれる人間が書いたプログラムの指示を、コンピュータが直接実行できる形に変換する、非常に時間とメモリを消費する重い処理だ。プロジェクト全体のビルドは、リポジトリ(コードの保管場所)の中でも最もコストがかかる操作の一つで、通常は一台のコンピュータで一つずつ実行するように設計されている。ところが、この承認チェックは、15個のユニットに対してそれぞれフルビルドを順番に実行していたため、一台のコンピュータが耐えられるメモリ容量をはるかに超え、OOM killerが何度も作動する結果となっていた。
この設計の根本的な問題は、「read(読み取り)」という名前と、実際の処理内容が全く異なっていたことにある。なぜこのような非効率な設計になってしまったのか。著者は、過去の設計者が「記録された結果は過去のものであり、現在の状態を知るには再実行するしかない」と考えていた可能性を指摘する。一見、この考え方は「今、本当に正しいか」を確認するための厳密な方法のように思えるかもしれない。
しかし、このプロジェクトの「control run(制御実行)」という別のステップでは、既に実行時の「コミット(コードの変更履歴を識別するID)」と、その時点での詳細な実行結果(出力)を一つのブロックとして永続的に記録する仕組みがあった。つまり、「このコミットで、テストはパスしたか?」という質問に対する答えは、既に記録として存在しており、それをただ読み取ればよかったのだ。
ここで重要なのは、二つの異なる「問い」があることだった。一つは「今、このコードベースは有効か?」という問いで、これには実際にコードを実行して、その結果を新しい記録として残す(control runが担当する)ことが適切だ。もう一つは「特定のコミットの時点で、control runは何と言っていたか?」という問いで、これには既存の記録を読み取ること(acceptance readが担当すべき)が正しい方法だった。しかし、承認チェックは後者の問いに答えるべきなのに、前者の問いに対する方法(ビルド)を実行してしまっていたため、制御実行の作業を重複させ、無駄なメモリ消費を引き起こしていたのである。
また、「記録が古いコミットのもので、現在のコードとずれているのではないか?」という「陳腐化(staleness)」に関する懸念も重要だが、これもビルドをせずに解決できる。承認チェックの際に、記録ブロックに保存されているコミットIDと、現在承認しようとしている最新のコミットIDを比較するだけでよい。もし両者が異なれば、そのユニットは陳腐化していると判断し、その旨を数字で示すようにすれば十分だ。高コストなビルドを再実行することで「新鮮さ」を得ようとしていたが、実際には二つのIDを比較するだけで簡単に確認できる情報だったのだ。コンパイラや大量のメモリを消費する必要は全くなかった。
この経験から、著者はいくつかの重要な教訓を得た。まず、もし何らかのステップが「read(読み取り)」「check(確認)」「report(報告)」「gather(収集)」「audit(監査)」といった観察や収集を表す動詞の名前を持ちながら、実行に数分かかり、ギガバイト単位のメモリを消費するようであれば、そのステップは「観察」ではなく、本来どこかに存在するはずの「事実を再生成している」可能性が高いということだ。観察や収集のコストは、どれだけの情報を観察・収集するかという量に比例すべきであり、その情報を作るためのコストに比例すべきではない。リソースの使用量がそのステップの名前の動詞と一致しない場合、それは設計上の隠れた問題を示唆している。
そして、「事実を生成する」ことと「事実を収集する」ことを明確に分離することが非常に重要であると認識した。これは単なるコードの整理整しではなく、収集ステップを軽量化し、開発プロセスのあらゆる節目で頻繁に実行できるようにするための根本的な改善なのだ。もし収集ステップが重すぎると、誰もが毎日実行を避け、一日に一度だけ、それも誰かが意を決して実行するような状況に陥ってしまう。
最後に、システムエンジニアとして忘れがちな重要な教訓として、「あるプロセスが繰り返し強制終了される場合、再起動を繰り返す前に、そのプロセスが実際に何をしているのかを調べるべきだ」という点を挙げている。著者は問題が起きた際、まず3回再起動し、さらに2回も並列処理の数を減らすという、リソース不足に対する一般的な対処法を試みた。しかし、根本原因の特定は、そのステップの実際の動作を調べたわずか2分後に判明した。これは、目の前の現象(メモリ不足)にとらわれず、その現象を引き起こしている真の原因(不必要なビルドの繰り返し)を探求することの重要性を示している。
これは、システム開発におけるパフォーマンス問題やリソース最適化の典型的な事例であり、見た目の現象だけに囚われず、システムの内部動作を深く理解することの大切さを教えてくれる。