1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめての負荷テスト:実務を通して整理した基礎知識と現場での進め方

1
Posted at

目次

はじめに

これまでのエンジニアキャリアでシステムの負荷テストを行ったことがなかった著者ですが、最近になって実務で行う機会がありましたので、そこで知り得た知見を備忘録として当記事にまとめてみました。

当記事の想定読者は以下の通りです。

  • 負荷テストについて、概要や各プロセスについてザックリ知りたい方

また、当記事は具体的な環境構築方法やテスト実施方法については割愛しています。

負荷テストの基礎知識

負荷テストとは?

CF5E3388-BD51-409B-A81A-5EDC322DE82B_1_201_a.jpeg

負荷テスト(Load Testing)とは、テスト対象となるシステムに本番環境で想定される大量のリクエストを擬似的に発生させて、システムのパフォーマンスや安定性を測定するテスト手法です。

例えば、実際に運営している EC サイトをテスト対象とした場合、
「セール開始と同時に 1秒間 に 500人 のユーザーが商品一覧や商品ページを閲覧」
するような現実のユーザー行動の負荷を、そっくり再現できます。

そして Gatling や JMeter といった負荷テストツールで負荷をかけながら、

  • エラーは発生しないか
  • レスポンスタイムは遅くならないか
  • データベースの接続プールは枯渇しないか
  • インフラ環境のオートスケーリングは問題なく動作しているか
  • 負荷に耐えられる限界値はどこか

などを測定します。

要するに「本番で本当に来るかもしれない負荷に、耐えられるかどうかを事前に試す」ためのテストです。

負荷テストの種類

負荷テストにはいくつか種類があり、目的によって使い分けるのが重要です。

種類 概要
負荷テスト 想定される通常〜ピーク負荷で、レスポンスタイムやスループットなどのパフォーマンスを測定
ストレステスト 限界を超える負荷をかけ、限界値や破壊点、回復力を調べる(リミットテストを含む)
スパイクテスト 短時間で急激に負荷が増加した時の耐性を測定
ソークテスト 長時間(数時間〜数日)に渡って一定負荷をかけ続け、安定性やメモリリークなどを確認
スケーラビリティテスト 負荷を段階的に増やした時のインフラのスケーリング挙動(例:Fargate の ECS タスク自動増減など)を測定

負荷テストで監視すべき主なメトリクス

負荷テストの結果を正しく評価するためには、以下のメトリクスを監視します。これらはシステム全体だけでなく、特に DB を使う Web サイトや API では DB 関連のメトリクスがボトルネックの大半を占めることも多いです。

メトリクス 概要
RPS (Requests Per Second) 1秒あたりのリクエスト処理数(スループットの最も基本的な指標)
レスポンスタイム リクエストからレスポンスまでの時間
p95(95%のリクエストがこの時間以内に完了)、p99、平均値、最大値は必ず確認。これが「大多数のユーザーが体感する速度」
エラー率 失敗したリクエストの割合(主に 4xx/5xx エラー)
スループット 処理されたデータ量(バイト/秒)
リソース使用率 CPU使用率、メモリ使用率、ネットワーク I/O(受信/送信バイト数、パケット数、帯域利用率)
DB 関連メトリクス アプリケーション視点:クエリ実行時間、DB 接続数、キャッシュヒット率、IOPS
DB インスタンス視点:CPU 使用率、FreeableMemory(空きメモリ)

監視方法のポイント

  • インフラ側(サーバー/コンテナ/クラウド)
    標準の監視ダッシュボード(例:Amazon CloudWatch、GCP Monitoring、Azure Monitorなど)で CPU/メモリ/ネットワークをリアルタイム確認します。
     
  • アプリケーション側
    アプリログ、クエリログ、レスポンスタイムを追跡します。
    APM ツール(New Relic、Datadog、AppDynamicsなど)を使うと内部の詳細(クエリ時間、メソッド実行時間)が一目でわかります。
     
  • その他
    ロードバランサーのアクセスログ、DB の Performance Insights(クエリ遅延や待機時間)も並行して確認するとボトルネック特定が早くなります。

 
これらをダッシュボードにまとめておくと、テスト中の異常を検知しやすくなります。

負荷テストツール

負荷テストツールは、システムに大量のリクエストを送り込んでパフォーマンスや安定性を測るためのツールですが、ツールによって「テストコードの書きやすさ」「スケーラビリティ」「学習コスト」「チームのスキル親和性」などが大きく異なります。

以下に、主な人気ツールを比較表でまとめました。

ツール名 使用言語 特徴
Gatling Scala
Java
Kotlin
- シナリオをコードベースで記述。高パフォーマンスで大量リクエスト可能。グラフィカルレポート出力
- クラウド環境で大規模・高負荷テストに向いている
JMeter Java - GUI ベースで初心者でもとっつき易い。プラグイン豊富。HTTP 以外のプロトコルも対応
- GUI ベースで初心者向け、多様なプロトコルの複雑なシナリオも可能
Locust Python - コードベースで柔軟性あり。分散実行可能。リアルタイムダッシュボード
- Python エンジニア向けのユーザー行動シミュレーションや分散大規模テストに向いている
k6 JavaScript - スクリプトベースで簡単。クラウド統合(Grafana)と相性良し
- DevOps ワークフローでの API 中心の軽量テストやモニタリング重視のケースに向いている
Artillery JavaScript - YAML/JSON でシナリオ記述。CI/CD 統合しやすい
- シンプルな API/エンドポイントテストや CI/CD パイプライン統合に向いている

ツールのシェア率で言うと Jmeter が一番多いようですが、私は実務では Gatling を採用しました。私の在籍組織が Gatling の使用実績が豊富だったこと、高負荷テストには Gatling が適していることが主な理由です。

負荷テストのモデル

負荷テストでは、どのように負荷をかけるかを決める「負荷モデル」というものがあります。

これらのモデルは、システムの挙動を現実的にシミュレートするための分類で、主に「オープンモデル」と「クローズドモデル」の2つがあります。

オープンモデルは負荷を強制的にかけ続けるアプローチで、クローズドモデルはシステムのレスポンスに合わせて負荷が調整されるアプローチです。どちらを使うかは、テストの目的によって変わります。以下で詳しく解説します。

オープンモデル

項目 説明
概要 固定の RPS を設定し、システムのレスポンスタイムやエラー率を測定。システムの遅延に関係なくリクエストを次々と送るので、負荷が常に一定に保たれる
使いどころ システムの限界値確認やボトルネック特定に最適。
例:Fargate 環境で「1秒に300リクエストをかけ続け、オートスケーリングが発動するポイントを調べる」
メリット 現実のピークトラフィック(急なユーザー急増でシステムが圧倒される状況)を再現しやすい。ストレステストにぴったり
デメリット システムが詰まるとキューが長くなり、テスト結果が予測しにくくなる場合あり
ツール例 Gatling の constantUsersPerSec や JMeter の Constant Throughput Timer で実装

クローズドモデル

項目 説明
概要 固定の仮想ユーザー数を設定し、システムがどれだけの RPS を処理できるかを測定。各ユーザーがレスポンスを待ってから次のリクエストを送るので、システムが遅くなると負荷が自動的に減る
使いどころ 新旧環境の性能比較や通常運用時の確認に最適。
例:Fargate 環境で「100ユーザー同時接続で、安定したスループットがどれだけ出るか測定」
メリット 現実のユーザー行動(ページロードを待って操作する)を再現しやすい。テストが制御しやすく、再現性が高い。
デメリット ユーザー数が多すぎるとツール側のリソースが限界になる可能性あり。また、負荷が自己調整されるため、現実のピーク負荷を完全に再現しにくい(Coordinated Omission 問題が発生しやすい)
ツール例 Gatling の constantConcurrentUsers や JMeter の Thread Group で実装

負荷テストのプロセス

負荷テストは「一度やって終わり」の作業ではなく、計画 → 実行 → 分析 → 改善 を繰り返す継続的なプロセスです。

一度のテストで完璧なシステムになることは稀で、ボトルネックを発見・修正し、再テストするというイテレーションを回すことで、本番での急激なトラフィック増加や予期せぬ負荷にも耐えられるようになります。

以下では、主な4つのプロセス(テスト計画、テストシナリオ作成、テスト実施、分析・レポート)を詳しく解説します。

テスト計画

テスト計画は、テストを進める上での基盤となります。
ここで目標や要件を明確にしないと、後のプロセスで無駄な作業が発生したり、結果の信頼性が低くなったりします。主なステップは以下の通りです。

  1. テスト目標の設定
    何を達成したいかを具体的に定義します。

    例:
    - テスト対象の API がピークトラフィック想定の 500 RPS で安定稼働できるかを確認する。
    - 成功基準として、レスポンスタイム p95 値が 500ms 以内、エラー率が 1%未満 であること。
    - 基準を満たさない場合は、主なボトルネックを特定し、改善案を出す。
    

    このように数値目標と成功/失敗の基準を明確にしておくと、テスト合否がはっきりし、チーム内共有や次のアクションが決めやすくなります。
     

  2. 対象エンドポイントの選択
    負荷をかける URL や API エンドポイントを絞り込みます
    例:高負荷が予想される/users(ユーザー一覧)、/posts(投稿作成)
     

  3. 環境準備
    負荷テストでは、テスト対象環境(実際に負荷を受けるシステムの稼働環境)と負荷送信環境(負荷テストツールを動かす側)を明確に分けておくのが良いです。
    同じ環境で負荷をかけると、ツール自体の CPU/メモリ消費がボトルネックになったりするので、必ず分離しましょう。

    特にテスト対象環境については、本番環境に近づけることで結果の信頼性が高まります。主な注意点は以下の通りです。

    • データ量(DBのレコード数など)を本番規模に近づける
      • テスト用データが少なすぎると、クエリ性能が過大評価される
    • インフラ構成(VPC、セキュリティグループ、サブネット、ネットワーク帯域など)を本番同等にする
      • 構成が違うとネットワーク遅延やセキュリティ制限の影響が再現されない
    • オートスケーリングのポリシー(閾値、クールダウン時間、最大/最小インスタンス数など)も本番同等に設定する
      • オートスケーリングが遅いとテスト結果が本番と乖離する

 
