mcpField
L
load-testing-plan

load-testing-plan

github.com/mohitagw15856テスト・品質1.5k36 回インストール5 日前に更新
リポジトリ

load-testing-plan について

サービスの負荷テスト計画を作成するスキルです。明確なシナリオと合否基準を必要とするエンジニアリングチーム向けに、目標、閾値、ツール、環境、指標、分析、CI 連携を扱います。

主な機能

  1. 01基準、ストレス、スパイク、耐久シナリオ
  2. 02SLO に基づく合否基準
  3. 03ツールとテスト環境の指針
  4. 04指標の収集と結果分析
  5. 05CI のリリースゲート

活用シーン

  1. 01サービスリリースの性能テスト計画
  2. 02サービス SLO に沿った閾値設定
  3. 03定期的なストレス・耐久テストの実施

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mohitagw15856/pm-claude-skills --skill load-testing-plan

インストール後、Claude Code で/load-testing-planを入力して読み込まれたか確認します。

詳細

負荷テスト計画スキル

サービスの包括的な負荷・パフォーマンステスト計画を作成します。テストの目的、シナリオの定義、ツールの設定、合格基準、CI への統合を網羅します。優れた負荷テスト計画では、「許容できるパフォーマンス」とは何かを明確にするため、エンジニアは生の数値を解釈せずにテストを実行し、合否を判断できます。

必要な入力

まだ提供されていない場合は、以下を確認してください。

  • サービス名と主要なエンドポイント — テスト対象のエンドポイント(パス、メソッド、一般的なリクエスト/レスポンスの形式)
  • 現在のトラフィックのベースライン — 通常負荷時の現在のリクエスト数/秒、p50/p99 レイテンシ、エラー率
  • ピークトラフィックの想定 — 想定されるピーク RPS(例:フラッシュセール時のベースラインの 10 倍、または季節的なピーク)
  • SLO の目標 — レイテンシ SLO(p99 < X ms)、エラー率 SLO(< Y%)、可用性の目標
  • 希望するテストツール — k6、Locust、JMeter、Gatling、または指定なし
  • テスト環境の利用可否 — 専用の負荷テスト環境、ステージング環境、または本番環境(トラフィック制御を実施)

出力形式


負荷テスト計画:[Service Name]

作成者: [Name] | チーム: [Team name] 日付: [Date] | レビュー周期: 主要リリース前、および四半期ごと テストツール: [k6 / Locust / JMeter / Gatling] テスト環境: [Environment name and URL]


1. 目的と範囲

テスト対象: [Service name] は、[機能の説明 — 例:「モバイルクライアントおよびウェブクライアントからのユーザー認証リクエスト」] を処理します。この計画では、想定されるトラフィックおよび高負荷時にサービスが SLO を満たすことを検証します。

対象範囲:

  • [Endpoint 1: METHOD /path — 説明]
  • [Endpoint 2: METHOD /path — 説明]
  • [Endpoint 3: METHOD /path — 説明]

対象外:

  • [明示的に除外するエンドポイントとその理由 — 例:「管理 API — トラフィックが少ないため、負荷テストから除外」]
  • [負荷テストを実施できないサードパーティ連携 — 代わりにモックを使用]

2. パフォーマンス目標(合格基準)

すべてのシナリオに明示的な合否基準を設定します。いずれかの基準に抵触した場合、テスト実行は不合格となります。

指標ベースラインシナリオストレスシナリオスパイクシナリオソークシナリオ
p50 レイテンシ< [X] ms< [X × 1.5] ms< [X × 2] ms< [X] ms
p95 レイテンシ< [Y] ms< [Y × 1.5] ms< [Y × 2] ms< [Y] ms
p99 レイテンシ< [Z] ms< [Z × 2] ms< [Z × 3] ms< [Z] ms
エラー率< [0.1]%< [1]%< [2]%< [0.1]%
スループット≥ [N] RPS≥ [N × 3] RPS該当なし≥ [N] RPS
失敗したリクエスト0 (5xx)< [threshold]< [threshold]0 (5xx)

SLO の参照値: これらの基準はサービスの SLO(p99 < [Z ms]、エラー率 < [0.1]%、可用性 [99.9]%)に基づいています。


3. トラフィックモデル

ベースラインのトラフィック(現在の本番環境):

  • 平均 RPS:[N] req/sec
  • ピーク RPS(観測値):[N] req/sec
  • エンドポイント別のリクエスト分布:
    • [Endpoint 1]:[X]% のトラフィック
    • [Endpoint 2]:[Y]% のトラフィック
    • [Endpoint 3]:[Z]% のトラフィック

