🎃

負荷テストツールのGatlingを使ってみた

に公開

はじめに

はじめまして、三菱UFJインフォメーションテクノロジーの鬼頭です。

最近、業務改善としてJavaのパフォーマンス計測の見直しについて検討しており、負荷テストを行うツールについて知る機会がありました。
これまではJMeterのみを使用しておりましたが、今回は数ある負荷テストツールの中から社内でもモダナイズ推進されているGatlingをJMeterと比較してどちらがツールとして利用しやすいのか比較調査し、採用したツールを実際に使用してみました。

負荷テストにおける最適なテストツールとは?

負荷テストにおける最適なテストツールを選定するために、まず以下の4つの基本要件を満たしていることが重要であると分かりました。

  1. 可読性
  2. 構成可能性
  3. 正確な計測
  4. 分散負荷の生成

これらの要件を踏まえ、GatlingとJMeterについて比較してみます。

GatlingとJMeterの比較

GatlingとJMeterを以下の4つの基本要件に基づいて比較してみました。

可読性

JMeter

  • シナリオをXMLで表現しており、何をテストしているのか理解することが比較的簡単です。しかし、新しいステップを追加したり、現在のステップをパラメータ化したりすると、内容が複雑化し可読性が低下します。
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.3"></jmeterTestPlan>
  <hashTree></hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"></TestPlan>
      <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"></elementProp>
        <collectionProp name="Arguments.arguments"></collectionProp>
      </elementProp>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
      ...
	  That's a very, very long XML.
      ...     

Gatling

  • シナリオをソースコードで表現するため、全体の流れが明確に分かります。
  • ソースコード(Scala)で書かれており、DSL(ドメイン特化言語)を使用してテストシナリオを記述するためScalaの文法やDSLの設計が正しくできていれば可読性が向上します。
val scn =
    scenario("Example scenario")
      - exec(http("go to main page").get("/"))
      - exec(http("find computer").get("/computers?f=macbook"))
      - exec(http("edit computer").get("/computers/6"))
      - exec(http("go to main page").get("/"))
      - repeat(4, "page") {
        exec(http("go to page").get("/computers?p=${page}"))
      }
      - exec(http("go to create new computer page").get("/computers/new"))
      - exec(
        http("create new computer")
          - post("/computers")
          - formParam("name", "Beautiful Computer")
          - formParam("introduced", "2012-05-30")
          - formParam("discontinued", "")
          - formParam("company", "37")
      )
構成可能性

JMeter

  • GUIで操作するため、繰り返し処理をするシナリオを構成する場合、コピー&ペーストが多くなり、エラーが発生しやすくなります。

Gatling

  • プログラミングの知識や技術があれば、一度作成したコードの部分を他の場所でも繰り返し使えるように作成することができます。
  • シナリオを細かく分割し、組み合わせることで、より複雑なシナリオを作成することができます。
正確な計測

JMeter

  • 正確な計測が可能でありますが、グローバル・パーセンタイルの精度がGatlingより少し劣っています。

Gatling

  • 正確な計測が可能であり、HdrHistogramライブラリを使用してパーセンタイルを計算し、データの分布や特性を迅速に把握することが可能です。
分散負荷の生成

JMeter

  • 多くのマシンから同時に分散テストを実行するオプションがありますが、手動でテスト設定する必要があります。

Gatling

  • 上記のJmeterと同様であるため、ツール間での差分はありません。

GatlingとJMeterの比較(メリット・デメリット)

JMeterとGatlingについてそれぞれメリット・デメリットを表にして記載しています。
先ほど紹介した4つの基本要件以外で挙げております。

