Elastic APM:推測に頼らず、ソースコードを「内視鏡」のように分析してシステムのボトルネックを特定する

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

なぜPrometheusとGrafanaだけでは不十分なのか?

私のシステムでは以前、15台のサーバーを監視するためにPrometheus + Grafanaの構成を運用していました。ダッシュボード上ではCPU使用率は20%以下、RAMも余裕があり、すべてが正常(グリーン)に見えており、一見順調でした。しかしある日、ユーザーから「アプリの読み込みが異常に遅い」という苦情が届きました。その時、私は気づきました。Prometheusはシステムが「動いていること」は教えてくれますが、「なぜ特定のレクエストの処理に10秒もかかっているのか?」という問いには答えてくれないのです。

問題はどこにあるのでしょうか?コードの処理が遅いのか、SQLクエリにインデックスが不足しているのか、それともサードパーティAPIがタイムアウトしているのか?アプリケーションの内部を深く「内視鏡」のように調査するには、APM(Application Performance Monitoring)が必要です。

監視レイヤーの違いを理解する

多くのエンジニアがログ、メトリクス、トレーシングを混同しがちです。目的を誤らないよう、明確に区別しましょう:

1. ログによる監視 (Logging)

ELK StackやGraylogを使用してログを中央集約します。この手法は、すでにエラーが発生したことが分かっており、詳細な原因(スタックトレース)を探す際に役立ちます。しかし、100万件のリクエストのレスポンスタイムをログだけで測定するのは不可能です。

2. メトリクスによる監視 (Infrastructure Monitoring)

これがPrometheusの役割です。CPU、RAM、ネットワークなどのハードウェアの健全性に焦点を当てます。サーバーの増設時期は教えてくれますが、コード内部のロジックについては全く関知しません。

3. パフォーマンス監視 (APM – Tracing)

New Relic、Datadog、Elastic APMなどのツールは、コードにインストルメンテーション(計測用のコード埋め込み)を行います。リクエストの開始から、関数の通過、データベースへの問い合わせ、結果の返却までのジャーニーを記録します。これはユーザー体験を最適化するための最後の砦です。

Elastic APMを選んだ理由

New Relicを試した際、トラフィックの増加に伴い「目玉が飛び出るような」高額な請求が来たため、Elastic APMに切り替えました。以下は実際の使用感です:

  • メリット: すでにElasticsearchをログサーバーとして使用している場合、統合は非常にスムーズです。Distributed Tracing(分散トレーシング)をサポートしており、1つのリクエストが5〜7つの異なるマイクロサービスを通過する様子を追跡できます。どのサービスが遅延の「犯人」であるかが一目瞭然です。
  • デメリット: トレーシングデータは非常に重いです。適切に設定しないと、1日に数百GBのディスク容量を消費する可能性があります。また、エージェントはCPUに約1〜3%のオーバーヘッドを発生させますが、提供される価値を考えれば十分に許容範囲内です。

基本的な導入構成

このモデルは、連携して動作する4つのコンポーネントで構成されています:

  1. APM Agent: コードに埋め込むライブラリ(Python, Node.js, Goなど)。
  2. APM Server: エージェントからデータを受け取り、Elasticsearchに送る中継局。
  3. Elasticsearch: トレーシングデータを蓄積する巨大なストレージ。
  4. Kibana: エラーを分析し、グラフを表示するための可視化インターフェース。

実践的な導入ガイド

Dockerを使用してシステムを素早く構築し、Pythonアプリケーションに統合する例を示します。

ステップ1: DockerでElastic APMクラスターを起動する

最小限の構成で docker-compose.yml ファイルを作成します:

version: '3.8'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"

  kibana:
    image: docker.elastic.co/kibana/kibana:7.17.0
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch

  apm-server:
    image: docker.elastic.co/apm/apm-server:7.17.0
    ports:
      - "8200:8200"
    command: >
       apm-server -e
         -E apm-server.rum.enabled=true
         -E setup.kibana.host="kibana:5601"
         -E output.elasticsearch.hosts=["elasticsearch:9200"]
    depends_on:
      - elasticsearch
      - kibana

docker-compose up -d を実行し、サービスが完全に起動するまで1分ほど待ちます。

ステップ2: Pythonアプリケーションにエージェントを組み込む

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

pip install elastic-apm

Flaskアプリケーションの場合、数行の設定を追加するだけです:

from flask import Flask
from elasticapm.contrib.flask import ElasticAPM

app = Flask(__name__)
app.config['ELASTIC_APM'] = {
  'SERVICE_NAME': 'order-service',
  'SERVER_URL': 'http://localhost:8200',
  'ENVIRONMENT': 'production',
}

apm = ElasticAPM(app)

Kibanaでのデータ活用

トラフィックが流れ始めたら、Observability > APM セクションを開いてください。そこには「雄弁な数字」が表示されています:

1. レスポンスタイム (Latency)

p95とp99の指標に注目してください。もしp99が3秒であれば、1%の顧客が許容しがたい遅延を経験していることを意味します。これは開発チームがどのエンドポイントを優先的に最適化すべきかの判断基準になります。

2. エラー率 (Error Rate)

APMはログよりもスマートで、類似のエラーを自動的にグループ化(Group)します。DB接続エラーのログを1,000行眺める代わりに、発生頻度のグラフとともに1行のサマリーとして確認できます。

3. データベースクエリの追跡

これが最も価値のある機能です。遅延しているリクエストをクリックすると、Kibanaはウォーターフォール図を表示します。SELECT * FROM orders... というクエリに何ミリ秒かかったかが分かります。実際、私のチームはこの機能のおかげで、システム全体を低速化させていたインデックス不足のクエリを発見できました。

運用における「血の教訓」

実際のシステムでAPMを運用する際は、以下の3つの重要なポイントに注意してください:

  • サンプリングレート (Sampling Rate): 毎秒10,000リクエストがあるようなシステムでは、決して100%ログを取らないでください。transaction_sample_rate を 0.05(5%サンプリング)程度に調整しましょう。これにより、統計的な妥当性を保ちつつ、Elasticsearchへの負荷を軽減できます。
  • セキュリティ: APM Serverには必ず secret_token を設定してください。そうしないと、外部から誰でもゴミデータを送り込むことができ、ディスク容量を使い果たされてしまいます。
  • アラート設定 (Alerting): ダッシュボードをチェックするのを待つのではなく、エラー率が5分間連続で2%を超えた場合にTelegram経由で通知が飛ぶように設定しましょう。

Elastic APMを使いこなすことは、単にツールをインストールすることではなく、データに基づいた最適化의 文化を築くことです。導入から3ヶ月後、私たちのチームは平均レスポンスタイムを40%削減し、「原因不明の遅延」を完全に排除することができました。

Share: