はじめに
K6という負荷試験ツールの使い方をまとめたいと思います。
今回はサーバサイドのパフォーマンスに絞ってまとめます。
ブラウザベースのパフォーマンスについては、別途まとめます。
インストール
筆者はMacなので、Brewでインストールしてます。
テストファイル作成
k6 new <ファイル名>.js
ターミナルでコマンド入力してもらうと、テンプレート作成してくれます。
絶対、newコマンドでファイル作成しないといけないということではないです。
touchとかで新規ファイル作成しても、中身が動くスクリプトなら問題ないです。
テスト実行
k6 run <ファイル名>.js
先ほど、作成したテンプレートを動かしてみましょう。
k6 run auth_senario.js
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: auth_senario.js
output: -
scenarios: (100.00%) 1 scenario, 10 max VUs, 33s max duration (incl. graceful stop):
* default: 10 looping VUs for 3s (gracefulStop: 30s)
data_received..................: 273 kB 89 kB/s
data_sent......................: 5.4 kB 1.8 kB/s
http_req_blocked...............: avg=283.78ms min=2µs med=282.73ms max=571.57ms p(90)=569.62ms p(95)=570.6ms
http_req_connecting............: avg=99.43ms min=0s med=95.4ms max=237.47ms p(90)=202.8ms p(95)=206.21ms
http_req_duration..............: avg=239.42ms min=190.62ms med=243.6ms max=282.24ms p(90)=282.03ms p(95)=282.06ms
{ expected_response:true }...: avg=239.42ms min=190.62ms med=243.6ms max=282.24ms p(90)=282.03ms p(95)=282.06ms
http_req_failed................: 0.00% ✓ 0 ✗ 20
http_req_receiving.............: avg=2.36ms min=49µs med=312µs max=7.56ms p(90)=6.47ms p(95)=6.66ms
http_req_sending...............: avg=348.95µs min=9µs med=24µs max=1.73ms p(90)=1.69ms p(95)=1.7ms
http_req_tls_handshaking.......: avg=181.48ms min=0s med=161.83ms max=373.62ms p(90)=371.68ms p(95)=372.57ms
http_req_waiting...............: avg=236.71ms min=185.29ms med=243.37ms max=281.96ms p(90)=281.73ms p(95)=281.77ms
http_reqs......................: 20 6.531551/s
iteration_duration.............: avg=1.52s min=1.28s med=1.52s max=1.77s p(90)=1.77s p(95)=1.77s
iterations.....................: 20 6.531551/s
vus............................: 10 min=10 max=10
vus_max........................: 10 min=10 max=10
running (03.1s), 00/10 VUs, 20 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs 3s
上記のような結果になれば、問題なく動いてます。
テストファイル構成
K6のテストファイルは大きく4つのブロックに分かれます。
オプションブロック
export const options = {
vus: 10, // 仮想ユーザー数
duration: '20s', // テストの実行時間
};
テスト実行時のオプションをファイルに記載できます。
ファンクションブロック
export default function() {
http.get('https://test.k6.io');
sleep(1);
}
実行するAPIの情報を記載します。
セットアップブロック
export function setup() {
}
処理するデータを設定し、VU間でデータを共有できます。
tear downブロック
export function teardown(data) {
}
セットアップコードの処理結果、テスト環境の停止します。
実行オプション
よく使用するオプションを紹介します。
vus
同時に実行する VU の数を指定する数値
duration
テスト実行の合計期間を指定する.
vusと組み合わせることで、指定期間で同時実行時のシナリオを流せる。
stages
増加または減少する VU の目標数を指定するオブジェクトのリスト
export const options = {
stages: [
{duration: '30s', target: 5},
{duration: '30s', target: 5}
]
}
このように記述できる。
例だと、最初の30秒でVuを5まで増やしていき、次の30秒でVuを0に減らしていくことを表現している。
Thresholds
テストが成功するか失敗するかの条件を設定する
Checkメソッド
K6はレスポンスを評価することができます。
試しにレスポンスのステータスコードが200かをチェックします。
import { check, sleep } from 'k6';
export default function() {
const res = http.get('https://test.k6.io');
check(res, {
'status was 200': (r) => r.status === 200,
});
sleep(1);
}
作成したテンプレートに上記のcheckを追加します。
✓ status was 200
このようにターミナルに表示されていればOKです。
✗ status was 200
↳ 0% — ✓ 0 / ✗ 28
失敗するとこのように表示されます。
シナリオテスト
K6では、複数のシナリオを統合してテストすることができます。
import senario1 from './sinario1.js'
import senario2 from './sinario2.js'
export {
senario1,
senario2
}
export const options = {
scenarios: {
scenario1: {
executor: 'per-vu-iterations',
exec: '<finction_name>', // デフォルトだとdefaultという名前の関数が呼ばれる
startTime: '10s',
gracefulStop: '5s',
env: { EXAMPLEVAR: 'testing' },
tags: { example_tag: 'testing' },
// executor-specific configuration
vus: 10,
iterations: 200,
maxDuration: '10s',
},
scenario2: {
executor: 'per-vu-iterations',
exec: '<finction_name>', // デフォルトだとdefaultという名前の関数が呼ばれる
startTime: '10s',
gracefulStop: '5s',
env: { EXAMPLEVAR: 'testing' },
tags: { example_tag: 'testing' },
// executor-specific configuration
vus: 10,
iterations: 200,
maxDuration: '10s',
},
},
};
export default function() {}
サンプルのように、それぞれの呼び出したいテストファイルをインポートした後に、export記載しないと認識されないのでご注意ください。
オプションは公式を参照してください。
閾値
export const options = {
thresholds: {
http_req_duration: ['p(95) < 500'] // 95%のリクエストが500ms未満で完了すること
},
};
サンプルのように、簡単に閾値を設定できます。
Jsonを使用したデータインポート
K6でもJson形式でデータをインポートしてリクエストに使用できます。
[
{ "bookId": "20c22160-a72f-4b3b-9bf2-0d6832934e22", "userId": "usebc9e6634-89c6-4e93-b2c7-049a1e53cc71"},
{ "bookId": "20c22160-a72f-4b3b-9bf2-0d6832934e23", "userId": "usebc9e6634-89c6-4e93-b2c7-049a1e53cc72"},
{ "bookId": "20c22160-a72f-4b3b-9bf2-0d6832934e24", "userId": "usebc9e6634-89c6-4e93-b2c7-049a1e53cc73"},
{ "bookId": "20c22160-a72f-4b3b-9bf2-0d6832934e25", "userId": "usebc9e6634-89c6-4e93-b2c7-049a1e53cc74"},
{ "bookId": "20c22160-a72f-4b3b-9bf2-0d6832934e26", "userId": "usebc9e6634-89c6-4e93-b2c7-049a1e53cc75"}
]
import http from 'k6/http';
// 外部ファイル 'loan_data.json' から配列データを読み込み
const data = new SharedArray('loan data', function () {
return JSON.parse(open('./loan_data.json'));
});
export default function () {
const url = 'http://localhost:8080/graphql'; // GraphQLエンドポイント
data.forEach(item => {
for (let i = 0; i < 10; i++) {
const payload = JSON.stringify({
query: `
mutation {
loan(bookId: "${item.bookId}", userId: "${item.userId}")
}
`,
});
// ヘッダー設定(JSON形式でリクエストを送る)
const params = {
headers: {
'Content-Type': 'application/json',
},
};
// POSTリクエストでGraphQLのMutationを送信
const res = http.post(url, payload, params);
// レスポンスのステータスコードが200であるかをチェック
check(res, {
'status was 200': (r) => r.status === 200,
});
}
});
}
サンプルでは、Jsonで表現したデータの配列をループで処理するようになってます。
負荷テスト以外でも機能テストでリクエストパターン試したいときなんかも便利です。
メトリクス
確認することが多いメトリクスは以下かなと思うので、ピックアップします。
メトリクス名 | 意味 |
---|---|
checks | チェック関数の成功割合 |
iteration_duration | 一度のイテレーションにかかった時間 |
vus | アクティブな仮想ユーザ数 |
vus_max | 最大仮想ユーザ数 |
http_req_connecting | TCP確立までの時間 |
http_req_duration | レスポンスを受け取るまでの時間 |
http_reqs | HTTPリクエストの総数 |
他にもメトリクスは存在するので、公式を参照ください。
さいごに
個人的にK6は使いやすく、シナリオを書きやすいので重宝してます。
ブラウザテストで使用するパターンもタイミングいい時に記載できればなと。