僕が負荷試験を担当する…ってコト!?k6使ったら泣いちゃうの回避できた話

はじまり

はじめまして、テクノロジー戦略室TQCチームの柳澤といいます。ちいかわ大好きエンジニアをしています。 TQCとはあまり聞き慣れない名前かもしれませんが、Total Quality Controlの略で全社的なシステム品質管理を担当しており、負荷試験や脆弱性診断などのテストを通じて各システムの品質向上に取り組むことも多いです。

かくいう私も負荷試験を担当することが多くなり、どうやって負荷試験をやっているのかと聞かれることが増えてきたため、自分が初めて負荷試験を担当した時を思い出しつつチームの負荷試験実施方法についてご紹介できればと思います。

さて、あれはTQCチームに入って数ヶ月、仕事にも慣れてきた頃…突然先輩に言われました。

「そろそろ柳澤さんに負荷試験やってもらおうかな!」

なんということでしょう。前職で負荷試験を担当していたのはエンジニア歴が20年くらいの熟練エンジニアばかり。チームに入ってきたばかりの私がそんな難易度高そうな仕事ができるだろうか。負荷試験をやったことがない私には最終的に失敗して「泣いちゃった!」なんて言われる未来しか見えません。

「わァ…あ…」なんとか言葉を捻り出し、申し訳ありませんがお断りしますの意を伝えたところ、「k6だったら簡単に負荷試験できるから大丈夫ですよ!ブラウザで見た内容をそのままテストシナリオにできるんで!」え、そうなの?それならなんとかなるかな…。こうして頭の中で「なんとかなれーッ!」と叫びながら実施する負荷試験が始まったのでした。

k6とは

タイトルにもある通り、TQCチームはk6というオープンソースの負荷試験ツールを使用しています。Go言語で作られていてパフォーマンスが良いのが特徴ですが、私たちエンジニアにとって嬉しいのは、テストシナリオをJavaScriptで書けること。普段フロントエンドやNode.jsで慣れ親しんだ言語で書けるので、学習コストが低く、直感的にシナリオを組み立てることができます。

主な特徴はこんな感じです。

  • 開発者フレンドリー: JavaScriptでシナリオを書けるほか、CLIツールが非常に使いやすいです。
  • パフォーマンス: Go言語製のため、少ないマシンリソースで高い負荷を生成できます。
  • 豊富な連携機能: 実行結果をGrafanaなどの外部サービスに連携すればリッチな可視化もできます。

これまで負荷試験というと、専門的なツールや複雑な設定が必要なイメージがありましたが、k6は「開発者が、開発プロセスの中で継続的に実施すること」を重視して作られているため、私のように初めて負荷試験を担当するような人でもとっつきやすい設計になっているのです。

har-to-k6を使ってテストシナリオを効率的に作る

先輩が言っていた「ブラウザで見た内容をそのままテストシナリオにできる」の正体がこれ、har-to-k6です。これは、ブラウザの開発者ツールで記録した通信内容(HARファイル)を、k6のテストシナリオに変換してくれる魔法のようなツールです。 使い方はとっても簡単。

1. ブラウザで操作を記録する
  • Firefoxの開発者ツールを開き、「Network」タブを選択します。(Chromeをご利用の方は適宜読み替えてください)

  • 設定ボタンから「Persist Logs」にチェックを入れ、記録を開始します。

  • 負荷試験の対象としたい一連の操作(例:ログインして、見たい記事へのリンクをクリックするなど)を実際に行います。

2. HARファイルを保存する
  • ここまでの操作でネットワークログが貯まったはずなので、それらを負荷試験対象サイトのドメインのログだけに絞り込むため、「Filter URLs」の枠内に「domain:hoge.jp」のように入力しておきます

  • ログの絞り込みができたらネットワークログが表示されているところで右クリックし、「Save all as HAR」を選択してHARファイルを保存します。

3. k6スクリプトに変換する
  • 保存したHARファイルをhar-to-k6コマンドで変換します。
har-to-k6 your-file.har -o loadtest.js

たったこれだけで、実際のブラウザ操作に基づいたリアルなテストシナリオの雛形が完成します。あとは生成されたloadtest.jsを少し手直し(不要なリクエストを削除したり、動的な値をパラメータ化したり)するだけで、すぐに負荷試験を開始できるのです。APIの仕様書を片手に、一つ一つリクエストを組み立てる…なんて手間が不要なので喜びがあります。

次のようなコマンドでテストシナリオを実行するだけで負荷試験が動き始めます。

k6 run loadtest.js

しかし、このままでは負荷試験としては十分ではありません。実際にこのまま実行した方はお気づきだと思うのですが、並列実行数=1、実行シナリオ数=1となるため負荷が全くかかっていない状態にあることが分かります。 ここでloadtest.jsのソースコード中のoptionsを以下のように変更してみましょう。

export const options = {
  discardResponseBodies: true,
  scenarios: {
    contacts: {
      executor: 'shared-iterations',
      vus: 20,
      iterations: 300,
      maxDuration: '300s',
      gracefulStop: '30s',
    },
  },
}

