Erlang アプリケーションの CPU 使用率が 200% を超える場合、ボトルネックは BEAM VM ランタイムの深部に隠れていることがよくあります。OpenResty XRay で実行中の Erlang プロセスをプロファイリングしたところ、Erlang の高 CPU 使用率の原因が check_resp_content 内の PCRE 正規表現バックトラッキング、具体的には Erlang ランタイムで PCRE ライブラリをラップする C 関数 erts_pcre_exec であることを特定しました。コード変更、再コンパイル、再起動は一切不要でした。

本ウォークスルーでは、top で CPU 使用率 200% 超の Erlang プロセスを観測するところから、Erlang レベルおよび C レベルのフレームグラフ分析を経て、原因となるソースファイルと行番号を正確に特定するまでの、完全なプロファイリングプロセスを示します。

Erlang プロセスの 200% 超 CPU 使用率の観測

Erlang の高 CPU 使用率は、通常 tophtop の出力で BEAM VM プロセスのビジー状態として現れます。プロセス名は beam.smp であることが多いですが、今回のケースのように、起動した escript の名前が表示される場合もあります。いずれにしても、重要なのはどの Erlang コードパスが原因かということです。

top コマンドを実行して、CPU 使用率を確認します。

Erlang プロセスが 200% 超の CPU を消費していることを示す top コマンド

このプロセスが CPU コアの 200% 以上を消費していることがわかります。

Erlang プロセスの CPU 使用率が 200% 超の詳細

このプロセスは rebar3 という名前で、Erlang プロジェクトを管理およびビルドするためのツールです。ここではプロジェクトの起動に使用されています。

rebar3 プロセス名を示すプロセスリスト

ps コマンドを実行して、このプロセスの詳細を確認します。

rebar3 Erlang プロセスの完全なコマンドラインを表示する ps コマンド

この rebar3 は Linux ディストリビューション標準の実行ファイルです。このプログラムは当然、標準のディストリビューションに付属の Erlang でコンパイルされています。

標準の rebar3 バイナリと Erlang ディストリビューションを確認する ps 出力

OpenResty XRay による Erlang CPU 使用率のプロファイリング

OpenResty XRay は、この未変更の Erlang プロセスを Erlang 言語レベルと BEAM VM の C レベルの両方でリアルタイムに分析できます。対象アプリケーションにモジュールやプラグインをインストールする必要はありません。Web コンソールで「Guided Analysis」に移動し、問題タイプとして「High CPU Usage」を選択します。次に、対象マシン上の Erlang アプリケーションを選択します。

検出されたアプリケーションリストから Erlang アプリケーションを選択

CPU コアを 200% 近く消費しているプロセスを選択します。これは先ほど top で確認したものです。

分析対象として CPU 使用率 200% の Erlang プロセスを選択

OpenResty XRay は複数の言語レベルを同時に分析できます。ここでは Erlang と C/C++ の両方を選択したままにし、Erlang コードレベルと BEAM VM 内部の両方でホットスポットを確認します。最大分析時間はデフォルトの 300 秒のままにします。

デュアルレベル分析のために Erlang と C/C++ 言語レベルを選択

分析を開始すると、システムは複数回の分析を継続的に実行します。この例では 2 回で十分です。

自動生成された分析レポートが表示されます。

自動生成された CPU 分析レポート

Erlang レベル CPU フレームグラフ:check_resp_content における正規表現

最もホットな Erlang コードパス(第 1 位)を確認します。

check_resp_content が最大の CPU 消費元であることを示す最もホットな Erlang コードパス

check_resp_content 関数は、レスポンスのコンテンツをチェックするための業務ロジック関数です。

Erlang CPU レポートでハイライトされた check_resp_content 関数

この関数は lists モジュールの filter 関数を呼び出してリストをフィルタリングしています。

コードパスに表示された check_resp_content 内の lists:filter 呼び出し

フィルタリング条件は無名関数で、この無名関数内で正規表現マッチングを使用しています。これらの呼び出しはすべて check_resp_content 関数内で発生しています。

lists:filter 内の re:run による正規表現マッチングを含む無名関数

「More」をクリックして、この最もホットなコードパスの詳細を表示します。

コードパスの詳細を展開して確認

このコードパスは、Erlang レベルの CPU フレームグラフから自動的に導き出されたものです。

