GatlingでAPIの負荷テストを実施する:サンプルコードあり

# 動機・背景

- Webサービスのバックエンドで利用するAPIの負荷テストを行いたい
- [Gatling](https://gatling.io/)というツールが良さそうだったので使ってみた備忘録
  - Pythonベースの[Locust](https://locust.io/)は以前使ったことがあったので今回は別のものを

# 対象読者

- 開発したAPIの負荷テストを実施したい方


# 検証環境

ホスト環境

- CentOS 7.4.1708
- Docker 18.09.3

[公式?のDockerイメージ](https://hub.docker.com/r/denvazh/gatling)の環境

- Gatling 3.2.1 (Scala 2.12)

# 準備

## Docker

[公式?のDockerイメージ](https://hub.docker.com/r/denvazh/gatling)を使います。


## Scala

Dockerを使うのでローカル環境でScalaを用意しなくても何とかなりますが、[Intellij で SCALA を始める](https://docs.scala-lang.org/ja/getting-started/intellij-track/getting-started-with-scala-in-intellij.html)などに沿って環境を準備しておくと、コードの確認等がスムーズです。

sbt-based Scala projectを作成します。


# コード

csvファイルから読み込んだデータをランダムに流し込んでAPIのエンドポイントをテストするサンプルコードです。

https://github.com/kazuhirokomoda/load-test-template

## データ

```:src/main/resources/data/search.csv
cookie,query
cookie 1,test
cookie 2,iPhone SE
cookie 3,wallet
cookie 4,scarf
cookie 3,banana
cookie 2,men's shoes
cookie 1,socks
cookie 5,microSD
```

## Scalaコード

- ビジネスプロセスごとにobject(Scalaのシングルトン)に分割しておくと見通しがよく[コードの再利用もしやすくなります](https://gatling.io/docs/current/advanced_tutorial#step-01-isolate-processes)
- ```constantUsersPerSec```, ```rampUsersPerSec```, ```nothingFor```などのDSLを使って、定義したシナリオにユーザを注入していきます。
  - 一定数のユーザを注入したり、注入するユーザの数を徐々に増やしたり

```scala:src/main/scala/template/LoadTest.scala
package template

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class LoadTest extends Simulation {

  val feeder = csv("search.csv").random

  val httpProtocol = http
    .baseUrl("http://example.com")
    .acceptHeader("application/json")
    .connectionHeader("keep-alive")

  object Search1 {
    val search = feed(feeder)
      .exec(addCookie(Cookie("cookie", "${cookie}")))
      .exec(http("Search1")
        .get("/api/v1/search1")
        .queryParam("query", "${query}")
        .header("Keep-Alive", "150")
        .header("Content-Type", "application/json")
      )
  }

  object Search2 {
    val search = feed(feeder)
      .exec(addCookie(Cookie("cookie", "${cookie}")))
      .exec(http("Search2")
        .get("/api/v1/search2")
        .queryParam("query", "${query}")
        .header("Keep-Alive", "150")
        .header("Content-Type", "application/json")
      )
  }

  def run(): Unit = {
    val scenarioSearch1 = scenario("scenario Search1")
      .exec(Search1.search)
    val scenarioSearch2 = scenario("scenario Search2")
      .exec(Search2.search)

    val stepInjections = Seq(
      nothingFor(5 seconds),
      constantUsersPerSec(1) during (3 seconds),
      constantUsersPerSec(2) during (3 seconds),
      constantUsersPerSec(3) during (3 seconds),
      constantUsersPerSec(4) during (3 seconds),
      constantUsersPerSec(3) during (3 seconds),
      constantUsersPerSec(2) during (3 seconds),
      constantUsersPerSec(1) during (3 seconds)
    )

    val waveInjections = Seq(
      nothingFor(5 seconds),
      rampUsersPerSec(1) to 4 during (9 seconds),
      rampUsersPerSec(4) to 1 during (9 seconds)
    )

    setUp(
      // will be tested in parallel
      scenarioSearch1.inject(stepInjections),
      scenarioSearch2.inject(waveInjections)
    )
  }

  run()

}
```

実行は、こんなイメージ。
存在しないAPIなのでこのままでは動作確認できないですが...

```bash:run.sh
#!/bin/sh

if [ $# -ne 0 ]; then
  echo "No arguments accepted."
  exit 1
fi

mkdir -p "$PWD/results"

docker run -it --rm \
    -v $PWD/src/main/resources/opt/gatling/user-files/resources \
    -v $PWD/src/main/scala:/opt/gatling/user-files/simulations \
    -v $PWD/results:/opt/gatling/results \
    denvazh/gatling:3.2.1
```

# まとめ

Scalaベースの負荷テストツール[Gatling](https://gatling.io/)を試したので、備忘録としてサンプルコードとともに紹介しました。
負荷テストをコードとして残しておけるのは便利ですね。
[チートシート](https://gatling.io/docs/current/cheat-sheet/)があるのはありがたいですが、そこから詳しいコード例に飛べないのが玉にキズだなと思いました。

# 参考・関連

- [Gatling公式ドキュメント](https://gatling.io/docs/current/)