Locust と uv による負荷テスト
このスキルは、プロジェクトの「どれだけ耐えられるか?」という疑問を、チームが行動に移せる数値のレポートに変えます。各ステップは次のステップに情報を提供するため、順序通りに実行してください。ユーザーは、テストレベルとトラフィックシナリオの 2 つの決定を行いますが、それはコードで見つけた内容を確認した後でのみ行われます。
$SKILL はこのスキルのフォルダ、つまりこの SKILL.md を読んでいるディレクトリです。シェルで一度設定してください(SKILL=/path/to/locust-load-test)。負荷テスト自体はターゲットリポジトリの loadtest/ またはユーザーが選択した場所に配置されます。
1 発見 -> loadtest/endpoints.json すべてのエンドポイント、その呼び出し元とコストヒント付き
2 分類 -> loadtest/PLAN.md コア / サポート / 周辺 テーブル; ユーザーに表示
3 質問 -> ティア + シナリオ + 環境 + 制約
4 構築 -> loadtest/ (uv プロジェクト) harness.py, payloads/, 実行スクリプト
5 実行 -> loadtest/results/<run>/ 最初にスモーク、次に選択されたティア、最もリスクの高いものを最後に
6 収集 -> results/<run>/metrics, profiles サーバー側のビュー、到達可能な場合
7 レポート -> loadtest/report.html + 平易な言葉での所見
1. エンドポイントの発見
uv run $SKILL/scripts/discover_endpoints.py . -o loadtest/endpoints.json --markdown loadtest/endpoints.md
スキャナーはコードを読み取り、それ以外は何も書き込みません。一般的なスタック全体でルート宣言を検出します: Python、Node、JVM、Go、Ruby、.NET、PHP フレームワーク、さらに OpenAPI ファイル、GraphQL スキーマ、gRPC プロト、WebSocket、SSE。可能な場合はルータープレフィックスとマウントを解決します。また、エンドポイントごとにクライアントコールサイト(fetch、axios、httpx、requests、生成されたクライアント、テスト、およびサーバーレンダリングテンプレートのリンク、フォーム、htmx 属性)の数をカウントするため、製品自身の UI とサービスが何を使用しているかを確認できます。各エントリには、メソッド、パス、file:line、ハンドラー、認証ヒント、ボディヒント、ストリーミングヒント、コストヒント、ハンドラーが行う呼び出し、および誰がそれを呼び出すかが含まれます。
フレームワーク自身のルートリストは、静的スキャンよりも優れています。なぜなら、フレームワークはすべてのプレフィックス、マウント、生成されたルートを既に解決しているからです。アプリが生成できる場合は、それを真実のソースとして使用してください。つまり、実行中の開発サーバーからの OpenAPI ドキュメント(/openapi.json)、flask routes、bin/rails routes、php artisan route:list --json、または Spring の /actuator/mappings などです:
uv run $SKILL/scripts/discover_endpoints.py . --route-dump http://localhost:8000/openapi.json -o loadtest/endpoints.json
次に、出力を信頼する前に批判的に読んでください:
unmatched_client_callsは、スキャンが見逃したルートか、他のサービスへの呼び出しです。prefix_resolved: falseとnotesは推測を示します。shared_handler: trueは、1 つの関数が複数のメソッドを処理し、スキャンがコストヒントがどのメソッドに属するかを判断できなかったことを意味します。そのハンドラーを読んでください。existing_load_testsは、リポジトリに既にある locustfile または k6 スクリプトをリストします。それらのペイロードを再利用してください。- 5 つのエントリをソースファイルと照合してスポットチェックしてください。
インベントリが薄い場合、スタックがカバーされていない場合、またはフレームワークのルートダンプコマンドが必要な場合は、references/discovery.md を読んでください。
2. 分類: コア、サポート、周辺
エンドポイントを判断基準で分類してください。キーワードで分類しないでください。同じ /search でも、ショップではコア、管理コンソールではサポートになります。
| ティア | 説明 | 典型的な例 |
|---|---|---|
| コア | ユーザーが利用する目的であり、通常は重い処理(計算、モデル呼び出し、データ処理、主要な書き込みパス)が行われる場所 | ショップでのチェックアウト; ドキュメントサービスでの変換または抽出; チャットAPIでの完了 |
| サポート | コアを使用可能にするもの | ログイン、セッション、設定、ユーザー自身のデータのリストと検索、コアに供給するアップロード、管理CRUD |
| 周辺 | 製品が存在しないためにあるもの | ヘルス、メトリクス、ドキュメント、静的ファイル、デバッグルート、所有していないサービスからのWebhook |
考慮すべき証拠:
- README とドキュメントが製品の目的として述べていること;
- UI が最も呼び出すもの(
client_calls)とテストが実行するもの(test_calls); - ハンドラーのコスト:
callsをサービスレイヤーまで追跡します。cost_hintsはハンドラーの本体しか見ないためです。モデル呼び出し、DB 書き込み、外部呼び出し、CPU ワーク(パスワードハッシュ、圧縮、画像:cpu)、ファイル処理、キュー公開は、エンドポイントを高コストにするものです; - 命名;
- チーム自身の監視またはログが最もビジーであると示しているもの(確認できる場合)。
このテーブルを loadtest/PLAN.md に書き込みます。負荷をかける予定のエンドポイントごとに1行、および除外したエンドポイントとその理由を短いリストで記述します:
| エンドポイント | ティア | 理由 | コストヒント | ペイロードソース |
|---|---|---|---|---|
| POST /api/orders/{id}/checkout | コア | 購入パス; UIが3か所から呼び出す | DB書き込み、決済API呼び出し | tests/fixtures/order.json |
質問をする前にこのテーブルをユーザーに表示し、訂正を促してください。間違ったティアは、後のすべての数値を間違った質問への回答にしてしまいます。また、ユーザーはどのスキャンよりも自社の製品をよく知っています。
3. ユーザーへの質問
AskUserQuestion ツールを使用してください。利用できない場合は、同じ質問をプレーンな番号リストとして尋ね、回答を待ちます。ユーザーが計画テーブルを確認した後にのみ質問してください。
- Q1 コアレベル. ティア 1、2、または 3。
- Q2 サポートレベル. ティア 1、2、または 3、あるいはサポートを除外。
- Q3 ユーザー負荷シナリオ. このプロジェクトから導出された 2 ~ 4 つのオプション(以下を参照)。最初に推奨するものを "(推奨)" とマークし、なぜ適しているかの 1 行を添えてください。
- Q4 ターゲット環境. どのデプロイメントに負荷をかけるか(ステージング、ローカルスタック、ロードテストコピー)。ユーザーが明示的に承諾しない限り、本番システムに負荷をかけないでください。負荷は共有データベース、テナント、および請求に波及します。
| ティア | 実行内容 | 回答 | コスト |
|---|---|---|---|
| 1 スモーク / ベースライン | 低い安定した負荷(1~数ユーザー)を数分間 | すべてのパスが機能するか、競合がない場合のレイテンシはどの程度か | 数分; リスクは無視できる |
| 2 想定負荷 | 想定ピークまでステップランプ、それを維持し、そのレベルで短時間のソーク | ピーク時にレイテンシ目標を達成するか、どの程度の余裕があるか | 約1時間; ターゲットは実際のピークで動作 |
| 3 ストレス / 容量 | 停止ルールが発動するまでステップランプ(knee と破綻点)、さらにスパイク | どこでどのように壊れるか、回復するか | 1~2時間; ターゲットは飽和し、ダウンする可能性があるため、時間枠と監視者が必要 |
Q3 のオプションは、プロジェクトがトラフィックについて述べていることから導出してください: README とドキュメント(誰が使用するか、シート数、テナント)、設定(レート制限、ワーカー数とレプリカ数、オートスケーラーの範囲、キューサイズ、コネクションプール)、cron とバッチジョブ、および呼び出し元自身のタイムアウトとリトライ。一般的な形状は、営業時間のステップランプ、朝のスパイク、安定したソーク、別のサービスからの統合バースト、バッチインポート、およびインタラクティブトラフィックとバックグラウンドトラフィックの混合です。すべてのオプションは、その数値(ユーザー数、生成レート、思考時間、期間、リクエストミックス)を明記します。プロジェクトがトラフィックについて何も述べていない場合は、その旨を伝え、各数値が基づく前提に名前を付け、Q3 で想定ピークを尋ねてください。references/tiers-and-scenarios.md は、設定をそれらの数値に変換する方法を示しています。質問する前に読んでください。
回答後、プレーンテキストで 2 つのことを確認します:
- 認証情報. ハーネスが読み取るファイルまたは環境変数から取得します。チャットに貼り付けられることはありません。
- 制約. 見つけたものをリストし、残りを尋ねてください: レート制限、ライセンスまたはアクティベーションウィンドウ、再起動のコスト、共有テナント、リクエストあたりのコスト(モデルトークン、有料API)。ライセンスウィンドウ内での再起動は、サービスを1日ロックアウトする可能性があり、いくつかの並列な heavy ジョブと1つの巨大な入力がターゲットをダウンさせる可能性があります。負荷の後ではなく、前に尋ねてください。
- データ状態. 実際の使用でリストと検索が保持する行数、実行間でデータをリセットできるかどうか、テストユーザーが編集または削除できる行。空のテーブルはリストページを無料にし、実行によって追加された行は後続の各ステップで以前よりも多くのデータを読み取らせます。
4. ハーネスの構築
mkdir -p loadtest && cd loadtest
cp $SKILL/templates/pyproject.toml . && uv sync # 独自の .venv と uv.lock; locust 固定、Python 3.11+
cp $SKILL/templates/{harness.py,run_local.sh,run_plan.sh,collect.sh,seed.py,loadtest.env.example} .
cp $SKILL/templates/run_docker.sh . # ジェネレーターがターゲットのDockerネットワーク上で実行される場合
cp -r $SKILL/templates/{run_k8s.sh,k8s} . # ターゲットのクラスター内で実行される場合
git -C .. status --short # loadtest/ のみが表示されること
ここで uv init を実行しないでください。それ自体が uv プロジェクトであるリポジトリでは、loadtest をアプリの [tool.uv.workspace] に追加し、locust をアプリの .venv にインストールし、アプリレベルの uv.lock を書き込みます。それが発生した場合は、元に戻してください: アプリで git checkout -- pyproject.toml を実行し、新しい uv.lock を削除し、そこで uv sync を実行します。次にテンプレートに記入します:
REQUESTS.harness.pyのテーブルを PLAN.md から埋めてください: ティア、名前、重み、ミックス、ボディ、想定ステータス。ペイロードはプロジェクトのテスト、フィクスチャ、スキーマから取得し、loadtest/payloads/に配置してください。本番データは決して使用しないでください。サーバーレンダリングアプリは、ログインページとエラーページの両方に 200 を返すため、正しいページだけが持つものを確認してください:expect_text、およびリダイレクトが答えである場合はexpect_location。そう言わずにリダイレクトされる行は失敗します。REQ_TIMEOUT. 実際の呼び出し元のタイムアウトに設定します: ゲートウェイ、フロントエンドのアボート、クライアント SDK、またはプロキシの読み取りタイムアウト。ハーネスはすべてのリクエストをその期限まで保持するため、後の応答は、製品が既に失敗として報告していたはずの成功としてカウントされる代わりに、deadline exceededとして失敗します。MIX. それぞれコストの異なるトラフィックミックスに独自のタグを付け、各ミックスを独自のシナリオとして実行します(短いチャットサイズの入力、ドキュメントサイズの入力、追加コストがかかるオプション)。それらの容量は数倍異なる可能性があり、混合実行では高価なものが隠れてしまいます。- 認証. 各シミュレーションユーザーが 1 回だけログインし、リクエストごとにログインしないように
AUTHを設定します。セッション Cookie を使用するフォームログインでは、LOGIN_EXPECT_LOCATIONまたはLOGIN_EXPECT_TEXTも必要です。間違ったパスワードも 200 を返すためです(loadtest.env.exampleを参照)。サインインはステージloginを取得し、ステップ数と停止ルールの対象外とします。各サインインは、そのユーザーを追加したステップの開始時に着地し、低速なパスワードハッシュは、定常負荷が決して支払わないコストで実行を終了させる可能性があります。ログイン自体に負荷をかけるには、それを一行にします。 - データ. アプリ用に
seed.pyを埋め、python3 seed.py up --users N --rows Mを実行します: アプリのAPIを通じて、各テストアカウントは独自の行を取得し、SEED_MANIFESTはそれらをそのユーザーに渡します。行キーcaptureとconsumeは、ユーザーが作成および削除するものを追跡します。作成と削除をペアにするか、実行間で再シードします。 - ジャーニー. 複数ステップのフロー(アップロードしてからポーリング、作成してから読み取り)は、ユーザークラスのメソッドです。テンプレートに例があります。
references/harness.md は、すべてのフィールド、形状、認証モード、ストリーミング、非 HTTP プロトコル、および 3 つの配置をカバーしています。構築中に読んでください。
5. 安全な実行
- 最初にスモーク。
RUN_NAME=smoke SHAPE=smoke ./run_local.shを実行します: ユーザークラスごとに 1 ユーザーがすべての行を 1 回ずつ、テーブル順に(ID を作成する行を使用する行の前に配置)送信し、その後アイドル状態になります。ログはNOTE smoke: every row was sentで終了するか、送信されなかった行を指定します。ゼロ失敗、妥当なレイテンシ、正しいボディを確認してください。これは、負荷がかかる前に配線が機能することを証明します。 - マシンが静かであることを確認。 容量を測定する実行の前に、ジェネレーターとターゲットのホストで
uptimeとvmstat 1 5を実行します。同じコア上の他のジョブは、すべての数値を競合の尺度にします。ハーネスは各実行の前に自身のホストをサンプリングし、run.json(host_checkとメモ)に記録します。ターゲットホストが別のマシンである場合は、その数値をNOTESに配置します。レポートは、実行中にホストまたはジェネレーターの CPU が 90% に達した実行にバナーを配置し、その knee に注釈を付けます。マシンがビジーな場合は、待機するか、アイドルコアに固定するか(taskset)、移動し、その旨を伝えます。 - 次に、選択されたティアを最も安全なものから順に。 想定負荷、ソーク、ストレス、スパイクの順に実行します。リスクの高い入力(最大のペイロード、追加コストオプション)は最後にします。これらがターゲットにダメージを与えた場合でも、それ以前のものが失われないようにします。
run_plan.shはリストを順番に実行し、ターゲットのヘルスチェックが成功した場合にのみ各実行を開始します。 - ネットワークパスが重要な場合は、ジェネレーターを実際の呼び出し元の隣に配置します: 同じクラスター、VNet、またはコンポーズネットワーク(
run_k8s.sh、run_docker.sh)。ラップトップでの実行は、サービスと同じくらいインターネットを測定します。すべての実行はGENERATORを記録し、ジェネレーター自身の CPU はmetrics/client.csvに送られます。CPU が約 90% を超えると、数値はジェネレーターを説明します。 - 停止ルールを尊重します。 ハーネスは、失敗率が
STOP_FAIL_RATIOを超えた場合、p95 がSTOP_P95_MSを超えた場合、最後の 20 秒の半分以上が失敗した場合、またはほとんどのサインインが失敗した場合に実行を終了します。多くのサービスは負荷を捨てる代わりにキューイングします: エラーはゼロで、レイテンシのみが増加します。そのため、p95 の上限を呼び出し元のタイムアウトまたは SLO に設定してください。"確認のために" 停止ルールを超えてプッシュしないでください。 - 実行間のヘルスを監視し、キューをドレインさせます(
run_plan.shのCOOLDOWN)。 - 作成したすべてをクリーンアップします:
./run_k8s.sh down(Pod、ConfigMap、Secret)、停止したコンテナ、テストユーザー、テストデータ。ソフトデリートされた行や監査エントリなど、削除できないものをユーザーにリストします。
スモーク以外の最初の実行の前、および共有システムに対する実行の前に、references/safety.md を読んでください。
6. サーバー側の収集
クライアントの数値は何が起こったかを示します。サーバーの数値はなぜ起こったかを示します。locust の前にコレクターを起動し、locust の後に停止し、同じ実行ディレクトリに書き込みます:
uv run $SKILL/scripts/collect_server_metrics.py <target> -o results/<run>/metrics
uv run $SKILL/scripts/cprofile_to_flamegraph.py results/<run>/profiles/*.prof -o results/<run>/flamegraph.svg
利用可能な場合(SSH、kubectl exec、またはメトリクスエンドポイント)、ターゲットホストから CPU プロファイル(py-spy、perf、または async-profiler)、メモリプロファイル、およびアプリケーションメトリクス(リクエストレイテンシ、エラー率、DBプールサイズ)を収集します。ターゲットが収集をサポートしていない場合、collect_server_metrics.py はスキップし、そのことをメモします。
すべてのメトリクスは、同じ実行名の下に、クライアント側のデータと一緒にタイムスタンプ付きで保存されます。
7. レポート
uv run $SKILL/scripts/generate_report.py results/<run>/ -o loadtest/report.html
レポートには以下が含まれます:
- 実行サマリー: 日時、期間、ターゲット、ジェネレーター、シナリオ。
- レイテンシ分布: p50、p95、p99、平均、および時系列プロット。
- スループット: RPS、成功/失敗の内訳。
- knee の特定: スループットがレイテンシ増加に屈するポイント。
- 障害モード: どのエンドポイントが、どのように、どの負荷レベルで失敗したか。
- リソース使用率: ジェネレーターとターゲット(利用可能な場合)の CPU、メモリ。
- 推奨事項: 行動可能な洞察(「
/api/orders/checkoutは 50 RPS でタイムアウトします。タイムアウトを増やすか、ワーカーを追加してください」)。
レポートは loadtest/ に保存され、ユーザーはブラウザで開くことができます。