6
Help us understand the problem. What are the problem?

posted at

k6使ったら今までで一番負荷テストが捗った

これは「「はじめに」の Advent Calendar 2021」21日目の記事です。

k6とは

Load ImpactというSaaSで負荷テストサービスを提供していたが、2020年に名称変更したものらしいです。

クラウドサービスはリモートから手軽に負荷テストを実行出来るようですが、今回ネタにするのはそのOSS版。

ちなみに、Google検索だと4番目。(k6ってなんて発音するんだろ?ケーシックスでいいのかな?)
スクリーンショット 2021-12-21 23.18.46.png

使ってみる

インストールのページの通り。Macで動かすときは

brew install k6

ローカルで適当にWebサーバーを立ち上げておく
sh
docker run -d -p 80:80 nginx

javascriptでテストシナリオを記述する

sample.js
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のテストデータを用意するのがめんどう向いていなさそうなツールでした。

ファイルアップロードするときのシナリオはこう

upload.js
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分くらいで出来たので、嬉しくなって書きました。良いツールはどんどん紹介していかないと。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
6
Help us understand the problem. What are the problem?