初めての 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点を押さえておくと、出た数字を安心して読めます。
Anycloudでは一緒に働くメンバーを募集しています!
Anycloudは、ユーザーの心を動かす体験を届けることを大切にしています。フルリモート・フルフレックスの環境のもと、ライフスタイルに合わせた働き方を実現しながら挑戦したい方を歓迎します。詳細はこちらをご覧ください。