見出し画像

負荷テストをカジュアルに実施したい ~k6で負荷テストしてみたよ~

優柔不断で安パイを選びがちですがたまには冒険もしてみたい「ぎだじゅん」です。
ライフイズテックという会社でサービス開発部 インフラ/SREグループに所属しています。

よく気分転換に一人で飲みに行ったりしまが、最近は空前の「大衆居酒屋」ブームのようで、安くて一人でも通いやすいくて洒落た雰囲気な「大衆居酒屋」がたくさん増えていて、そんなネオ「大衆居酒屋」にも行ったりしています。

大衆居酒屋が大好きです。

「大衆居酒屋」といえば、狭い間隔のカウンター席で昭和世代のサラリーマンが串と煮込みと安い酒だけで単純に酔いたいためだけに通うようなイメージ(?)ですが、ネオ「大衆居酒屋」はそれらとは違い、料理もバラエティ豊富でインスタ映えするようなものも多く、店の作りもクールなコンクリート打ちっぱなしな感じや「古民家カフェ」のようなレトロな感じだったり、お洒落に敏感な層にもささるような感じの店が多いです。

ネオ「大衆居酒屋」はどこの店も繁盛している印象です

また、これまでの「大衆居酒屋」にあるようなコの字型のカウンターの店も多く、近い距離間で肩を寄せ合いながら酒を交わすコミュニケーションが再認識されているのか、それもファッションとしてとらえているのか(?)わかりませんが、おじさんのイメージがあった「大衆居酒屋」の客層も若い人たちが増えているようにみえます。

カウンターで飲んでたら、隣同士で知らない方とつながっていく感じもいいですよね。

個人的には老舗の「大衆居酒屋」の独特な雰囲気やレパートリーは少なくても奥深いおつまみや飲み物も好きですが、ネオ「大衆居酒屋」のバエるおつまみも大好きです。
いろんな特徴のあるものから選択できる幸せをかみしめて、日々気分転換しています。

いろんな特徴のある一人飲みおつまみも充実してます

本題です。



イベントで大量のアクセスが見込まれます

Webサービスのインフラ運用の仕事をしていると、こんな話を相談されることがあります。
(以下は著者がこれまで勤めた会社でよくあったような話です)

  • ゴールデンタイムのテレビ番組でサービスが紹介されるけど、うちのサービス、アクセス耐えられそうですか?

  • イベントでサービスを無料提供して一斉にアクセスされることが考えられるけど、サーバーがダウンしたりしないよね・・・?

  • 今度キャンペーンCMを打つみたいなので、対策しておいてね。

昔はCMからWEBサイトへアクセス集中することがよくありました

最近のクラウドネイティブなシステム環境であれば、リソースを必要に応じてスケールすれば、大量なアクセスも比較的捌けたりはしますが、これらの例のようなスパイク的なアクセスが見込まれる場合だと、スケーリングが間に合わず、肝心なときにアクセス障害が起きて、機会損失となることもあります。
かといって、いつも膨大なリソースで稼働させておくと、コストがかかってしまう問題もあります。

アクセス集中でサーバリソースがパンクしないように運用する

例のような事象については、一斉にアクセスがくることを想定して、システムが通常のリソースでどれくらいのアクセスが処理できて、リソースを増やすとどれくらいのアクセスに耐えられそうか、事前に負荷テストで検証しておくことで、イベントや広告の公開タイミングに合わせたスケジュールでスケーリングするようにします。

しかし、こういう話って結構突発的に来たりするんですよね。
常に負荷テストの実施環境を用意(または必要に応じてIaCでビルドする)ができて定期的に行うのが理想ではありますが、機能追加や仕様変更などでパスが変わったりパラメータ追加などがある場合はシナリオのメンテナンスも必要だったり実施するツールのアップデートをしたり、環境を維持するための運用コストもかかります。シナリオを作るのもツールによっては正直大変そう。

もう、日数があまりないじゃない!!!

JMeterだと準備が大変(な印象)

負荷テストのツールとして最初に浮かぶのは Apache JMeter ですよね。

結構昔からあるツールで、これまでの世間的な利用実績からもナレッジが充実しており、機能もとても充実しています。GUIでも設定や実施などができるのも大きな特徴です。
自分も数年前までは JMeter を使って負荷テストの計画を立てて実施したことがありますが、以下のように環境を構築したりシナリオの作成に若干苦労をした印象があり、もっとカジュアルに環境構築できるものがないかなぁと考えていました。

  • 環境構築

    • 負荷をかける規模によっては1台の負荷実施環境だけではできないので、シナリオを作成したり実施指示をするマスター環境に加え、負荷を実際にかける複数台の実行環境が必要

    • OSチューニングだけでなく、Javaベースの環境のためヒープサイズなどの調整も必要

  • シナリオ作成

    • 使いこなすのには JMeter での設定のための知識が必要

      • 充実した機能を活かすには、設定項目が多い

      • シナリオの作成に JMeter独自の知識が必要(な印象)

ここ数年 JMeter をさわってなかったので、ここに挙げたようなデメリットから、いろいろ変わって改善されていることもあるかもしれませんので、ご了承ください。

ご了承ください。

Grafana k6 がよさそう

もう少し以下のような要件を満たす、簡単に負荷テストできるようなツールがないかなと思って、調べたところ「k6」がよさそうな印象を受けました。

  • 環境構築(セットアップ)が超簡単

  • ある程度の負荷は単体のサーバで実施できる

  • シナリオの作成が簡単

    • ログインしてアクセスするようなサイトや、ログインアカウント毎にURLのパラメータが異なるサイトなどもシナリオ作成できる

k6 はGrafana Labsが開発したOSSのツールです。

AWSの規範ガイダンスでも以下のように紹介されています。

k6 は、負荷テストを整理、実行、分析するためのサポート、ロードソースのホスティング、および統合されたウェブインターフェイスを提供する無料のツールです。

k6 は Go で記述され、単一の実行可能ファイルとして出荷されます。ソースシステムのすべてのコアを使用します。のサブセットを使用して複雑なシナリオ JavaScript を実行し、テストプロファイルが含まれます。で、効率的に JavaScript 実行される複雑なテストシナリオを作成できます。出力は概要でも、複数のターゲットストアの詳細な出力でもかまいません。拡張機能はサポートされていますが、公式の k6 拡張機能を除いて、適切に管理された拡張機能はほとんどありません。

サーバーが十分に大きければ、ほとんどの負荷テストを単一のサーバーから実行できます。これにより、複雑な分散負荷テストを回避できます。

ロードテスト結果は、Amazon Managed Service for Prometheus Amazon CloudWatch、または別のモニタリングサービスに転送して、より詳細な分析を行うことができます。シナリオコードに成功基準を含めて、継続的インテグレーション (CI) パイプラインで実行することもできます。

「AWS 規範ガイダンス」の「アプリケーションの負荷テスト」の「使用するツール」より

Goで作成された軽量なツールで効率よく実行環境のリソースを使用する印象もあり、公式マニュアルでも、実行環境のリソースが十分にあれば単一のサーバ環境から30,000 ~ 40,000 の同時ユーザ(VU/バーチャルユーザ)によるリクエストを実行でき、1 秒あたり 100,000 ~ 300,000 件のリクエストに対応できるとありました。

また、シナリオの作成につても、Webブラウザ「Chrome」や「Firefox」の拡張機能「Browser recorder」を使ってテストしたいサイトのアクセス遷移をレコーディングしてシナリオを作成する方法や、Chromeのdeveloper toolsの「Network」から生成できるHAR fileをコンバートしてシナリオを作る方法があり、(比較的)簡単にシナリオ作成ができます。

また、シナリオは JavaScript ベースで書かれているため、JavaScript がわかる方であればシナリオのコードレベルでのレビューや直接修正したりすることもできます(著者はあまり JavaScript は詳しくはありませんが)。

このk6での負荷テスト環境をSaaSで提供している Grafana Cloud k6 もあります。こちらは有償サービスですが、期間内はフリートライアルで利用もできます。

ということで、今回、負荷テストをする機会があり、k6を使ってみました。


(参考)Distributed Load Testing on AWSは構築が簡単

AWSだと、「Distributed Load Testing on AWS」という負荷テストソリューションがあります。

