はじめに
Apache Tomcatは、小型でありながら高い運用負荷を処理できる非常に優れたサーブレットコンテナとして好評を博しています。Tomcatの役割で最も一般的なのは、おそらく軽量のJ2EEアプリケーション(サーブレットとPOJO(通常のJavaオブジェクト)を使い、EJBを使わないもの)を実行するためのアプリケーションサーバでしょう。このような役割のサーバは、膨大な運用負荷を処理することが少なくありません。
しかし、Tomcatのデフォルトのインストール環境は、中程度の負荷を処理するように設定されています。高負荷環境のTomcatでアプリケーションを実行するには、さらにチューニングを行う必要があります。最近、私はあるプロジェクトでまさにこの問題に直面しました。あるTomcatサーバが大量のトラフィック(最大持続負荷では、2~4台のサーバに振り分けられた150以上のユニークな同時ユーザーが毎秒1,450件の要求を発行)を抱えているという状況で、この膨大なトラフィックを処理するようにTomcatサーバを設定しなければならず、さらに、トラフィックの急激な増加や増え続ける負荷などの変動する負荷要件に対して、Tomcatの設定がどの程度適切かを判断する必要があったのです。
この問題に取り組んでみて発見したのは、Tomcatのリソースプールの設定とサイズが、サーバ全体のスケーラビリティとパフォーマンスに大きな影響を及ぼすということでした。リソースプールが適切に設定されたTomcatは、大量のWebトラフィックを持続的なパフォーマンスで処理することができます。しかし、リソースプールの適切なサイズを判断し、その使用率をリアルタイムで追跡する方法を知るにはどうすればよいのでしょうか。プールが小さすぎれば、当然そこがボトルネックとなり、エンドユーザーの操作感に直接影響を及ぼすでしょう。プールが大きすぎるとCPUやメモリなどの重要なシステムリソースを消費し、プラットフォームの安定性を脅かすおそれがあります。
これらのきわめて重要なパラメータと比率を解明するなかで、私は最も適切なサーバ容量関連の設定を追跡および把握するのに役立つ手法を編み出しました。これは、他の類似のリアルタイム追跡と監視にも応用できると思います。本稿では、私が提案するテクニックの利点を検証し、各自のアプリケーションで類似の手法を実装するための具体的な手順を説明します。
Tomcatのリソースプール
最初に、Tomcatコンポーネントのプールの種類と役割を見ていきましょう。リソースプールは、アプリケーションの処理に不可欠でありながら、要求に応じてインスタンス化するのにコストがかかる再利用可能なオブジェクトのプールです。Tomcatのコネクタスレッドプールやデータベース接続プールはこのプールの一種です。これらのプールはサーバ全体のスループットだけでなく、個々のアプリケーションにも直接影響を与えます。
コネクタスレッドプール
コネクタプールは、標準のTomcatコネクタポート(HTTPでは8080、AJPでは8009など)から接続を受け付けて、それを処理用のコンポーネントに渡すスレッドのプールです。このプールは、Tomcatの処理スループット能力できわめて重要な役割を果たします。Tomcatに対する要求は1つ残らずコネクタプールを通過します。プールが小さすぎて未処理の要求があまりに増えると、要求は拒否されます。
このプール内のアクティブな要求処理オブジェクトの数が少なすぎるときに、トラフィックが劇的に増加すると、プールコンポーネントのインスタンス化によって要求処理に遅れが生じます。プールが大きすぎると(つまり、待機中のスレッドの数が多すぎると)、CPUサイクルとメモリが過剰使用になることがあります。
従って、このプールの適切なサイズやその他のパラメータの設定方法を知っておくことが、高負荷の下でWebアプリケーションを正常に機能させるためにきわめて重要です。デフォルトでは、コネクタプールは次のように設定されています。
- スペアスレッドの最小数(常にプール内にあるスレッドの数) - 4
- プール内のスレッドの最大数 - 200
データベース接続プール
データベース接続プールは、プールされたJDBC接続を利用するJ2EEアプリケーション(私の経験ではほとんどのJ2EEアプリケーション)に不可欠なコンポーネントです。データベース接続プールは、JakartaのDBCPのTomcatの実装で提供されているように、コネクタプールと同じような設定パターンに従います。
デフォルトでは、データベースプールは常に3つのアイドル接続を保持し、最大で15個のアクティブ接続を保持します。接続を使い果たすとアプリケーションはデータベースに接続できなくなり、アプリケーションに重大な影響を及ぼしかねない実行時エラーが発生します。
高負荷におけるリソースプール
これら2つのプールは、高負荷の状況ではすぐにパフォーマンス上の大きなボトルネックになる可能性があります。これはアプリケーションのスケーラビリティにおける厳しい制約要因です。その反面、リソースの使用率をリアルタイムで適切に把握すれば、アプリケーションの応答性とスケーラビリティの面で目覚ましい結果を実現することができます。
問題は、プールされたリソースの適切なデータ収集と監視を、高負荷の状況でオーバーヘッドを抑えつつリアルタイムに実行する必要があることです。そのソリューションとなるのが、オーバーヘッドの低い、ほぼリアルタイムの監視およびデータ収集コンポーネントです。これにより、Tomcatのプールされたリソースの使用率を的確に把握することができます。この情報があれば、予想される要求を処理できるようにTomcatのプールを比較的容易に設定することができます。
ソリューションの詳しい説明に入る前に、このカスタムパフォーマンス監視コンポーネントで重要な役割を果たすTomcatコンポーネントについて説明します。
Tomcat Valveの有用性
Valveは、Tomcatサーバのコアアーキテクチャコンポーネントです。Valveの主な目的は、Tomcatサーバに送られてくる要求にサーバまたはユーザー定義のイベントを要求ごとに結び付けるための、標準的かつ柔軟で拡張可能な手段を提供することです。その意味で、ValveはJava Webアプリケーションのフィルタに似ています。いくつかのValveを連結することで、個々の要求に応じて何らかのアクションを実行することができます。この「要求ごと」の性質が、カスタムパフォーマンス監視メカニズムにとっての重要な要素となります。
Web環境では、サーバーパラメータをある一定の間隔で追跡するよりも、要求ごとに追跡する方がはるかに有用です。Webには、不規則で、急な増減が起きやすい性質があるため、ある瞬間にアイドル状態だったサーバに、次の瞬間、大量の要求が押し寄せることがあります。このため、Tomcatのリソースが要求ごとにどのように利用されているかを調べたいときは、一定間隔で追跡するよりも、Valveを使用した方がはるかに有用で的確な情報を入手できます。
Tomcat MBeanを使ったリソースの監視
Tomcatサーバには、たくさんのMBeanオブジェクトが付属しています(JMX標準を参照)。これらのMBeanオブジェクトを利用すると、サーバの制御および管理機能のほか、サーバの管理対象リソースや展開済みのアプリケーションに対して、従来にないアクセスを行うことができます。
JMXベースのサーバ制御および管理機能は強力で包括的ですが、運用サーバの管理と監視にJMXを使うことについては議論の余地があります。通常のJMXベースの管理には、クライアントソフトウェアとリモート接続コンポーネントが必要です。これらのコンポーネント(JConsoleやRMIベースのJMXサーバなど)によって、もともと使用率の高い運用マシンにさらにオーバーヘッドが加わる可能性があります。また、監視を行うために厳重なセキュリティ規則を緩めなければならないことがあります。こうした理由から、本稿では総合的なJMXサポートを必要としないソリューションを提案します(TomcatのJMX機能の扱いに関する説明は、本稿の範疇を越えています。MBeanを使ってTomcatを監視する有効な方法については、以前に掲載されたDevXの記事を参照してください)。
監視用のValveは、外部コンソールを通じてMBeanにアクセスするのではなく、サーバ上でローカルMBeanに直接アクセスするため、オーバーヘッドが発生したり、システムのセキュリティ規則に支障を及ぼしたりすることはありません。このソリューションに関係するのは、MBeanServer
オブジェクトと、リソースプールMBeanの基本属性の取得のみです。JDBCプールでは、DataSource MBean
を使います。
プロパティ | 値 | 説明 |
name | "jdbc/testDB" | context.xmlファイルのJDBCデータソースの名前と一致している必要があります。 |
class | "javax.sql.DataSource" | 定数値です。 |
host | "localhost" | server.xmlファイルで定義されているホストサーバの名前です。 |
path | "/Test" | JDBCソースが接続されているWebアプリケーションのパスです。 |
type | "DataSource" | 定数値です。 |
コネクタスレッドプールでは、Connector MBean
を使います。
プロパティ | 値 | 説明 |
name | "jk-8009" | 追跡するコネクタの種類です。一般的に使われるコネクタは、Tomcat AJPコネクタのjk-8009とHTTPコネクタのhttp-8080の2つです。Tomcatがmod_jk経由でWebサーバに接続されている場合はjk-8009を使い、そうでない場合はhttp-8080を使います。 |
type | "ThreadPool" | 定数値です。 |
JDBC DataSource MBeanでは、次のMBean属性を読み取ります。
numActive
- プールで現在アクティブなJDBC接続の数maxActive
- 利用可能な接続の最大数
コネクタスレッドプールMBeanでは、次の属性を読み取ります。
currentThreadsBusy
- 送られた要求を現在処理しているスレッドの数currentThreadCount
- 要求を処理するために待機しているスレッドの合計数(トラフィックが急に増加したときに直ちに対応できるスレッド)