Amazon Web Services ブログ

Amazon CodeGuru Profiler を使用したアプリケーションパフォーマンスの最適化

Amazon CodeGuru (プレビュー) は AWS re:Invent 2019 で開始されたサービスで、アプリケーションのパフォーマンス特性を分析し、改善方法に関する自動推奨を提供します。これを行うには、アプリケーションのランタイムをプロファイリングし (CodeGuru Profiler を使用)、ソースコードの変更を自動的にレビューします (CodeGuru Reviewer を使用)。詳細については、Amazon CodeGuru Profiler とはを参照してください。

この記事では、CodeGuru Profiler の仕組み、一般的な使用方法、本番環境でのアプリケーションのパフォーマンスに対する理解を向上させる方法についての概要を説明します。JVM (Java Virtual Machine) の基本的な知識と、スレッドや呼び出しスタックなどの関連概念を前提としています。

CodeGuru Profiler を使用する理由

CodeGuru Profiler は、常時実行される継続的なプロダクションプロファイラーを使用して、アプリケーションのランタイムパフォーマンスに関する洞察を提供します。現在実行中のスレッドとスレッドが実行しているコードに関するアプリケーションのランタイムからデータを収集します。CodeGuru Profiler は、アプリケーションの実行を理解するのに役立つ視覚化と、最適化の方法に関する自動推奨を提供します。これらの推奨事項は、CPU 使用率とアプリケーションレイテンシーの削減に重点を置いて、インフラストラクチャコストを削減し、顧客により快適な体験を提供します。

これらの最適化によって、大規模なフリートに適用する時にコストを大幅に削減できます。Amazon 内で、CodeGuru Profiler は 80,000 以上のアプリケーションで実行されています。推奨される最適化により数千万 USD を節約できました。また、運用上の問題とパフォーマンスへの影響を分析するために一貫して使用されます。

CodeGuru Profiler の概要

CodeGuru Profiler は、3 つの主要コンポーネントで構成されています。

  • エージェント – アプリケーション内で実行され、現在のランタイム状態をポーリングし、このデータを CodeGuru Profiler に送信します
  • コンソール – データの視覚化と潜在的な改善のための推奨事項を提供します
  • API – プロファイリンググループの管理、プロファイルと推奨事項を取得できるように許可します

CodeGuru Profiler は現在、JVM アプリケーションで使用するために設計されたエージェントを提供しています (Java、Kotlin、Scala をプロファイルできますが、CodeGuru は Java に対する推奨事項のほとんどを調整しています)。アプリケーションでエージェントを起動すると、エージェントはアプリケーションフリートのすべてのインスタンスで JVM に新しいスレッドを生成します。エージェントスレッドは毎秒 (デフォルトで) 起動し、JVM の各スレッド (またはスレッドのサブセット) が現在何をしているか、各スレッドで現在の状態をスタックトレースするよう JVM にリクエストします。

5 分ごと (デフォルト) に、エージェントはこの情報の概要 (プロファイル) を CodeGuru Profiler バックエンドサービスに送信します。すべてのホストからのプロファイルは、5 分間にまとめられます。個別で 5 分間を表示できますが、統計的に優れた結果が得られるため、より正確なプロファイルを作成するには 1 時間または 1 日間の方がよい場合がよくあります。次の図は、このワークフローを示しています。

その後、CodeGuru Profiler を使用して、アプリケーションに属するプロファイルを視覚化できます。アプリケーションのパフォーマンスを視覚化する方法は複数あります。 この記事では、いくつかの基本事項を確認します。CodeGuru Profiler アナライザーを実行した後、コンソールからアプリケーションのパフォーマンス改善方法に関する推奨事項にアクセスできます。これらの推奨事項は、潜在的な最適化規模に関するコンテキストを提供し、調査の優先順位を付ける時に役立ちます。また、検出された問題がパフォーマンスに及ぼす影響を軽減または除去するために実証済みの手順を示します。

CodeGuru Profiler のデプロイ

アプリケーション動作の現実的なビューを取得するには、実稼働 (実際の顧客) トラフィックがある環境で CodeGuru Profiler を実行することをお勧めします。また、運用環境に変更をリリースする前に、運用環境で実行してパフォーマンスの変更をテストすると便利です。本番稼働ソフトウェアのプロファイリングは、最適化に費やす時間によって実際の効率を大幅に向上できるようにするための最良の方法です。

