Elixirで負荷テストツールを作ってみた
はじめに
社内ISUCON的な物をしてみたいと思いたちここ最近準備をしています。
仕様策定やアプリのリファレンス実装は良いとして考えたのは負荷テストツールです。
選択肢としては3つありました
- JUnitやGatlingなどの負荷テストツールを使う
- ISCONの負荷テストツールを流用する
- 自作
知識の有効活用という意味では1, 先人に学ぶという意味では2を選択するべきでしたが、まあ半ば趣味優先で面白そうだったので自作してみる事にしました。負荷テストツールを真面目に書いた事なかったですし。
もちろん、自分で1から作れば自由度が高いのでやりたい事をもっともシンプルに実現できるだろうという算段も一応あります。
そこで悩んだのが言語です。手慣れたJavaやRubyで書くのが一番楽そうですが、負荷テストツールという特性上たくさんの並行処理を管理する必要がありました。それならそう言うのが得意なElixirで行こうと決めました。Goも考えたんですが一応以前触った事はあったのでせっかくなのでかねてより興味のあったElixirで。
とりあえず今回のコードはこちら。
GitHub - koduki/load-generator
負荷テストツールに求める事
今回負荷テストツールには以下の点を求めました
- シナリオベースのテスト。値の検証ができる事
- 1万程度のユーザ数を取り扱える事(# 同時アクセス数では無くユーザ数)
- グラフィカルなレポーティング機能
1と3に関しては概ね完成したものの2に関しては上手く出来てないです。元々、軽量プロセスなら大量プロセスを同時に扱っても死なないかな、と思ってElixirにしたのですが
基本構造
LoadGenerator.App
が本体です。こちらにテストシナリオを書いていきます。
parallel(users_num, fn -> continue(duration, run_user(base_url, test_id, merchant_ids)) end)
parallel関数でユーザ数分プロセスを立ち上げcontinue関数で指定の時間まで再帰で処理を繰り返します。
テストシナリオ側は以下のように普通にプログラムでテスト内容を記述していきます。
# pay amount1 = :rand.uniform(100_000) _tid1 = req_authorize(base_url, test_id, account_no, rand_select(merchant_ids), amount1, datetime) amount2 = :rand.uniform(10_000) _tid2 = req_authorize(base_url, test_id, account_no, rand_select(merchant_ids), amount2, datetime) # show summary usedMonth = Timex.format!(datetime, "%Y%m", :strftime) body = req_summary(base_url, test_id, account_no, usedMonth) count = count + 2 sum = sum + amount1 + amount2 # validate assert = assert_template(body) it("validate transaction summary", [ assert.("accountNo", :should_be, account_no), assert.("count", :should_be, count), assert.("summary", :should_be, sum) ])
assertは検証結果が異なる時に例外を発生するメソッドです。最初はテスト失敗数のカウントとか考えてたのですが500エラーならともかく今回のように計算結果のエラーになるPGは負荷テストを続ける意味がないので例外でアプリを殺す方向に切り替えました。
今の所、同値を判定する命令しか追加していませんが、バリエーションはいくつか増やしていく予定
3のグラフィカルなレポーティングに関してはGCPに投げてBigQuery + Metabaseで解析しています。
ローカルで観れた方が便利な時も多いのでローカルに出力するコードとログのビューアも一応入れています。
まとめ
初めてのElixirアプリですがElixir自体は個人的には結構手に馴染んだ感じがしました。
まだβどころかαレベルの品質なので精度をあげてアップデートしていきたいと思います。