JMeterによる負荷テスト
JMeterは素晴らしいテストツールです。特に負荷テストでその真価を発揮します。さまざまなインターフェースを使用してアプリケーションやデータベースのパフォーマンスをテストできるだけでなく、並列ユーザーの流入をシミュレートすることで、さまざまなレベルの負荷を生成できます。ユースケースに応じて、さまざまな状況をテストしたいと思うかもしれません。例:
- システムが耐えられる最大負荷
- どのコンポーネントがボトルネックか:
- データベース
- バックエンド
- マイクロサービスの特定のノード
- フロントエンド
- システムはスケーラブルか(垂直/水平)
- 負荷分散は正しく機能しているか
- システムは最適に構成され、さまざまなコンポーネントにわたって最適にスケーリングされているか
分析
テストの準備を始める前に、システムがどのように使用されているか、最も一般的なユースケースは何かを分析する必要があります。バックグラウンドタスクや、非常にまれにしか使用されないが、負荷の増加と組み合わせることでシステムを詰まらせる可能性のある特定のメソッドについて覚えておくことが重要です。テストするインターフェースとその使用率を定義した後の次のステップは、実行シーケンスと期間を計画することです。ガベージコレクタが数回呼び出されるのに十分な時間を選択することを検討してください。データベースやキャッシュ層にとって最悪の状況を達成するために、ある程度の多様性も必要かもしれません。最後に、システムにヒットする並列スレッド(ユーザー)の数は、負荷の増加とともに計画する必要があります。
検討する価値のあるいくつかの副次的なトピックは次のとおりです。
- テストはどの環境で実行されるか:
- 接続
- ハードウェア
- ソフトウェア
- 設定
- 本番環境との比較
- バックアップは必要か
- 1台のPCで十分な負荷を生成できるか
- 全員がテストを実行するための事前トレーニングを受けており、必要なアクセス権を持っているか
- 監視目的で他にどのようなツールが必要か(例: Zipkin/Kibana/Nagios)
- 入力データはどのようにロードされるか:
- テスト中にデータベースから
- テスト開始時にデータベースから
- 入力ファイルから
- 実際の目的は何か
- 結果をどのように分析し、どのような種類のレポートを作成するか
ご覧のように、分析中に明確にすべき多くの未解決の点があります。将来のテストとの比較を可能にする意味のある結果を得るためには、適切な計画が不可欠です。
サンプル実装
負荷テストの基本的な実装には、次のステップと要素が含まれます。
- インターフェースへの接続の設定(オプションでパラメータ化)[Test Plan/User Variables Config/Config Defaults]。
- 入力データのロード[setUp Thread Group]:
- データベースから[JDBC Config/Sampler]
- CSVファイルから[CSV Config]。
- インターフェースを呼び出すテストケースの準備[Thread Group]:
- 単一実行の入力データのランダム化[CSV Config/JSR223 Pre Processor]
- 呼び出すインターフェースの比例的なランダム化[Controllers]
- 追加要件の定義:
- 短いピークを達成するために集めるスレッド数[Timers]
- シナリオで発生することが予想される追加のインターフェース呼び出し[Samplers]。
- テストを監視するための集計ビューの追加[Listeners]。

