コードを一行も変更せずに Python のメモリ使用量をプロファイリングするには、OpenResty XRay を稼働中のプロセスにアタッチするだけです。独自開発の Python GC オブジェクトメモリ分布フレームグラフが、どのオブジェクト・モジュール・辞書が最も RAM を消費しているかを正確に指し示し、それぞれに至る完全なデータ参照パスを提示します。

以下では、未変更の gunicorn プロセスに対してこの分析をリアルタイムで実行します——@profile デコレータも再起動も不要です——さらに、メモリを浪費している箇所を継続的に洗い出す自動レポートもご紹介します。Python コードのメモリリークや大規模なメモリ使用は、もはや謎ではありません!

どの Python プロセスが最もメモリを消費しているかを特定する

top コマンドを実行してメモリ使用状況を確認します。gunicorn という名前の Python プロセスが最もメモリを消費しており、RSS が 600MB を超えていることがわかります。

top の出力:gunicorn の Python プロセスが最もメモリを消費し、RSS が 600MB を超えている

ps を実行して詳細を確認します。ここでは、Linux ディストリビューションに付属の標準 Python 3 バイナリ(/usr/bin/python3)が、order_service の gunicorn WSGI アプリケーションを実行しています。

ps の出力:gunicorn プロセスが標準の /usr/bin/python3 バイナリで実行されている

ガイド分析で Python メモリをプロファイリングする:最大のオブジェクトとそのモジュールを特定する

OpenResty XRay を使用して、この未変更のプロセスをリアルタイムで検査します。Web コンソールを開き、観察しているマシンが正しいことを確認して、Guided Analysis(ガイド分析)ページに移動します。ここでは OpenResty XRay が診断できる問題の種類を選択できます。

OpenResty XRay のガイド分析の問題タイプ(High memory usage を含む)

High memory usage を選択し、Python アプリケーションと top が示した gunicorn プロセス(約 600MB を消費しているもの)を選び、PythonC/C++ の両方の言語レベルを選択したまま開始します。1〜2 ラウンドの分析の後、OpenResty XRay は自動的にレポートを生成します。

これが、今回分析対象としている問題の種類、すなわちメモリです。

OpenResty XRay レポートで分析対象となっている問題の種類:メモリ(High memory usage)

メモリの大部分が Libc アロケータによって割り当てられており、580 MB 以上に達していることがわかります。

メモリの大部分が libc アロケータによって割り当てられ、580MB を超えていることを示すレポート

これは最もメモリを占有している Python GC オブジェクトの参照パスです。この Python 仮想マシンが Libc アロケータを使用してメモリを要求していることが明確です。

OpenResty XRay レポート:最もメモリを占有する #1 の Python GC オブジェクト参照パス。570MB の辞書が prev_processor モジュールの order_name_cache に保持されている

この Python 辞書は、ロードされたすべての Python モジュールを保存するために使用されています。

ロード済みのすべての Python モジュールを保持する Python 辞書

これは order_service.service.order.prev_processor という名前の Python モジュールです。

参照パス上の Python モジュール order_service.service.order.prev_processor

このモジュール内に order_name_cache という名前のフィールドがあります。

prev_processor モジュール内の order_name_cache という名前のフィールド

このフィールドの値は Python 辞書です。

order_name_cache フィールドの値である Python 辞書

クリックして詳細を表示します。

クリックして order_name_cache 辞書の詳細を展開

このデータ参照パスは、Python GC オブジェクトメモリ分布フレームグラフから自動的に導出されたものです。

Python GC オブジェクトメモリ分布フレームグラフ。order_name_cache に至る最もホットなバックトレースを強調表示

以下は問題に関するより詳細な説明と提案です。先ほど確認した prev_processor モジュールについて言及しています。

OpenResty XRay レポートの説明:order_name_cache 辞書に至る Python 参照パスを段階的に分解

また、order_name_cache 辞書についても言及しています。

データ参照パスに戻りましょう。このアイコンをクリックして、モジュール名をコピーします。

データ参照パス上でアイコンをクリックしてモジュール名をコピー

ターミナルで find コマンドを使用して Python ソースファイルを検索します。

ターミナルで find コマンドを使用して Python ソースファイルを検索

先ほどコピーしたモジュール名を貼り付けます。ここでは、モジュール名のドットを grep コマンドのワイルドカードとして利用しています。

モジュール名を貼り付け、そのドットを grep のワイルドカードとして利用

完全なファイルパスをコピーします。vim エディタを使用してこのソースファイルを開きます。お好みのエディタを使用していただいて構いません。

完全なファイルパスをコピーし、vim でソースファイルを開く

先ほどのレポートで言及された order_name_cache 変数を見つけることができます。

ソースファイル内でレポートに記載された order_name_cache 変数を発見

この変数がどのように使用されているかを確認し、メモリリークの問題があるかどうかを判断することもできます。

prev_processor.py のソース:order_name_cache はモジュールレベルの辞書で、handle() が注文ごとに書き込み、削除しない

order_name_cache はモジュールレベルの辞書で、handle() は処理する注文ごとに新しいエントリを書き込みますが、エントリを削除する箇所はどこにもありません。この上限のない増加こそが、プロセスが数百 MB を保持し続ける原因です——古典的な Python のメモリリークであり、今やその発生源で修正できます。

定期レポートで Python のメモリ使用量を自動監視する

OpenResty XRay はオンラインプロセスを自動的に監視し、分析レポートを生成することもできます。Insights ページでは日次および週次のレポートを確認でき——同じメモリ参照パスがひとりでに浮かび上がります——毎回手動でガイド分析を開始する必要はありません。とはいえ、ガイド分析はアプリケーションの開発やデモンストレーションには依然として有用です。

OpenResty XRay Insights の日次レポートが、最もメモリを消費する Python 参照パスを自動的に表示

よくある質問

コードを変更せずに Python のメモリ使用量をプロファイリングするには?

OpenResty XRay を既に稼働中のプロセスにアタッチし、「High memory usage」のガイド分析を開始するだけです。稼働中の Python プロセスを直接読み取るため——@profile デコレータもインストルメンテーションも再起動も不要——本番の gunicorn やその他の未変更の Python プログラムがトラフィックを処理し続けたままプロファイリングできます。

どの Python オブジェクトやモジュールが最もメモリを消費していますか?

ガイド分析は Python GC オブジェクトメモリ分布フレームグラフを生成し、最も重いオブジェクトに至るデータ参照パスを推論します。本例では、order_service.service.order.prev_processor モジュール内の order_name_cache 辞書を直接指し示しました——まさにこのフィールドが数百 MB のメモリを占有していたのです。

Python プロセス内で最大のオブジェクトを見つけるには?

OpenResty XRay はオブジェクトが保持するメモリ量で順位付けし、モジュールから個々の辞書や値に至るまでの完全な参照パスを提示します。sys.getsizeof でオブジェクトを一つずつ推測する必要はなく、最大のメモリ消費者とそれがコードのどこにあるかを直接得られます。

Python のメモリ使用量を自動監視できますか?

はい。「Insights」ページがスケジュールに沿って同じ分析をオンラインプロセスに対して実行し、日次および週次のレポートを生成します。繰り返し現れるメモリの浪費箇所が自動的に浮かび上がるため、毎回手動でガイド分析を開始する必要はありません。

OpenResty XRay について

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

著者について

章亦春(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 以上のオープンソースソフトウェアライブラリを執筆しております。

翻訳

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