k6とは、JavaScript(またはTypeScript)でシナリオを書き、Webサービスに負荷をかけて性能を測るオープンソースの負荷テストツールです。Grafana Labsが開発しており、コマンド1つで実行でき、「95%のリクエストが500ms以内」のような合格基準をコードで決められるのが特長です。

この記事では、k6のコマンドや書き方だけでなく、「どの種類の負荷テストを、どの順番で、どう判定するか」という負荷テストの進め方までをまとめます。入門記事ではあまり触れられない、k6 v2系での変更点(--no-summary の廃止など)、scenarios と executor の選び方、結果の数値の読み方、他ツールとの比較表も載せています。スクリプトは公式ドキュメント(grafana.com/docs/k6)の仕様に沿って書いています。

負荷テストとk6の早見表

知りたいこと 答え
k6のインストール(macOS) brew install k6
k6のインストール(Windows) winget install k6 --source winget
スクリプトのひな形を作る k6 new script.js
実行する k6 run script.js
仮想ユーザー10人で30秒 k6 run --vus 10 --duration 30s script.js
合格基準を決める options.thresholds(例:http_req_duration: ['p(95)<500'])
レスポンスの中身を確かめる check(res, { 'status is 200': (r) => r.status === 200 })
段階的に負荷を上げる options.stages または scenarios の ramping-vus
「1秒あたり○件」で負荷をかける scenarios の constant-arrival-rate
最初に見る数値 http_req_duration の p(95) と http_req_failed
最新のメジャーバージョン k6 v2系(2026年9月時点の公式ドキュメントは v2.3.x)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「負荷テストの進め方とk6の使い方|スクリプト例と結果の読み方」

負荷テストの種類と使い分け

負荷テストは「とりあえずたくさんアクセスする」ものではありません。知りたいことに合わせて、負荷のかけ方を変えます。k6の公式ドキュメントでは、次の種類に分けて説明しています。

種類 負荷の大きさ 時間の目安 確かめること
スモークテスト ごく小さい(1〜数VU) 数秒〜数分 スクリプトが正しく動くか、最小限の負荷で問題がないか
平均負荷テスト(ロードテスト) 普段の本番と同じくらい 5〜60分 普段の利用量で、目標の応答速度を保てるか
ストレステスト 普段より高い 5〜60分 ピーク時や想定以上の負荷で、どう振る舞うか
スパイクテスト 急激にとても高い 数分 急なアクセス集中に耐えられるか、落ちた後に戻るか
耐久テスト(ソークテスト) 普段と同じくらい 数時間 長時間動かしたときのメモリ増加や性能の劣化
ブレークポイントテスト 少しずつ上げ続ける 限界まで どこが性能の上限か

※VU(Virtual User)は仮想ユーザーのことです。1VUが1人の利用者のようにスクリプトを繰り返し実行します。

どの順番で進めるか

スモークテストから始めて、段階的に負荷を上げるのが基本です。

  1. スモークテスト:スクリプトの間違いや、テスト環境の設定ミスを先に見つける
  2. 平均負荷テスト:普段の利用量で目標を満たすか確認する(ここが基準値になる)
  3. ストレス/スパイクテスト:ピーク時やキャンペーン時を想定して確認する
  4. 耐久テスト:長時間の運用で問題が出ないか確認する

負荷のかけ方は「増やす → 一定に保つ → 減らす」のシンプルな形にすると、結果を比べやすくなります。複雑に上下させると、どこで遅くなったのかが分かりにくくなります。

k6のインストール

公式ドキュメントに記載されている方法です。

# macOS
brew install k6

# Windows
winget install k6 --source winget

# Debian / Ubuntu
curl -fsSL https://dl.k6.io/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/k6-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

# Docker
docker pull grafana/k6

k6本体はGoで作られた単体のプログラムです。スクリプトはJavaScriptで書きますが、Node.jsの上で動くわけではありません。そのため、npmパッケージ(fs や axios など)はそのままでは使えない点に注意します。npmの k6 パッケージはk6本体ではないので、インストールは上記の方法で行います。

k6 v1.0以降は、TypeScriptのファイル(.ts)もそのまま実行できます。

最初のスクリプト

// script.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10,           // 仮想ユーザー10人
  duration: '30s',   // 30秒間
};

