前回は、アプリケーションのデバッグについて詳しく説明しました。今回はWebアプリケーションがさらに快適な状態で安定稼働するための性能チューニングについて詳しく説明します。
Webアプリケーションのチューニングを考える
前回までで、アプリケーションのデバッグを終えて一通り動作するようになりました。しかし、それだけでは快適なシステムが完成したとは言えないのです。たとえば、たくさんの利用者が一斉に同じアプリケーションを利用したとき、快適な応答性能は得られているでしょうか? 長時間そのアプリケーションを利用し続けた場合、利用時間とともに応答性能が悪くなるような現象は起きていないでしょうか? ハードウェアやネットワークの問題も考えられますが、ソフトウェアの性能チューニングによって改善できる場合も多くあります。
この性能チューニングを行うためには、Webアプリケーションサーバがどのような構造になっていて、その動作原理はどのようになっているのか、その基本を知っておく必要があります。
Webアプリケーションサーバのしくみを復習
今回の連載で取り上げた例題アプリケーションは、サーブレット、JSP、EJBという3つの部分(コンポーネント)に分けて開発しました。この3つのコンポーネントがWebアプリケーションサーバ上でどのように動作するかについて見ていきましょう。
図1で示すように、WebアプリケーションサーバはWebサーバとJava VMを含むJ2EEサーバから構成されています。
ここでWebサーバは、Webアプリケーションサーバの入口としての役割を担い、Webブラウザからのリクエスト受け付けや、処理結果画面をWebブラウザに返す処理を行います。
一方、J2EEサーバはWebサーバから送られてきたリクエストを処理するために、「コンテナ」と呼ばれるJ2EEサーバを担う基幹部分で、サーブレット、JSP、EJBを動かします(コンテナはコンポーネントに応じて2つに分かれています)。また、必要に応じてリソースアダプタを介してデータベースにアクセスし、処理結果画面を生成します。もちろん複数の利用者が同時にアクセスするため、Webアプリケーションサーバは待ち行列を作り、同時に実行できるリクエストの多重度を制御して、スムーズな運用を実現します。
チューニングはボトルネックを特定することから
チューニングは目標とする性能が出ていない場合、パラメータの調整やボトルネックの調査・解消によって性能向上を図る作業です。
まず模擬的な負荷をかけながら、単位時間あたりのリクエスト処理件数を測定していきます。負荷を増して送信するリクエスト数を増加させても単位時間あたりの処理件数が増加しなくなったり、レスポンスが極端に遅くなったりしたとき、そこにボトルネックがあります。そこでCPU利用率やログなどを調査して、ボトルネックの要因を見つけて取り除き、再度測定を行います。これを目標性能に達するまで繰り返すのです。
ボトルネックが発生したときのシステムの状態と、想定されるボトルネックの要因を表1にまとめました。
表1 システムの状態とボトルネックの想定要因
状態 | ボトルネックの想定要因 | 確認方法 |
CPU利用率が100%に迫る | 重いロジックを繰り返し実行するなど、アプリケーションの構造やコーディングに問題がある | リクエストに対する処理を追いかけ、どこで時間がかかっているかを突き止める |
同時に実行するリクエストの数が多すぎる | システムが使用するCPUの利用率が通常より高いか確認する |
CPU利用率は特に高くないが、レスポンスが遅い | フルガーベージコレクション(以下、Full GC)が多発している ※1 | ガーベージコレクション(GC)ログでFull GCが発生しているか確認する |
データベースへの接続に時間がかかっている | リクエストに対する処理を追いかけ、どこで時間がかかっているかを突き止める |
ログ出力に時間がかかっている | リクエストに対する処理を追いかけ、どこで時間がかかっているかを突き止める |
シリアライズ処理、排他処理に時間がかかっている | リクエストに対する処理を追いかけ、どこで時間がかかっているかを突き止める |
※1 CPU1個搭載かつ1コアの場合は、CPU利用率が100%に近くなります。
チューニングを行うためには、ボトルネックの要因を見つけることが必要です。しかし、図1で示すようにWebアプリケーションサーバの構造は複雑であり、リクエストに対する処理のどこで時間がかかっているかを調べることはなかなか困難な作業です。
性能解析トレース機能でボトルネック個所を特定する
時間がかかっている処理を特定するために、ログデータやトレース情報からリクエストを追跡していきますが、これはたいへん困難な作業です。その理由は、どのログデータがどのリクエストに対応するかの関連付けが簡単にはできないからです。よく使われるのがタイムスタンプによる追跡ですが、数ミリ秒単位で同じような情報が次々ログとして出力されるので、判別は経験と勘に頼る部分が多くなってしまいます。
Cosminexusではこの問題を、性能解析トレース機能で解決しています。性能解析トレース機能の概要を図2に示します。
性能解析トレース機能では、リクエストにユニークなIDを付与して、リクエスト受け付けから結果の送信まで引き継ぎ、このIDを埋め込んだデータをトレース取得ポイントで出力してトレース情報ファイルに格納していきます。このトレース情報をリクエストIDでフィルタリングすれば、調査したいリクエストの情報だけを時系列に見ることができます。このため、どのように処理が行われたかが一目瞭然となり、どの部分でどれだけ時間がかかっているか、簡単に特定できるのです(図3)。
図3の例では詳細レベルの情報を取得しているため、図2より多くのポイントでトレース情報が出力されています。時刻の列を見ていくと、イベントIDが0x8c00と0x8c01の間で時間がかかっていることがわかります。0x8c00はデータベース接続の処理開始、0x8c01は処理終了のイベントであり、データベース接続の確立にボトルネックがあることがわかります。
チューニング
先に説明したボトルネックの要因については、次のようにチューニングを行います。
表2 チューニング方法
ボトルネックの要因 | チューニング方法 |
重いロジックを繰り返し実行するなど、アプリケーションの構造やコーディングに問題がある | アプリケーションの構造やコーディングを見直し、改修する |
同時に実行するリクエストの数が多すぎる | 同時にリクエストを処理するスレッドの数や、実行待ちリクエストのキューサイズの設定を変更する |
Full GCが多発している | Java VMのメモリ割り当てを変更する |
データベースへの接続に時間がかかっている | データベースへの接続(コネクション)をあらかじめ生成しておくコネクションプーリングを使用する。すでに使用している場合は、プールするコネクション数を変更する |
ログ出力に時間がかかっている | Log4J ※2などの高速なログ処理を使用する。また出力するログ情報をレベル分けし、状況に応じて必要な情報だけを出力する |
シリアライズ処理、排他処理に時間がかかっている | シリアライズや排他を使用する個所、範囲を必要最小限にする |
※2 Apache Software Foundationのフリーソフトウェアで、Javaプログラム用のログAPI
表2に記載した以外にも、次のような観点からのチューニングが有効です。
- HTMLや画像などの静的コンテンツをWebサーバに配置し、J2EEサーバでのリクエスト処理と、WebサーバとJ2EEサーバ間の通信を減らす(特に画像などデータサイズの大きなファイルについて有効)
- データベースアクセス時は、コネクションプーリングだけでなく、SQL文をデータベースに渡すためのステートメントをあらかじめ生成しておくステートメントプーリングも使用する
- 何らかのタイミングで応答が返らなくなった場合に備えて、タイムアウトを設定する
おわりに
Webアプリケーションの動作のしくみから構築、実行、チューニングに至るまで、12回にわたり連載してきました。今までおつきあいいただきありがとうございました。読者のみなさんが、Webアプリケーションエンジニアとしてご活躍されることを祈念して連載を終わります。
ココがポイント
今回のポイントとなるのは、以下の用語です。これらの説明ができるようにしておきましょう。
- Webサーバ
- J2EEサーバ
- コンテナ
- チューニング
- ボトルネック
- ガーベージコレクション
- 性能解析トレース機能
- コネクションプーリング
- ステートメントプーリング
- Cosminexusでホッと一息 ~Cafe Cosminexus
- URL:http://www.cosminexus.com