check_resp_content の完全なコールスタックを示す Erlang レベル CPU フレームグラフ

先ほどのコードパスのより詳細な説明です。

関数の説明を含む Erlang コードパスの詳細説明

先ほどのホットコードパスに戻ります。最初の関数の緑枠にマウスカーソルを合わせると、ツールチップにソースファイルのパスが表示されます。

check_resp_content のソースファイルパスを表示するツールチップ

Erlang ソースコードの行番号は 15 です。

ホットスポット関数のソース行番号 15 の表示

アイコンをクリックしてソースファイルのパスをコピーします。

OpenResty XRay レポートから Erlang ソースファイルパスをコピー

Vim エディタを使用して Erlang ソースファイルを開きます。任意のエディタをご使用いただけます。

ホットスポットを調査するために Erlang ソースファイルを vim で開く

OpenResty XRay の提案に従って、15 行目に移動します。

Erlang ソースファイルの 15 行目に移動

これは re モジュールの run 関数で、PCRE 正規表現エンジンへの標準的な Erlang インターフェースです。

15 行目の re:run 関数呼び出しによる PCRE 正規表現マッチング

これは先ほどレポートで確認した lists:filter 関数呼び出しです。

フレームグラフの結果と一致するソースコード内の lists:filter 関数呼び出し

このコードは確かに check_resp_content 関数内にあります。正規表現を最適化して高コストのバックトラッキング操作を回避するか、バックトラッキングを行わない正規表現エンジンの使用を検討できます。

正規表現ホットスポットの位置を確認する check_resp_content 関数本体

上位 2 つの Erlang コードパスは類似しており、どちらも正規表現マッチングを実行しています。

レポート内の 2 番目に最もホットな Erlang コードパスも正規表現マッチングを表示

3 番目の Erlang コードパスは、マッチング前に入力文字列の長さを計算しています。

iolist サイズ計算を示す 3 番目の Erlang コードパス

iolist はマッチング対象の入力文字列で、erts_iolist_size は PCRE エンジンに渡す前に文字列の長さを計算しています。

入力 iolist の長さを計算する erts_iolist_size

Content 変数がマッチング対象の入力文字列です。

正規表現マッチングの入力文字列としての Content 変数

C レベル分析:erts_pcre_exec と PCRE バックトラッキング

Web コンソールに戻ります。C レベル分析は BEAM VM 内部のパフォーマンスホットスポットを明らかにし、CPU ボトルネックが PCRE ライブラリの深部にまで達していることを確認します。

最もホットな C 関数として PCRE match を示す C レベルコードパス

PCRE ライブラリの match 関数が、実際の正規表現マッチングを実行しています。

末端の CPU 消費元として特定された PCRE match 関数

2 番目の関数 erts_pcre_exec は、Erlang ランタイムによる PCRE ライブラリの pcre_exec 関数のラッパーです。

BEAM VM ランタイムで pcre_exec をラップする erts_pcre_exec

Erlang の re モジュールの re_run 関数が、正規表現の実行に使用されています。

正規表現実行のために erts_pcre_exec を呼び出す re_run

この 16 進数アドレスは、このコードパスが JIT コンパイルされた Erlang コード内で実行されていることを示しています。

JIT コンパイルされた Erlang コードを示すコールスタック内の 16 進数アドレス

BEAM VM における PCRE 正規表現バックトラッキングが高コストな理由

C レベルのフレームグラフにより、CPU 時間が erts_pcre_exec(BEAM VM に組み込まれた PCRE ライブラリへのバインディング)内で消費されていることが確認されました。Erlang の re モジュールはすべての正規表現処理を PCRE に委譲しており、PCRE はバックトラッキング NFA エンジンを使用しています。パターンに .*.+、ネストされた選択肢などの量指定子が含まれている場合、エンジンはマッチが存在しないと結論づける前に、指数的な数のマッチパスを探索する可能性があります。これは壊滅的バックトラッキングとして知られています。

このような PCRE 正規表現のボトルネックを修正するには、正規表現エンジンによる高コストのバックトラッキング操作を回避するよう正規表現を最適化するか、バックトラッキングを行わない正規表現エンジンの使用を検討できます。

自動監視とレポート

本番環境のワークロードでは、OpenResty XRay が Erlang プロセスを継続的に監視し、Insights ページで日次および週次のレポートを自動生成します。手動のガイド付き分析は不要です。