テストが単純であるほど、実行速度が速くなり、より大きな負荷を生成できることに注意してください。また、テストを単純化することで、考えられる障害点を減らすことができます。例としてステップ2を見てみましょう。テストでデータベースに接続することにした場合、テストの複雑さと依存関係が増加します。例えば、データベースへのパスワードをテスト内に保存することはお勧めできません。レビューのために共有したり、リポジトリにアップロードしたり、オフの時間に実行する人々に渡したりしたいかもしれません。それらにアクセスできるすべての人が、データベースへのアクセス権を持っている/持つべきであるとは限りません。また、間違った資格情報を提供して時間を無駄にする可能性もあります。特に、テストを実行するための時間枠が限られている場合はなおさらです。
別の問題は、テスト計画の実行数を増やすこと(複数の人/マシン)で非常に大きな負荷を生成したい場合に発生する可能性があります。入力データのためにデータベースをクエリするのを同期しないと、アプリケーションがデータベースのリソース(接続)をすべて使い果たしてしまう可能性があります。この場合、セットアップステップ中にタイムアウトが発生し、次のステップが信頼できなくなる可能性があります。したがって、そのような場合は、事前に取得した(例えば、1日前に)入力データをロードすることが好ましいです。ただし、時間に追われていない場合は、自分で試してみることができます。
実装例については、ページの下部にあるソースを確認してください。このプロジェクトには、Springで実装されたREST APIが含まれており、いくつかの簡単な負荷テストが含まれています。適切なセットアップのためにREADMEを読んでください(データベースドライバをダウンロードしてJMeterのクラスパスに配置する必要があります)。
JMeterの変数スコープ
JMeterでは、変数はスレッドごとにスコープが設定されます。これは、setUp Thread Groupでデータをロードした場合、インターフェースを呼び出す役割を持つ関連するThread Groupからはアクセスできないことを意味します。もちろん、それらの中にロードロジックを置くこともできますが、状況によっては、これは現実的ではないかもしれません。テスト時間中にデータベースに人為的な負荷をかけたくないかもしれません。そのような場合は、Thread Group間で共有されるJMeterプロパティを利用することで対処できます。setUpスレッドでプロパティを保存するには、次を使用します。
- __setProperty 関数
- JSR223 Sampler/Post Processorと
java.util.Properties
インターフェースを持つJMeterProperties propsオブジェクト — JDBC結果セット変数を保存できます。
これらのプロパティの読み取りは、次を使用するのと同じくらい簡単です。
- __P または __property 関数
- JSR223 Sampler/Pre Processorとpropsオブジェクト。
プロパティは、コマンドラインで-J
プレフィックスを付けて渡されたパラメータ(例: -Jparameter=value
)を取得するためにも使用されます。
データのランダム化
入力データを共有した後、単一の実行のためにそれをランダム化し、必要な情報を変数に保存できます。後で同じThread Group内で${variable_name}
構文を使用してアクセスでき、各スレッドは事実上異なる入力を持ちます。
import java.util.Random;
Random rand = new Random();
def index = rand.nextInt(props.get("resultSet").size());
vars.put("id", props.get("resultSet").get(index).get("USER_ID").toString());
乱数を生成する他の方法を比較したいと思うかもしれません。私は1つのランダムな整数を生成してログに記録することからなるいくつかのパフォーマンステストを実行しました。これらは緩い方法で実行されたものであり、あくまで簡単な参考用です(10スレッド x 100000回繰り返し)。
ランダマイザ | スループット [実行/秒] | 備考 |
---|---|---|
java.util.Random | 10900 | - |
java.util.concurrent.ThreadLocalRandom | 11377 | java.util.Randomと同様のパフォーマンスは、実行が既にスレッドグループ内でスレッドローカルであることを示しています(スクリプト内でスレッドプールでの明示的な実行なし) |
org.apache.commons.lang3.RandomUtils | 11704 | ごくわずかな差(1%)で最速 |
__Random | 5065 | 2倍遅い |
ご覧のように、最初の3つの方法のいずれも有効な選択肢です。*__Random*はなぜか非常に遅いように見えるので、使用はお勧めしません。しかし、__RandomString、__RandomDate、__time、、__threadNumなど、ダミーデータを生成するのに非常に便利な他の関数があります。Groovyコードは__groovyを使用してインライン化することもできます。
負荷の増加
負荷と1秒あたりのターゲットリクエスト量をパラメータ化する際には、テスト対象コンポーネントのいくつかの設定プロパティを書き留めておくとよいでしょう。データベースの場合は、最大接続数です。サーバーの場合は、並列リクエスト数とキューのサイズです。それをノード数で掛け、ある程度のオーバーヘッドを考慮に入れます。
前述のように、負荷を増やすのはユーザー数を増やすのと同じくらい簡単です。マシンの仕様とテストの実装に応じて、約5000の並列スレッドを設定できるでしょう。しかし、ある時点で、追加のスレッドを作成するオーバーヘッドが事実上パフォーマンスを低下させ、場合によってはテスト実行マシンをフリーズさせる可能性さえあります。高性能なマルチノードシステムがある場合、最大負荷制限を達成するには十分ではないかもしれません。また、ユーザー数に基づいて、単位時間あたりのインターフェース実行数を推定するのは難しいことにも注意してください。デフォルトでは、各スレッドはインターフェースにリクエストを送信し、レスポンスを待つ必要があります。
レスポンスタイムアウトを設定することで、事実上レスポンスを待つのをスキップし、次のリクエストをより速く開始できます。この欠点は、レスポンスとそのステータスを監視する可能性を失うことです。負荷を監視するための別のツールがある場合は、有効なオプションです。非常に低いレスポンスタイムアウトを設定する場合は、ステータスチェックのために設定されていないスレッドグループを1つ残しておくことをお勧めします。未知のファイアウォールによってブロックされても気づかないかもしれません。特に監視ツールがオンラインデータを表示しない場合はなおさらです。
最後に考慮すべきは接続です。ローカルネットワークでは、通常、サーバーに到達するまでの時間はかなり短くなります。ターゲット環境がインターネット上にあるか、VPN経由でのみアクセス可能な場合、テストは遅くなり、事実上より低い負荷しか生成できません。最後に、帯域幅を忘れないでください。これはしばしば制限要因となります。
まとめ
JMeterは負荷テストのための優れたツールですが、追加の監視ツールと組み合わせる必要があります。テストを意味のあるものにするためには、考慮すべきことがたくさんあります。各事項は準備中に認識されるべきです。テストが成功裏に実行された後、結果を分析し、レポートを準備する時です。これは、SLAを満たすため、あるいは定義するための将来のステップを定義する上で不可欠な部分です。
サンプルプロジェクトをチェックアウトするのを忘れないでください。テストをいじって、Tomcatのスレッドプール、キューサイズ、H2データベースの接続プールサイズ、およびタイムアウト値のデフォルトの数値を見つけることができます。CSV/データベースからのデータロード、変数のスコープ、負荷の増加といったトピックもそこでカバーされています。