こちらはAWSが用意しているAWS CloudFormation スタックで簡単に負荷テスト環境を構築でき、用意されたWebコンソールから以下の項目を設定して負荷テストを実施するだけ。
(以下はこちらのサイトからの抜粋です)

  • Name : <任意の名前>

  • Description : <任意の説明>

  • Task Count : <タスク数>

  • Concurrency : <タスク毎の同時接続数>

  • Ramp Up : <設定した Concurrency 数に達するまでの時間>

  • Hold for : <設定した Concurrency 数を保持する時間>

  • HTTP endpoint under test : <テストを実行するターゲット URL>

実施結果のレポートも用意されて以下の結果を含むレポートも用意してくれます。
(以下もこちらのサイトからの抜粋です)

  • Average response time : テストによって生成されたすべてのリクエストの平均応答時間 (秒)

  • Average latency : テストによって生成されたすべてのリクエストの平均レイテンシー (秒)

  • Average connection time : テストで生成されたすべてのリクエストについて、ホストへの接続にかかった平均時間 (秒)

  • Average bandwidth : テストで生成されたすべてのリクエストの平均帯域幅

  • Total Count : リクエストの総数

  • Success Count : 成功したリクエストの総数

  • Error Count : エラーの総数

  • Requests Per Second : テストで生成されたすべてのリクエストの 1 秒あたりの平均リクエスト数

  • Percentile : テストの応答時間のパーセンタイル値 (最大応答時間は 100 %、最小応答時間は 0 %)

テスト実施後はCloudFormationのスタック削除で一括で削除できます。
JMeter のシナリオも利用できる模様なので、JMeter を使われていた方には、こちらのツールを利用するのもよいかもしれません。


Grafana k6 での負荷テスト

ググればk6の使い方の紹介ブログがたくさんあって、すでに擦られまくっていることから、多くの場所で使われていそうなツールになっていますが、私も負けじと、セットアップや今回実施した試験で得たナレッジを共有したいと思います。

k6での負荷テスト実施までの流れは以下の通りです。

  1. k6のインストール(およびOSのファインチューニング)

  2. スクリプトファイル(シナリオ)を準備する

  3. スクリプトファイルを指定して実行

  4. 結果を確認する

k6のインストールは超簡単

k6のインストールは簡単です。
以下のマニュアルにある環境にあわせて、コマンドを実行するのみです。

今回、私はAWSのEC2でOSは「Amazon Linux 2023」を使って構築しました。以下のコマンドでインストールができます。

# k6パッケージのリポジトリを登録するためrepo.rpmをセットアップ
$ sudo dnf install https://dl.k6.io/rpm/repo.rpm

# k6をインストール
$ sudo dnf install k6

ただ、ARM ベースのインスタンスだと、k6のパッケージのリポジトリにARM用のrpmパッケージが用意されていない模様で、インストールできませんでした。
Linux環境はx86ベースのインスタンスで用意したほうがよいです。
(MacだとARM版でも使えそうなのに・・・)

インストールが完了し、コマンド「k6 version」を実行してバージョンが確認できたらインストールは成功です。

$ k6 version
k6 v0.51.0 (commit/33d3caa7d1, go1.22.3, linux/amd64)


OSのファインチューニング

あと、負荷テスト環境の構築でよくあるのが、ネットワークリソースなどOSでの制限を調整する作業です。
k6でも単体のサーバで必要な数だけのリクエストを送信する必要がある想定なので、同様に必要です。
例えば、k6での実施でリクエスト量に応じて使用されるファイルディスクリプタの制限に引っかかった場合は以下のようなエラーが出てきます。

WARN[0127] Request Failed     error="Get http://example.com/: dial tcp example.com: socket: too many open files"

同時で数千〜3万VU(仮想ユーザ)によるリクエストなどを行えば、使用されるネットワーク接続の量に応じてオープンファイル数の上限に達してしまったり、ローカルポートが枯渇してくることも考えられます(感覚ではローカルポートが1VUあたり2ポート消費する模様でした)。

ついては、Linux環境においては、後ほど紹介するk6の実行コマンドを実施する前に、コンソール上で以下を実行しておくことで、ネットワーク接続の再利用、ネットワーク接続の制限の増加、およびローカル ポートの範囲の拡大が適用されます。
(Bashなどの場合は、コマンドを ~/.bash_profile に記載しておいても良いかと思います)

