初めての k6 で負荷テスト:使い方と、つまずいたところ

村井 謙太

代表取締役

村井 謙太

初めての k6 で負荷テスト:使い方と、つまずいたところ

クライアントのプロダクト開発支援で、数千rps規模のリアルタイム機能が想定ピークに耐えられるかを確かめることになりました。負荷ツールは k6 を初めて使ったので、基本的な使い方と、初見でつまずいたところをまとめます。

つまずいたのはツールの使い方そのものより、出てきた数字の読み方のほうでした。そこは後半に厚めに書きます。

k6 とは、入れ方

k6 は Grafana が出しているオープンソースの負荷テストツールです。テストシナリオを JavaScript で書き、Go 製の本体が高速にリクエストを撃ちます。Mac なら Homebrew で入ります。

brew install k6

最小のスクリプト

export default した関数が、各仮想ユーザー(VU)ごとに繰り返し実行されます。まずは1つのエンドポイントに POST するだけの最小形です。

import http from "k6/http";

export default function () {
  http.post("https://example.com/api/echo", JSON.stringify({ hello: "world" }), {
    headers: { "Content-Type": "application/json" },
  });
}
k6 run script.js

これだけで、しばらくリクエストを撃って結果のサマリを出してくれます。

出力の読み方

最初に見るべきはこのあたりです。http_reqs(総数と1秒あたりのレート)、http_req_duration(レイテンシ。特に p(95))、http_req_failed(失敗率)、そして自分で書いた合否判定の checks

http_reqs..............: 201309  2232.68/s
http_req_duration......: avg=62ms  med=40ms  p(95)=117ms  max=5.35s
http_req_failed........: 0.00%   0 out of 201309

平均(avg)より、外れ値に強い p(95)(95パーセンタイル)を見ると実感に近いです。上の例だと「95%のリクエストは 117ms 以内、ただし最悪 5.35 秒のものもあった」と読めます。

目標rpsを出す:arrival-rate

負荷テストで測りたいのは「毎秒どれだけ捌けるか(rps)」です。

k6 には負荷のかけ方(executor と呼びます)が何種類かあります。大きく分けると、「同時に何ユーザーいる状態か(VU=仮想ユーザー数)」を決める方式と、「毎秒何件送るか(rps)」を決める方式です。今回は rps を知りたいので、後者の arrival-rate 系(constant-arrival-rate / ramping-arrival-rate)を使います。

この方式だと VU は裏方になります。k6 は目標のrpsを出すために、応答待ちのリクエストを抱える VU を必要なだけ回します。その上限が maxVUs で、小さすぎると目標rpsを出しきれません(preAllocatedVUs は最初に用意しておく数)。

ここで出てくる「ランプ(ramp)」は、負荷を一気にかけず、時間をかけて徐々に上げていくことです。語源は傾斜路で、グラフにすると右肩上がりの坂になります。いきなり全開にすると「壊れた」しか分かりませんが、じわじわ上げると「だいたい何rpsから崩れ始めるか」という天井が見えます。逆に、決めた負荷にずっと耐えられるかを見たいときは、一定rpsをかけ続ける constant-arrival-rate を使います。

次は 500rps から 90 秒かけて 4,000rps まで上げるランプです。

export const options = {
  scenarios: {
    ramp: {
      executor: "ramping-arrival-rate",
      startRate: 500,
      timeUnit: "1s",
      preAllocatedVUs: 500,
      maxVUs: 2000,
      stages: [{ target: 4000, duration: "90s" }],
    },
  },
  thresholds: {
    http_req_duration: ["p(95)<1500"],
    http_req_failed: ["rate<0.01"],
  },
};

thresholds に合格ラインを書いておくと、超えたときに k6 が失敗として教えてくれます。ランプで rps を上げていって「どこで壊れるか」を探すのが、天井を知る素直なやり方でした。

つまずき①:実効rpsが目標に届かないとき、詰まっているのは誰か

「3,000rps を出す」と指定しても、結果の実効rpsが 1,500 くらいで止まることがあります。ここで「サーバの限界だ」と早合点しがちですが、まず疑うべきは自分側でした。

必要なVU数は、ざっくり rps × レイテンシ です。レイテンシが 200ms なら 3,000rps に 600VU 要ります。maxVUs が足りないと Insufficient VUs の警告が出て、サーバが元気でも実効rpsは頭打ちになります。

見分け方はシンプルです。成功しているのにレイテンシが伸びていればサーバの飽和そもそも送れていない(接続エラーやVU不足)ならこちら側の限界です。

つまずき②:ランプの平均rpsと、ピークrpsは別物

ランプで測ると、サマリの平均rpsは当然ピークより低くなります。「平均 2,232rps」と出ていても、ランプの最後には目標の 4,000rps に到達して維持できている、ということがあります。

さらに k6 は、テスト終了後も応答待ちを捌く猶予(graceful stop)を取り、その待ち時間も分母に入って平均を薄めます。知りたいのは平均ではなく「ピーク到達時の成功率とレイテンシ」なので、そこを見るようにしました。

まとめ

k6 は JavaScript でシナリオを書けて、初めてでもすぐ動かせました。arrival-rate で狙ったrpsを撃ち、thresholds で合否を決め、ランプで天井を探す、という流れがつかめれば十分に実用的です。

むしろ効いたのは、ツールの使い方より「数字の読み方」を先に決めておくことでした。実効rpsが頭打ちしたら自分側を疑う、平均ではなくピークで判断する。この2点を押さえておくと、出た数字を安心して読めます。

記事を書いた人

村井 謙太

代表取締役

村井 謙太

Twitter

東京大学在学中にプログラミング学習サービスのProgateを立ち上げ、CTOとしてプロダクト開発に従事。 Progate退任後に株式会社Anycloudを立ち上げ、現在は多数のクライアントの技術支援を行っている。