この計画はしっかりとドキュメント化しておきましょう。

テストシナリオ作成

テストシナリオ作成は、負荷テストの本番再現度を決める最も重要なプロセスです。

  1. ユーザー行動のモデル化
    負荷テストでは、まず対象エンドポイント単体の負荷テストで基礎性能を確認するのが一般的ですが、現実のユーザー体験をより正確に再現するために、ユーザーの一連の行動フローをモデル化して負荷を配分してテストすることも可能です。

    例(典型的な Web アプリのユーザー行動モデル)
    - トップページ閲覧 → ログイン → プロフィール確認 → 投稿作成
    
    負荷の割合例(ユーザー行動の頻度を反映)
    - トップページ:     50%  
    - ログインページ:   20%  
    - プロフィールページ:20%  
    - 投稿作成ページ:   10%
    

    これにより「単なる API の耐久性」ではなく「実際のユーザーが操作する全体の流れでシステムが耐えられるか」を検証できます。
     

  2. ランプアップ(Ramp-up)の設定
    いきなり最大負荷をかけると異常検知が難しくなるので、徐々に負荷を上げる設定を入れます。
     

  3. テストデータの準備
    ログインに必要な認証データ(ユーザー名、パスワードなど)は CSV や JSON で投入できるようにします。

    例:users.csv
    username,password
    user001,PassW0rd
    user002,PassW0rd!
    user003,PassW0rd!!
    

     

  4. テストコード実装
    上記内容を元にシナリオコードを実装します。以下は上記シナリオを Gatling で実装したサンプルです。

    import io.gatling.core.Predef._
    import io.gatling.http.Predef._
    import scala.concurrent.duration._
    import io.gatling.core.structure.PopulationBuilder
    
    class UserJourneySimulation extends Simulation {
    
      // HTTP 設定
      val httpProtocol = http
        .baseUrl("https://your-app.example.com")
        .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
        .acceptEncodingHeader("gzip, deflate")
        .acceptLanguageHeader("ja,en-US;q=0.7,en;q=0.3")
        .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
    
      // Feeder: ユーザー認証情報(CSV から読み込み)
      val userFeeder = csv("users.csv").random
    
      // シナリオ定義
      val userJourney = scenario("Typical User Journey with Weighted Actions")
        .feed(userFeeder) // ユーザー情報を注入
    
        .during(60 seconds) { // 1ユーザーあたり最大60秒の行動を繰り返す
          randomSwitch(
            50.0 -> exec(
              http("Top Page")
                .get("/")
                .check(status.is(200))
            ),
    
            20.0 -> exec(
              http("Login")
                .post("/login")
                .formParam("username", "${username}")
                .formParam("password", "${password}")
                .check(status.is(302))
                .check(header("Location").saveAs("redirectUrl"))
            ).exec(
              http("Follow Redirect after Login")
                .get("${redirectUrl}")
                .check(status.is(200))
            ),
    
            20.0 -> exec(
              http("Profile Page")
                .get("/profile")
                .check(status.is(200))
                .check(css("#username", "text").is("${username}"))
            ),
    
            10.0 -> exec(
              http("Create Post")
                .post("/posts")
                .formParam("title", "Test Post ${__randomString(10)}")
                .formParam("body", "This is a test post created during load test.")
                .check(status.is(201))
            )
          )
          .pause(2, 10) // 現実的な思考時間(2〜10秒)
        }
    
      // 負荷注入設定(ランプアップ)
      setUp(
        userJourney.inject(
          // 0 → 100ユーザー/秒 まで30秒で増加
          rampUsersPerSec(0) to (100) during (30 seconds)
        )
      ).protocols(httpProtocol)
    }
    

    実装が終わったら、シナリオコードを含む負荷テストリポジトリを負荷送信環境に配置&ビルドを行い、テスト実施の準備をしておきましょう。


    具体的な環境構築の手順は割愛しますが、筆者が実際に構築した際のザックリ構成例を紹介します。
    • テスト対象環境(負荷を受ける側)
      項目 内容
      インフラ AWS Fargate を使用、ステージング環境内にテスト専用環境を構築
      ネットワーク セキュリティグループの設定を変更し、後述する「負荷送信環境」からのインバウンド通信を許可
      アプリ 本番と同等の Docker イメージをデプロイ
    • 負荷送信環境(負荷をかける側)
      項目 内容
      インフラ AWS EC2 インスタンスを新規構築
      (テスト対象環境と別の VPC で構築)
      セットアップ EC2 インスタンスに Git および Docker をインストール
      デプロイ - 負荷テスト用のコードを管理しているリポジトリを git clone
      - 上記リポジトリから Docker イメージをビルド
      - コンテナを起動し、テストシナリオを実行