ここで重要なのはvusiterationsです。

vus: Virtual Usersの略。サイトに同時アクセスしているユーザー数と考えて良い。

iterations: 負荷試験シナリオが全体として回った数。300と指定するのであれば全VUsがシナリオを合計300回実行完了したら終了となる。

この2つを使いこなせれば多くの負荷試験課題に取り組めるようになります。

実行結果の集計がわかりやすい

k6は、実行後の結果サマリーが非常にわかりやすくまとまって出力されるのも魅力です。 実際にこちらの画像のような結果が標準出力で表示されます。

http_req_duration(リクエストの応答時間)の平均(avg)、中央値(med)、最大(max)、p(95)(95パーセンタイル値)などが一目でわかり、システムの性能を多角的に評価できます。

また、先述のvusとiterationsについては画像下部のvus_maxと(紛らわしいですが)iterationsに数値が反映されていることが分かると思います。

実際の運用のお話

さて、ここまでで負荷試験を実施して結果を出す、というところまではできるようになるはずです。そうなると、次はどうやって使えば効果的な負荷試験が実施できるのかという話になってきます。 あくまで私の経験的な持論にはなるのですが、負荷試験は試験回数を増やして負荷状況を立体的に把握していくということが大事になります。

ここで再びvusiterationsに焦点を当て、その値を調整しながら何度も負荷試験を実行してみましょう。例えばvus=10、iterations=100で実行したらシステムは負荷が小さくサクサク試験が動いていくはずです。 しかし、vus=100、iterations=500にして実行してみた時はどうでしょうか?あるいはvus=300、iterations=1000にして実行してみてください。かなり負荷が高くなって完了まで時間がかかってきたり、あるいはiterationsの完了数が全く増えないなどの変化が見えてくるのではないでしょうか。

もし想定通りの負荷状況になっていなければ、そこには何かボトルネックとなる要素があるはずです。例えば実はDBが負荷に耐えられなくなって処理が止まってしまっていたり、ネットワークのトラフィックが捌ききれなくなっていたりなど様々な可能性があります。このような隠れていた要素を明らかにすることに私は負荷試験の価値があると考えます。

もちろん、同時アクセス数=100に耐えられることが確認できればOK、というような要件の場合は単純にvus=100で実行してhttp_req_durationが大きく増えたりしないことが確認できれば完了にしても良いかもしれませんが、先述のような負荷状況の変化を把握できるようにしておけば、過剰でも過小でもない最適なインフラリソースを実現できているかといった結論に辿りつけるため、実際に負荷試験を始める方はまずはここを目指していくのが良いと思います。

気になる部分と課題

k6は素晴らしいツールですが、使っていく中で「こうだったらもっと良いのに…」と感じる部分もありました。

複数のシナリオやvus設定で実行して結果を一覧で見たい時に辛い

負荷試験を進めていくとvusを小さい値から大きい値にして変化を見たり、ユースケースに従って複数のテストスクリプトを作り実行するということが発生すると思いますが、そうなったときに前述の画像のような結果出力だけだと、まとめるのに苦労するようになります。

Grafanaなどの外部サービスと連携させていれば複数シナリオをまとめて把握するのも楽になるのかもしれませんが、私たちのチームではまだ連携するまでには至っていなかったため、標準出力の結果をスプレッドシートにまとめて関係者には共有していました。しかし、作業としては大変だったため、ここも自動化することが課題となっています。

認証情報の更新が大変

Webアプリケーションの負荷試験では、ログインして取得した認証トークン(JWTなど)を後続のリクエストに含める必要がありますが、このトークンの有効期限が短い場合、テストの途中でトークンが無効になっても自動で更新する仕組みは標準では提供されていないため、「リクエストが失敗したらトークンを再取得してリトライする」ということをなんとか実現していました。しかし、毎回それをやるのは手間なのでプラグインなどを利用してログイン情報の自動取得を実現したいと思っています。

また、ログイン情報の自動取得が実現できれば多数の別個のユーザーがWebアプリケーションに負荷をかけるというシナリオを作成できるようになるため、かなり負荷試験の幅が広がると考えており、それが今後解決するべき課題となっています。

おわりに

「負荷試験」と聞いただけで震えていた私ですが、k6har-to-k6のおかげで、なんとか負荷試験を完了させ、無事にタスクをやり遂げることができました。特に、実際のブラウザ操作を元にシナリオを自動生成できる手軽さは、負荷試験初心者にとって本当に心強い味方です。

もちろん、認証周りのように少し工夫が必要な場面もありますが、基本的な負荷試験であれば驚くほど簡単に始められます。もし、あなたがかつての私のように「負荷試験こわい…」と思っているなら、ぜひk6を試してみてください。

最後になりますが、TQCチームはシステム品質の向上を目指し全社的に活動できるメンバーを募集中です!システムの品質向上やテストに関心がある方はカジュアル面談もできますのでお気軽にご連絡ください!色々とお話しできることを楽しみにしています。

hrmos.co