$ sysctl -w net.ipv4.ip_local_port_range="1024 65535"
$ sysctl -w net.ipv4.tcp_tw_reuse=1
$ sysctl -w net.ipv4.tcp_timestamps=1
$ ulimit -n 250000

くれぐれも負荷テスト専用のLinux環境以外でこちらの制限の調整をしないようにお願いします。
k6の実施環境上に、既存でサービスや別のアプリケーションなどが動いているLinux環境で実施すると、それらのサービスやアプリケーションに支障を来す場合があるので、ご注意ください。

また、必要なメモリ量ですが、スクリプトの複雑さと依存関係に応じて変わるかとは思いますが、参考情報として以下のサイトでは、1VU(仮想ユーザ)あたり1 MB ~ 5 MB の RAM が必要(1,000 VU でのテストに必要なシステム RAM がおよそ 1 GB ~ 5 GB)とありますので、負荷テストを実施する側のサーバスペックを決める際の参考にするとよいかと思います。


デフォルトのスクリプトファイル(シナリオ)を見てみる

k6では、以下の内容が記載されたスクリプトファイルを用意して、「k6 run」コマンドでスクリプトファイルを指定して実行することで、指定した内容で診断が実施できます。

  • 診断対象のURL、シナリオ(診断するサイトの遷移)

  • 負荷内容(仮想ユーザ数やテストの総実行時間など)

このスクリプトファイルを初期化(作成)して、ローカルやテストサイトなどへk6からリクエストを投げてみましょう。
以下で紹介されている手順に従って説明します。

k6 new」コマンドを実行すると、テストで使用できるシナリオのテンプレートとなるようなスクリプトファイルを作成します。

$ k6 new
Initialized a new k6 test script in script.js. You can now execute it by running `k6 run script.js`.

k6 new」コマンド実行するディレクトリ直下に、既存で「script.js」のファイルがある場合は、以下のエラーが出ますので、ご注意ください。

ERRO[0000] script.js already exists, please use the `--force` flag if you want overwrite it

コマンド実行すると、コマンド実行するディレクトリ直下に「script.js」ファイルが生成されており、以下ような基本的な単体のURLへ負荷テストするシナリオが書かれたスクリプトを確認できます。
(実際にはコメント行もありますが、邪魔なので削除してます)

import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  vus: 10,
  duration: '30s',
};

export default function() {
  http.get('https://test.k6.io');
  sleep(1);
}

スクリプトファイルの内容は、JavaScriptで記述されています。

  • import ~

  • export const options

    • どのような負荷をかけるかの指定をしています。

      • vus」では、同時(並列)にリクエストを実行する仮想のユーザ数(VUs)を指定します。

      • duration」では、繰り返し実行する期間を時間(分は「m」、秒は「s」)で指定します。
        各仮想ユーザ(VU)は、ここで指定した期間は指定のスクリプトをループ実行します。

  • export default function()

    • 実際にリクエストを投げる先のURLやサイト遷移のシナリオを指定します。
      リクエスト先のURLでは、メソッド(GET、POSTなど)やリクエストヘッダ内容なども指定できます。

生成されたスクリプトでは、「export const options」で「vus」が「10」、「duration」は「30s」と指定されていて、10VU(仮想ユーザ)が同時に30秒間、繰り返して指定したURLへリクエストを送り続けます。
また、「export default function()」では「sleep(1);」が指定されているので繰り返すリクエストの間に1秒間のスリープが発生します。

では、まず「export default function()」の「http.get」で指定しているURL「https://test.k6.io」をテストでリクエストしたいURLに変更して、実施してみたいと思います。


実施するとこんな感じの結果が出力

コマンドで「 k6 run script.js」のように、「k6 run」の後にスクリプトファイル名(ファイルのパス)を指定して実施すると、負荷テストが実施され、完了すると以下のように実施結果の概要レポートを返してくれます。

