これは Money Forward Advent Calendar 2019 🎄 14日目の記事です。
こんにちは! @machisukeです。
マネーフォワードでは、半年に1回全社員集まっての半期総会を開催しています。
そして昨日、2019年12月13日がちょうど半期総会でした。
半期総会後の全社懇親会 で、僕たちマネーフォワード新卒はあるリベンジを果たそうとしていました
もうサーバーは落とさない。
今年6月に開催された半期総会で、僕たちは懇親会のコンテンツとして「MFクイズダービー」を担当しました。
スマホを使ってリアルタイムに順位が発表されるという内容に大盛り上がりでコンテンツはスタート。
会場の盛り上がりを見た僕たちは、作った甲斐があったなあと安堵していました。しかしその直後に事件は起きます。300名を超える参加者にサーバーが耐えきれず、途中でシステムが止まっていたのです。詳細は弊社のエンジニアブログ「新卒が社内懇親会アプリを開発したら、障害対応まで経験できた話」をぜひ読んでみてください。
このままでは終われないと、半年後の全社懇親会でのリベンジを心に決めました。
リベンジの過程で、僕は負荷テストを実施し、その時使った「JMeter」がとても便利で面白かったので、皆さんに手順を共有したいと思います。
(※ちなみに、今回の懇親会が成功したのかどうかは誰かがブログを書くと思うので楽しみに待ちましょう。)
JMeter上でのテスト計画と結果のイメージ
このような感じで、JMeterを使ってクイズの参加登録(sign_up)、クイズ取得、クイズ回答などが正しく動作しているか検証できます。本番は50チームで行いますが、テストは300チームで行いました。
Railsアプリの負荷テストに挑戦してみよう
今回は負荷テストを検証するアプリケーションとしてRailsチュートリアルで作成するSampleAppを拝借したいと思います。
SampleAppはTwitterのように「Micropost」を投稿するサービスです。
30ユーザーを同時アクセスさせ、1秒あたり1投稿させても、アプリは落ちることなく動き続けるでしょうか!?
環境
- Mac OS Mojave
- JMeter 5.2.1
- ruby 2.6.5
- rails 5.1.2 (sample_appの最新版に合わせました)
手順
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に変更を加えます。
①、②の変更を行ってください。
# 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
HTTP Request Defaults作成
Test Plan 右クリック > Add > Config Element > HTTP Request Defaults
起動しているサーバーのアクセス情報を入れます。
Thread Gropu(ユーザーグループ)の作成
Test Plan 右クリック > Add > Threads (Users) -> Thread Group
同時にアクセスするユーザー数を適当に決めます。
今回は、30人のユーザーが30秒の間に操作を開始するという設定にします。
ユーザ毎に登録内容を変える準備
i番目のユーザーは
name: name_i
email: name_i@example.com
password: password_i
としましょう。
Test Plan 右リクック > Add > Config Elemennt > Counter
coutnerという変数名で取得できるようにします。
4. JMeterでユーザー登録、ログインさせる
ユーザー登録・ログインのリクエストをグルーピングする
Thread Group 右クリック > Add > Logic Controller > Simple Controller
ユーザー登録(sign_up)フォームの取得
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_upフォーム取得
にします。
sign_upフォームが表示されるURLは
http://localhost:3000/signup
なので、Pathにsignup
を入力します
正しくリクエストできているか検証
Test Plan 右クリック > Add > Listener > View Results Tree
を追加
JMeterの上側の緑色の三角ボタンを押してテストをスタートするとリクエスト結果が出ます。
AuthenticityTokenの取得
今回使うRailsアプリはCSRF対策が施されているので、AuthenticityTokenをリクエストパラメーターに含める必要があります。
AuthenticityTokenは、「登録フォーム取得」のレスポンスに含まれています。
これは、Regular Expression Extractorで抜き出します。
sign_upフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。
ユーザー登録
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_up
にします。
urlencodeにチェック入れるのを忘れないようにしましょう。
ログイン状態を保持できるようにする(Cookie)
Test Plan 右クリック > Add > Config Element > HTTP Cookie Manager
追加するだけでOKです。
ログイン
登録同様、下記の手順を行います。
- フォーム取得
- authenticity_token抜き出し
- ログイン
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_inフォーム取得
にします。
sign_inフォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。
sign_up/sign_in 右クリック > Add > Sampler > HTTP Request
名前はsign_in
にします。
試しにログインしてみる
ブラウザでhttp://localhost:3000/login
を開き、適当なユーザーでログインして、http://localhost:3000/users
にアクセスしてみる。
※テストを実行すると、ユーザーが登録されてDBに保存されます。
テストの度にDBをリセットするとユーザー登録から正しくテストを行うことができます。
$ bundle exec rails db:migrate:reset
5. 各ユーザーに、Micropost(Tweet)を50件登録させる
sign_in/sign_up同様、下記の手順でMicropostを投稿します。
- フォーム取得
- authenticity_token抜き出し
- 登録
投稿リクエストをグルーピングする
Thread Group 右クリック > Add > Logic Controller > Simple Controller
さらに
Thread Group 右クリック > Add > Logic Controller > Loop Controller
名前は50回投稿
にします
投稿ごとにメッセージを分けるための変数を用意
50回投稿 右リクック > Add > Config Element > Counter
micropost_coutnerという変数名で取得できるようにします。
投稿フォーム取得
50回投稿 右クリック > Add > Sampler > HTTP Request
名前は投稿フォーム取得
にします。
URLはhttp://localhost:3000
なので、pathは何も入力しません。
AuthenticityToken取得
投稿フォーム取得 右クリック > Add > Post Processors > Regular Expression Extractor
tokenを正規表現でキャプチャして、authenticity_tokenという変数に代入します。
投稿
50回投稿 右クリック > Add > Sampler > HTTP Request
名前は投稿
にします。
投稿間隔の調整
50回投稿 右クリック > Add > Timer > Constant Timer
投稿間隔を一人につき、1秒1回に調整します。
6. Listener(レポート機能)の設定
テストが終わるまでのレスポンスタイムの遷移を見る
Test Plan 右クリック > Add > Listener > jp@gc - Response Times Over Time
7. テスト実行
JMeterの上部にある、緑色の三角ボタンを押したら始まります。
8. テスト結果
サーバは落ちませんでした
ただ、ところどころピークが生まれていて、ログを見るとDBのRollbackが行われている様子。同時書き込みに弱いSQLiteだから発生したRollbackでしょうか・・?
まとめ
JMeterは気軽に負荷テストを行えるツールでした。
どのようにインフラ/実装を変えれば、レスポンスタイムが短くなるかを考えてみるのは、課題として面白そうですね。