CPU 使用率の罠:平均指標がパフォーマンスの殺し屋を隠す理由

多くのエンジニアにとって、システムが遅くなると最初に思い浮かぶのは CPU 使用率グラフを確認することです。ラインが 40% や 60% 前後であれば、CPU がボトルネックではないという結論にすぐたどり着きます。しかし、平均使用率に依存することは危険な罠となり得ます。特に Kubernetes のようなコンテナ化環境では注意が必要です。

本番環境の Go 関数が context deadline exceeded で失敗し始めても、ダッシュボードでは CPU が健全に見える場合、総容量が不足しているわけではなく、Linux カーネルがリソース制限を適用する細かな仕組みが原因である可能性が高いです。

平均の誤謬

平均的な CPU 使用率はコスト指標であり、パフォーマンス指標ではありません。「実際に使う以上の費用を支払っているか?」という質問には答えますが、「アプリケーションは現在必要な CPU 時間を確保できているか?」という質問には答えません。

レイテンシに敏感なワークロードでは、使用率と待ち時間の関係は非線形です。M/M/1 キューイングモデルに基づくと、使用率が 80% から 81% に上がると、10% から 11% に上がる場合に比べて待ち時間がはるかに大きく増加します。

CPU 使用率 10 ms リクエストの待ち時間
10% ~11 ms
80% ~50 ms
95% ~200 ms

使用率が上がるにつれて「余裕」はなくなり、CPU が 100% に達する前にレイテンシが急上昇します。しかし、これだけでは最も陰険な問題である「見えない」スロットリングを説明できません。

抽象化の漏れ:CFS スロットリング

Docker と Kubernetes では、CPU 制限は Completely Fair Scheduler (CFS) のクオータシステムで実施されます。2000m(2 vCPU)という制限を設定しても、カーネルはコンテナを 2 コアに厳密に制限するわけではなく、スケジューリング期間(通常 100ms)ごとに 時間予算 を付与します。

ここが抽象化の漏れです。コンテナはホストノード上の すべて の利用可能なコアに対して、200ms の予算全体を使い切ることができます。ホストに 4 コアがある場合、リソース集中的なリクエストは 4 コアすべてにバーストし、壁時計時間でたった 50ms で 200ms の予算を使い果たすことがあります。

予算が尽きると、コンテナは スロットル されます。次の 100ms 期間が始まるまで完全に凍結されます。1 分間で CPU を平均化する監視ツールから見ると使用率は低く見えますが、51ms 時点で到着したリクエストにとっては、次の 49ms はシステムが全く応答しません。

この結果、p99 のレイテンシが急上昇する一方で、平均 CPU 使用率は見かけ上低く保たれるパターンが生まれます。この現象は Indeed Engineering によって有名に記録されており、割り当てられたコア数よりはるかに低い使用率でも、ほとんどの 100ms 期間でスロットリングが発生していました。

飢餓状態の検出と緩和方法

標準的なダッシュボードはこれらのバーストを隠すため、真実を見つけるにはカーネルレベルの指標を確認する必要があります。

1. Cgroup 統計の確認

スロットリングされているかどうかを直接確認する最も簡単な方法は、/sys/fs/cgroup/cpu.stat をチェックすることです。以下を探します:

  • nr_throttled:コンテナがスロットリングされた回数。
  • throttled_usec:コンテナが凍結された合計時間。

これらのカウンタが増加している場合、リソース制限がバーストパターンに対して厳しすぎます。

2. Pressure Stall Information (PSI) の監視

カーネル PSI(cpu.pressure)は飽和のシグナルを提供します。タスクが実行可能だったが実行できなかった時間の割合を報告し、CFS クオータ内にいても競合を検出します。

3. スティールタイムの監視

仮想化環境では「スティールタイム」(top%st)を確認してください。ハイパーバイザーが別のテナントに物理 CPU を割り当てるために VM から CPU を奪うと、内部制限に関係なくコードが停止します。

4. アプリケーションレベルの検出

最も堅牢な解決策は、アプリケーション内部で飢餓状態を検出することです。Redpanda(「reactor stalls」)や CockroachDB のような高性能システムは、goroutine が実行可能になるまでと実際に実行されるまでの時間を監視します。このレイテンシが閾値(例:1ms)を超えると、アプリケーションはバックグラウンド作業を削減してフォアグラウンドリクエストを優先させることができます。

グラフを超えて

開発者と IT/運用部門の間にはしばしば緊張があります。開発者がレイテンシスパイクを解消するために CPU の増強を求めても、管理者は 40% の使用率グラフを根拠に「効率」や厳格な制限を義務付けるコンプライアンスガイドに基づいて要求を却下することがあります。

この行き詰まりを打破するには、会話の焦点を 利用率 から 飽和度 にシフトする必要があります。プロダクションシステムの目的は CPU 使用率を最大化することではなく、アプリケーションがスムーズに動作することです。スロットリング指標と PSI を平均利用率と共に可視化することで、チームは誤解を招く平均ではなく実際のパフォーマンスに基づいた判断ができるようになります。

あるコミュニティ貢献者が指摘したように、問題は指標そのものではなく解釈にあります。「穴は永遠に続き、指標は正しく解釈できなければ嘘をつくのです。」

Sources