export default function () {
  const res = http.get('https://test.k6.io/');
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
  sleep(1);          // 利用者が画面を見る時間の代わり
}
k6 run script.js
部分 役割
export const options 負荷のかけ方と合格基準の設定
export default function 各VUが繰り返し実行する処理(1回を「イテレーション」と呼ぶ)
check レスポンスが期待どおりかを確かめる。失敗してもテストは止まらない
sleep(1) 次のリクエストまで1秒待つ

sleep を入れないと、1VUが休みなくリクエストを送り続けます。実際の利用者より強い負荷になるため、「VU10人=利用者10人」という対応が崩れます。利用者の動きを再現したいときは sleep を入れます。

https://test.k6.io/ は、k6の公式が練習用に公開しているサイトです。自分が管理していないサイトには負荷をかけないでください(後述)。

options:負荷のかけ方を決める

stages:段階的に負荷を上げ下げする

export const options = {
  stages: [
    { duration: '2m', target: 50 },   // 2分かけて50VUまで増やす
    { duration: '5m', target: 50 },   // 50VUを5分維持
    { duration: '1m', target: 0 },    // 1分かけて0まで減らす
  ],
};

これが平均負荷テストの基本形です。target を普段の利用量より大きくすればストレステスト、短い duration で一気に上げればスパイクテストになります。

テストの種類 stages の例
スモーク vus: 1, duration: '1m'
平均負荷 2分で50 → 5分維持 → 1分で0
ストレス 5分で200 → 10分維持 → 2分で0
スパイク 30秒で500 → 1分維持 → 30秒で0
耐久 5分で50 → 4時間維持 → 5分で0

VUの数は例です。普段の利用量(アクセスログや監視ツールの値)から逆算して決めます。

thresholds:合格・不合格の基準を決める

thresholds(しきい値)は、テストの合否をコードで決める仕組みです。基準を満たさないと、k6は0以外の終了コードで終わるため、CIでそのまま「失敗」として扱えます。

export const options = {
  thresholds: {
    http_req_failed: ['rate<0.01'],     // エラー率 1% 未満
    http_req_duration: ['p(95)<500'],   // 95%のリクエストが500ms未満
    checks: ['rate>0.99'],              // check の成功率 99% 超
  },
};

明らかに基準を超えたら途中で止めることもできます。

export const options = {
  thresholds: {
    http_req_failed: [
      { threshold: 'rate<0.05', abortOnFail: true, delayAbortEval: '30s' },
    ],
  },
};
項目 意味
abortOnFail: true 基準を超えた時点でテストを止める
delayAbortEval: '30s' 開始直後のばらつきで止まらないよう、判定を30秒遅らせる

エンドポイントごとに基準を変えるには、リクエストにタグを付けます。

export const options = {
  thresholds: {
    'http_req_duration{name:list}': ['p(95)<300'],
    'http_req_duration{name:search}': ['p(95)<800'],
  },
};

export default function () {
  http.get('https://example.com/api/items', { tags: { name: 'list' } });
  http.get('https://example.com/api/search?q=abc', { tags: { name: 'search' } });
}

checks:レスポンスの中身を確かめる

check はレスポンスの検証、thresholds は全体の合否、という役割分担です。

const res = http.get('https://example.com/api/items');
check(res, {
  'status is 200': (r) => r.status === 200,
  'has items': (r) => r.json('items').length > 0,
  'fast enough': (r) => r.timings.duration < 500,
});

check は失敗してもテストを止めません。失敗の割合で合否を判定したいときは、上のように thresholds に checks: ['rate>0.99'] を書きます。

「ステータスが200でも、中身がエラー画面だった」ということは実際によくあります。ステータスコードだけでなく、中身も check すると、見かけだけ速い結果にだまされにくくなります。

scenarios:本番に近い負荷を組み立てる

scenarios を使うと、複数の負荷パターンを1つのテストで同時に動かせます。各シナリオには「executor(実行方式)」を指定します。

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

export const options = {
  scenarios: {
    browse: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 40 },
        { duration: '5m', target: 40 },
        { duration: '1m', target: 0 },
      ],
      exec: 'browse',
    },
    api_orders: {
      executor: 'constant-arrival-rate',
      rate: 20,              // 1秒あたり20イテレーション
      timeUnit: '1s',
      duration: '8m',
      preAllocatedVUs: 20,
      maxVUs: 100,
      exec: 'order',
    },
  },
  thresholds: {
    'http_req_duration{scenario:browse}': ['p(95)<500'],
    'http_req_duration{scenario:api_orders}': ['p(95)<800'],
    http_req_failed: ['rate<0.01'],
  },
};

export function browse() {
  const res = http.get('https://example.com/');
  check(res, { 'top 200': (r) => r.status === 200 });
  sleep(Math.random() * 3 + 1);   // 1〜4秒ほど画面を見る
}

export function order() {
  const res = http.post(
    'https://example.com/api/orders',
    JSON.stringify({ itemId: 1, qty: 1 }),
    { headers: { 'Content-Type': 'application/json' } },
  );
  check(res, { 'order 201': (r) => r.status === 201 });
}

executorの選び方

executor 負荷の決め方 向いている場面
shared-iterations 全VUで合計○回 決まった件数を処理するテスト
per-vu-iterations 各VUが○回ずつ データを1件ずつ割り当てるテスト
constant-vus VU数を一定に保つ シンプルな負荷テスト
ramping-vus VU数を段階的に変える stages と同じ。平均負荷・ストレス
constant-arrival-rate 1秒あたりのイテレーション数を一定に保つ APIの処理能力(RPS)の確認
ramping-arrival-rate 1秒あたりの数を段階的に変える ブレークポイントテスト

「VUで決める」と「到着率で決める」の違い

ここは負荷テストで特に大切な考え方です。

方式 サーバーが遅くなると…
VUで決める(ramping-vus など) 各VUが応答を待つため、送るリクエスト数が減る
到着率で決める(constant-arrival-rate など) 決めた数のリクエストを送り続ける(足りなければVUを追加)

VU方式では、サーバーが遅くなるほど負荷が自然に下がるため、限界を実際より高く見積もることがあります。「1秒あたり○件をさばけるか」を確かめたいときは、到着率方式(arrival-rate)を使います。maxVUs まで使い切ると、k6は dropped_iterations(実行できなかったイテレーション)を記録します。この値が出たら、狙った負荷をかけられていないというサインです。

テストのライフサイクル(setup / teardown)

export function setup() {
  // テスト開始前に1回だけ実行(ログインしてトークンを取るなど)
  const res = http.post('https://example.com/api/login',
    JSON.stringify({ user: 'loadtest', password: __ENV.LT_PASSWORD }),
    { headers: { 'Content-Type': 'application/json' } });
  return { token: res.json('token') };
}

export default function (data) {
  http.get('https://example.com/api/me', {
    headers: { Authorization: `Bearer ${data.token}` },
  });
}

export function teardown(data) {
  // テスト終了後に1回だけ実行(テストデータの後片付けなど)
}
段階 実行回数 用途
初期化(ファイルの一番外側) VUごとに1回 import、options、テストデータの読み込み
setup 1回 ログイン、テストデータ作成
default 繰り返し 測りたい処理
teardown 1回 後片付け

パスワードなどはスクリプトに直接書かず、k6 run -e LT_PASSWORD=... script.js のように環境変数(__ENV)で渡します。

複数ユーザーのテストデータは SharedArray で読み込むと、全VUで1つのデータを共有でき、メモリを節約できます。

import { SharedArray } from 'k6/data';

const users = new SharedArray('users', function () {
  return JSON.parse(open('./users.json'));
});

export default function () {
  const user = users[__VU % users.length];   // VUごとに別のユーザー
  // ...
}

結果の読み方

k6 run が終わると、結果のまとめ(end-of-test summary)が表示されます。k6 v1.0以降は、合否(THRESHOLDS)と全体の結果(TOTAL RESULTS)を中心にした「compact」形式が既定になりました。以下は公式ドキュメントに掲載されている出力形式の例(一部抜粋)です。

█ THRESHOLDS
  http_req_duration
  ✓ 'p(95)<1500' p(95)=148.21ms

█ TOTAL RESULTS
  checks_total: 90  13.122179/s
  checks_succeeded: 100.00%

  HTTP
  http_req_duration: avg=140.36ms min=119.08ms
  http_reqs: 45  6.56109/s

まず見る指標

指標 意味 見るポイント
http_req_duration リクエストを送ってから応答を受け取り終わるまでの時間 平均ではなく p(95)・p(99) を見る
http_req_failed 失敗したリクエストの割合 既定では 4xx・5xx などが失敗扱い
http_reqs リクエストの総数と1秒あたりの数 狙ったRPSが出ているか
checks check の成功率 中身の検証が通っているか
iterations default 関数の実行回数 想定の回数を回せているか
vus / vus_max 実行中のVU数 狙いどおりに増減したか
dropped_iterations 実行できなかったイテレーション 出ていたら負荷が足りていない
http_req_waiting サーバーが処理している時間(TTFB) ここが大きいならサーバー側の処理が遅い

p(95) を見る理由

p(95) は「95%のリクエストがこの時間以内に終わった」という値です。平均値は、ほとんど速くて一部だけとても遅い場合でも、それなりの数字になってしまいます。

リクエスト 応答時間
96件 100ms
4件 3,000ms
平均 216ms
p(95) 100ms
p(99) 3,000ms

平均だけを見ると「216msなら十分速い」と判断しがちですが、実際には100人に4人が3秒待っています。この例では p(95) も100msのため、p(99) まで合わせて見ると、遅い利用者がどれくらいいるかがわかります。

結果の判断表

結果の様子 考えられること 次に見るもの
VUを増やしても RPS が増えず、応答時間だけ伸びる どこかが上限に達している CPU・DB接続数・スレッド数
エラー率が途中から急に上がる 接続上限やタイムアウト サーバーのログ、DBの接続プール
時間とともに応答時間がじわじわ伸びる メモリリーク、キャッシュ肥大 耐久テストでメモリの推移
dropped_iterations が出る k6側のVUが足りない maxVUs を増やす、k6マシンの性能
k6マシンのCPUが高い k6自体が上限 k6を複数台に分ける、スクリプトを軽くする

k6を動かしているマシンの CPU やネットワークも必ず確認します。負荷をかける側が上限に達していると、サーバーではなくk6側の限界を測ってしまいます。

結果をファイルや外部に出す

# 全データを JSON(1行1データ)で出力
k6 run --out json=results.json script.js

# まとめを詳しく表示(グループ・シナリオごとの結果も出す)
k6 run --summary-mode=full script.js

# 実行中の様子をブラウザで見る(Webダッシュボード)
K6_WEB_DASHBOARD=true k6 run script.js

handleSummary 関数を書くと、まとめを JSON や HTML など好きな形式でファイルに保存できます。

export function handleSummary(data) {
  return {
    'summary.json': JSON.stringify(data, null, 2),
  };
}

k6 v2系での主な変更点

古い記事の書き方をそのまま使うとエラーになるものがあります。

以前の書き方 v2系での書き方
--no-summary --summary-mode=disabled
--summary-mode=legacy 廃止(compact か full を使う)
k6 cloud script.js k6 cloud run script.js
options.ext.loadimpact options.cloud
executor: 'externally-controlled' 廃止(ramping-vus などに置き換える)
k6/experimental/redis k6/x/redis

ツールの比較:k6・JMeter・Locust・Gatling

項目 k6 JMeter Locust Gatling
シナリオの書き方 JavaScript / TypeScript GUIで作成(XMLで保存) Python Java・Kotlin・Scala(JS/TSのSDKもあり)
実行の軽さ 1プロセスで多くのVUを動かせる スレッドごとにメモリを使う 軽い(並行処理に gevent を使用) 軽い
合否の判定 thresholds で標準対応 アサーション+プラグイン等 コードで自前実装 assertions で標準対応
CIとの相性 終了コードでそのまま判定できる CLIモードで実行 ヘッドレスモードで実行 Maven/Gradle 等から実行
画面で操作 なし(Webダッシュボードで観察) GUIが充実 Web UIあり なし(HTMLレポートあり)
対応プロトコル HTTP、WebSocket、gRPC、ブラウザ 非常に幅広い(JDBC、FTP、JMSなど) HTTP中心(拡張可能) HTTP、WebSocket、JMS など
向いているチーム 開発者がコードで管理したい GUIで作りたい、多様なプロトコルが必要 Pythonに慣れている JVM系の開発が中心

どれが優れているかではなく、チームの言語と目的で選ぶのがよい考え方です。コードレビューやGitでの管理、CIでの自動判定を重視するならk6が扱いやすく、GUIで試行錯誤したい場合や特殊なプロトコルが必要な場合はJMeterが便利です。

負荷テストの計画と注意点

テスト前に決めておくこと

決めること 例
目的 「新しい検索APIが、ピーク時の2倍に耐えられるか」
対象 検索API、商品一覧ページ(ログイン後)
目標値 p(95) 500ms 未満、エラー率 1% 未満、100 RPS
負荷の根拠 直近のピーク時のアクセス数(監視ツールの値)
環境 本番と同じ構成の検証環境
実施日時と連絡先 インフラ担当・関係チームへの事前連絡
止める条件 エラー率5%超で中止(abortOnFail)

目標値を先に決めておくと、結果を見て「速い/遅い」で迷わずに済みます。

やってはいけないこと・気をつけること

  • 本番環境にいきなり負荷をかけない:利用者に影響が出ます。本番と同じ構成の検証環境で行うのが基本です。やむを得ず本番で行う場合は、関係者の合意、時間帯、止める条件を決めてから実施します
  • 自分が管理していないサイトに負荷をかけない:相手のサービスを止めてしまうおそれがあり、攻撃とみなされることもあります
  • 外部サービスへの通信に注意する:支払いやメール送信、SMSなど外部APIを呼ぶ処理は、モック(テスト用の偽物)に置き換えるか、テスト用の環境を使います
  • クラウドやCDNの設定を確認する:WAFやレート制限でk6の通信がブロックされると、正しい結果になりません。事前に検証環境の設定を確認します
  • ネットワーク距離をそろえる:k6を動かす場所とサーバーの距離によって応答時間が変わります。毎回同じ場所から実行します
  • テストデータを用意する:同じユーザー・同じ商品ばかりだとキャッシュが効きすぎて、実際より速い結果になります
  • 同時にサーバー側も監視する:k6の結果だけでは原因がわかりません。CPU、メモリ、DB、ログを並べて見ます

実行前のチェックリスト

チェック 確認
対象のURLは検証環境になっているか □
関係者に日時と内容を連絡したか □
スモークテストでスクリプトが正しく動いたか □
thresholds で合格基準を決めたか □
止める条件(abortOnFail)を決めたか □
サーバー側の監視画面を開いているか □
外部APIへの通信はモックに置き換えたか □
k6マシンのCPU・ネットワークに余裕があるか □

CIで定期的に動かす

thresholds を満たさないとk6は0以外の終了コードで終わるため、GitHub Actions などのCIでそのまま合否を判定できます。CIでは短いスモークテストや平均負荷テストを動かし、性能が落ちる変更を早めに見つける使い方が向いています。 k6公式の Docker イメージ(grafana/k6)を使えば、CIのジョブ内でもインストールなしで実行できます。

よくある質問

Q. k6はブラウザの操作も測れますか? A. はい。k6/browser モジュール(v1.0で正式機能になりました)で、実際のブラウザを動かして画面表示の速さを測れます。ただしブラウザは1VUあたりの負荷が大きいため、大量の負荷はHTTPのテストでかけ、ブラウザのテストは少数で体感速度を測る組み合わせが一般的です。

Q. VUはいくつに設定すればいいですか? A. 実際の利用量から決めます。「1秒あたりのリクエスト数」が分かっている場合は、VUではなく constant-arrival-rate で RPS を直接指定する方が、狙った負荷を正確にかけられます。

Q. k6はnpmでインストールできますか? A. npmの k6 パッケージはk6本体ではありません。k6本体は Homebrew、winget、apt、Docker などでインストールします。スクリプトはJavaScriptですが、Node.jsではなくk6の実行環境で動きます。

Q. 平均値が速いのにユーザーから「遅い」と言われます。 A. p(95)・p(99) を確認してください。一部のリクエストだけが極端に遅い場合、平均値には表れにくくなります。タグを付けてエンドポイントごとに分けると、どこが遅いのかを絞り込めます。

まとめ

  • k6は JavaScript/TypeScriptでシナリオを書き、合否までコードで決められる負荷テストツール
  • 負荷テストは スモーク → 平均負荷 → ストレス/スパイク → 耐久 の順に進める
  • 合否は thresholds(p(95)、エラー率、check成功率)で決め、CIでも判定できる
  • 「1秒あたり○件」を確かめるなら 到着率方式(constant-arrival-rate)。VU方式は遅くなると負荷が下がる
  • 結果は 平均ではなく p(95)・p(99) と http_req_failed、dropped_iterations を見る
  • 本番環境や他人のサイトに負荷をかけない。検証環境・事前連絡・止める条件をそろえる
  • v2系では --no-summary が --summary-mode=disabled に変わるなど、古い書き方に注意

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「負荷テストの進め方とk6の使い方|スクリプト例と結果の読み方」

株式会社RJCでは、システムの性能を確かめる方法も、プロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?