サービスへの影響に関して、CodeGuru Profiler エージェントは通常、アプリケーションに 1% 未満の CPU 使用オーバーヘッドを追加し、最大 100 MB のメモリを使用します。CodeGuru Profiler エージェントを追加する時は、通常の QA またはリリースプロセスを実行する必要があります。これにより、データが正しく送信されていることを確認できます。アプリケーションの主要なメトリックには影響を与えません。

アプリケーションをプロファイリンググループに整理する

プロファイルグループは、アプリケーションの複数のインスタンスを単一の集約プロファイルにグループ化する CodeGuru Profiler の方法です。最も一般的なユースケースは、すべてが同じアプリケーションを実行する同一フリート内の複数のホストからのプロファイルをマージすることです。

プロファイリンググループは通常、アプリケーションを表しますが、アプリケーションの定義方法はほとんどがスタイルによって決まります。複数の関連 API を単一のアプリケーション (したがって単一のプロファイリンググループ) としてプロファイリングするのが一般的ですが、API が完全に関連していない場合は、異なるプロファイリンググループに分けることで、より明確で読みやすいプロファイルになります。

以下は、プロファイリンググループに推奨されるパターンです。

  • アプリケーションごとに個別プロファイリンググループを使用します。さまざまな目的でさまざまなアプリケーションを使用している場合、プロファイリンググループを共有しないでください。視覚化の明確さが低下する可能性があります。
  • アプリケーションの各リージョンに個別プロファイリンググループを使用します。これにより、リージョンの動作を比較できます。これは、アプリケーションの特定のリージョンに影響する運用上の問題を診断するのに役立ちます。
  • アプリケーションの各ステージに個別プロファイリンググループを使用します。テスト環境とステージング環境では、本番稼働サービスと同じプロファイリンググループにプロファイルを送信しないでください。ほとんどの合成テスト環境は、実稼働環境 (データとコードの両方) を完全にエミュレートするわけではないため、パフォーマンス特性が異なります。

これらの推奨事項は、<my-application>-eu-west-1-beta および <my-application>-us-west-2-prod のようなプロファイルグループ名につながります。

CodeGuru Profiler の使用開始

次の例は、CodeGuru Profiler を使用してアプリケーションをオンボーディングすることで期待できること、それによって得られる視覚化と推奨事項を示しています。詳細については、Amazon CodeGuru Profiler のセットアップを参照してください。手続きには次の手順が含まれます。

  1. プロファイリンググループを作成します。CodeGuru Profiler コンソールから簡単に行えます。
  2. CodeGuru に送信する IAM 権限をアプリケーションに付与します。アプリケーションには、プロファイルを送信してエージェントを設定するための IAM 権限が必要です。次のサンプルコードは、これらの権限を与えます。リージョン、アカウント ID、プロファイリンググループ名を追加するだけです。
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "codeguru-profiler:ConfigureAgent",
                    "codeguru-profiler:PostAgentProfile"
                ],
                "Resource": "arn:aws:codeguru-profiler:<region>:<accountID>:profilingGroup/<profilingGroupName>"
            }
        ]
    }
  3. アプリケーションへの依存関係としてエージェントを追加します。アプリケーションで Maven または Gradle を使用している場合は、ドキュメントに記載されている指示通りに進めてください。それ以外の場合は、エージェント JAR をビルドシステムに手動でインポートする必要があります。
  4. アプリケーションの起動時にプロファイラーを起動します。アプリケーションのスタートアップに数行のコードを追加して、プロファイルデータを送信するプロファイリンググループとリージョン、そして以前設定したロールを引き受けるために必要な認証情報を設定します。次のサンプルコードは、この作業がどのように見えるかを示しています (CodeGuru のプレビュー中、これらの API は変更される可能性があります)。
    import software.amazon.codeguruprofilerjavaagent.Profiler;
    import software.amazon.awssdk.regions.Region;
    
    public class MyService {
        public static void main(String[] args) {
    
            // Start CodeGuru Profiler using default AWS credentials from the environment
            new Profiler.Builder()
                    .profilingGroupName("my-profiling-group")
                    .awsRegionToReportTo(Region.EU_WEST_1)    // If your application runs in a different region from your profiling group
                    .build().start();
    
            // ... continue starting up my service
        }
    }

