懇親会での障害対応はもうこりごり！JMeterを使ったRailsアプリの負荷テストの流れ

これは [Money Forward Advent Calendar 2019 🎄](https://adventar.org/calendars/4136) 14日目の記事です。


こんにちは！ @machisukeです。
マネーフォワードでは、半年に1回全社員集まっての半期総会を開催しています。
そして昨日、2019年12月13日がちょうど半期総会でした。


半期総会後の**全社懇親会** :beers: で、僕たちマネーフォワード新卒はあるリベンジを果たそうとしていました :fire: 


# もうサーバーは落とさない。
[今年6月に開催された半期総会](https://www.wantedly.com/companies/moneyforward/post_articles/175842)で、僕たちは懇親会のコンテンツとして「MFクイズダービー」を担当しました。

スマホを使ってリアルタイムに順位が発表されるという内容に大盛り上がりでコンテンツはスタート。
会場の盛り上がりを見た僕たちは、作った甲斐があったなあと安堵していました。しかしその直後に事件は起きます。300名を超える参加者にサーバーが耐えきれず、途中でシステムが止まっていたのです。詳細は弊社のエンジニアブログ[「新卒が社内懇親会アプリを開発したら、障害対応まで経験できた話」](https://moneyforward.com/engineers_blog/2019/07/17/induction-training-appli-2019/)をぜひ読んでみてください。



このままでは終われないと、半年後の全社懇親会でのリベンジを心に決めました。

リベンジの過程で、僕は負荷テストを実施し、その時使った「JMeter」がとても便利で面白かったので、皆さんに手順を共有したいと思います。
(※ちなみに、今回の懇親会が成功したのかどうかは誰かがブログを書くと思うので楽しみに待ちましょう。)

#### JMeter上でのテスト計画と結果のイメージ

このような感じで、JMeterを使ってクイズの参加登録(sign_up)、クイズ取得、クイズ回答などが正しく動作しているか検証できます。本番は５０チームで行いますが、テストは300チームで行いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/fa1c1af5-d547-0b93-364c-c30503669cd2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/8ebe254c-42ec-0d95-5668-de56cbd01965.png)




# Railsアプリの負荷テストに挑戦してみよう :rocket: 
今回は負荷テストを検証するアプリケーションとしてRailsチュートリアルで作成する[SampleApp](https://github.com/yasslab/sample_apps)を拝借したいと思います。
SampleAppはTwitterのように「Micropost」を投稿するサービスです。

30ユーザーを同時アクセスさせ、1秒あたり1投稿させても、アプリは落ちることなく動き続けるでしょうか！？

SampleAppの画面 :point_down: 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/6d6b1280-fc1e-e0d5-0b36-bb50caf4fb52.png)




## 環境

- Mac OS Mojave 
- JMeter 5.2.1 
- ruby 2.6.5
- rails 5.1.2 ([sample_app](https://github.com/yasslab/sample_apps)の最新版に合わせました)

## 手順
### 1. jmeterインストール
```
$ brew install jmeter
```

### 2. Railsアプリケーションの起動

```
$ git clone https://github.com/yasslab/sample_apps.git
$ cd sample_apps/5_1_2/ch14
$ bundle install
$ bundle exec rails db:create
$ bundle exec rails db:migrate
```

今回はメール認証を強制的にスキップするため、app/controllers/users_controller.rbに変更を加えます。
①、②の変更を行ってください。

```ruby:app/controllers/users_controller.rb
  # POST /users
  def create
    @user = User.new(user_params)
    if @user.save # => Validation
      # Sucess
      # ①↓コメントアウト
      #@user.send_activation_email
      # ②↓追加
      @user.activate        
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      # Failure
      render 'new'
    end
  end
```

起動

```
$ bundle exec rails s
```
http://localhost:3000 にアクセスすると画面が開かれるはずです。


### 3. JMeter起動

```
$ jmeter
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/f97a5166-7cda-5c78-ed26-2271f163b79e.png)

##### HTTP Request Defaults作成
`Test Plan 右クリック > Add > Config Element > HTTP Request Defaults`
起動しているサーバーのアクセス情報を入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/e26d8c6d-907a-65bc-000c-bb5cb010b74e.png)




##### Thread Gropu(ユーザーグループ）の作成
`Test Plan 右クリック > Add > Threads (Users) -> Thread Group`
同時にアクセスするユーザー数を適当に決めます。
今回は、30人のユーザーが30秒の間に操作を開始するという設定にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/caeaa6b0-2c3b-7ccf-ffc5-54a7a9b79c2e.png)



##### ユーザ毎に登録内容を変える準備
i番目のユーザーは

```
name: name_i
email: name_i@example.com
password: password_i
```

としましょう。

`Test Plan 右リクック > Add > Config Elemennt > Counter`
coutnerという変数名で取得できるようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/a92a8640-7fc9-0c8d-631e-4c3eb8e44fdc.png)



### 4. JMeterでユーザー登録、ログインさせる

##### ユーザー登録・ログインのリクエストをグルーピングする
`Thread Group 右クリック > Add > Logic Controller > Simple Controller`

名前は`sign_up/sign_in`にします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/7668ab23-c075-e0ee-f3ed-072c7b39b494.png)


##### ユーザー登録(sign_up)フォームの取得
`sign_up/sign_in 右クリック > Add > Sampler > HTTP Request`

名前は`sign_upフォーム取得`にします。
sign_upフォームが表示されるURLは
`http://localhost:3000/signup`なので、Pathに`signup`を入力します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/f497ff11-a4a7-a3be-53a9-2787fb9e44e0.png)


##### 正しくリクエストできているか検証
`Test Plan 右クリック > Add > Listener > View Results Tree`を追加

JMeterの上側の緑色の三角ボタンを押してテストをスタートするとリクエスト結果が出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/07c9b3e4-798f-1c45-8215-9b45d0cea461.png)



##### AuthenticityTokenの取得
今回使うRailsアプリはCSRF対策が施されているので、AuthenticityTokenをリクエストパラメーターに含める必要があります。
AuthenticityTokenは、「登録フォーム取得」のレスポンスに含まれています。

これは、Regular Expression Extractorで抜き出します。


`sign_upフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor`
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/a539d90f-2113-0f37-321f-ab830091b78c.png)

##### ユーザー登録
`sign_up/sign_in 右クリック > Add > Sampler > HTTP Request`
名前は`sign_up`にします。
urlencodeにチェック入れるのを忘れないようにしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/ced31fe6-d074-f32d-ce0b-92a459023a83.png)



##### ログイン状態を保持できるようにする（Cookie)
`Test Plan 右クリック > Add > Config Element > HTTP Cookie Manager`

追加するだけでOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/d7de4493-fca9-4fbb-60da-e2e1442e215c.png)

