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

> リアルタイム機能の負荷テストで、初めて負荷テストツールの k6 を使いました。インストールから最小スクリプト、目標rpsの出し方、そして初見でつまずいた「数字の読み方」までをまとめます。

- 公開日: 2026-07-16
- 著者: 村井 謙太
- タグ: k6, 負荷テスト, パフォーマンス, ツール紹介
- URL: https://tech.anycloud.co.jp/articles/k6-first-load-test

---

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

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

## k6 とは、入れ方

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

```sh
brew install k6
```

## 最小のスクリプト

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

```js
import http from "k6/http";

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

```sh
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 まで上げるランプです。

```js
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点を押さえておくと、出た数字を安心して読めます。