シミュレーションするユーザーの行動:

  • リクエスト間の思考時間:[X–Y] 秒(ランダム化)
  • セッション時間:平均 [N] 分
  • 認証済みユーザーと匿名ユーザーの比率:[X]%/[Y]%
  • 地理的な分布:[Region 1 X]%、[Region 2 Y]%

4. テストシナリオ

シナリオ 1:ベースライン(定常状態)

目的: 通常の本番負荷でサービスが許容可能なパフォーマンスを発揮することを確認します。 時間: 10 分 負荷プロファイル: 2 分かけて [N] RPS まで段階的に上げ、8 分間維持します。 同時実行数: [N] 仮想ユーザー

合格基準: 上記の目標テーブルの「ベースライン」列にあるすべての基準を満たすこと。


シナリオ 2:ストレステスト

目的: 限界点を見つけます。SLO に違反する前に、サービスがどれだけの負荷を処理できるかを確認します。 所要時間: 20~30 分 負荷プロファイル: 5 分ごとに [N] RPS(ベースライン)から [N × 5] RPS まで段階的に増加させます。各段階を 5 分間維持します。最初に SLO に違反した時点で停止します。 同時実行数: RPS の目標値に応じて増加します

記録する内容:

  • p99 レイテンシが初めて SLO を超えたときの RPS
  • エラー率が初めて SLO を超えたときの RPS
  • 負荷をベースラインに戻したときにサービスが回復するかどうか

シナリオ 3:スパイクテスト

目的: 突然のトラフィック急増(フラッシュセール、バイラルイベント、ボット攻撃)をシミュレートします。 所要時間: 15 分 負荷プロファイル: [N] RPS(ベースライン)を 3 分間維持し、瞬時に [N × 10] RPS まで増加させて 5 分間維持した後、ベースラインに戻して 7 分間維持します。

記録する内容:

  • スパイク中および回復中のレイテンシ
  • サービスが負荷を適切に抑制できるかどうか(レート制限、キューの深さ)
  • スパイク終了後、ベースラインのレイテンシに戻るまでの時間

シナリオ 4:ソークテスト/耐久テスト

目的: メモリリーク、コネクションプールの枯渇、時間経過に伴う緩やかな劣化を検出します。 所要時間: 4~8 時間(夜間に実行) 負荷プロファイル: 全期間にわたり、[N × 1.5] RPS(ベースラインより 50%高い値)を一定に維持します。

監視する項目:

  • 時間経過に伴うメモリ使用量の傾向(無制限に増加しないこと)
  • エラー率の傾向(上昇し続けず、横ばいであること)
  • GC 停止の頻度(JVM/Go サービス)
  • データベース接続プールの使用率
  • p99 レイテンシの傾向(数時間かけて上昇しないこと)

5. テスト環境の要件

インフラストラクチャ
コンポーネント要件備考
テスト対象のサービス本番環境から分離[N] 個のレプリカ。本番環境と同等のリソース上限
データベース本番環境規模のデータを含む別インスタンスセクション 7 のシードスクリプト
キャッシュ(Redis/Memcached)テスト開始時に空コールドスタート条件でテストされることを保証します
負荷生成ツールテスト対象のサービスとは別に配置最低 [N] vCPU、[N] GB RAM
ネットワークサービスへの低レイテンシ経路生成ツールを同じホストで実行しないでください
データの投入

各テストの実行前に、環境が以下の状態であることを確認します。

# Seed test users (needed for authenticated endpoint tests)
[seed command or script path — e.g. python scripts/seed_load_test_users.py --count 10000]

# Seed test data for read endpoints
[seed command — e.g. ./scripts/seed_products.sh --count 50000]

# Verify seed completed
[verification command — e.g. psql $DB_URL -c "SELECT COUNT(*) FROM users WHERE load_test=true"]

テストデータのルール:

  • 負荷テストで本番環境の実ユーザーデータを使用しないでください
  • テストで生成したすべてのレコードに、簡単にクリーンアップできるよう load_test=true タグを付けてください
  • 各テスト後にクリーンアップを実行してください:[cleanup command]

6. ツールのセットアップ

k6 スクリプトのひな形
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

// Custom metrics
const errorRate = new Rate('error_rate');
const endpointLatency = new Trend('endpoint_latency', true);

// Test configuration — override per scenario
export const options = {
  scenarios: {
    baseline: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: [BASELINE_VUS] },
        { duration: '8m', target: [BASELINE_VUS] },
        { duration: '1m', target: 0 },
      ],
    },
  },
  thresholds: {
    http_req_duration: [
      'p(95)<[Y_MS]',
      'p(99)<[Z_MS]',
    ],
    error_rate: ['rate<0.01'],
    http_req_failed: ['rate<0.01'],
  },
};

