エムオーテックス株式会社が運営するテックブログです。

Apache JMeterを活用したAWS環境での負荷テスト

Apache JMeterを活用したAWS環境での負荷テスト

はじめに

こんにちは。プロフェッショナルサービス本部の四方です。
私はインフラエンジニアとして、AWSクラウドプラットフォームのシステム運用を担当しています。

サービス運用・保守の担当者には、システムの稼働状況を継続的に把握することが求められます。ユーザー数や提供機能の変化は、システム性能に影響し、サービス品質の低下につながるためです。こうした背景から、将来の負荷増加を見据えて事前に性能を検証することが重要です。その手法の一つが、実運用を想定した負荷テストです。

本記事では、AWS 環境で Web アプリケーションの負荷テストを行う方法を解説します。負荷生成ツールには Apache JMeter を使用します。

負荷テストの目的

負荷テストを行う目的としては、以下4点が挙げられます。

  • 性能目標の確認
    規定のシステム構成において、目標とする同時接続数やリクエスト処理能力を維持できるかを検証する。

  • 安定性の確認
    想定されるピーク負荷を一定時間継続させ、システムに異常が発生しないことを確認する。

  • スケーラビリティの検証
    負荷増大に伴い、リソースの拡張が迅速かつ正常に行えることを検証する。

  • 異常系の挙動検証
    過負荷状態における性能劣化や、エラー発生時の挙動をあらかじめ特定する。

このような確認を行うためには、以下のような構成要素やサービスが必要です。

  • スケーラブルなコンピューティング基盤
    システム負荷の増大に応じて、サーバーなどのコンピューティングリソースを動的に拡張する環境を指す。 代表的なサービスとして、設定に基づき自動でインスタンスを増減させる「Amazon EC2 Auto Scaling」が挙げられる。

  • オブザーバビリティ(観測性)サービス
    リソースやアプリケーションの内部状態を、ログ・メトリクス・トレースなどを通じて把握するための統合管理サービスを指す。 代表的なサービスとして、AWS環境の稼働状況を監視する「Amazon CloudWatch」が挙げられる。

  • 負荷生成ツール
    システムに対して擬似的なリクエストを継続的に送り、パフォーマンスを検証するためのツールを指す。

本記事での負荷テスト環境

本記事で解説する、AWS 環境での負荷テスト構成を以下に示します。

JMeter 実行用 EC2 から ALB 配下の Web アプリケーションへ負荷を送る AWS 構成

Amazon EC2(以下、EC2)上で稼働する負荷テスト対象に対し、同じくEC2環境に構築した負荷生成ツールから負荷トラフィックを送信します。負荷送信側にもAWSを採用することで、試験規模に応じた柔軟なリソース調整が可能になります。例えば、より高い負荷が必要になった場合は、インスタンスタイプの見直しによって CPU、メモリ、ネットワーク帯域を比較的容易に増強できます。
また、この構成を採用することで、Application Load Balancer(以下、ALB) を含む AWS 環境の構成に対し、実際のサーバー間通信に近い条件で負荷テストを実施できます。これにより、アプリケーションサーバーやデータベースを含むシステム全体の性能評価や、ボトルネックの切り分けを効率的に行えます。ただし、負荷生成元も AWS 環境にあるため、家庭回線やモバイル回線、公衆インターネットの混雑状況といった、実ユーザーの通信環境そのものを再現するものではありません。

本記事の手順は、利用許諾を得た自社管理環境、または検証利用が明示的に許可された環境を対象としています。
第三者が管理するサイトやサービスに対して、許可なく負荷リクエストを送信してはいけません。
実施前に、社内の承認手続きと利用規約を確認してください。

Apache JMeterとは

