ミドルウェアを利用していると、大規模なオブジェクトを処理するシステムで最適なパフォーマンスを確実に実現しなければならない事態に直面することがよくあります。一般に、「大規模」とみなされる 1 M 以上のオブジェクトを扱う際には、特別な注意が必要です。この記事では、64 ビットの本番環境で大規模なオブジェクトを効率的に処理する上で、WebSphere Enterprise Service Bus (ESB) V7 製品を最大限に活用するために必要な情報と助言を提供します。
このセクションでは、大規模なメッセージを処理する場合に、最適なパフォーマンスを実現するために考慮すべき主な事項と、パフォーマンスに影響を与える要因について説明します。
64 ビット・アーキテクチャーを使用する主な利点はメモリー管理とアクセス範囲の拡大にあります。データ・バスの幅が広がることにより、32 ビット・アーキテクチャーで一般に使用可能な 4 GB のメモリー空間を超えて、もっと大きなメモリー空間をアドレス指定できるようになります。Java ヒープ・サイズの制限はオペレーティング・システムに依存しますが、32 ビットの JVM が最大でも約 1.4 GB しか使用できないことも珍しくありません。大規模なデータ・オブジェクトを処理する場合、32 ビット・システムでは Java ヒープ・サイズが制限要因となる場合がありますが、64 ビット・アーキテクチャーではサポートされるメモリーが増加するため、その制約を緩和することができます。
一般的なルールとして、大規模なオブジェクトを扱う際には必ず 64 ビットの JVM で実行するようにします。
ビジネス・オブジェクト (BO) のサイズは、ネットワーク上を流れるサイズよりも、メモリー内に格納されるサイズの方がはるかに大きいことに注意してください。これにはいくつか理由がありますが、主に文字エンコーディングの違い、メッセージがシステムを経由する際に加えられる変更、エラー処理やロールバックが可能なトランザクションの実行中に保持される BO のコピーなどが原因となっています。
どの程度の応答時間を実現できるかは主に、同時に処理されるオブジェクトの数に反比例します。ただし最近の SMP ハードウェアでは、この制約はある程度まで緩和されています。システムからの応答時間を最短にするための手段としては、同時に処理されるメッセージの数を制限する方法が考えられます。大規模なデータ・オブジェクトを処理する際には Java ヒープに大きな負荷がかかる可能性があるため、同時に処理されるメッセージの数には特に注意する必要があります。
同時に処理されるメッセージの数を制限する方法としては、以下の 2 つを挙げることができます。
- ワークロードの処理に使用されるクライアントの数を制限する
- スレッド・プールを適切にチューニングし、並行スレッドの数を制限する
大規模なメッセージを処理する際、ネットワーク帯域幅が制限要因になる場合があります。ここで単純なクライアント・サーバー・モデルを考えてみましょう。このモデルでは、1 G ビットの LAN を介し、クライアントは無視できる程小さなサイズのリクエスト・メッセージを送信し、50 MB のレスポンスを受信します。この場合の理論的な最大スループットを計算すると以下のようになります。
帯域幅 (1000 M ビット) / メッセージ・サイズ (400 M ビット) = 毎秒 2.5 メッセージ
この式から、クライアント・スレッドが 1 つであれば 400 ms の応答時間を実現できることがわかります。実際には、アプリケーション・レイヤーで NIC (Network Interface Card: ネットワーク・インターフェース・カード) の転送速度の公称値を実現することは、下位レイヤー (TCP/IP など) のオーバーヘッドがあるため不可能です。最大スループットが NIC の転送速度の 70% 程度であることも珍しくはありません。
複数階層の構成でメッセージを処理する場合 (図 1)、中間階層のネットワーク負荷は、クライアントまたはサービス・プロバイダーの負荷の実質 2 倍になります。その結果、実現可能なスループットは上記で定義したシナリオの半分になります。
図 1. 複数階層の構成
このセクションでは、大規模なメッセージを処理する際のパフォーマンスを改善するためのデザイン・パターンをいくつか紹介します。
1 つの方法として、入力メッセージを分解する方法があります。この方法では大規模なメッセージを複数の小規模なメッセージに分解し、個々のメッセージとして送信します。
大規模なメッセージが基本的に小規模なビジネス・オブジェクトの集合である場合、それらの小規模なオブジェクトをサイズが 1 MB 未満の複合オブジェクトにまとめる方法が考えられます。ただし一時的な依存関係がある場合や、個々のオブジェクトが「すべて必要、またはすべて不要」という場合には、この方法は複雑になります。
クレーム・チェック・パターンというのは、大規模なメッセージのうち、メディエーションに必要な属性が非常に少ない場合にメモリー内の BO のサイズを減らすための手法です。
- メッセージからデータ・ペイロードを分離します。
- 必要な属性を抽出し、小さな「コントロール」BO の中に入れます。
- 大規模なデータ・ペイロードをデータ・ストアに永続化し、リファレンスとして「クレーム・チェック」を「コントロール」BO の中に格納します。
- メモリー・フットプリントが小さい、小規模の「コントロール」BO を処理します。
- 大規模なペイロード全体が再度必要になった時点で、「クレーム・チェック」キーを使用して大規模なペイロードをデータ・ストアからチェックアウトします。
- 大規模なペイロードをデータ・ストアから削除します。
- 「コントロール」BO の属性が変更されたことを考慮に入れた上で、大規模なペイロードと「コントロール」BO の属性をマージします。
最も有効なソリューション・アーキテクチャーとして実装するとよいのは、大規模なメッセージを処理するために別の JVM (専用サーバー) を利用するアーキテクチャーです。小規模なメッセージ・ペイロード (高スループットで応答時間が短い) と大規模なメッセージ・ペイロードが混在するトランザクションを実行する場合、このアーキテクチャーは特に有効です。大規模なメッセージ・ペイロードがたまにしか発生しない場合であっても、応答時間が比較的長くなっている場合には、この方法を採用するとよいでしょう。
大規模なメッセージ・ペイロードを処理するサービスと小規模なメッセージ・ペイロードを処理するサービスが混在する複数のサービスをホストするシステムでは、大規模なメッセージを処理するための GC とメッセージ処理のオーバーヘッドにより、他のサービスのパフォーマンスに悪影響を及ぼす場合があります。
例えば以下の 2 つのサービスがあるとします。
- サービス A – 主に大規模なメッセージ・ペイロードを処理します。
- サービス B – 主に小規模なメッセージ・ペイロード (高スループットで応答時間が短い) を処理します。
サービス A が必ずサービス B とは別の JVM で処理されるようにすると、以下のようなメリットがあります。
- サービス A で大規模なメッセージを処理することによる GC とメッセージ処理のオーバーヘッドは、(高スループットで応答時間が短いという) サービス B のパフォーマンスにそれほど大きく影響することはありません。
- それぞれの JVM を個別にチューニングできるため、想定されるワークロードに対して JVM ごとに最適化することができます。
このセクションでは、いくつかのチューニング方法に関する情報と助言を提供します。これらの方法を理解して適切に構成を行うことで、最適なパフォーマンスを実現する必要があります。
このセクションでは、JVM のチューニングに関して考慮すべき事項について説明します。
ガーベッジ・コレクションとは何か?
ガーベッジ・コレクション (GC: Garbage Collection) は JVM のメモリー管理に関する 1 つの形式です。GC は通常、割り当てに失敗したときにトリガーされます。これが起こるのは、JVM のヒープで使用可能な領域が不足したために、オブジェクトを割り当てられない場合です。GC の目的は、不要になったオブジェクトをすべて JVM のヒープから削除し、割り当てに失敗したオブジェクトのために十分な領域を提供することです。GC がトリガーされたものの、そのオブジェクトに対して十分な空間を確保できない場合には、JVM のヒープを使い切ったことになります。
ミドルウェア・ソリューションに典型的な、存続期間の短いオブジェクトを大量に作成するアプリケーションに最適の方法が、世代別 GC です。世代別 GC では、JVM のヒープは 3 つのセクション (Allocate 領域、Survivor 領域、Tenured 領域) に分割され、この状態でさまざまな状況におけるパフォーマンスが最適化されますが、大規模なメッセージを処理する場合には JVM のヒープの使用状況を把握しておく必要があります。このように、世代別 GC を使用すると JVM のヒープ・サイズに制約を伴うため、32 ビットの JVM ではこのサイズが制約要因となる可能性があります。そのため、32 ビット JVM で大規模なメッセージを処理する場合には世代別 GC を使用しないことをお奨めします。64 ビット JVM ではサポートされるメモリーが増加するため、世代別 GC が問題になることはありません。
JVM のヒープ・サイズを増加してはどうか?
大規模なメッセージを複数処理する場合、特に複数のスレッドが同時に実行されているときには、JVM のヒープを使い切ってしまう場合があります。JVM のヒープ・サイズを増加すると、JVM のヒープの枯渇が問題となるケースの大半で問題を軽減することができます。ただし、この変更によってパフォーマンスが制限される副作用が起きないように、バランスをとる必要があります。
JVM のヒープ・サイズを増加することによって JVM のヒープの枯渇に対応しようとすると、GC がトリガーされるまでに、より多くのオブジェクトを割り当てられるようになります。それに伴い、GC が発生してから次の GC が発生するまでの間隔が長くなり、割り当ての失敗を処理するための時間も長くなります。
GC が実行されている間は、他のすべての JVM スレッドは一時的にブロックされます。つまり、完了に通常 3 秒かかるグローバル GC があり、サービス・レベル・アグリーメント (SLA) で応答時間が 1 秒とされている場合、そのトランザクションの間にグローバル GC が発生すると、応答時間が 1 秒を超えてしまいます。
32 ビットの JVM を実行している場合 (大規模なオブジェクトの処理には推奨されません)、世代別 GC を使用しないことにより、大規模な BO の処理に使用可能な空間を最大にすることができます。その結果ヒープはフラットになり、新世代 (Nursery) 領域だけではなくヒープ領域全体を一時オブジェクトに割り当てられるようになります。
その他に方法はあるか?
複数の大規模なメッセージが同時にサービスによって処理されている場合、JVM のヒープ内で使用可能な領域はすぐになくなります。Web コンテナー・スレッドの数を制限すると、管理者は同時に処理されるメッセージの数を詳細に制御できるようになります。この方法により、JVM のヒープを過度に大きくしなくてもヒープの枯渇の問題を軽減することができます。
また、WebSphere ESB にメッセージを供給するクライアントを 1 つのみにすることにより、1 度に処理される大規模なメッセージを 1 つのみに制限することもできます。この方法により、メモリーの使用量を減らすことができ、応答時間を最短にすることができます。例えば DataPower アプライアンスなどのフロントエンド・サーバーを使用することで、クライアントから順次送られてくる大規模なメッセージを含んだリクエストが WebSphere ESB に到着するのを制限することができます。
この記事の「管理チューニング」のセクションでは、WebSphere ESB 管理コンソールで設定できるパラメーターとその設定方法について説明します。
このセクションでは、チューニングに関係するさまざまなパラメーターと、チューニングに関する考慮事項、推奨事項、そしてこれらの設定を管理コンソールのどこで行えるのかを説明します。
MDB ActivationSpec
以下に挙げる方法をはじめとする何通りかの方法で、MDB ActivationSpec チューニング・パラメーターにアクセスすることができます。
「Resources (リソース)」 > 「Resource Adapters (リソース・アダプター)」 > 「J2C Activation Specifications (J2C アクティベーション仕様)」 > 「ActivationSpec Name Resources (ActivationSpec 名リソース)」 > 「JMS」 > 「Activation Specifications (アクティベーション仕様)」 > 「ActivationSpec Name (ActivationSpec 名)」の順に選択します。
図 2. アクティベーション仕様
大規模なメッセージを処理する際に考慮が必要なプロパティーは以下の 2 つです。
図 3. アクティベーション仕様のプロパティー
maxConcurrency – このプロパティーは、JMS キューから MDB スレッドに同時に供給されるメッセージの数を制限します。
maxBatchSize – このプロパティーは、1 つのステップでメッセージング・レイヤーからアプリケーション・レイヤーに供給されるメッセージの数を決定します。
スレッド・プール
通常、以下のスレッド・プールのチューニングをする必要があります。
- デフォルトのスレッド・プール
- ORB.thread.pool
- WebContainer
これらのスレッド・プールの最大サイズを設定するには、「Servers (サーバー)」 > 「Application Servers (アプリケーション・サーバー)」 > 「Server Name (サーバー名)」 > 「Thread Pools (スレッド・プール)」 > 「Thread Pool Name (スレッド・プール名)」の順に選択します。
図 4. スレッド・プール
JMS コネクション・プール
管理コンソールから JMS 接続ファクトリーと JMS キュー接続ファクトリーにアクセスする方法は以下のとおりです。
「Resources (リソース)」 > 「Resource Adapters (リソース・アダプター)」 > 「J2C Connection Factories (J2C 接続ファクトリー)」 > 「Factory Name (ファクトリー名)」の順に選択する方法。
「Resources (リソース)」 > 「JMS」 >「Connection Factories (接続ファクトリー)」 > 「Factory Name (ファクトリー名)」の順に選択する方法。
「Resources (リソース)」> 「JMS」 > 「Queue Connection Factories (キュー接続ファクトリー)」 > 「Factory Name (ファクトリー名)」の順に選択する方法。
図 5. 接続ファクトリー
接続ファクトリーの管理パネルから「Additional Properties (追加プロパティー)」 > 「Connection Pool Properties (接続プール・プロパティー)」の順に開きます。ここから最大接続数を制御することができます。
図 6. 接続ファクトリーのプロパティー
64 ビット・アーキテクチャーでは、サポートされるメモリーが増加するため、大規模なデータ・オブジェクトを処理する際に 32 ビット・システムを制限する要因となり得る Java のヒープ・サイズの制約を緩和することができます。
JVM のヒープ・サイズを増加することにより、JVM のヒープの枯渇が問題となるケースの大半で問題を軽減することができます。ただし、この変更によってパフォーマンスが制限されないように、バランスをとる必要があります。チューニングの方法には以下のようなものがあります。
- GC の実行間隔と GC の実行による停止時間とのバランスが取れるように、JVM を適切にチューニングする
- JVM に対する負荷を軽減するようなデザイン・パターンを検討する
- 大規模なメッセージを処理するための専用のサーバーを使用する
- 大規模なメッセージを処理するサーバーを使用することにより、同時に処理されるリクエストを制限する、またはリクエスト・スレッドを 1 つに制限する
- WebSphere
Application Server v7 インフォメーション・センターには IBM Java
仮想マシンのチューニングに関する詳細な方法と情報が用意されています。
- WebSphere
Enterprise Service Bus v7 インフォメーション・センターにはチューニングと管理に関する詳細が説明されています。
- Java
Diagnostics Guide 6 には世代別並行ガーベッジ・コレクターに関する詳細が説明されています。