// Auth helper — get token once per VU
export function setup() {
  const loginRes = http.post('[BASE_URL]/auth/login', JSON.stringify({
    username: `load_test_user_${Math.floor(Math.random() * 10000)}@example.com`,
    password: '[LOAD_TEST_PASSWORD]',
  }), { headers: { 'Content-Type': 'application/json' } });

  check(loginRes, { 'login ok': (r) => r.status === 200 });
  return { token: loginRes.json('access_token') };
}

export default function (data) {
  const headers = {
    Authorization: `Bearer ${data.token}`,
    'Content-Type': 'application/json',
  };

  // Endpoint 1: [Description]
  const res1 = http.get('[BASE_URL]/[endpoint-1]', { headers });
  check(res1, {
    '[endpoint-1] status 200': (r) => r.status === 200,
    '[endpoint-1] latency < [X]ms': (r) => r.timings.duration < [X],
  });
  errorRate.add(res1.status >= 400);
  endpointLatency.add(res1.timings.duration, { endpoint: '[endpoint-1]' });

  sleep(Math.random() * [THINK_TIME_MAX] + [THINK_TIME_MIN]);

  // Endpoint 2: [Description]
  const res2 = http.post('[BASE_URL]/[endpoint-2]',
    JSON.stringify({ [key]: '[value]' }),
    { headers }
  );
  check(res2, {
    '[endpoint-2] status 201': (r) => r.status === 201,
  });
  errorRate.add(res2.status >= 400);
}
Locust スクリプトのひな形(代替)
from locust import HttpUser, task, between
import random

class [ServiceName]User(HttpUser):
    wait_time = between([THINK_TIME_MIN], [THINK_TIME_MAX])
    token = None

    def on_start(self):
        """Called once per simulated user — authenticate."""
        user_id = random.randint(1, 10000)
        response = self.client.post("/auth/login", json={
            "username": f"load_test_user_{user_id}@example.com",
            "password": "[LOAD_TEST_PASSWORD]",
        })
        self.token = response.json()["access_token"]
        self.headers = {"Authorization": f"Bearer {self.token}"}

    @task([WEIGHT_1])  # Weight = relative frequency
    def [endpoint_1_task](self):
        """[Endpoint 1 description]"""
        with self.client.get(
            "/[endpoint-1]",
            headers=self.headers,
            catch_response=True
        ) as response:
            if response.elapsed.total_seconds() > [LATENCY_THRESHOLD]:
                response.failure(f"Too slow: {response.elapsed.total_seconds()}s")

    @task([WEIGHT_2])
    def [endpoint_2_task](self):
        """[Endpoint 2 description]"""
        self.client.post(
            "/[endpoint-2]",
            json={"[key]": "[value]"},
            headers=self.headers,
        )
テストの実行
# k6 — run baseline scenario
k6 run --env BASE_URL=https://[test-env-url] scripts/load_test.js

# k6 — run stress scenario with output to InfluxDB
k6 run --out influxdb=http://[influxdb-host]:8086/k6 \
  --env SCENARIO=stress \
  scripts/load_test.js

# Locust — headless run
locust -f locustfile.py \
  --headless \
  --users [N] \
  --spawn-rate [N] \
  --run-time 10m \
  --host https://[test-env-url] \
  --csv=results/[run-id]

# Locust — web UI (interactive)
locust -f locustfile.py --host https://[test-env-url]

7. 取得するメトリクス

各テストの実行中に、以下すべてを取得してください。いずれかが欠けると、結果の比較の信頼性が損なわれます。

メトリクスソース重要な理由
エンドポイントごとの p50、p95、p99、p999 レイテンシ負荷ツールSLO の検証
エンドポイントごとのエラー率(4xx、5xx)負荷ツールSLO の検証
リクエスト数/秒(スループット)負荷ツール容量のベースライン
CPU 使用率(%)インフラ監視飽和の兆候
メモリ使用率(%)インフラ監視リークの検出
GC 停止時間/頻度JVM/Go のメトリクスレイテンシ急増の根本原因
DB 接続プール:アクティブ/アイドル/待機中DB メトリクスプール枯渇の検出
DB クエリレイテンシ(p99)DB メトリクス下流のボトルネック
キャッシュヒット率キャッシュメトリクスミスストームの検出
Pod/インスタンス数(オートスケーリングの場合)インフラスケーリングの挙動
ネットワークの受信/送信バイト数インフラ帯域幅の飽和

8. 結果分析フレームワーク

各テストの実行後、次の順序で分析を進めます。

ステップ 1 — 合否の確認 取得したすべてのメトリクスをセクション 2 のしきい値と比較します。シナリオごとに合否を記録します。