$ k6 run script.js

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

     execution: local
        script: script.js
        output: -

     scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
              * default: 10 looping VUs for 30s (gracefulStop: 30s)


     data_received..................: 12 MB 403 kB/s
     data_sent......................: 46 kB 1.5 kB/s
     http_req_blocked...............: avg=1.06ms   min=262ns    med=515ns    max=37.54ms  p(90)=823ns   p(95)=1.06µs
     http_req_connecting............: avg=64.92µs  min=0s       med=0s       max=2.51ms   p(90)=0s      p(95)=0s
     http_req_duration..............: avg=13.38ms  min=6.91ms   med=10.74ms  max=107.41ms p(90)=20.89ms p(95)=23.03ms
       { expected_response:true }...: avg=13.38ms  min=6.91ms   med=10.74ms  max=107.41ms p(90)=20.89ms p(95)=23.03ms
     http_req_failed................: 0.00% ✓ 0300
     http_req_receiving.............: avg=872.78µs min=140.01µs med=494.63µs max=49.96ms  p(90)=1.51ms  p(95)=1.93ms
     http_req_sending...............: avg=69µs     min=21.9µs   med=59.26µs  max=1.01ms   p(90)=86.94µs p(95)=143.47µs
     http_req_tls_handshaking.......: avg=456.52µs min=0s       med=0s       max=19.28ms  p(90)=0s      p(95)=0s
     http_req_waiting...............: avg=12.44ms  min=6.39ms   med=9.74ms   max=107.15ms p(90)=20.16ms p(95)=22.09ms
     http_reqs......................: 300   9.810602/s
     iteration_duration.............: avg=1.01s    min=1s       med=1.01s    max=1.1s     p(90)=1.02s   p(95)=1.02s
     iterations.....................: 300   9.810602/s
     vus............................: 10    min=10     max=10
     vus_max........................: 10    min=10     max=10

概要レポートにはいろいろな項目がありますが、自分は「*」でマークしてある「http_req_duration (リクエストの合計時間)」や「http_req_failed (失敗したリクエストの割合)」、「http_reqs (HTTPリクエストの総数)」、「iteration (仮想ユーザがリクエスト実行した集計結果)」などを見て結果を確認し、思ったほどパフォーマンスがあがらない場合はその他の項目を見て、どのあたりでボトルネックになっているかを見ていきました。

  • data_received

    • レスポンス(受信)データ量

    • 出力結果:トータルByte数、秒間Byte数

  • data_sent

    • リクエスト(送信)データ量

    • 出力結果:トータルByte数、秒間Byte数

  • http_req_blocked

    • リクエストを開始する前にブロックされていた時間
      (TCPコネクションスロットの空きの待ち時間)

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_req_connecting

    • リクエスト先とのTCPコネクション確立にかかった時間

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_req_duration *

    • リクエストの合計時間
      (http_req_sending + http_req_waiting + http_req_receiveing)

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_req_duration { expected_response:true }

    • 正常応答のみのリクエストの合計時間
      (http_req_sending + http_req_waiting + http_req_receiveing)

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_req_failed *

    • 失敗したリクエストの割合

    • 出力結果:割合、失敗した件数、総リクエスト件数

  • http_req_receiving

    • リクエスト先からのレスポンスデータを受信している時間
      (レスポンスのデータを受け始めてからすべてを受信するまでの時間)

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_req_sending

    • リクエスト先へのリクエストの送信にかかった時間

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_req_tls_handshaking

    • リクエスト先とのTLSセッションのハンドシェイクにかかった時間

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_req_waiting

    • リクエスト送信処理の完了からレスポンス(応答)を受信開始するまでの待ち時間
      (別名 "time to first byte"、"TTFB")

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • http_reqs *

    • k6が生成したHTTPリクエストの総数

    • 出力結果:リクエスト総数、秒間リクエスト数

  • iteration_duration

    • durationで指定した期間に繰り返し実施されるリクエスト(シナリオ)のループで、1回分のループのリクエスト(シナリオ)実施にかかった時間

    • 出力結果:平均[avg]、最小[min]、中間[mid]、最大[max]、90パーセンタイル[p(90)]、95パーセンタイル[p(95)]

  • iterations *

    • 仮想ユーザ(VU)がリクエスト(シナリオ)を実行した回数の集計結果

    • 出力結果:総実施回数、秒間の実施回数

  • vus

    • (試験終了時点での)アクティブな仮想ユーザー(VU)数

    • 出力結果:仮想ユーザー数(VUs)、最小アクティブ仮想ユーザー数[min]、アクティブ仮想ユーザー数[max]

  • vus_max

    • (実行中に)処理できた仮想ユーザー(VU)数(最大並列処理数)

    • 出力結果:最大仮想ユーザ数(VUs)、最小仮想ユーザー数[min]、最大仮想ユーザー数[max]

