ウェブエンジニア問題集

エラーとデバッグ — Segfault・StackOverflow・OOM・デッドロック

エラーメッセージを読むと、これまでの章で学んだCS基礎の単語がそのまま登場します。「stack size exceeded」「out of memory」「connection refused」などはすべて、コンピュータの仕組みを知っていればどこで何が起きているか を想像できる言葉です。

この本の最後の章では、実務でよく遭遇する代表的なエラーを、原因と対処 がセットで頭に入る形で整理します。

エラーが起きている「場所」を先に見分ける

Webアプリのエラーは、おおまかにどの層で起きたか を切り分けるところから始めます。

どの層で起きているかで読み解き方と直し方が違う ので、エラーメッセージを見たら最初に「これはどの層の話だ?」と分類する癖をつけます。

Segmentation Fault — 触ってはいけないメモリを触った

Segmentation fault(通称 segfault / セグフォ)は、プログラムがアクセスを許可されていないメモリ領域 にアクセスしようとしたときに発生します。

$ ./my_program
Segmentation fault (core dumped)

典型的な原因は以下です。

  • NULLポインタの参照 — 初期化していないポインタを使った
  • 解放済みメモリへのアクセスfree() 済みのメモリを使った
  • 配列の範囲外アクセス — 境界を超えたインデックス

CやC++のような手動でメモリ管理する言語 でよく出ます。JavaScriptやPythonでは、ランタイムがガベージコレクションで守ってくれるので、直接遭遇するのはネイティブモジュールの不具合やランタイム自体のバグ がほとんどです。

StackOverflow — コールスタックが溢れた

StackOverflowError は、コールスタック が上限を超えたときに発生します。第2章のスタック(LIFO)がそのままイメージの中心です。

最も多いのは終了条件のない再帰呼び出し です。

function countdown(n) {
  return countdown(n - 1);
}
countdown(10);
// → RangeError: Maximum call stack size exceeded

直し方はシンプルで、ベースケース(終了条件)を書く だけです。

function countdown(n) {
  if (n <= 0) return;
  console.log(n);
  return countdown(n - 1);
}

「再帰で書いたら StackOverflow が出た」というときは、たいてい終了条件が想定外のパスで発動しない(配列が空のとき、null が来たとき、循環参照のデータが来たとき)のが原因です。

Out of Memory (OOM) — メモリが尽きた

OOMエラーは、プログラムが使用可能なメモリを使い切った ときに発生します。

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed
  - JavaScript heap out of memory

主な原因は3つです。

  • メモリリーク — 不要になったオブジェクトへの参照が残り続け、GCで回収されない
  • 巨大データの一括読み込み — 数GBのファイルやDB結果をそのままメモリに載せた
  • 無限に溜め込む処理 — ループの中で配列に追加し続ける、キャッシュに期限を設定しない、など

メモリリークの典型

const cache = [];
 
function handleRequest(req) {
  cache.push(req);
  // 取り出す側がいない → 永遠に増える
}

こうしたコードは、ローカルでは動くが本番で数時間〜数日後に落ちる という質の悪いバグになります。

対処の基本

「メモリを増やす」は最後の手段 です。根本の原因(リーク・設計)を放置すると、増やした分を消費するまでの時間が延びるだけで、いずれ再発します。

  • Node.js なら --max-old-space-size=4096 のような指定で一時的に回避
  • 大きなファイル はストリームで流す(fs.createReadStream
  • DB結果 はページングやカーソルで分割する
  • キャッシュ はTTLや上限サイズを設ける(LRUキャッシュなど)

デッドロック — 互いのロックを待ち続けて止まる

デッドロック は、2つ以上のスレッドが互いの持っているリソースを待ち続けて、どちらも進めなくなる 状態です。

デッドロックが発生するには、以下の4条件がすべて同時に満たされる 必要があります。

  1. 相互排他 — リソースは一度に1つしか使えない
  2. 保持と待機 — あるリソースを持ったまま、別のリソースを待つ
  3. 横取り不可 — 他のスレッドから強制的に奪えない
  4. 循環待ち — A→B→A のように待ちが環状になっている

4つめの 循環待ち を崩すのが最も実践的です。ロックを取る順番をシステム全体で統一 すれば、循環ができなくなります。

Web開発で直接スレッドを扱うことは少ないですが、DBのトランザクション では普通に発生します。「同じ順序で行をUPDATEする 」「トランザクションを短く保つ 」「タイムアウトを設定する」というのが基本の対策です。

Connection Refused — 接続先が受け付けてくれない

Connection refused は、クライアントがサーバーに接続しようとしたが、明示的に拒否された エラーです。

Error: connect ECONNREFUSED 127.0.0.1:3000

原因は多くの場合、次のいずれかです。

  • サーバーが起動していない — 本命。まずはプロセスが動いているか確認
  • ポート番号が違う.envDB_PORTAPI_URL を確認
  • バインドしているアドレスが違う0.0.0.0 でなく 127.0.0.1 で起動していてコンテナ外から見えない、など
  • ファイアウォール / セキュリティグループで遮断 — AWSなどで SG のインバウンドルールが足りていない

切り分けの順序は、前章でも触れたとおり pingnslookupcurl -vどの層で失敗するか を確認する流れが最短です。

エラーに向き合うときの型

どんなエラーでも、対処の基本姿勢は共通です。

  1. エラーメッセージを最後まで読む スタックトレースの最下層 ではなく、自分のコードに最も近い行 を探します
  2. 再現条件を特定する 必ず出るのか、特定の入力だけで出るのか、時間が経つと出るのか
  3. 直前の変更を疑う git loggit diff で、エラーが出始めたタイミングの変更を確認
  4. そのままメッセージで検索 固有の値(ファイルパス、ID等)を消した形で検索すると、同じエラーに遭った人の記事が見つかります
  5. 最小再現コードを作る 他人に聞く前の最後の一手。たいていの場合、この過程で自分で原因に気づきます

ちゃんと使うためのポイント

  • エラーを見たら、まずどの層で起きたか(フロント / アプリ / ランタイム / インフラ)を切り分ける
  • Segfault は主にCやネイティブの世界。NULL・解放済みメモリ・配列外アクセス が三大原因
  • StackOverflow は再帰の ベースケース漏れ を疑う
  • OOM はメモリを増やす前にリークか巨大データ処理 を疑う。ストリームとページングが対処の基本
  • デッドロック はDBトランザクションで身近に起きる。ロック順を揃える・短く終わらせる
  • Connection refused はまずプロセス起動・ポート・バインドアドレス を確認

最後に

この本で扱ったのは以下の内容でした。

  1. コンピュータの仕組み — CPU・メモリ階層・プロセス/スレッド・仮想メモリ
  2. データ構造 — 配列・スタック・キュー・連結リスト・ハッシュテーブル
  3. アルゴリズムと計算量 — Big-O・線形/二分探索・代表的なソート
  4. ネットワーク基礎 — IP・DNS・TCP/UDP・HTTP
  5. エラーとデバッグ — Segfault・StackOverflow・OOM・デッドロック・Connection refused

これらはフレームワークが変わっても、言語が変わっても、残り続ける土台の知識 です。明日すぐ書くコードを変えるわけではないかもしれませんが、パフォーマンスの勘所、障害対応、アーキテクチャ選定 の判断力として、長く効いてきます。

全部を完璧に覚える必要はありません。「この単語を見たことがある」「何を調べればいいか分かる」 という状態になっていれば、実務でハマったときに一気に調べ進められるようになります。

あとは、ウェブエンジニア問題集のクイズで繰り返し確認して、知識を定着させていきましょう。