ステップ 2 — レイテンシ分布 パーセンタイルだけでなく、レイテンシの全体的なヒストグラムをプロットします。二峰性分布(山が 2 つある分布)は、異なるコードパスが 2 つあることを示しています。遅いほうの山を調査します。

ステップ 3 — エラーの相関分析 エラーが発生した場合は、次の項目と関連付けて分析します。

  • 発生時刻(ランプアップ中、定常状態、またはスパイク中のどのタイミングか)
  • 特定のエンドポイント(1 つのエンドポイントか、すべてか)
  • インフラストラクチャのイベント(CPU スパイク、OOM、DB 接続の枯渇など)

ステップ 4 — 飽和状態の分析 CPU、メモリ、接続プールを時系列でグラフ化します。リソースのいずれかが容量の 80% 以上に達した場合、今回の実行で SLO を達成していても、ボトルネック候補となります。

ステップ 5 — ベースライン実行との比較 すべての実行結果を前回の実行結果と比較します。p99 レイテンシが 10% 悪化した場合、SLO の範囲内でも調査が必要です。

リグレッションの分類:

変化分類対応
p99 が前回の実行結果の 5% 以内緑 — リグレッションなし対応不要
p99 が前回より 5~15% 悪化黄 — 要監視次回リリース前に調査
p99 が前回より 15% 超悪化赤 — リグレッションリリースをブロックし、チケットを起票
エラー率が前回より上昇赤 — リグレッションリリースをブロック
SLO のしきい値を超過重大リリースをブロックし、オンコール担当者に通知

9. CI への統合

リリースパイプラインのゲート付きステップとして負荷テストを追加します。リリース候補ごとにベースラインシナリオを実行し、すべてのシナリオを毎週実行します。

# Example: GitHub Actions step (adapt for your CI platform)
load-test:
  runs-on: ubuntu-latest
  needs: [deploy-staging]
  if: github.ref == 'refs/heads/main'
  steps:
    - uses: actions/checkout@v3

    - name: Install k6
      run: |
        curl -s https://dl.k6.io/key.gpg | sudo apt-key add -
        echo "deb 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

    - name: Seed test data
      run: [seed command]

    - name: Run baseline load test
      run: |
        k6 run \
          --env BASE_URL=${{ secrets.LOAD_TEST_ENV_URL }} \
          --out json=results.json \
          scripts/load_test.js
      env:
        LOAD_TEST_ENV_URL: ${{ secrets.LOAD_TEST_ENV_URL }}

    - name: Check thresholds
      run: |
        # k6 exits with non-zero if any threshold fails — this step fails the build
        echo "k6 threshold check complete"

    - name: Upload results
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: load-test-results-${{ github.run_id }}
        path: results.json

    - name: Cleanup test data
      if: always()
      run: [cleanup command]

CI ゲートの概要:

  • ステージングへのリリースごとにベースラインシナリオを実行
  • 全シナリオスイート(ストレス、スパイク、ソーク)をスケジュールに従って毎週実行
  • しきい値に達しない場合は、本番環境への昇格をブロック
  • 傾向分析のため、結果をアーカイブ

品質チェック

  • すべての主要なエンドポイントを少なくとも 1 つのテストシナリオでカバーしている — 本番環境のエンドポイントに未テストのものがない
  • しきい値は推測ではなく、実際の SLO 目標に基づいて設定している
  • テストデータのシード処理をスクリプト化し、再現可能にしている — 既存の環境状態に依存しない
  • 負荷ジェネレーターは、テスト対象のサービスとは別のインフラストラクチャで実行している
  • CI 統合によって、しきい値に達しない場合は昇格をブロックしている — 結果を記録するだけではない
  • メモリと接続プールのベースラインを確立するため、ソークテストを少なくとも一度実行している
  • 前回の実行結果との比較を分析に含めている — 絶対的な合否判定だけではない

アンチパターン

  • 実際の SLO 目標や本番環境のベースラインを根拠にせず、しきい値を設定しないでください — 恣意的な数値では、意味のない合否判定になります
  • 負荷ジェネレーターをテスト対象のサービスと同じホストで実行しないでください — テスト結果とサービスのメトリクスの両方が汚染されます
  • 負荷テスト用のシードデータに本番環境のユーザーデータを使用しないでください — すべてのテストデータは合成データとし、タグを付けて、各実行後にクリーンアップする必要があります
  • 初回デプロイ時にソークテストを省略しないでください — 短時間のテストでは見つからない、緩やかなメモリリークや接続プールの枯渇を明らかにできるのはソークテストだけです
  • ベースラインテストに合格したことを、サービスがスパイクに対応できる証拠として扱わないでください — ベースライン、ストレス、スパイク、ソークの各シナリオでは、根本的に異なる障害モードをテストします

コメント