新しくオンボードされたアプリケーションをデプロイした後、プロファイリングの 5 分後にプロファイルが送信されます。すべてのホストからプロファイルを集約するまで最大 15 分かかる場合があります。その後、CodeGuru Profiler コンソールで最初の視覚化を確認できます。最初の視覚化の粒度は、プロファイリングの最初の 5 分間におけるアプリケーションのアクティブ度に依存します。ほとんどの時間アイドル状態のアプリケーションには、デフォルトの視覚化でプロットするデータポイントが多くありません。ただし、アプリケーションの CPU 使用率が非常に低い場合は、1 日または最大 1 週間など、プロファイルされたデータでより長期間を調べることで改善できます。

最初の推奨レポートを生成するまで最大 24 時間かかります。分析するプロファイルデータの量とともに推奨の精度が上がります。この期間中 (更新された推奨事項を受け取りたい場合はその後) はアプリケーションのプロファイリングを継続することをお勧めします。

レポートは、CodeGuru Profiler が特定した潜在的な最適化を強調し、推奨される変更を実行する方法の手順を提供します。レポートに推奨事項が含まれていない場合、アプリケーションには CodeGuru Profiler が認識しているパフォーマンス問題はありません。ただし、CodeGuru Profiler が新しい問題を認識することを学習し、アプリケーションが時間とともに変化するので、将来確認する価値があります。

CodeGuru Profiler の視覚化を理解する

次に、CodeGuru Profiler を使用してアプリケーションを最適化する方法の例をご紹介します。アプリケーションを CodeGuru Profiler にオンボードした後、アプリケーションのランタイムパフォーマンスを初めて見ることができます。このタイプの視覚化は、多くの場合フレームグラフと呼ばれ、ログとメトリックを補完する強力なツールになる可能性があります。

フレームグラフにアプローチする方法としては、使い慣れたフレームを見つけることが最も簡単です。多くの場合、アプリケーションのエントリポイントが開始点として適しています。この記事では、Main (前のスクリーンショットの 1) がアプリケーションの開始点で、ImageProcessor (Main のすぐ上) がほとんどのビジネスロジックを含むクラスです。

視覚化を詳しく見ると、グラフ (2) の中央に Amazon SQS に関する言及があります。その右側には、オブジェクトのシリアル化に費やされた時間 (3)、ログ記録 (4)、右端近くの画像処理フレーム (5) が表示されます。左下には、いくつかのガベージコレクションフレーム (6) があります。私のアプリケーションは Amazon S3 および Amazon SQS Java クライアント、およびいくつかの画像処理ライブラリを使用しているため、全体的には理にかなっています。

フレームグラフを効果的に使用するには、グラフの基本的な内容を理解する必要があります。各長方形は、呼び出しスタック内の特定の位置でメソッド呼び出しを表すフレームです。すぐ上のフレームは、このフレーム (時には受信者と呼ばれる) によって呼び出されるメソッドに対応し、下のフレームはこのフレームを呼び出す (発信者) メソッドに対応します。たとえば、AwsSyncClientBuilder.build フレーム (2) の場合、Main.sqsClient によって呼び出され (2 の下のフレーム)、AmazonSyncClientBuilder.build (2 の上のフレーム) のみを呼び出していることがわかります。これにより、アプリケーション全体で呼び出しの階層をまとめることができます。

各フレームの幅は、メソッドが使用した CPU 時間の割合に対応します。フレームが広いほど、そのメソッドと呼び出したメソッドの実行により多くの時間がかかります。フレームグラフを移動するとき、最初に最も広いフレームを見て、それらのフレームが CPU の時間のほとんどを占めている理由を確実に理解する必要があります。

エージェントが JVM スレッドをサンプリングする時、スレッドは複数のスレッド状態のいずれかになります。CodeGuru Profiler は、これらを次の状態にマッピングします。

  • 実行可能 – サンプリング時にスレッドが実行されています。これは、実行中または実行予定であったことを意味します。
  • ブロック済み – スレッドは Java 同期ブロックに入るのを待っているか、モニターを待っています。しかし、別のスレッドが現在実行または保持しているため、このスレッドの実行を妨げています。
  • 待機中 – スレッドは別のスレッドからの信号を待っています。これは、ネットワークリクエスト、ディスク I/O、notify () および notifyAll () 呼び出しを処理する場合に一般的に見られるスレッド状態です。
  • 待ち時間待機中に似ていますが、タイムアウトでスリープするなど、例外のタイムアウトがあります。
  • アイドル – スレッドは CPU またはレイテンシーに影響を与えません。たとえば、新しいリクエストを待っているスレッドプールです。
  • ネイティブ – スレッドが Java Native Interface (JNI) を介してネイティブコードを実行している間にサンプリングされました。CodeGuru Profiler は、既知のネイティブフレームを他のスレッド状態にマップします。このフレームではフレームが行っている作業がわかりますが、フレームがネイティブ状態のままである場合、スレッドが実行中か待機中かについての情報はありません。