##### ログイン

登録同様、下記の手順を行います。

1. フォーム取得
1. authenticity_token抜き出し
1. ログイン


`sign_up/sign_in 右クリック > Add > Sampler > HTTP Request`
名前は`sign_inフォーム取得`にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/02d8e4d6-6dd1-168b-6c87-874382df922d.png)

`sign_inフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor`
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/8b91c791-ddc7-1063-6f24-610b6e8ba66d.png)

`sign_up/sign_in 右クリック > Add > Sampler > HTTP Request`
名前は`sign_in`にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/1ea0623c-ae5d-6c08-7cf2-719f581e3383.png)


##### 試しにログインしてみる
ブラウザで`http://localhost:3000/login`を開き、適当なユーザーでログインして、`http://localhost:3000/users`にアクセスしてみる。

ユーザーが作られているのがわかりますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/95ca7db5-5970-9b95-6ede-ebe1b8881b3e.png)

※テストを実行すると、ユーザーが登録されてDBに保存されます。
テストの度にDBをリセットするとユーザー登録から正しくテストを行うことができます。

```
$ bundle exec rails db:migrate:reset
```

### 5. 各ユーザーに、Micropost(Tweet)を5０件登録させる
sign_in/sign_up同様、下記の手順でMicropostを投稿します。

1. フォーム取得
1. authenticity_token抜き出し
1. 登録

##### 投稿リクエストをグルーピングする

`Thread Group 右クリック > Add > Logic Controller > Simple Controller`

名前は`Micropost投稿`にします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/ca9de88d-8804-ac2d-9165-e3b1573c30a7.png)

さらに
`Thread Group 右クリック > Add > Logic Controller > Loop Controller`

名前は`50回投稿`にします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/9423d38f-363a-0f48-0f74-c1ac207016ea.png)

##### 投稿ごとにメッセージを分けるための変数を用意
`50回投稿 右リクック > Add > Config Element > Counter`
micropost_coutnerという変数名で取得できるようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/9af595a8-8c67-bce4-79ab-e289600fbf23.png)

##### 投稿フォーム取得
`50回投稿 右クリック > Add > Sampler > HTTP Request`
名前は`投稿フォーム取得`にします。
URLは`http://localhost:3000`なので、pathは何も入力しません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/58c8c276-4b62-6800-63de-52dd748e1918.png)

##### AuthenticityToken取得

`投稿フォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor`
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/2f4cc810-141c-7570-c43e-9198f850e696.png)

##### 投稿

`50回投稿 右クリック > Add > Sampler > HTTP Request`
名前は`投稿`にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/c327365a-11cb-0b56-58fd-80e5198b073e.png)

##### 投稿間隔の調整
`50回投稿 右クリック > Add > Timer > Constant Timer`
投稿間隔を一人につき、1秒１回に調整します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/73e6175b-6225-0eec-7881-57eb49881726.png)

### 6. Listener(レポート機能)の設定

#### テストが終わるまでのレスポンスタイムの遷移を見る
`Test Plan 右クリック > Add > Listener > jp@gc - Response Times Over Time`


### 7. テスト実行
JMeterの上部にある、緑色の三角ボタンを押したら始まります。


### 8. テスト結果
サーバは落ちませんでした :tada:
ただ、ところどころピークが生まれていて、ログを見るとDBのRollbackが行われている様子。同時書き込みに弱いSQLiteだから発生したRollbackでしょうか・・？


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116472/a113eed2-6b01-dd04-38b9-4db83b2f646b.png)


# まとめ
JMeterは気軽に負荷テストを行えるツールでした。

どのようにインフラ/実装を変えれば、レスポンスタイムが短くなるかを考えてみるのは、課題として面白そうですね。