日次・週次の自動分析レポートを表示する Insights ページ

FAQ:Erlang 高 CPU 使用率

Erlang プロセスの CPU 使用率が非常に高いのはなぜですか?

Erlang の高 CPU 使用率の一般的な原因は、ホットコードパスにおける高コストな操作です。正規表現マッチング、ガベージコレクションの負荷、非効率なリスト処理などが該当します。今回のケースでは、OpenResty XRay が CPU ボトルネックを check_resp_content 関数内の PCRE 正規表現バックトラッキングまで追跡しました。この関数では lists:filter/2 ループ内の各要素に対して re:run/2 が呼び出されていました。Erlang レベルと C レベル両方の CPU フレームグラフにより、erts_pcre_exec までの完全な呼び出しチェーンが明らかになりました。

Erlang の高 CPU の原因となるコードをどのように特定しますか?

OpenResty XRay のガイド付き分析を使用して、Erlang と C の両方の言語レベルで CPU フレームグラフを生成します。フレームグラフは関数呼び出しをスタック表示し、幅が CPU 時間を表します。関数のボックスにマウスカーソルを合わせるとソースファイルのパスと行番号が表示されるので、任意のエディタでそのファイルを開いて原因となるコードを調査できます。コード変更や再起動は不要です。OpenResty XRay は実行中の Erlang プロセスに直接アタッチします。

PCRE 正規表現パターンは Erlang で高 CPU の原因になりますか?

はい。Erlang の re モジュールは erts_pcre_exec を介して PCRE ライブラリに正規表現処理を委譲しており、PCRE はバックトラッキング NFA エンジンを使用しています。ネストされた量指定子や選択肢を含むパターンは壊滅的バックトラッキングを引き起こし、指数的な CPU 時間を消費する可能性があります。今回のケースでは、PCRE の match 関数が C レベルのフレームグラフで末端の CPU 消費元として表示され、正規表現のバックトラッキングが Erlang の高 CPU 使用率の根本原因であることが確認されました。

OpenResty XRay について

OpenResty XRay動的トレーシング製品であり、実行中のアプリケーションを自動的に分析して、パフォーマンスの問題、動作の問題、セキュリティの脆弱性を診断し、実行可能な提案を提供いたします。基盤となる実装において、OpenResty XRay は弊社の Y 言語によって駆動され、Stap+、eBPF+、GDB、ODB など、様々な環境下で複数の異なるランタイムをサポートしております。

このチュートリアルを気に入っていただけましたら、ぜひ当ブログサイトおよび YouTube チャンネルのご登録をお願いいたします。ありがとうございます!

著者について

章亦春(Zhang Yichun)は、オープンソースの OpenResty® プロジェクトの創始者であり、OpenResty Inc. の CEO および創業者です。

章亦春(GitHub ID: agentzh)は中国江蘇省生まれで、現在は米国ベイエリアに在住しております。彼は中国における初期のオープンソース技術と文化の提唱者およびリーダーの一人であり、Cloudflare、Yahoo!、Alibaba など、国際的に有名なハイテク企業に勤務した経験があります。「エッジコンピューティング」、「動的トレーシング」、「機械プログラミング」 の先駆者であり、22 年以上のプログラミング経験と 16 年以上のオープンソース経験を持っております。世界中で 4000 万以上のドメイン名を持つユーザーを抱えるオープンソースプロジェクトのリーダーとして、彼は OpenResty® オープンソースプロジェクトをベースに、米国シリコンバレーの中心部にハイテク企業 OpenResty Inc. を設立いたしました。同社の主力製品である OpenResty XRay動的トレーシング技術を利用した非侵入型の障害分析および排除ツール)と OpenResty Edge(マイクロサービスおよび分散トラフィックに最適化された多機能ゲートウェイソフトウェア)は、世界中の多くの上場企業および大企業から高い評価を得ております。OpenResty 以外にも、章亦春は Linux カーネル、Nginx、LuaJITGDBSystemTapLLVM、Perl など、複数のオープンソースプロジェクトに累計 100 万行以上のコードを寄与し、60 以上のオープンソースソフトウェアライブラリを執筆しております。

翻訳

英語版の原文と日本語訳版(本文)をご用意しております。読者の皆様による他の言語への翻訳版も歓迎いたします。全文翻訳で省略がなければ、採用を検討させていただきます。よろしくお願いいたします。