- マネジメント
性能テストの進め方|性能要件の決め方と合格基準のテンプレート
性能テストは「速いかどうか」を見るテストではなく、「業務量から決めた目標(何件/秒を、何秒以内で、エラー何%以下で)を満たすか」を確かめるテストです。 目標は、平均ではなく 90・95パーセンタイル(遅い方から 10%・5% を除いたときの最大)で決めます。
| 手順 | やること | 決めるもの・出すもの |
|---|---|---|
| ① 性能要件を決める | 業務量から、ピーク時の件数・同時接続を計算する | 目標スループット・応答時間(p90/p95)・エラー率 |
| ② 計画を立てる | テストの種類・シナリオ・環境・データを決める | 性能テスト計画書 |
| ③ 測る | 負荷をかけ、応答時間とサーバーの資源を同時に記録する | k6 などの結果・CPU/メモリ/DB の記録 |
| ④ 判定して報告する | 合格基準と比べ、ボトルネックと限界を報告する | 性能テスト報告書 |
実際に測ると、平均とパーセンタイルはこれだけ違いました。 同時に4件まで処理できるサーバー(1件約50ms、5% だけ約200ms)に、k6 で 40件/秒の負荷を 30秒かけた結果です。
| 指標 | 値 | 読み方 |
|---|---|---|
| 平均(avg) | 62.55 ms | 遅い 5% が平均に薄まって見えない |
| 中央値(med) | 51.69 ms | 半分の要求はこれより速い |
| p90 | 64.39 ms | 90% の要求はこれより速い |
| p95 | 186.23 ms | 95% の要求はこれより速い(遅い 5% の入り口) |
| p99 | 239.71 ms | 100件に1件はこれより遅い |
確認環境:k6 v2.3.0(GitHub のリリースから入れ、チェックサムを照合)、Node.js 22.22.2 で作ったテスト用の HTTP サーバー、Python 3.11(Ubuntu 24.04)で測り、計算しました。図は mermaid-cli 11.14.0 で描画できることを確かめました。サーバーと k6 は同じマシンで動かしています。k6 の機能・テストの種類はGrafana k6 のドキュメント(v2.3.x)で確かめています(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「性能テストの進め方|性能要件の決め方と合格基準のテンプレート」
① 性能要件の決め方:業務量から計算する
性能要件は「速いほどよい」ではなく、業務量から数字を出します。 根拠のない「3秒以内」は、テストで満たしても本番で足りるか分からず、満たせなくても直す量が決められません。
必要な数字と、どこから取るか
| 数字 | 例 | どこから取るか |
|---|---|---|
| 1日の業務の件数 | 注文 12,000件/日 | 業務の資料、今のシステムの DB の件数 |
| ピークの1時間への集中 | 1日の 15% | アクセスログの時間ごとの件数 |
| 1件の業務で呼ばれる API の回数 | 8回 | 画面の流れ(一覧→詳細→カート→注文…)を数える |
| 1時間の中の偏り | 3倍 | 1分ごとの最大 ÷ 1時間の平均(ログから) |
| 将来の伸び | 1.5倍 | 事業計画(何年先まで今の構成で持たせるか) |
| 1人がいる時間 | 600秒 | アクセスログのセッションの長さ |
Python で計算する
# 業務量から性能目標を出す
orders_per_day = 12_000 # 1日の注文件数(業務の資料から)
peak_hour_share = 0.15 # 一番多い1時間に、1日の何割が集中するか(アクセスログから)
requests_per_ord = 8 # 1件の注文で呼ばれる API の回数(画面遷移から数える)
burst_factor = 3 # 1時間の中の偏り(1分単位の最大 ÷ 平均)
growth = 1.5 # 3年後の伸び
session_sec = 600 # 1人がサイトにいる時間(秒)
resp_sec = 0.3 # 目標の応答時間(秒)
peak_orders_h = orders_per_day * peak_hour_share
avg_rps = peak_orders_h * requests_per_ord / 3600
target_rps = avg_rps * burst_factor * growth
users_per_sec = peak_orders_h / 3600
concurrent_users = users_per_sec * growth * session_sec # リトルの法則 L = λW
inflight = target_rps * resp_sec # サーバーの中で同時に処理中の要求
print(f"ピーク1時間の注文 {peak_orders_h:,.0f} 件/時")
print(f"ピーク1時間の平均 {avg_rps:.1f} req/s")
print(f"目標スループット {target_rps:.1f} req/s(偏り×{burst_factor}・伸び×{growth})")
print(f"同時に使っている人 {concurrent_users:,.0f} 人")
print(f"同時に処理中の要求 {inflight:.1f} 件")
ピーク1時間の注文 1,800 件/時
ピーク1時間の平均 4.0 req/s
目標スループット 18.0 req/s(偏り×3・伸び×1.5)
同時に使っている人 450 人
同時に処理中の要求 5.4 件
「同時接続数」は2つの意味で使われるので、分けて書きます。
| 言葉 | 意味 | この例 | 計算 |
|---|---|---|---|
| 同時に使っている人(同時ユーザー) | ログインして画面を見ている人 | 450人 | 1秒あたりに来る人 × いる時間(リトルの法則) |
| 同時に処理中の要求 | サーバーの中で処理している要求 | 5.4件 | 1秒あたりの要求 × 応答時間 |
「同時ユーザー 450人」は「450件の要求が同時に来る」ではありません。画面を読んでいる時間があるので、サーバーの中で同時に処理しているのは、この例では 5〜6件です。負荷テストのツールで「仮想ユーザー(VU)450」を設定するときは、待ち時間(think time)を入れないと、実際よりずっと重い負荷になります。 同時接続・スループットの見積もりは、関連記事「キャパシティ見積もりの計算方法|同時接続数・スループット・ディスク容量」で扱っています。
応答時間は平均ではなくパーセンタイルで決める
パーセンタイルは、速い順に並べて、その割合の位置にある値です。 p95 = 186ms なら、「95% の要求は 186ms 以内に返った」という意味です。
測った 40件/秒・801件の応答時間を、Python で並べ直した結果です。
件数 801
平均 58.2 ms / 中央値 50.7 ms
p90: 59.0 ms
p95: 150.1 ms
p99: 236.4 ms
200ms を超えた割合: 2.5%
k6 の要約: {'avg': 58.2, 'med': 50.7, 'p(90)': 59.0, 'p(95)': 150.1, 'p(99)': 236.4}
0–50 ms 368件 ####################################
50–60 ms 372件 #####################################
60–100ms 18件 #
100–150ms 2件
150–200ms 21件 ##
200–250ms 20件 ##
Python で計算した値は、k6 の要約と一致しました。分布は 50ms 前後の山と、150〜250ms の小さな山の2つに分かれています。 平均(58.2ms)はどちらの山にも無い値で、「遅い 5% の人がどれだけ待ったか」は平均からは分かりません。
| 決め方 | 向いている場面 |
|---|---|
| p90・p95 で決める | 画面・API の応答時間(ほとんどの人の体感) |
| p99 も見る | 決済・ログインなど、遅いと離脱や二重送信につながる処理 |
| 最大値 | 目標にはしにくい(1件の外れ値で変わる)。タイムアウトの設定と合わせて見る |
| 平均 | 目標には使わない。スループットの計算(リトルの法則)に使う |
p95 は、遅い要求の割合が 5% に近いと、測るたびに大きく変わります。 同じ 40件/秒の負荷で、30秒の1回目は 186ms、20秒の2回目は 174ms、3回目は 150ms でした。遅い要求がちょうど 5% 前後あり、p95 の位置がその境目にあるためです。目標の判定は、1回ではなく複数回・長めの時間で測って行います。
② テストの種類の比較
性能テストには目的の違う種類があり、計画書でどれを行うかを決めます。 名前と中身は、Grafana k6 のドキュメントの分け方をもとにしています。
| 種類 | 目的 | 負荷のかけ方 | 時間の目安(k6 ドキュメント) |
|---|---|---|---|
| スモーク | スクリプトとシステムが最小の負荷で動くか | ごく少ない負荷 | 数秒〜数分 |
| 負荷(平均負荷) | 想定の普段・ピークの負荷で、目標を満たすか | 目標のスループット | 5〜60分 |
| ストレス | 想定を超えたときにどうなるか | 目標の 1.5〜2倍など | 5〜60分 |
| 耐久(ソーク) | 長時間で性能が落ちないか(メモリリーク・接続のもれ) | 普段の負荷を長く | 数時間 |
| スパイク | 急な短い集中に耐えて、元に戻るか | 急に何倍にもする | 数分 |
| 限界(ブレークポイント) | どこまで耐えられるかを探す | 少しずつ上げ続ける | 必要なだけ |
| 種類 | 合格かどうかの見方 | よく見つかる問題 |
|---|---|---|
| 負荷 | 目標の p90/p95・エラー率を満たすか | 遅い SQL、N+1、キャッシュが効いていない |
| ストレス | エラーにならず遅くなるだけか、どこから崩れるか | 接続プールの枯渇、タイムアウトの連鎖 |
| 耐久 | 始めと終わりで p95・メモリが変わらないか | メモリリーク、ログ・一時ファイルの増加 |
| スパイク | 集中が去ったあと、何秒で元の応答時間に戻るか | 待ち行列が捌けない、オートスケールの遅れ |
| 限界 | 何件/秒で頭打ちになるか | 一番弱い資源(CPU・DB・外部 API) |
実測:限界の見つけ方(ストレス・限界テスト)
負荷を段階的に上げて、スループットが増えなくなる点(限界)を探します。 同時に4件までしか処理できないテスト用サーバーに、20〜90件/秒をそれぞれ 20秒かけた結果です。
| かけた負荷(件/秒) | 実際に処理した件/秒 | p95 | 処理しきれず落とした数 |
|---|---|---|---|
| 20 | 20.0 | 60.2 ms | 0 |
| 40 | 39.9 | 174.4 ms | 0 |
| 60 | 59.9 | 160.9 ms | 0 |
| 70 | 69.1 | 453.2 ms | 0 |
| 75 | 68.7 | 1,245.3 ms | 41 |
| 80 | 70.3 | 1,663.4 ms | 73 |
| 90 | 69.2 | 3,124.1 ms | 187 |
xychart-beta
title "かけた負荷と p95(ms)"
x-axis "かけた負荷(件/秒)" [20, 40, 60, 70, 75, 80, 90]
y-axis "p95(ms)" 0 --> 3500
line [60, 174, 161, 453, 1245, 1663, 3124]
処理できる量は約70件/秒で頭打ちになり、それ以上の負荷は「待ち時間」になりました。 エラー率は最後まで 0% でしたが、p95 は 60件/秒の 161ms から、90件/秒では 3.1秒になっています。エラーが出ないから大丈夫、とは判断できません。 応答時間とスループットを並べて見ます。
この上限は計算とも合います。同時に処理できる数 4 ÷ 1件の平均処理時間 0.0575秒(95% が 50ms、5% が 200ms)= 約69.6件/秒 です。本番のシステムでは、この「同時に処理できる数」が、スレッド数・DB の接続プール・CPU のコア数などで決まります。
「落とした数」は k6 の dropped_iterations です。 k6 のドキュメントでは、到着率を決める実行方法(constant-arrival-rate など)で、空いている仮想ユーザーが無いと、その回の要求は送られずに落とされます。テストの始めに出るなら仮想ユーザーの不足、途中から出るならシステムが遅くなったことが原因です。この表では 75件/秒から出ていて、サーバーが遅くなったことを示しています。
実測:スパイクからの戻り
普段 30件/秒のところに、5秒で 120件/秒まで上げて 10秒続け、また 30件/秒に戻しました。応答が返った時刻で、5秒ごとに区切った p95 です。
| 経過(秒) | 処理した件/秒 | p95 |
|---|---|---|
| 0〜20 | 28〜30 | 59〜62 ms |
| 20〜25(急増) | 60 | 490.6 ms |
| 25〜45(ピーク〜下げ) | 68〜72 | 1,788〜4,394 ms |
| 45〜50 | 42 | 1,762.0 ms |
| 50〜55 | 30 | 161.4 ms |
| 55〜70 | 30 | 60〜100 ms |
負荷を戻し始めてから(35秒)、応答時間がほぼ元に戻るまで(55秒)約20秒かかりました。 たまった待ち行列を処理する時間です。スパイクテストでは「耐えたか」に加えて、「何秒で戻ったか」を合格基準に入れます。 この実行では、p95 が全体で 4.34秒になり、閾値(p95 < 300ms)を満たせずに k6 は終了コード 99 で終わりました。
性能テスト計画書の項目
| 項目 | 書くこと | 例 |
|---|---|---|
| 目的 | 何を判断するためのテストか | 本番リリースの可否、構成の決定 |
| 対象と範囲 | 測る機能・API、測らないもの | 注文の流れの API 8本。管理画面は対象外 |
| 性能要件 | 目標スループット・応答時間・エラー率と、その計算の根拠 | 18 req/s、p95 300ms 以内(①の計算) |
| テストの種類 | 負荷・ストレス・耐久・スパイクのどれを行うか | 負荷・ストレス・耐久(4時間) |
| シナリオ | 業務の流れと、API ごとの比率・待ち時間 | 一覧 40%・詳細 30%・注文 10%… |
| 環境 | 本番との違い(台数・スペック・データ量・外部連携) | 本番の 1/2 構成、外部決済はモック |
| データ | 件数・作り方・テスト後の戻し方 | 商品 10万件、会員 50万件 |
| 測るもの | クライアント側とサーバー側の指標 | p90/p95/p99、エラー率、CPU、メモリ、DB 接続数 |
| 合格基準 | 下の表 | ― |
| 体制・日程 | 実施者、立ち会い(DB・インフラ)、再テストの枠 | 2週間、うち再テスト 3日 |
| 中止の条件 | 環境を壊さないための止め方 | エラー率 10% を超えたら止める |
本番との違い(台数・データ量)は、必ず一覧にします。 半分の構成で測ったなら、結果をそのまま本番の性能とは言えないためです。テスト計画書の全体の項目は、関連記事「テスト計画書の書き方と項目一覧|開始基準・終了基準のテンプレート」で扱っています。
合格基準のテンプレート
合格基準は「指標・条件・目標値・測り方」をそろえて表にします。 「速いこと」「安定していること」のような書き方では、判定できません。
| 指標 | 条件 | 目標値 | 測り方 |
|---|---|---|---|
| スループット | 負荷テスト(目標負荷 30分) | 18 req/s を処理できる | k6 の http_reqs |
| 応答時間(画面の API) | 同上 | p90 200ms 以内、p95 300ms 以内 | k6 の http_req_duration |
| 応答時間(注文確定) | 同上 | p95 1秒以内、p99 2秒以内 | 注文 API だけをタグで集計 |
| エラー率 | 同上 | 1% 未満 | k6 の http_req_failed |
| 資源 | 同上 | CPU 平均 70% 以下、メモリの増加なし | サーバーの監視 |
| 余力 | ストレス(目標の 1.5倍) | エラー率 1% 未満(応答時間は記録のみ) | 同上 |
| 長時間 | 耐久(普段の負荷 4時間) | 最初と最後の30分で p95 の差が 20% 以内 | 30分ごとの p95 |
| 戻り | スパイク(4倍・1分) | 負荷が戻ってから 60秒以内に p95 が目標内 | 5秒ごとの p95 |
k6 では、合格基準を「閾値(thresholds)」としてスクリプトに書けます。 閾値を満たさないと、k6 は 0 以外の終了コードで終わるので、CI でも判定できます(手元では 99 でした)。
import http from 'k6/http';
import { check } from 'k6';
export const options = {
scenarios: {
steady: {
executor: 'constant-arrival-rate', // 1秒あたりの件数を決めて送る
rate: 40, timeUnit: '1s', duration: '30s',
preAllocatedVUs: 20, maxVUs: 100,
},
},
thresholds: {
http_req_failed: ['rate<0.01'], // エラー率 1% 未満
http_req_duration: ['p(90)<200', 'p(95)<300'], // p90・p95
},
summaryTrendStats: ['avg', 'min', 'med', 'p(90)', 'p(95)', 'p(99)', 'max'],
};
export default function () {
const res = http.get('http://127.0.0.1:8060/');
check(res, { 'status is 200': (r) => r.status === 200 });
}
█ THRESHOLDS
http_req_duration
✓ 'p(90)<200' p(90)=64.39ms
✓ 'p(95)<300' p(95)=186.23ms
http_req_failed
✓ 'rate<0.01' rate=0.00%
constant-arrival-rate(到着率を決める実行方法)を使うと、「18 req/s」のような業務量からの目標を、そのまま負荷として書けます。 仮想ユーザーの数を決める constant-vus では、サーバーが遅くなると送る件数も減るので、目標の件数をかけられたかを別に確かめる必要があります。k6 のスクリプトの書き方とインストールは、関連記事「負荷テストの進め方とk6の使い方|スクリプト例と結果の読み方」で扱っています。
実験に使ったテスト用サーバー
// 同時に4件までしか処理できないサーバー(1件あたり約50ms+ときどき遅い)
const http = require('http');
const SLOTS = 4;
let busy = 0;
const queue = [];
function work(res) {
busy++;
const r = Math.random();
const ms = r < 0.95 ? 40 + Math.random() * 20 : 150 + Math.random() * 100; // 5% は遅い
setTimeout(() => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, ms: Math.round(ms) }));
busy--;
if (queue.length) work(queue.shift());
}, ms);
}
http.createServer((req, res) => {
if (busy < SLOTS) work(res); else queue.push(res);
}).listen(8060, () => console.log('listening on 8060'));
段階的に負荷を上げたときは、件数を環境変数で渡し、結果を JSON に書き出して表にしました。
for R in 20 40 60 70 75 80 90; do
k6 run -q -e RATE=$R --summary-export=sum_$R.json step.js
done
k6 run -e RATE=40 --out csv=raw.csv step.js # 1件ずつの応答時間を CSV に
④ 結果の判定と報告
報告では「合格か」に加えて、「どこが限界で、何がボトルネックか」を書きます。 次の構成で書くと、読む人が判断しやすくなります。
| 項目 | 書くこと | 例 |
|---|---|---|
| 結論 | 合格基準ごとの判定 | 8項目中 7項目合格、耐久の p95 増加が 28%(基準 20%) |
| 結果の表 | 指標・目標値・測った値・判定 | 上の合格基準の表に「結果」「判定」の列を足す |
| 限界 | スループットが頭打ちになった点 | 約70件/秒(目標 18件/秒の約3.9倍) |
| ボトルネック | 何が先に限界になったか、その根拠 | DB の接続プール(上限 4 で待ちが発生) |
| 本番との違い | 環境の違いと、結果への影響の見込み | 本番は 2倍の台数、データは 1/10 |
| 残った課題 | 基準を満たさなかったもの、対応と期限 | 耐久のメモリ増加を調査、次回リリース前に再測定 |
グラフは「時間 × p95」と「負荷 × スループット」の2枚があると、限界と戻りが一目で分かります。上のスパイクの表を時間の折れ線にすると、急増からの戻りが見えます。
不合格のときは、どれだけ足りないか(例:目標 300ms に対して 450ms)と、ボトルネックの根拠(CPU・DB・接続プールのどれが先に限界か)を書きます。サーバーを増やして解決するかの判断は、関連記事「スケールアップとスケールアウトの違い|負荷分散方式の一覧と選び方」で扱っています。
よくある質問
Q. 性能要件が要件定義書に書かれていません。 A. ①の表の数字(1日の件数・ピークの集中・1件あたりの API 回数)を、業務の担当者と今のシステムのログから集めて、目標の案を作ります。根拠の数字と一緒に合意しておくと、あとで目標が変わったときに計算し直せます。非機能要件の全体は、関連記事「非機能要件一覧|IPA非機能要求グレード6大項目と定義書テンプレート」で扱っています。
Q. p90 と p95 のどちらを使えばよいですか? A. 画面の応答は p90 か p95、決済など遅れると影響の大きい処理は p99 まで決めることが多いです。測る件数が少ないと p99 は数件の値で決まるので、p99 を使うなら、少なくとも数千件は集めます(1,000件なら p99 は遅い方から10件目あたりの値です)。
Q. テスト環境が本番より小さいです。 A. 本番との違いを計画書と報告書に書き、限界(頭打ちの点)とボトルネックを中心に報告します。「本番の 1/2 構成で目標の 3.9倍まで処理できた」のように、余力として示すと判断に使えます。
Q. 負荷をかけるマシンが先に限界になることはありますか? A. あります。負荷をかける側の CPU が高いと、応答時間が実際より長く測られます。この記事の測定は、サーバーと k6 を同じマシンで動かしていて、上限が約70件/秒と小さいので影響は小さいですが、大きな負荷をかけるときは、負荷をかける側の CPU も記録します。
まとめ
- 性能要件は 業務量(件数・ピークの集中・API 回数・偏り・伸び)から計算する。例では 12,000件/日 → 18 req/s
- 「同時ユーザー」と「同時に処理中の要求」は別物。リトルの法則(L = λW)で計算する
- 応答時間の目標は p90・p95(重要な処理は p99)。実測では平均 62ms、p95 186ms と大きく違った
- p95 は遅い要求の割合が 5% 前後だと測るたびに変わる。複数回・長めに測る
- ストレス・限界テストでは、スループットの頭打ち(実測で約70件/秒)と p95 の急増を見る。エラー 0% でも p95 は 3秒を超えた
- スパイクは 「何秒で戻ったか」 を合格基準に入れる(実測では約20秒)
- 合格基準は 指標・条件・目標値・測り方 の表にし、k6 なら thresholds で自動判定する
参考・出典
確認日はいずれも 2026年10月13日です。
- Grafana k6 ドキュメント「Thresholds」:https://grafana.com/docs/k6/latest/using-k6/thresholds/
- Grafana k6 ドキュメント「Executors」:https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/
- Grafana k6 ドキュメント「Dropped iterations」:https://grafana.com/docs/k6/latest/using-k6/scenarios/concepts/dropped-iterations/
- Grafana k6 ドキュメント「Load test types」:https://grafana.com/docs/k6/latest/testing-guides/test-types/
- k6 リリース(v2.3.0):https://github.com/grafana/k6/releases
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「性能テストの進め方|性能要件の決め方と合格基準のテンプレート」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。