スレッド状態の詳細については、JVM Tool Interface ウェブサイトでスレッド状態を取得を参照してください。

もう 1 つの便利な概念は、実際の経過時間CPU 時間です。実際の経過時間とは、コードの実行中 (または待機時間) に実際に経過した時間を指します。対照的に、CPU 時間は操作を完了するのにかかった CPU サイクルの数を反映します。実際の経過時間には、ネットワーク、ディスク I/O、およびその他のスレッドの終了を待つために費やされた時間が含まれます。これら 2 つの違いは、レイテンシーを最小限に抑えたいリクエスト/応答スタイルのアプリケーションで特に重要です。

デフォルトの視覚化は CPU モードと呼ばれます。実行可能ブロック済みネイティブスレッド状態にあるフレームからの情報のみを表示し、これらの 3 つの状態を区別することはありません。ホストの CPU がビジー状態であることがわかります。おそらくフリートを縮小するのが最終目標で、CPU 使用率を減らしたい場合に役立ちます。

レイテンシーモードに変更すると、他のスレッド状態の一部を表示できます。アイドルを除くすべてのスレッド状態が含まれます。これを使用して、アプリケーションの実際の経過時間に影響を与えているものを把握できます。次のスクリーンショットは、レイテンシーモードのフレームグラフです。このモードには、ネットワークとディスク I/O が含まれています。この場合、アプリケーションはまだほとんどの時間を ImageProcessor.extractTasks (下から 2 番目の行) に費やし、その中のほぼすべての時間を実行可能に費やしています。つまり、待機していませんでした。

推奨レポートと最適化

上記のスクリーンショットの上部 (1) に表示されているボタンに、CodeGuru Profiler には 4 つの推奨事項があることが示されています。推奨事項を選択すると、このプロファイルの推奨レポートが表示されます。次のスクリーンショットは、表示しているアプリケーションでの推奨事項の 1 つを示しています。注目すべき点の 1 つは、アプリケーションが CPU 時間の 18.57% を費やして新しい AWS SDK クライアントを作成したことです。CodeGuru Profiler は、ほとんどのアプリケーションで CPU 時間の 1% 未満だと予想しているため、可能な限りクライアントを再利用する必要があると述べています。

このユースケースでは、SDK クライアントを再利用しない理由はありません。そこで、リクエスト間で Amazon SQS クライアントをキャッシュするように変更を加え、この変更をデプロイしました。次のスクリーンショットは、レイテンシーモードで更新されたプロファイルを示しています。実行可能状態のプロファイルははるかに少なく、待機中状態のプロファイルはより多くなります。つまり、プロファイルの大部分がネットワーク操作と、(明らかに) 無駄な CPU 操作に費やされます。以下のスクリーンショットでは比較のために、前のフレームグラフの実行可能時間のほとんどを占めていた ImageProcessor.extractTasks フレームにカーソルを合わせました。プロファイルされた合計時間のわずか 0.62% に過ぎず、以前の 14.70% から減少しています。優れた改善です。

最適化の結果、各リクエストでアプリケーションのレイテンシーが短縮され、顧客はより満足しました。また、このアプリケーションを実行しているホストの CPU コア数を減らすこともできました。最適化を続ける場合、リクエストを並列化するか、可能であればキャッシュすることで、ネットワークスループットを向上させる方法を考えましょう。

まとめ

この記事では、CodeGuru Profiler の表面レベルより少し下位の概念について説明しました。エージェントが行う作業、本番環境でのプロファイリングがほとんどのアプリケーションに適している理由、プロファイリンググループの設定方法について触れました。また、フレームグラフを解釈し、最適化の推奨事項を使用する方法について簡単な例を確認しました。

今後も JVM エージェントの詳細、フレームグラフの視覚化の詳細、CodeGuru Profiler を使用してさまざまなタイプのアプリケーションを最適化する方法などのトピックを扱う記事をご覧ください。

CodeGuru Profiler を使用するには、CodeGuru コンソールにアクセスしてください。


 

著者について

Isaac Jordan は、ロンドンの Amazon CodeGuru Profiler チームのソフトウェア開発エンジニアです。以前は Amazon Advertising の SDE を務めていました。彼は、顧客の観点から Amazon 製品について考えることが大好きです。