この概要レポートだけでは、実際にレスポンスで思ったような結果(HTTPステータスコードなど)が返ってきているかがよくわかりません。
その場合は、シナリオの中にチェック条件を追加することでチェック結果を集計して、概要レポートに追加して返してくれます。

シナリオの中にリクエストの結果として意図しているHTTPステータスコードを返してくれているかのチェックポイント的なメソッドを追加することで、実行結果にチェックした結果を出力します。
HTTPステータスコード以外にも、レスポンス本文の文字をチェックしたり、レスポンス本体のサイズをチェックすることもできます。

以下のように「import」で「check」モジュールをインポートし、定数「res」で指定した「http.get」のURLへのリクエストに対して、「check(~);」で「is status 200」の名目で、レスポンスのステータスコードが200で返ってきたかをチェックするようにシナリオを修正してみます。

import http from 'k6/http';
import { check } from 'k6';

(省略)

export default function () {
  const res = http.get('http://test.k6.io/');
  check(res, {
    'is status 200': (r) => r.status === 200,
  });
}

前項のデフォルトで出力されたスクリプトファイル(script.jp)の内容を以下のように変更しました。

import http from 'k6/http';
import { sleep } from 'k6';
import { check } from 'k6';

export const options = {
  vus: 10,
  duration: '30s',
};

export default function() {
  const res = http.get('【リクエスト先URL】');
  check(res, {
    'is status 200': (r) => r.status === 200,
  });
  sleep(1);
}

概要レポートは以下のようになりました。
(使用するコンソールにもよりますが実際にはこんな配色で表示されます)

checkモジュールを加えてチェックポイントを指定して診断したときの概要レポート

以下の2行が、チェックの設定によって追加された項目です。

is status 200

     checks.........................: 100.00% ✓ 3000
  • checks

    • 指定したチェックポイントすべてにおいてのチェック成功率

    • 出力結果:チェックポイント全体で成功した割合、成功数(✓)、失敗数(✗)

1行目は、チェックポイントごとの結果として、今回は「is status 200」の名目でチェックされた箇所の結果を表示しています。
複数のURLをシナリオで指定している場合に、各URL毎にページ毎の名目でチェックポイントを指定しておくと、以下のように各ポイント毎で「成功(✓)」や「失敗(✗)」を表示してくれます。

     ✓ page_1 - is status 200
     ✗ page_2 - is status 20097% — ✓ 975 / ✗ 25
     ✓ page_3 - is status 200
     ✓ page_4 - is status 200
  • 「✓」となっているチェック項目

    • 緑色の文字で表示

    • HTTPステータスコードがすべて指定したもの(200)が返ってきていている

  • 「✗」となっているチェック項目

    • 赤色の文字で表示

    • HTTPステータスコードが指定したもの(200)以外で返ってきてきたものがある

      • 「✗」の場合は、すべてのリクエストのうち、下に成功「✓」したリクエスト数と失敗「✗」したリクエスト数、リスエスト成功の割合を表示


よりリアルなアクセス状況を目指して

デフォルトでは「export const options」では以下のように指定されていますが、この場合、診断実施と同時に10VU(仮想ユーザ)が30秒間リクエストを送り続けます。

export const options = {
  vus: 10,
  duration: '30s',
};

でも、実際のアクセスでは、徐々にアクセスが増えていったり、最初にスパイク的にアクセスして徐々に減ってくるようなシナリオを想定して診断したい場合があります。

その場合は「export const options」で「stages」オプションにて「duration」と「target」を使って指定することで、段階的に同時のリクエスト量を増減させて負荷をかけることができます。

以下のパターンでは最初にピークの同時リクエストを迎えた後に時間が経過して同時リクエストが減っていくパターンです。

export const options = {
  stages: [
    { duration: '30s', target: 30 },
    { duration: '1m30s', target: 10 },
    { duration: '20s', target: 0 },
  ],
};
  1. 最初の30秒の間で同時アクセス数を0から30に徐々に増やす

  2. 次に1分30秒の間で同時アクセス数を30から10に徐々に減らす

  3. 最後に20秒の間で同時アクセス数を10から0に徐々に減らす

