は、想定する負荷をかけて性能と限界を確認する試験です。負荷をかけること自体は道具があれば簡単で、難しいのは条件の再現です。
条件を外した試験は、「通ったのに本番で落ちる」という最悪の結果を生みます。この回は、何を揃え、何を見るかを扱います。
1. 何のためにやるのかを先に決める
負荷試験には複数の目的があり、目的が違えば設計も違います。まとめて1回で済ませようとすると、どれも中途半端になります。
| 目的 | かける負荷 | 見るもの |
|---|---|---|
| 目標を満たすか確認する | 想定する最大値 | 分位点が目標内か |
| 限界を知る | 壊れるまで上げる | どこから劣化するか、壊れ方 |
| 長時間の耐久を見る | 中程度を長時間 | 、蓄積による劣化 |
| 急増に耐えるか | 短時間に一気に上げる | 立ち上がりの挙動、復帰できるか |
最も多いのは1行目です。EP.1 で決めた目標値を満たすかの確認で、合否がはっきりするのが利点です。2行目の「限界を知る」は別種の試験で、壊れ方の把握が目的になります。混同しないでください。
限界を超えたときに、緩やかに遅くなるのか、突然すべて失敗するのかは大きな違いです。後者なら、上限を設けて超過分を明確に断る設計が必要になります。壊れ方は設計の入力になります。
2. 揃えるべき条件
環境を完全に一致させるのは現実的ではありません。優先度をつけて揃えます。上ほど、ずれると結果が意味を失います。
- 1データ量 — 最優先。件数が違えば が変わり、まったく別の挙動になる
- 2データの偏り — 均等なテストデータでは、実際の遅さが再現しない
- 3アクセスの分布 — 全機能を均等に叩く試験は現実を反映しない
- 4同時実行数 — や接続の枯渇はここでしか出ない
- 5マシンの規模 — 揃わなくても、比率が分かれば判断に使える
1番と2番が特に重要です。件数が少ないと、データベースは違う処理方法を選びます。索引を使わず全件読むほうが速いと判断され、本番とは別の経路で動く。これでは何を試験したのか分かりません。
2番の偏りも同様です。実際のデータは偏っています。注文が数件の顧客が大半で、数万件の顧客が少数いる。均等に1人100件のテストデータを作ると、この構造が消え、最も遅いケースが再現しません。
import random
def generate_order_counts(n_customers: int, rng=random) -> list[int]: """実際の分布に近い偏りを作る。大半は少なく、少数が極端に多い。""" counts = [] for _ in range(n_customers): r = rng.random() if r < 0.80: counts.append(rng.randint(0, 10)) # 大半はごく少数 elif r < 0.98: counts.append(rng.randint(11, 500)) # 中規模 else: counts.append(rng.randint(501, 50_000)) # 少数の極端なケース return counts
rng = random.Random(42)counts = generate_order_counts(10_000, rng)counts.sort()print(f" 中央値 {counts[len(counts) // 2]:6d} 件")print(f" p95 {counts[int(len(counts) * 0.95)]:6d} 件")print(f" 最大 {counts[-1]:6d} 件")重要なのは最後の2%です。ここが試験の合否を分けます。均等なデータには存在しない層なので、意図的に作らなければ再現しません。生成時に乱数の種を固定しておくと、次回も同じデータで比較できるので、継続的な試験に向きます。
自動生成したデータは均等になりがちです。均等なデータでは極端なケースが存在しないため、試験は通ります。そして本番で、データの多い顧客だけが破綻します。分布を意図的に偏らせる必要があります。
3. 何を送るか
アクセスの内訳も、実際に合わせます。全機能を均等に叩く試験は、現実にはあり得ない負荷をかけていることになります。
import random
# アクセス記録から求めた実際の比率SCENARIOS = [ ("一覧表示", 0.55), ("詳細表示", 0.30), ("検索", 0.10), ("登録・更新", 0.05),]
def pick_scenario(rng=random): """重み付きで1つ選ぶ。実際の分布に合わせて負荷をかける。""" r = rng.random() cumulative = 0.0 for name, weight in SCENARIOS: cumulative += weight if r < cumulative: return name return SCENARIOS[-1][0]
# 分布を確認するfrom collections import Counterrng = random.Random(42)counts = Counter(pick_scenario(rng) for _ in range(10000))for name, weight in SCENARIOS: print(f" {name:8s} 想定 {weight:5.1%} 実測 {counts[name] / 10000:5.1%}")比率は実際のアクセス記録から取ります。想像で決めると、たいてい書き込み系を多く見積もりすぎます。実際の業務システムは、読み取りが圧倒的に多いのが普通です。
4. 何を見るか
結果として見るのは、応答時間の分位点と失敗率、そしてサーバ側の資源です。1つだけでは判断できません。
| 見るもの | 確認すること |
|---|---|
| 分位点(p95 / p99) | 目標内に収まっているか。平均は見ない |
| 失敗率 | 速いが失敗している、という状態を見逃さない |
| 単位時間あたりの処理数 | 負荷を上げても増えなくなったら、そこが限界 |
| CPU・メモリ・接続数 | どの資源が先に尽きるか |
| 時間経過での変化 | 後半で劣化していないか(蓄積の兆候) |
2行目を見落とすと危険です。失敗したリクエストは速く返るため、失敗率を見ないと「負荷を上げたら速くなった」という誤った結論になります。応答時間と失敗率は必ずセットで見てください。
3行目は限界の判定に使います。負荷を上げても処理数が増えなくなった点が、そのシステムの上限です。それ以上かけると、応答時間だけが伸びます。待ち行列が伸びているだけで、実際にこなせる量は増えていません。この状態で が無いと、待ちが積み上がってメモリを使い果たします。限界を超えたときにどう振る舞うかは、試験で確認しておくべき項目です。
5. よくある失敗
試験そのものが間違っていて、結果が信用できないケースがあります。
- 負荷をかける側が先に限界に達している — 送信側の資源を必ず確認する
- 同じデータばかり触っている — キャッシュに乗って実態より速く出る
- ウォームアップを含めて集計している — 開始直後の遅さが混ざる
- 外部サービスを本物のまま叩いている — 先方に迷惑をかけるうえ、結果もぶれる
- 1回の結果で判断している — ばらつきがあるので複数回実施する
1番目は意外に多い失敗です。負荷をかける側の CPU が張り付いていると、そこが上限になります。試験対象ではなく試験側の限界を測っていた、ということが起こります。送信側の資源も必ず記録してください。
2番目も再現性を損ないます。同じ商品を1万回参照する試験は、 に乗るため実態より良い数字が出ます。実際の利用に近い散らばりを持たせる必要があります。
6. 結果をどう使うか
負荷試験はやって終わりになりがちです。結果を次に活かすには、記録の残し方が重要になります。
- 1条件を記録する — データ量、分布、負荷の内容。これがないと次回と比較できない
- 2限界と、そのときの資源を記録する — どこが先に尽きたか
- 3目標との差を記録する — 余裕があるのか、ぎりぎりなのか
- 4次に効きそうな改善を書く — 試験中に見えたものを残す
1番目が抜けると、次回の結果と比較できません。「前回より遅くなった」のか「条件が違う」のかが判別できないためです。負荷試験の価値は継続して比較できることにあるので、条件の記録は結果の記録と同じくらい重要です。
目的を先に決める(目標確認 / 限界把握 / 耐久 / 急増)。揃える条件の最優先はデータ量とデータの偏りで、均等なテストデータが最も危険。見るのは分位点・失敗率・処理数・資源・時間変化。負荷をかける側の限界を疑うこと。そして条件を記録しないと次回と比較できない。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。