k6で負荷テストを試す
負荷テストを実施する必要があって昔 JMeterは使ったことがあったので、今回はk6 を試してみる。
ゴールは ローカルから起動してテスト対象システムにブラウザ経由で試験が実施できること。負荷試験の具体的な性能目標は無いが k6でどのようなテストが行えるかを理解する。
また将来的なことを見据えて CIでも実施する方法も調べようとは思う
インストール
今回はMacOSに Homebrew でインストールする
チーム開発やCIでの実行はDockerを使った方が良いと思うけどそれは一通りテストした後にやってみる
❯ k6 version
k6 v1.1.0 (go1.24.4, darwin/arm64)
テストケースは JavaScript/TypeScript で書くようで、VSCodeとIntelliJにk6の拡張プラグインがある。
ただIntelliJのプラグインはサブスクリプションなので、今回はプラグインなしで進めてみて将来的にトライアルを試してみるって流れにしておく
負荷テスト用の開発環境を作成する
-
stress-test
ディレクトリを作りカレントディレクトリを移動する -
pnpm init
で package.json を作成し任意に設定を変更(自分の環境では事前にcorepackをglobal installしている -
pnpm -D typescript @types/k6
で、typescript と k6の型定義をインストール -
npx tsc --init
で tsconfig.json を作成。tsconfig.json は moduleをESNext
に変更しておく。サーバーサイドJSでは無い(CommonJSではない)ため
テストの作成
公式ドキュメント同じように my-first-testを作る。ただし、.js ではなく .tsとした
import http from 'k6/http';
import {sleep} from "k6";
export const options = {
iterations: 10
}
export default function () {
http.get("https://quickpizza.grafana.com");
sleep(1);
}
テストの実行は ターミナルを開いて k6 run
でテストファイルを指定する
こんな感じでテストが行えた。
テストのイメージとしては、1VU つまり 一人のバーチャルユーザーが getリクエストを10回実行した結果って感じかな
Checks
負荷テストの際に対象のリクエストから想定通りのレスポンスが返ってきているかが検証する関数。
HTTPステータスをチェックしたり、レスポンスBodyの内容をチェックすることで検証する。
通常のテストとは異なりこのチェックで失敗してもテストは停止(abort)したり終了(finish) はせずに 失敗としてカウントされ継続する。
成功/失敗のカウント数は、負荷テスト完了時のレポートで確認できる。また特定の回数失敗したらテスト自体をfailして止める場合は Thresholds と組み合わせると良さそう
Options
負荷テストの振る舞いを設定する。
Optionsの設定方法は様々あり、下に行くほど優先度が高い (2~5で設定されていないオプションは1のデフォルト値となる
- オプションに設定されているデフォルト値
- config ファイル (--config オプションでファイルを指定する
- script の optionsオブジェクトの設定値
- 環境変数
- CLI の オプション
Optionsの種類
軽く使った感じだとこの辺は基本的なオプションとして覚えておいた方が良さそう。それ以外も実践的には使っていくことになりそう
- Duration: テストの実行期間(時間)
- Iterations: スクリプトの実行回数
- Scenarios: テストの実行シナリオ
- Stages: VUの増減数を指定する
- VUs: テストの同時実行数
Scenarios
VUs と Iteration、つまり スクリプトの同時実行数と実行回数の詳細を指定することができる。 シナリオを使うことで より実際のWebアプリケーションの利用にあった負荷を掛けたテストができる。
シナリオはテストスクリプに対して複数定義することもできる。
Executor
シナリオには 必ず Executor プロパティを指定する。 Executor は テスト実行における実行量を計画したもの。次の6つの定義があり 「実行回数を起点とした計画」「同時実行数を起点とした計画」「実行頻度を起点とした計画」の3つに分類される
Shared iterations
指定した実行回数(iterations) を 指定した VU間で共有して実行する。例えば、実行回数を100、VUを5とした場合は、最大同時実行数5 で、合計100回スクリプトを実行する。
この時、それぞれが20回ずつ実行するとは限らず、空いているVUに実行が割り当てられるのでバラツキは発生する。
仕組み上、最大実行回数を超える 同時実行数は指定できない。(例えば、最大実行回数が10なのにVU数を20にするなど)
合計の実行回数と最大同時実行回数が固定値なので、とりあえず負荷テストを決められた回数回したいと言った場合などに使うことが多いかな
Per VU iterations
同時実行(VU)数とVUごとに実行回数テストする。例えば VU数が5で、iteration数が10であれば、それぞれのVUで10回実行するので合計 500回のテストを行う。
Constant VUs
実行期間(duration) の間、同時実行(VU)数で繰り返しテストする。 実行期間は必須項目となる。
指定した期間中、負荷を掛けたい場合に利用。また同時実行数を増やすことで スループットの検証も行える
Ramping VUs
stages オプションを利用して、実行回数と実行期間の段階的に指定することができる。例えば最初の10秒間で同時実行数を10まであげ、その後に 30秒かけて0まで落とす。などといった負荷の増減を設定することができる
Constant arrival rate
指定した時間単位ごとの実行回数を定義したテストになるため、必須設定項目が多い
-
duration: テストの実行時間
-
rate: timeUnit項目あたりに実行する回数
-
preAllocatedVUs: あらかじめ確保するVU数
以下は任意項目
- timeUnit: rateで指定した回数を実行する 時間単位。デフォルトは
1s
で つまり 1秒間にrateで指定した回数のテストを行う - maxVUs: 最大同時実行数でデフォルトは preAllocatedVUsと同じ。
- timeUnit: rateで指定した回数を実行する 時間単位。デフォルトは
負荷量を詳細に指定したテストが実行できる
Ramping arrival rate
Stageを使って、実行回数のrateを段階的に指定することができる。
arrival rate (Constant arrival rate/Ramping arrival rate) を利用する場合、テストスクリプトで sleep()
関数は入れないようにすること。arrival rate は 単位時間辺りの実行回数を管理するため Executor自体が時間の調整を行なっている。これに対して sleepを入れてしまうとその分も加味された実行時間で調整されてしまうため
Stages
ramping テスト (負荷量を増減するテスト)を実行する際にその 実行時間(duration)と実行量(target)を区分けして定義できる。
実行量は Ramping VUs の場合は 同時実行数(VUs)となり、Ramping arrival rate の場合は 実行回数(iterations)を指定する
ブラウザテスト
クライアント側のリクエストが画面(ブラウザ)の操作によって行われるテストに対応したもの
ブラウザテストを行う場合は scerario を定義して そのシナリオの中の optionになる browser.type: "chromium"
を指定することで実行ができる
- Scenarioを定義するので、必然的に Executorも指定が必要になる。
- ブラウザテストは標準では HEADLESSモードで行われる。実際の画面を見たい場合は 環境変数 K6_BROWSER_HEADLESS の値を falseに指定すること。
負荷テストのタイプ
負荷テストには目的に応じた様々な戦略を取る必要がある。k6のドキュメントでは次の6つが定義されている
- Smoke tests
- Average-load test
- Stress tests
- Soak tests
- Spike tests
- Breakpoint tests
Smoke tests
最小限の負荷でテストを実施し、性能のベースラインとなる情報を集める。 VUは2〜20 (最大でも5が理想)、少ない実行回数や 30秒から3分程度の時間で実施する。
目的
- テストスクリプトが誤っていないかを評価する
- 最小限の負荷でテストがシステムの性能面に関するエラーが起こらないかを評価する
シナリオは VUs, Iterations, duration などを固定値で実施する
- shared-iterations
- per-vu-iterations
- constant-vus
Avarage-load test
システムに対する平均的(一般的)な 負荷で テストを実施する。このタイプでは 初めは徐々にVUを増やした後平均的にVUsで実行する
目的
- 平均的な、つまり通常の負荷におけるシステムの性能を見る
- 負荷による早期の性能劣化を特定する
- システムを変更しても 標準的な性能を維持することを確認する
シナリオは 通常の負荷状態で実施するのと、前後で負荷を増減する形が良い
- ramping-vus
- arrival-rate を使って時間単位での負荷量までは調整しなくても良さそう
Stress tests
Average-load test に似ているが、異なる点は 負荷をさらに掛ける点にある。なので VU数も増え、実行回数や時間も増やしてテストする。
目的
- 高負荷な状態においてもシステムが安定し信頼できることを検証する
考慮する点
- システムに対する平均的な負荷量よりも高くすること
- average-load test の 後に実施すること
- average-load test のスクリプトを再利用すること
- average-load test の結果よりも性能が悪化すること
なのでシナリオも 必然的に ramping-vus になる。
Soak testing
Soak test も average-load test の一種で、長期間実施した時の観点でテストすることで以下の点を分析する
- 長期間のリソース消費による性能劣化
- 長期間における可用性と安定性
目的
- 長期による安定性と信頼性を評価する
- メモリやリソースのリーク、データ量増加に伴うストレージの枯渇等の栄養によるレスポンスの悪化といった長期利用による性能劣化を評価
考慮すること
- 数時間や数日といった長期スパンでテストする
- average-load test の 後に実施すること
- average-load test のスクリプトを再利用すること
- Stress test とは違って、VUは平均値のままでよく durationを伸ばす
- アプリケーションリソースのモニタリングも実施する
Spike tests
システムに対して瞬間的に大量の負荷が発生した時に システムが稼働できるかを検証する。
考慮すること
- Spike test の トリガは他のテストタイプと同じか異なるかを検討する
- 何度も繰り返し実施する
- アプリケーションのモニタリングも行うこと
シナリオ
- ramping を使って、短時間で大量の負荷を掛ける。減らす時も短い時間で実行回数を0まで落とす
- plateau (台形) となる実施期間は作らない
- ramping-vus でも良いが、短時間に高負荷を回す上で arrival-rate を使って設定することも考える
Breakpoint tests
システムの限界を探索するためのテスト。 システムの限界を知ることで故障を察知するなどの事前の対策を行うことができるようになる。
考慮する点
- Cloudサービスのようにリソースが可変するような環境での実施を避ける
- 段階的に負荷を増やしていく
- システムの故障の定義はチームによって異なる
- 性能面の劣化やトラブル、タイムアウト、エラーの発生、システムのダウンなど
- 繰り返しテストが行えるようにする
シナリオとしては、ramping-vus を使って長期間(数時間)かけて、少しずつ負荷を増やしていく。
browserモードでテストを行う場合、クライアントのブラウザを利用するのでクライアントのリソースがテストに影響する。
よくよく考えると負荷テストのSUTはバックエンドなどのサーバーで、サーバーの性能や処理能力を検証することが目的なので、テストの観点によってはブラウザでなくても良い場合もある。
負荷を掛けるためのトリガとなるリクエストを別の方法で送信すれば良いし、サーバー側で正常に処理ができたかどうかは レスポンスのステータスのみで判断しても良ければ ブラウザを使ってレンダリング結果まで参照しなくても良い場合など。