概要レポート結果の項目は大きく変わりはしませんが、「scenarios」の箇所には「Up to 30 looping VUs for 2m20s over 3 stages」のように最大30の同時リクエストで、3つのステージで合計2分20秒(30秒+1分30秒+20秒)の実施をした旨の内容が記載されています。
また、「vus」の項目では、試験終了時点でのアクティブな仮想ユーザー(VU)数として1になっていることを確認できます(「0」にはならないようですね)。

$ k6 run script.js

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

     execution: local
        script: script.js
        output: -

     scenarios: (100.00%) 1 scenario, 30 max VUs, 2m50s max duration (incl. graceful stop):
              * default: Up to 30 looping VUs for 2m20s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)


     ✓ is status 200

     checks.........................: 100.00% ✓ 23910
     data_received..................: 98 MB   695 kB/s
     data_sent......................: 338 kB  2.4 kB/s
     http_req_blocked...............: avg=101.47µs min=209ns   med=501ns    max=14.55ms  p(90)=773ns    p(95)=931ns
     http_req_connecting............: avg=22.28µs  min=0s      med=0s       max=2.96ms   p(90)=0s       p(95)=0s
     http_req_duration..............: avg=11.6ms   min=5.87ms  med=10.08ms  max=237.03ms p(90)=16.01ms  p(95)=18.29ms
       { expected_response:true }...: avg=11.6ms   min=5.87ms  med=10.08ms  max=237.03ms p(90)=16.01ms  p(95)=18.29ms
     http_req_failed................: 0.00%   ✓ 02391
     http_req_receiving.............: avg=839.1µs  min=84.28µs med=537.88µs max=13.61ms  p(90)=1.66ms   p(95)=2.05ms
     http_req_sending...............: avg=84.79µs  min=22.19µs med=67.16µs  max=1.62ms   p(90)=134.78µs p(95)=191.13µs
     http_req_tls_handshaking.......: avg=72.97µs  min=0s      med=0s       max=12.05ms  p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=10.68ms  min=5.38ms  med=8.94ms   max=236.61ms p(90)=15.35ms  p(95)=17.42ms
     http_reqs......................: 2391    16.967284/s
     iteration_duration.............: avg=1.01s    min=1s      med=1.01s    max=1.23s    p(90)=1.01s    p(95)=1.02s
     iterations.....................: 2391    16.967284/s
     vus............................: 1       min=1       max=30
     vus_max........................: 30      min=30      max=30


running (2m20.9s), 00/30 VUs, 2391 complete and 0 interrupted iterations
default ✓ [======================================] 00/30 VUs  2m20s

このような感じで、スクリプトファイルでシナリオを調整して、よりリアルなアクセス状況を仮定したシナリオを作成していきます。

他には、実際にサイトへアクセスする導線を想定してURLをリクエスト指定したり、実際にログインのあるサイトでログインをさせるリクエストを入れたりして、負荷テストのシナリオを作成していく内容を説明したいのですが・・・
長くなりそうなので、今回はここまでです(次回に続きます)。


最後に

今回は負荷テストにk6を選んだ経緯や、Linux環境でのk6のセットアップについて紹介しました。

使うサービスも特徴や要件によって選択

セットアップ自体はとても簡単で、かつ単体のサーバである程度の負荷は実施できることなど、導入までの工程はとても簡単です。
シナリオもJavaScriptを知っている人なら、直接スクリプトファイルを編集して作成することも可能です。

ただ、実際にサービスへのアクセスを想定したアクセス導線を想定した複雑なシナリオを作成するのは、どのツールでも苦労することは多いと思いますが、k6ではChrome拡張ツールを使ったシナリオ作成などもできたりするので、次回はこれらを使ったシナリオの作成や、ログインが必要なWebサイトなどで仮想ユーザごとに異なるアカウントを使ってログインするシナリオの作成方法などを紹介したいと思います。

シナリオ作りはどのツール使っても、それなりに大変ではあります。

新しいツールをいろいろ試せる今の会社はとてもいい会社なので、興味のある方はこちらも是非見てみてください。

あと、気軽にご参加いただけるカジュアルなイベントもたまに実施していますので、興味のあるイベントがあれば、ぜひ参加してみてください。



この記事が気に入ったらサポートをしてみませんか?