(1)はこちら、(2)はこちらから。
実際の分散Webアプリケーションの解析
(3)からは、実際の分散Webアプリケーションをトレースしてみましょう。
例として、2018年10月に開催されたISUCON8[1]本選問題を取り上げます。この問題は、運営が用意したブラックボックスとしての外部のWebサービスへのAPIアクセスが多数行われ、全体としては分散システムとして動作するものでした。
この問題をチューニングするには、外部へのAPIアクセスがどれだけ発生し、どれぐらいの時間を消費しているのかを把握することがポイントになります。
完全な問題、ベンチマーカ、ブラックボックスを含むリポジトリは、https://github.com/isucon/isucon8-final
で公開されています。本稿における改変を含めたリポジトリは、https://github.com/fujiwara/isucon8-final-wdpress111
で公開しています。
単体アプリケーションへの組込み
まず競技者がチューニングを行う対象であるWebアプリケーションのPerl実装に、X-Rayを導入します。アプリケーションの実行環境でX-Ray daemonを起動し、AWS::XRayと関連モジュールの組込みを数行追加するのみで、アプリケーションのコードに手を入れる必要はありません。
X-Ray daemonを起動する
ISUCON8の初期実装はDocker Composeで提供されています。リポジトリ内のwebapp/docker-compose.yaml
に、AWSが提供しているDockerイメージ[2]を利用してX-Ray daemonを起動する定義を追加します。
競技者がチューニングするWebアプリケーションはisucoinと呼ばれ、コンテナが定義されています。初期状態ではネットワークがブリッジモードのため、アプリケーションからxrayコンテナへ通信するためにはlinksの設定が必要です。環境変数AWS_XRAY_DAEMON_ADDRESSにX-Ray daemonが動作するアドレスとしてxray:2000
を指定します。
X-Ray daemonからAWSにトレースを送信するために、xrayコンテナには環境変数AWS_REGION、AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY を設定します[3]。
コードにAWS::XRayを組み込む
アプリケーションが起動するエントリポイントであるwebapp/perl/app.psgi
ファイルに、AWS::XRayと関連モジュールを利用する設定を追加します。やっていることはモジュールをuse
して、Plackのミドルウェアをenable
しているだけです。太字が追加箇所です。
モジュールのインストールを行うために、cpanfile
にrequires
を追加します。
これでAWS::XRayの組込みと、トレースの送信準備ができました。
競技アプリケーションを起動し、ブラウザからアクセスして、isucoinアプリケーションが表示されるのを確認してください。しばらくすると、docker-composeコマンドを実行しているコンソールに、daemonがAWSにトレースを送信しているログが出力されます[4]。
実際の競技時におけるアプリケーションのパフォーマンスを解析するために、リポジトリのREADMEにしたがってベンチマークを実行してみましょう[5]。
X-Ray console による解析
ベンチマークを実行後にX-Ray consoleを見ると、サービスマップが表示されます(図1)。
この画面にはX-Ray daemonが送信したトレースが可視化されています。個々の丸いノードがセグメントで、isucoinからDBI、DBI::st、Furl::HTTPに矢印が伸び、呼び出されているのが見て取れます。
処理の絞り込み
isucoinをクリックすると、そのセグメントの処理に要した時間(レイテンシ)が分布図になって表示されます(図2)。
ここでレイテンシが3秒以上の遅い処理を選択して「トレースの表示」をクリックすると、条件に該当するトレースの一覧が表示されます。上の欄には3秒以上かかった処理がフィルタリングされ、URLでグループ化されて表示されます。平均レイテンシが3.4秒の/info?cursor=0
を選択し(図3)、そのうちの1つのトレースを開いてみましょう。
セグメントの詳細を確認する
トレースの詳細画面では、そのトレースに含まれるすべてのセグメントが時系列に沿って並んでいます(図4)。全体の処理に要した3.5秒のうち、ひときわ大きく見える最初のDBI::st(DBIモジュールによるデータベースへのクエリ発行処理)のセグメントをクリックすると、セグメントの詳細が開きます(図5)。
セグメントが持っている情報として、自身のID、親のID、開始時刻と終了時刻のほかにも各種タブが並んでいます。SQLのタブでは発行されたクエリが、メタデータのタブではクエリに付随する情報が閲覧できます(図6)。
メタデータのrowsは、クエリによってデータベースから返却された行数です。図7では、23万行以上を読み取っていることがわかります。しかし、このクエリを発行しているアプリケーションのソースコード[6]をよく読むと、実はこのクエリ結果は最初の1行しか使われていないことがわかります。
つまり、大量の行を読み取っているのは単に無駄な処理です。最初の1行のみが必要ならば、クエリの最後にLIMIT 1
を追加するだけでアプリケーションの仕様を変えずにパフォーマンスを向上できるのではないか、と予想がつきます。
パフォーマンスの改善
実際にクエリの最後にLIMIT 1を追加して、再度ベンチマークを実行したあとのURL別集計を図8に示します。初期状態では平均レイテンシが3.4秒だった/info?cursor=0
が、平均1.0秒まで改善しました。
分散アプリケーションへの組込み
ここまでは、スタンドアローンのアプリケーションから発行されたデータベースへのクエリを可視化して解析しました。実際に競技でチューニングするアプリケーションは、外部へのHTTPS APIの呼び出しを多数行う分散アプリケーションです。
ISUCON8での競技中は、isucoinアプリケーションからHTTPを受信するアプリケーションは、運営が提供するサーバで動作するブラックボックスで改変は不可能でした。しかし、ここでは分散アプリケーションの可視化の例として、ブラックボックス側にもトレースを組込みます。
外部へのHTTPリクエストにトレースIDを送信する
外部APIに対するHTTPリクエストヘッダに呼び出しもとのID を設定するため、webapp/perl/lib/Isubank.pm
に手を入れて、X-Amzn-Trace-Idに$seg->trace_header
の値を渡します。差分を次に示します。
誌面の都合で詳しくは説明しませんが、外部APIのブラックボックス側にも競技アプリケーション同様X-Ray daemonを起動する設定を行い、SDKを組込みます。
分散アプリケーションの解析
ブラックボックス側にもトレースを組込み、アプリケーションからトレースIDを送信した状態でベンチマークを実行すると、X-Ray consoleのサービスマップは図9のようになりました。
isucoinからブラックボックス側のisubank、isuloggerノードに矢印が伸びて、呼び出しが伝わっていることがわかります。
図10のトレースの詳細では、別々のプロセスで動作しネットワークで通信しているisucoin、isubank、isuloggerの各アプリケーションが、一連の処理を行っている状況が時系列で把握できます。
まとめ
本稿では、分散トレーシングを活用して、多数のコンポーネントが連携して処理を行う分散アプリケーションの障害解析やパフォーマンスチューニングを容易にする方法を紹介しました。
AWS X-Rayのほかにも多数の事業者やOSSが製品を提供しています。みなさんの環境に合ったトレーシングシステムを組み込んで、よりよいシステム運用を行うきっかけになったら幸いです。
さて、次回の執筆者は門松宏明さんで、テーマは「自作ツールによる日常業務効率化」です。お楽しみに。
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT