負荷テスト
システムの限界性能を把握し、本番でのトラブルを未然に防ぐための負荷テストを学びます。
なぜ負荷テストが必要か
┌─────────────────────────────────────────────────────────────┐
│ 本番で初めて分かるのでは遅い │
├─────────────────────────────────────────────────────────────┤
│ │
│ 負荷テストなしで起こること: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 開発環境では快適に動く │ │
│ │ 2. 本番リリース │ │
│ │ 3. 大量アクセスが来る │ │
│ │ 4. システムダウン │ │
│ │ 5. 原因不明、対処できない │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 負荷テストで分かること: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・最大スループット(何req/secまで耐えられるか) │ │
│ │ ・飽和点(どこからレイテンシが悪化するか) │ │
│ │ ・ボトルネック(何が限界を決めているか) │ │
│ │ ・回復性(負荷が下がったら回復するか) │ │
│ │ ・並行バグ(単体テストでは再現しない問題) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
負荷テストの種類
┌─────────────────────────────────────────────────────────────┐
│ 目的に応じたテスト種類 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 負荷テスト (Load Test): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 想定される負荷での動作確認 │ │
│ │ 例: 通常時の2倍の負荷で30分 │ │
│ │ 目的: SLOを満たせるか確認 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ストレステスト (Stress Test): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 限界を超える負荷での動作確認 │ │
│ │ 例: 負荷を徐々に上げて破綻点を探す │ │
│ │ 目的: 限界値の把握、障害時の挙動確認 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ スパイクテスト (Spike Test): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 急激な負荷変動への耐性確認 │ │
│ │ 例: 10秒で10倍の負荷をかける │ │
│ │ 目的: オートスケール、バッファの検証 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ソークテスト (Soak Test): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 長時間の継続負荷での動作確認 │ │
│ │ 例: 通常負荷で24時間 │ │
│ │ 目的: メモリリーク、リソースリークの検出 │ │
│ │ 並行バグの検出(後述) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
負荷パターン
┌─────────────────────────────────────────────────────────────┐
│ 負荷のかけ方 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ランプアップ (Ramp-up): │
│ 負荷 ↑ │
│ │ ________ │
│ │ / │
│ │ / │
│ │ / │
│ │/ │
│ └──────────────────────→ 時間 │
│ │
│ ステップ (Step): │
│ 負荷 ↑ │
│ │ ┌──────── │
│ │ ┌───┘ │
│ │ ┌───┘ │
│ │────┘ │
│ └──────────────────────→ 時間 │
│ │
│ スパイク (Spike): │
│ 負荷 ↑ │
│ │ ∧ │
│ │ ╱ ╲ │
│ │ ╱ ╲ │
│ │─────╱ ╲───── │
│ └──────────────────────→ 時間 │
│ │
└─────────────────────────────────────────────────────────────┘
テスト設計
シナリオ設計
┌─────────────────────────────────────────────────────────────┐
│ 現実的な シナリオを作る │
├─────────────────────────────────────────────────────────────┤
│ │
│ 悪い例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・同じエンドポイントに連続リクエスト │ │
│ │ ・同じユーザーで全リクエスト │ │
│ │ ・キャッシュが効きすぎる │ │
│ │ → 本番とかけ離れた結果になる │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 良い例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・実際のユーザー行動をシミュレート │ │
│ │ - ログイン → 一覧取得 → 詳細閲覧 → 更新 │ │
│ │ ・複数ユーザーでリクエスト │ │
│ │ ・Think time(人間の操作間隔)を入れる │ │
│ │ ・エンドポイントの比率を本番に合わせる │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ エンドポイント比率の例: │
│ ┌──────── ─────────────────────────────────────────────┐ │
│ │ GET /api/users : 50% (一覧取得) │ │
│ │ GET /api/users/{id} : 30% (詳細取得) │ │
│ │ POST /api/users : 10% (作成) │ │
│ │ PUT /api/users/{id} : 8% (更新) │ │
│ │ DELETE /api/users/{id}: 2% (削除) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
テストデータ
┌─────────────────────────────────────────────────────────────┐
│ テストデータの準備 │
├─────────────────────────────────────────────────────────────┤
│ │
│ データ量: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・本番と同等のデータ量を用意する │ │
│ │ ・少ないデータでは問題が見つからない │ │
│ │ - インデックスの効果 │ │
│ │ - ページネーションの負荷 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ データの多様性: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・同じデータばかりだとキャッシュが効きすぎる │ │
│ │ ・ランダムなIDでアクセス │ │
│ │ ・様々なパターンのデータを用意 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ テストユーザー: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・十分な数のテストユーザーを用意 │ │
│ │ ・同じユーザーで全リクエストは非現実的 │ │
│ │ ・例: 仮想ユーザー1000人分の認証情報 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
結果の読み方
┌─────────────────────────────────────────────────────────────┐
│ 何を見るか │
├─────────────────────────────────────────────────────────────┤
│ │
│ 基本指標: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・スループット: 1秒あたりの成功リクエスト数 │ │
│ │ ・レイテンシ: P50, P95, P99 │ │
│ │ ・エラーレート: 失敗リクエストの割合 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ グラフの見方: │
│ │
│ レイテンシ │
│ ↑ │
│ │ / │
│ │ / │
│ │ ─────────/ ← ここが飽和点 │
│ │ ───────── │
│ └──────────────────────→ スループット │
│ │
│ エラーレート │
│ ↑ │
│ │ / │
│ │ / │
│ │ ──────────────────────── ← エラーが出始める点 │
│ └──────────────────────────→ スループット │
│ │
│ 判断基準: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・SLOを満たせるスループットはどこまでか │ │
│ │ ・エラーが出始めるのはどこからか │ │
│ │ ・余裕を持った運用ラインはどこか(70-80%) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ エラーログの確認(並行バグの検出): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 負荷テスト中のエラーは「量」だけでなく │ │
│ │ 「種類」を必ず確認する。 │ │
│ │ │ │
│ │ 要注意のエラーパターン: │ │
│ │ ・ArrayIndexOutOfBoundsException(散発的) │ │
│ │ ・ConcurrentModificationException │ │
│ │ ・NullPointerException(低負荷では再現しない) │ │
│ │ ・不正なレスポンスデータ(ハッシュ値の不一致等) │ │
│ │ │ │
│ │ これらは並行バグの兆候。 │ │
│ │ 単体テストでは再現せず、負荷テストで初めて │ │
│ │ 顕在化することが多い。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