これは「「はじめに」の Advent Calendar 2021」21日目の記事です。
k6とは
Load ImpactというSaaSで負荷テストサービスを提供していたが、2020年に名称変更したものらしいです。
クラウドサービスはリモートから手軽に負荷テストを実行出来るようですが、今回ネタにするのはそのOSS版。
ちなみに、Google検索だと4番目。(k6ってなんて発音するんだろ?ケーシックスでいいのかな?)
使ってみる
インストールのページの通り。Macで動かすときは
brew install k6
ローカルで適当にWebサーバーを立ち上げておく
docker run -d -p 80:80 nginx
javascriptでテストシナリオを記述する
import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
http.get('http://localhost/');
sleep(1);
}
テスト実行
$ k6 run sample.js
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: sample2.js
output: -
scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
* default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
running (00m01.0s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs 00m01.0s/10m0s 1/1 iters, 1 per VU
data_received..................: 853 B 819 B/s
data_sent......................: 75 B 72 B/s
http_req_blocked...............: avg=7.45ms min=7.45ms med=7.45ms max=7.45ms p(90)=7.45ms p(95)=7.45ms
http_req_connecting............: avg=533µs min=533µs med=533µs max=533µs p(90)=533µs p(95)=533µs
http_req_duration..............: avg=26.41ms min=26.41ms med=26.41ms max=26.41ms p(90)=26.41ms p(95)=26.41ms
{ expected_response:true }...: avg=26.41ms min=26.41ms med=26.41ms max=26.41ms p(90)=26.41ms p(95)=26.41ms
http_req_failed................: 0.00% ✓ 0 ✗ 1
http_req_receiving.............: avg=718µs min=718µs med=718µs max=718µs p(90)=718µs p(95)=718µs
http_req_sending...............: avg=170µs min=170µs med=170µs max=170µs p(90)=170µs p(95)=170µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=25.52ms min=25.52ms med=25.52ms max=25.52ms p(90)=25.52ms p(95)=25.52ms
http_reqs......................: 1 0.959874/s
iteration_duration.............: avg=1.03s min=1.03s med=1.03s max=1.03s p(90)=1.03s p(95)=1.03s
iterations.....................: 1 0.959874/s
vus............................: 1 min=1 max=1
vus_max........................: 1 min=1 max=1
項目
大体書いているとおりだけど一応解説
項目名 | 意味 |
---|---|
data_received | レスポンスデータ量(Total, /s) |
data_sent | リクエエストデータ量(Total, /s) |
http_req_blocked | TCP接続の順番待ちをした時間(avg, min, med, max, p(90), p(95) |
http_req_connecting | TCP接続にかかった時間(avg, min, med, max, p(90), p(95) |
http_req_duration | http_req_sending + http_req_waiting + http_req_receiveing(avg, min, med, max, p(90), p(95) |
expected_response | 正常応答のみのhttp_req_duration(avg, min, med, max, p(90), p(95)。正常な応答がない場合、この項目は表示されない |
http_req_failed | リクエストが失敗した割合(%) |
http_req_receiving | レスポンスの1バイト目が到達してから最後のバイトを受信するまでの時間(avg, min, med, max, p(90), p(95) |
http_req_sending | リクエストを送信するのにかかった時間(avg, min, med, max, p(90), p(95) |
http_req_tls_handshaking | TLSセッションのハンドシェイクにかかった時間(avg, min, med, max, p(90), p(95)。httpでは0 |
http_req_waiting | リクエストが送信完了してから、レスポンスが開始されるまでの時間(avg, min, med, max, p(90), p(95)。TTFB(Time To First Byte) |
http_reqs | リクエスト総数。(Total, /s) |
iteration_duration | シナリオ1ループにかかった時間(avg, min, med, max, p(90), p(95)。 |
iterations | シナリオを繰り返した回数(Total, /s) |
vus | Virtual UserS。最後のシナリオのときの並列数(だと思う) |
vus_max | 最大Virtual UserS。テスト中の最大並列数(だと思う) |
並列度を上げて試してみる
--vus 10
で並列数=10、 --duration 30s
で30秒継続
$ k6 run --vus 10 --duration 30s sample.js
実行中はこんな感じで進む
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: sample2.js
output: -
scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
* default: 10 looping VUs for 30s (gracefulStop: 30s)
running (0m16.5s), 10/10 VUs, 160 complete and 0 interrupted iterations
default [===================>------------------] 10 VUs 16.5s/30s
バーが右端まで到達したら完了。
結果
/\ |‾‾| /‾‾/ /‾‾/
/\ / \ | |/ / / /
/ \/ \ | ( / ‾‾\
/ \ | |\ \ | (‾) |
/ __________ \ |__| \__\ \_____/ .io
execution: local
script: sample2.js
output: -
scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
* default: 10 looping VUs for 30s (gracefulStop: 30s)
running (0m30.2s), 00/10 VUs, 300 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs 30s
data_received..............: 92 kB 3.1 kB/s
data_sent..................: 24 kB 785 B/s
http_req_blocked...........: avg=56.54µs min=1µs med=4µs max=1.74ms p(90)=11µs p(95)=28.05µs
http_req_connecting........: avg=21.89µs min=0s med=0s max=835µs p(90)=0s p(95)=0s
http_req_duration..........: avg=4.4ms min=1.07ms med=3.84ms max=16.27ms p(90)=6.99ms p(95)=9.86ms
http_req_failed............: 100.00% ✓ 300 ✗ 0
http_req_receiving.........: avg=47.36µs min=12µs med=38µs max=265µs p(90)=79.1µs p(95)=108.35µs
http_req_sending...........: avg=39.9µs min=6µs med=14µs max=2.2ms p(90)=37µs p(95)=83.75µs
http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...........: avg=4.31ms min=1.01ms med=3.79ms max=16.04ms p(90)=6.92ms p(95)=9.39ms
http_reqs..................: 300 9.934892/s
iteration_duration.........: avg=1s min=1s med=1s max=1.02s p(90)=1.01s p(95)=1.01s
iterations.................: 300 9.934892/s
vus........................: 10 min=10 max=10
vus_max....................: 10 min=10 max=10
予定通り、並列度 10 x 30s で300リクエスト処理されていることがわかりますね。
ファイルアップロード
これを記事にした理由です。ファイルアップロードを含む負荷テストをしたくて探しました。Locust、Gatlingは使ったことがあるものの今はPythonもScalaも使っていないので、今後を考えると別のツールが欲しいと思いました。
トップ画像が物議を醸すvegetaも考えましたが、multipartのテストデータを用意するのがめんどう向いていなさそうなツールでした。
ファイルアップロードするときのシナリオはこう
import http from "k6/http";
const binFile = open('./test.bin', 'b');
export const options = {
stages: [
{ duration: '3s', target: 10 },
{ duration: '30s', target: 10 },
],
};
export default function() {
let url = 'http://localhost/upload'
const data = {
file: http.file(binFile, 'test.bin'),
};
const response = http.post(url, data);
};
組み込みの open
を使いファイルを読み込み、 http.file
の行でmultipart/form-data
のパラメータfile
としてファイルとしてbodyデータを作っています。
また、options
でランプアップしています。
記述している内容だと、最初の3秒で10並列まで徐々に増やし、次の30秒は10並列固定で流しています。
ドキュメントはこちら。
サンプル・ドキュメントも豊富
コードを読んで雰囲気を掴み、
ドキュメントでコードの意味を調べることで機能把握が捗りました。
この記事で位置から説明できる量じゃないので、詳しくは公式サイトを見たほうが速いです。
気をつける点
rps
--rps int limit requests per second
rps
という便利なオプションがありますが要注意らしく、公式では到達率で制御することを推奨しています。分散して並列実行する仕組みがあるっぽい(k6 cloudでは出来る)けど、rps
は1インスタンスあたりの制御しかできないらしいです。
1インスタンスだとしても、vus
での並列リクエストの合計がrps
になるように制御するので、想定以上の負荷をかけたときのレポートはどういう制御がされたのか、パット見わからない状況でした。(もっと使い倒してからまた書くかもしれません)
ファイル
シナリオ中に const binFile = open('./test.bin', 'b');
は書けない。exportしているfunctionは毎回呼ばれるっぽいので当然といえば当然。
まとめ
見つけてテストするまで本当に5分くらいで出来たので、嬉しくなって書きました。良いツールはどんどん紹介していかないと。