本記事では、負荷生成ツールとして「Apache JMeter(以下、JMeter)」を使用します。JMeterは、Webアプリケーションなどのパフォーマンス検証を目的とした、Javaベースのオープンソースソフトウェアです。主な特徴は以下の通りです。

  • 完全無料のオープンソース
    Apacheソフトウェア財団によって開発されており、誰でも自由に使用・カスタマイズが可能です。
  • 多様なプロトコルに対応
    HTTP/HTTPSだけでなく、SOAP・FTP・SMTP・LDAPなどに対応しています。
  • 拡張性とレポート機能
    プラグインで機能を追加でき、テスト結果をグラフや表などのレポートとして視覚的に出力できます。
  • OSを選ばない
    Javaが動作する環境であれば、Windows、macOS、LinuxなどどのOSでも動作します。

負荷テストパラメータの定義

JMeterを用いた負荷テストの実施に際し、以下の主要なパラメータを定義します。

  • ターゲットURL
    リクエスト送信先となるエンドポイントの指定
  • リクエストパラメータ
    リクエスト時に送信するクエリ文字列やボディデータ
  • スループット(目標性能)
    性能検証の指標となる、1分間あたりのリクエスト数(RPM)などの処理件数。

設定した条件に基づき、JMeterを用いて負荷をかけます。
実行後は以下の指標を確認し、システムの妥当性を評価します。

  • リソース使用状況
    サーバーのCPU・メモリ使用率などの稼働状況
  • 応答性能
    応答時間やレイテンシ(遅延時間)

これらの測定結果が目標値を満たさない場合は、インフラ(スペック)の増強や、アプリケーションコードの最適化、DB接続プールの設定変更などといった対策を検討します。

負荷テスト環境のセットアップ

ここでは、JMeterのセットアップについて解説します。
まずは、実行基盤となるEC2インスタンスを作成します。本記事では、OS に Amazon Linux 2023 を使用します。インスタンスタイプには t4g.medium を採用するため、アーキテクチャは 64 ビット(Arm)を選択します。 JMeter に必要なメモリ量は、テスト内容に依存します。本記事では、non-GUI で 10 スレッド程度の基本的な検証を行う前提で、4 GiB のメモリを持つ t4g.medium を使用します。 ただし、t4g はバースト性能型インスタンスであるため、継続的に高い負荷を生成するテストでは、CPUクレジットの影響で負荷生成性能が安定しない場合があります。 そのため、本格的な負荷テストでは m7g や c7g などの固定性能型インスタンスの利用も検討が必要です。

Amazon Linux 2023 を使用した JMeter 実行用 EC2 インスタンスの作成画面

EC2インスタンス作成後、ログインしてセットアップを行います。
まず、AWS が提供する OpenJDK ベースの Java ディストリビューションである Amazon Corretto をインストールします。GUI関連のライブラリは必要ないため、-headlessを指定します。

sudo dnf install -y java-17-amazon-corretto-headless

続いて JMeter をインストールします。執筆時点では、JMeter は Amazon Linux 2023 の標準リポジトリに含まれていないため、dnf ではインストールできません。 wget コマンドで ZIP ファイルをダウンロードし、任意のディレクトリ(ここでは /opt)に展開します。続いて、jmeter コマンドのパスを設定します。以上で、JMeter のセットアップは完了です。
※JMeterのバージョンは、2026年1月時点の情報に基づいています。

sudo dnf install -y unzip wget
wget https://dlcdn.apache.org/jmeter/binaries/apache-jmeter-5.6.3.zip
sudo unzip apache-jmeter-5.6.3.zip -d /opt/
sudo ln -s /opt/apache-jmeter-5.6.3 /opt/jmeter
sudo ln -s /opt/jmeter/bin/jmeter /usr/local/bin/jmeter

負荷テストの実施

続いて負荷テストパラメータをJMXファイル(テスト計画ファイル)に定義します。以下にJMXファイルのサンプルを記載します。
任意のエディタで plan.jmx を作成します。

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
      <stringProp name="TestPlan.comments"></stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"/>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Continuous Load Test" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
          <boolProp name="LoopController.continue_forever">true</boolProp>
          <stringProp name="LoopController.loops">-1</stringProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">10</stringProp>
        <stringProp name="ThreadGroup.ramp_time">10</stringProp>
        <boolProp name="ThreadGroup.scheduler">true</boolProp>
        <stringProp name="ThreadGroup.duration">60</stringProp>
        <stringProp name="ThreadGroup.delay">0</stringProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
          <stringProp name="HTTPSampler.domain">example.com</stringProp>
          <stringProp name="HTTPSampler.port"></stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.path">/</stringProp>
          <stringProp name="HTTPSampler.method">GET</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout">5000</stringProp>
          <stringProp name="HTTPSampler.response_timeout">10000</stringProp>
        </HTTPSamplerProxy>
        <hashTree/>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

主要なパラメータについて解説します。

  • LoopController.loops
    各スレッドがテストシナリオを何回繰り返すかを指定します。

  • ThreadGroup.num_threads
    同時に動作する仮想ユーザー数です。(ただし、実際の同時リクエスト数やスループットは、応答時間や Timer 設定、シナリオ内容に依存するため、スレッド数だけで一意には決まりません。)

  • ThreadGroup.ramp_time
    全スレッドを起動し終えるまでの時間(秒)です。例えば100スレッドで10秒と設定した場合、0.1秒ごとに1スレッドずつ、緩やかに負荷を立ち上げます。

  • ThreadGroup.duration
    テスト実施時間(持続時間)。テストを何秒間継続させるかを指定します。

  • HTTPSampler.domain
    接続先ホスト名(ドメイン)。リクエストを送信するターゲットのドメイン名やIPアドレスを指定します。

JMXファイルを作成後、コマンドの引数にそのファイルを指定して負荷テストを開始します。 -l オプションでは、各リクエストの実行結果を保存する結果ファイルを指定します。 このファイルは通常 JTL 形式で扱うため、拡張子は .jtl を使用するのが一般的です。

jmeter -n -t plan.jmx -l result.jtl

result.jtl には各サンプルの結果が保存されます。
一方で、実行中の件数、スループット、応答時間などの要約情報は、標準設定では summary としてコンソールに表示されます。 表示されない場合は JMeter の summariser 設定を確認してください。 以下に、その summary 出力のサンプルを示します。

summary +   2548 in 00:00:30 =   84.9/s Avg:   109 Min:    41 Max:   486 Err:     0 (0.00%) Active: 10 Started: 10 Finished: 0
summary =   2548 in 00:00:30 =   84.9/s Avg:   109 Min:    41 Max:   486 Err:     0 (0.00%)
summary +   2987 in 00:00:30 =   99.6/s Avg:    98 Min:    39 Max:   421 Err:     0 (0.00%) Active: 10 Started: 10 Finished: 0
summary =   5535 in 00:01:00 =   92.3/s Avg:   103 Min:    39 Max:   486 Err:     0 (0.00%)
summary +     12 in 00:00:01 =   12.0/s Avg:    95 Min:    71 Max:   128 Err:     0 (0.00%) Active: 0 Started: 10 Finished: 10
summary =   5547 in 00:01:01 =   90.9/s Avg:   103 Min:    39 Max:   486 Err:     0 (0.00%)

この summary には、リクエスト総数、スループット、応答時間の平均・最小・最大値、エラー率などの統計情報が表示されます。

スループットが想定に達しない場合の対処

JMeterを用いて負荷テストを実施した際、想定のスループットが得られない場合があります。 このような場合、テスト対象システムの性能不足と判断する前に、ボトルネックになっているレイヤーを切り分けることが重要です。 本章では、実際に筆者が負荷テストの際に確認した観点を解説します。

1. JMeter

まず、負荷を生成しているJMeter自身に問題がないか確認します。 負荷テストでは、テスト対象よりも先に負荷生成側が限界に達してしまうことがあり、その場合はシステム側に余力があっても十分なリクエストを送り込めません。

  • DNSキャッシュによる負荷の偏り
    AWS の Application Load Balancer(以下、ALB)は、負荷状況に応じて内部的に処理を分散・拡張します。 このとき、ALB の DNS 名に対する名前解決結果が変化することがあります。 一方、JMeter は Java 上で動作するため、名前解決結果の扱いは主に JVM の DNS キャッシュ設定に依存します。 このキャッシュが長く保持されると、ALB の DNS 名に対する解決結果が一定時間固定され、一部の ALB ノードにトラフィックが偏る可能性があります。 その結果、ALB 全体の処理能力を十分に使い切れず、負荷テストの結果に偏りが生じることがあります。 そのため、負荷テストを実施する際は、必要に応じて JVM の DNS キャッシュ TTL を調整し、名前解決結果が適切に更新されるようにします。 ただし、TTL を短くしすぎると DNS 問い合わせが増えるため、テストの目的や規模に応じて設定を検討することが重要です。

  • JVMメモリ(ヒープサイズ)の不足
    JMeterはJavaで動作するため、JVMのヒープサイズが不足すると、負荷生成性能の低下につながります。 メモリに余裕がない状態では、JVMは不要オブジェクトを回収するためにGarbage Collection(以下、GC)を頻繁に実行します。 GCが多発すると、その間の処理効率が低下し、結果としてJMeterが十分な速度でリクエストを送信できなくなります。 そのためJMeter実行時のヒープサイズやGCの発生状況を確認し、必要に応じてJVMオプションを調整します。

  • JMeter実行基盤のリソース不足
    JMeterを実行しているEC2のCPU使用率が高止まりしている場合や、メモリ不足が発生している場合、 あるいはエフェメラルポートを使い切っている場合は、JMeter実行基盤そのものがボトルネックになっています。 このような場合は、EC2インスタンスタイプの見直しや、複数台に分散して負荷を生成する構成に変更します。

2. ロードバランサー

次に確認するのは、ロードバランサーが受信したリクエストを正常に処理し、各ターゲットへ適切に振り分けているかどうかです。 ロードバランサーの段階でエラーが発生していれば、期待するスループットには到達しません。

  • アクセスログによるエラーの確認
    まず ALB のアクセスログを有効化し、HTTP ステータスコードを確認します。 ALB のアクセスログには、ALB がクライアントへ返した elb_status_code と、ALB の背後にあるターゲットが返した target_status_code が記録されます。 ここで重要なのは、elb_status_code の 5xx が増えている場合でも、必ずしも ALB 自体の障害を意味するわけではないという点です。 たとえば、ターゲットへの接続失敗、ターゲットからの不正な応答、応答タイムアウトなどが発生した場合も、結果として ALB からクライアントに 5xx が返されることがあります。 一方、target_status_code の 5xx が増えている場合は、バックエンドのアプリケーションサーバーが 5xx を返していることを示しており、アプリケーション側の処理失敗や過負荷が疑われます。 そのため、切り分けの際は elb_status_code と target_status_code を単独で判断するのではなく、アクセスログの詳細項目や CloudWatch メトリクスも併せて確認し、どの層で問題が発生しているかを判断します。

  • CloudWatchメトリクスの確認
    CloudWatchメトリクスからALBの状態を確認します。以下の指標は、負荷テスト時の状況把握に有効です。

    • RequestCount
      ALBが処理したリクエストの総数
    • TargetResponseTime
      ALB がターゲットにリクエストを送ってから、ターゲットが応答を返し始めるまでの時間
    • HealthyHostCount
      ターゲットグループ内でヘルスチェックに合格しているターゲットの数

3. アプリケーションサーバー

ロードバランサーが正常に動作していても、アプリケーションサーバー側でリクエストの受け付けや処理が追いついていなければ、全体のスループットは頭打ちになります。 そのため、アプリケーションサーバーでは、接続受け付け能力、アプリケーションの実処理能力、ミドルウェア設定、OS リソースの各観点から確認します。

  • Webサーバーやアプリケーションサーバーの接続上限
    まず確認すべきは、Webサーバーやアプリケーションサーバーの同時接続数上限やワーカー数です。 たとえば nginx や Apache、あるいはアプリケーション実行基盤に設定された同時処理数が小さい場合、CPUやメモリにまだ余裕があっても、受け付け可能なリクエスト数そのものが制限されます。 この場合、表面的には「サーバー負荷は高くないのにレスポンスが悪い」という現象になります。 実際には、接続待ちやキューイングが発生しており、処理能力ではなく設定値がボトルネックになっているケースです。 そのため、必要に応じてWebサーバーのコネクション数上限やワーカー数、バックログ設定を見直します。

  • アプリケーションログの確認
    アプリケーションログには、性能劣化の直接的な原因が現れることがあります。 そのため、負荷テスト実施中はログを確認し、想定外の事象が発生していないかを確認します。 特に注意すべき兆候としては、以下のようなものがあります。

    • DB接続取得タイムアウト
    • OutOfMemoryError
    • リクエスト処理時間の急増
    • その他想定外例外の頻発
  • サーバーリソースの確認
    CPU、メモリ、ロードアベレージ、ネットワーク使用量などのサーバーリソースも、基本的な確認項目です。 CPU使用率が高止まりしている場合は、アプリケーション処理そのものがボトルネックになっています。 必要に応じて、インスタンスタイプの変更による垂直スケーリング、もしくは台数追加による水平スケーリングを行います。

4. データベースサーバー

アプリケーションサーバーまで明確な問題が見つからない場合は、データベースサーバーがボトルネックになっている可能性を確認します。 Webアプリケーションでは、多くの処理が最終的にデータベースアクセスへ到達するため、データベースの性能限界がシステム全体のスループット上限を決めることは少なくありません。

  • リソース使用状況の確認
    まずは、CPU、メモリ、ストレージI/O、接続数などの基本的なリソース状況を確認します。 CPU使用率が高い場合は、クエリ処理そのものが重い可能性があります。 メモリ不足がある場合は、キャッシュが十分に効かず、ディスクアクセスが増加して性能低下につながります。 また、I/O待ちが長い場合は、ストレージ性能が不足している可能性も考えられます。 データベースでは、CPUだけを見ていても原因の本質を見誤ることがあります。 CPU使用率がそれほど高くなくても、I/O待ちやロック待ちによって処理が滞留し、アプリケーション全体のレスポンスが悪化しているケースもあるためです。

  • 接続数と接続プールの確認
    負荷テスト時には、アプリケーションサーバーからのDB接続数が急増し、接続上限に近づくことがあります。 接続数が上限に達すると、新規接続が待たされたり拒否されたりし、その影響がアプリケーション全体の応答遅延として現れます。 このため、データベース側の接続数だけでなく、アプリケーション側のコネクションプール設定も併せて確認することが重要です。 接続数が多すぎればDBへの負荷が高まり、少なすぎればアプリケーション側で接続待ちが発生するため、システム全体としてのバランスを取る必要があります。

  • クエリ性能の確認
    データベースがボトルネックとなる場合、特定のSQLが全体の性能低下を引き起こしていることがあります。 そのため、スロークエリログや Performance Insights などを活用し、実行時間の長いSQLや実行回数の多いSQLを特定します。 確認すべきポイントとしては、以下が挙げられます。

    • インデックス不足
    • 不要なフルスキャン
    • 実行計画の悪化
    • 頻度の高い重いクエリの存在
    • ロック競合の発生

おわりに

本記事では、AWS 環境で JMeter を用いた負荷テストのセットアップ方法、実施手順、スループット不足時の確認観点を解説しました。
クラウドの柔軟性を活かした負荷テストは、システム品質を確認するうえで有効です。
本記事が、AWS 環境で負荷テストを始める際の参考になれば幸いです。
最後までお読みいただき、ありがとうございました。