項目 JMeter Gatling
メリット - オープンソースで無料
- 直感的なGUI
- 豊富なプラグイン
- 多くのプロトコルをサポート
- スクリプトの再利用が容易
-オープンソースで無料
- 高パフォーマンス
- CI/CDパイプラインに簡単に組み込むことが可能
- リアルタイムレポート生成が可能
- 軽量で効率的な負荷テスト
デメリット - 大規模な負荷テストでのパフォーマンス問題
- 複雑なシナリオでのスクリプトの複雑さ
- リソース消費が多い
- リアルタイムレポートの制限
- 学習曲線が急
- GUIがないためスクリプト作成に時間がかかる
- プラグインの数が少ない
- サポートしているプロトコルが少ない

比較結果

4つの観点およびその他でのメリット・デメリットを比較してみた結果、
コード単位でメンテナンスがしやすく、複雑なシナリオにも対応可能なGatlingを採用しようと思います。
デメリットで挙げた点を踏まえても、CI/CDパイプラインに組み込み可能で時短かつ効率的に負荷テストを実行できる点は
開発負荷を下げることができ、生産性向上にもつながります。
またテストシナリオをコードとして管理できるため、バージョン管理システム(Gitlab)を使用してシナリオを管理・共有することができるためメンテナンスを効率的かつ効果的に行うことが可能です。
以上のことからGatlingを採用することとします。

Gatlingを実行してみた

Gatlingを実行してみます。実行環境は以下の通りです。

  • OS : Windows 10
  • Visual Studio Code:1.94.2
  • Node.js:20.16.0
  • gatling:3.7.6
  • Apache Maven:3.9.9

Javaのサンプルコードは以下となります。

package com.example;

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import java.time.Duration;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

public class PenguinKeyStrokeSimulation extends Simulation {
        HttpProtocolBuilder httpProtocol =
                        http.baseUrl("実行URL").inferHtmlResources();
        ScenarioBuilder scn = scenario("PenguinScenario")
                        // 新規ペンギン登録フォームページへアクセス
                        .exec(http("LoadFormPage").get("/new").check(status().is(200))).pause(1)
                        // フォームからペンギンの名前とタイプをPOSTする
                        .exec(http("SubmitForm").post("/update").formParam("name", "ガトリングペンギン")
                                        .formParam("type", 4).check(status().is(200)))
                        .pause(1)
                        // TOPページにアクセスし上で登録されたペンギンの名前で要素を検索。IDを取得する
                        .exec(http("CheckPenguin").get("/").check(regex(
                                        "<td id=\"list-table-body-name-(\\d+)\">ガトリングペンギン</td>")
                                                        .find().saveAs("extractedId")))
                        .pause(1)
                        // 取得したIDで削除処理を行う
                        .exec(http("DeletePenguin").get("/delete/#{extractedId}")
                                        .check(status().is(200)))
                        .pause(1);
        {
                setUp(scn.injectOpen(rampUsers(10).during(5)).protocols(httpProtocol));
        }
}

実行方法はVisual Studio Code内のターミナルにて以下コマンドを実行になります。

mvn gatling:test

HTMLのファイルを開くと以下の画面が出力されます。

出力されている項目については主に以下となります。

  • response time XX percentile:
     計測したデータをレスポンスタイムの小さい順に並べたときに、全体のXX%に位置するレスポンスタイム(ms)

  • mean requests/sec:
    秒間あたりに処理したリクエスト数の平均値

  • KO:
    NGを表す

  • グラフにあるt < 800 ms等の指標値:
     レスポンスタイム境界値(800ms、1200ms)を表す

まとめ

今回は負荷テストツールについて調査し、その中の1つであるGatlingについて触れてみました。実際にサンプルで動かしてみたところ、JMeterと比較して操作が分かりやすく、グラフが自動生成されることでテスト結果も可視化しやすいと感じました。

シナリオ生成の方法やJenkinsやGitLab CIなどのCI/CDツールにGatlingを組み込み、コードの変更がある度に自動で負荷テストを実行する方法については応用編として今後試してみたいと思います。

なお、本記事の内容は私が調査した情報に基づいており、正確性を保証するものではありません。情報の利用に際しては、自己責任でお願いいたします。

参考文献

Discussion