テスト実施

実際に負荷をかけてシステムのパフォーマンスや安定性を確認します。テスト計画とテストシナリオが正しく機能しているかを検証する段階のため、慎重に進めます。失敗してもすぐに停止できるよう準備し、モニタリングを徹底しましょう。

主なポイントは以下の通りです。

  1. 負荷の実行
    負荷テストツールから実際にリクエストを送信します。

    # 例:Gatling の場合
    mvn gatling:test
    # または 
    gatling.sh -s SimulationClass
    

     

  2. リアルタイムモニタリング
    テスト中は前述のメトリクスを監視します。
    異常を即座に検知し、テストを安全に停止できるよう準備してください。
     

  3. インフラ側(サーバー/コンテナ/クラウド)
    CPU 使用率、メモリ使用率、ネットワーク I/O、オートスケーリング状況を監視。クラウド環境の場合、標準の監視ダッシュボードや Container Insights を活用すると詳細が把握しやすいです。
     

  4. アプリケーション側
    アプリログ、クエリログ、レスポンスタイム、DB クエリ実行時間などを追跡。APM ツールやフレームワーク内蔵の Profiler を併用すると内部挙動がわかりやすいです。
     

  5. その他おすすめ
    ロードバランサーのアクセスログ、データベースの Performance Insights(クエリ遅延や CPU 待機)、ネットワーク遅延などの外部要因も並行して確認しましょう。
     

テスト終了後はログを保存し、分析・レポートを行います。

分析・レポート

テスト結果を非機能要件と比較しながらボトルネックを特定します。
主なポイントは以下の通りです。

  1. 結果の視覚化
    負荷テストツールのレポートを活用してメトリクスをグラフ化します。
    Gatling の場合、HTML レポートでレスポンスタイム分布、エラー率、RPS の推移を一目で確認できます。
    【Gatling HTML レポートのサンプル】
    915D084B-9F50-4CBC-A9FF-9E5F06224784_1_201_a.jpeg
    他、AWS 環境であれば CloudWatch ダッシュボードを併用して、タスク数や CPU の変化を確認できます。
     

  2. 非機能要件との比較
    事前に定義した成功基準と結果を照合します。
    テストの合否を明確にし、チームに共有しやすいレポートを作成します。
     

  3. ボトルネック特定
    異常箇所をピンポイントで探します。

    例:レスポンスタイムが遅い
    → New Relic や Datadog のトレースツールで DB クエリを調べる
    
    例:エラー率が高い
    → ログ解析(CloudWatch Logs Insights など)で原因特定    
    

     

  4. 分析方法の考え方
    データを多角的に見るためのアプローチを以下に記載します。

    アプローチ 内容
    トレンド分析 負荷増加時のメトリクス変化をグラフで確認。
    例:RPS 増加で CPU が100%に達するポイントを特定し、スケーリング閾値を調整
    相関分析 メトリクス間の関係を調べる。
    例:DB クエリ時間とレスポンスタイムの相関が高い場合、クエリ最適化を優先。
    閾値比較 事前定義した非機能要件を超えた箇所をハイライトし、失敗要因をリストアップ。
    改善提案 ボトルネックに基づき、具体策を推奨。
    例:コード最適化、インフラ強化、キャッシュ導入など
  5. イテレーションとベストプラクティス
    問題修正後、再テストを実施して改善効果を検証します。 
     

レポートはグラフ付きのドキュメントでチーム共有し、次のアクションを明確にします。このフェーズを繰り返すことで、システムはどんどん強靭になります。

おわりに

負荷テストは一度やって終わりではなく、改善と検証を繰り返すプロセスです。最初は設定やシナリオ作成に戸惑うかもしれませんが、一度環境を作ってしまえば、システムの「限界」を知るための強力な武器になります。この記事が、これから負荷テストに挑む方の第一歩になれば幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?