Grafana Pyroscope:本番環境でCPUとメモリを浪費する「犯人」を特定する

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

突然のリソーススパイクという悪夢

想像してみてください。午前2時、システムのCPU使用率が98%に達したというアラートが届きます。急いでPCを開き、サーバーにSSH接続してtopコマンドを叩きます。しかし皮肉なことに、何事もなかったかのように使用率は通常の10%に戻っています。さらに厄介なのはメモリリーク(Memory Leak)です。毎日少しずつメモリが増え続け、最終的にOOM Killerがプロセスを強制終了し、無意味なログだけが残ります。

以前は、Goにはpprof、Pythonにはpy-spyを使ってパフォーマンスのスナップショット(snapshot)を取得していました。しかし、この方法は「その瞬間」しか捉えられないため、運任せな部分があります。**Grafana Pyroscope**を導入して6か月、これがMetrics、Logs、Tracesに並ぶ、オブザーバビリティ(Observability)を完成させるための欠けていたパズルのピースだと確信しました。

一般的なプロファイリング手法の比較

Pyroscopeの価値を理解するために、私がこれまで経験してきた3つのアプローチを振り返ってみましょう。

1. 手動プロファイリング (Manual Profiling)

障害が発生した瞬間にプロファイルファイルを出力するコマンドを実行し、ローカルにダウンロードして分析する必要があります。無料ですが、非常に手間がかかります。障害が数十秒しか続かない場合、「絶好のタイミング」を逃しやすくなります。

2. 高価なAPM (Datadog, New Relic)

これらのツールは非常にスムーズな継続的プロファイリング機能を備えています。しかし、コストが大きな壁となります。20〜50のマイクロサービスを持つスタートアップにとって、Datadogからの請求額を見ると、機能をオフにしたくなるほどです。

3. Grafana Pyroscope:バランスの取れた選択肢

これはコストと効率の両面で最適なアプローチです。Pyroscopeは、極めて低いレイテンシでアプリケーションのスタックトレース(Stack Traces)を継続的に収集します。データは圧縮され、リアルタイムで保存されます。これにより、過去の任意の時点に「タイムトラベル」して、どの関数が最もリソースを消費していたかを正確に確認できます。

なぜPyroscopeをスタックに加えるべきなのか?

長期間の実戦投入を経て、3つの大きな利点をまとめました:

  • 超軽量なオーバーヘッド: 実際の運用では、PyroscopeのCPU負荷はアプリケーションの約1〜2%程度です。本番環境で24時間365日稼働させても、システムの遅延を心配する必要はありません。
  • 直感的なフレームグラフ (Flame Graph): 無機質なログを読む代わりに、「炎のようなグラフ」を見ることができます。ブロックの幅が広いほど、その関数のリソース消費量が多いことを示します。自分のロジックに問題があるのか、外部ライブラリに問題があるのかが一目でわかります。
  • Grafanaエコシステム: Metrics (Prometheus)、Logs (Loki)、Profiles (Pyroscope)を一つのGrafanaダッシュボードに集約することで、調査速度が劇的に向上します。

注意点: プロファイルデータの保存には、それなりのディスク容量が必要です。調査の必要性とストレージコストのバランスを考え、7〜14日程度の保存期間(Retention Policy)を設定することをお勧めします。

3ステップでクイック導入

以下は、Dockerを使ってPyroscope環境を素早く構築し、試してみる方法です。

ステップ1:サーバーの起動

以下のシンプルな内容でdocker-compose.yamlファイルを作成します。

version: '3.9'
services:
  pyroscope:
    image: grafana/pyroscope:latest
    ports:
      - "4040:4040"
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin

docker-compose up -dを実行して起動します。収集サーバーはポート4040で待機します。

ステップ2:コードへの組み込み (Python)

エージェントライブラリをインストールします:

pip install pyroscope-io

メインの実行ファイルに以下の初期化コードを追加します:

import pyroscope

pyroscope.configure(
    application_name    = "api-service-prod",
    server_address      = "http://localhost:4040",
    tags = {"env": "production", "version": "1.2.0"}
)

# 重い処理のシミュレーション
def process_data():
    return [x**2 for x in range(1000000)]

if __name__ == "__main__":
    while True:
        process_data()

ステップ3:結果を確認

Grafana(ポート3000)にアクセスし、データソースとしてPyroscope(URL: http://pyroscope:4040)を追加します。**Explore**メニューを開くと、赤やオレンジのブロックが表示されます。これがアプリケーションのリソース消費の全貌です。

実践経験:フレームグラフを迷わずに読むコツ

初めて使うときは、数百もの色のついたブロックに圧倒されるかもしれません。2つの黄金律を覚えておいてください:

  1. 幅に注目する: 関数の深さは気にせず、横幅が最も広いものから優先的に最適化してください。
  2. Diff Viewを活用する: これは最も価値のある機能です。正常時と異常時の状態を比較できます。Pyroscopeは、消費量が急増したコード領域を赤くハイライトしてくれます。

アラート疲れ(Alert Fatigue)に陥らないために

私が最初に犯した最大のミスは、Pyroscopeから直接アラート(Alert)を設定したことでした。その結果、数秒間CPUを消費するクローンジョブ(Cronjob)が走るたびに、Telegramに通知が鳴り続けることになりました。

アドバイス: 全体的なCPU/RAMの閾値監視にはPrometheusを使用しましょう。システム負荷のアラートを受け取った後、Pyroscopeを使って「現場検証」を行うのが正解です。Pyroscopeは詳細な調査(デバッグ)ツールであり、第一段階のアラート層ではありません。

まとめ

継続的プロファイリング(Continuous Profiling)は、もはや大企業だけの贅沢品ではありません。Grafana Pyroscopeがあれば、潜在的なパフォーマンスの問題を心配することなく、自信を持って新しいコードをデプロイできます。ぜひ週末にでもセットアップしてみてください。ログだけでは決して分からなかった、コード内の意外な「非効率」が見つかるはずです。

Share: