リファレンス
行き詰まったとき、思い出せないときに開くページです。 コマンド、期待値、用語の対訳、よくある誤解をまとめてあります。
環境の切り替え
上のスイッチで「ローカル / サーバー」を切り替えると、 このページのコマンドが自動で書き換わります。実際に置換されるのは次の2か所だけです。
| ローカル | サーバー | |
|---|---|---|
| 対象のURL | http://localhost:8080/... |
http://<入力したIP>/... |
| Grafana | http://localhost:3800 |
http://<入力したIP>/grafana |
| JMeterの実行 | docker compose run --rm jmeter ... |
宛先(TARGET_HOST)と結果の送り先(INFLUX_URL)が自動で付く |
切り替わらないもの
- サーバー側で打つコマンド(
sudo docker compose ...など)… SSHしてから実行するものなので、そのままです - 手元の練習台の起動・停止(
docker compose --profile obs up -dなど)… ローカルで練習するときのコマンドです。サーバーで練習しているときは打ちません
サーバーを対象にするときの前提
- サーバーでは
-f docker-compose.yml -f docker-compose.server.yml --profile obsで 2枚重ね+obsプロファイルで起動している(ポート80で公開し、監視ツールも動かすため) - 手元で起動するのは JMeter だけ。練習台も監視ツールもサーバー側にある
- Grafana と InfluxDB は自分のグローバルIPからのみ許可。 他所からは403。回線を変えると開けなくなるので、その時はサーバーの許可リストを更新する
- 画像を高スループットで叩くと自分の回線が先に埋まる。 限界点の測定は軽いJSON APIで
コマンド早見表
練習台の操作
サーバーで練習しているときは、この節は不要です
練習台も監視ツールもサーバー側で動き続けています。 手元で起動するのは JMeter だけです。サーバー側の操作は この下のサーバー操作を見てください。
# 起動(観測ツールも含む)
docker compose --profile obs up -d
# 状態を見る
docker compose ps
# ログを見る
docker compose logs -f app1
# 停止(データは残る)
docker compose --profile obs down
# 全部消してやり直す(データも消える。次回起動時に再投入)
docker compose --profile obs down -v
curl.exe -s -X POST -H "x-admin-token: loadlab-admin" http://localhost:8080/api/admin/reset-data
curl.exe -s -X POST -H "x-admin-token: loadlab-admin" http://localhost:8080/api/admin/flags/reset
JMeter の実行
# 基本形
docker compose run --rm jmeter 01_smoke.jmx
# 人数と時間を指定
docker compose run --rm jmeter 04_stress_influx.jmx -Jthreads=50 -Jramp=15 -Jduration=180
# 別のサーバーを対象にする(上のスイッチを使えば自動でこの形になります)
docker compose run --rm -e TARGET_HOST=192.0.2.10 -e TARGET_PORT=80 jmeter 04_stress_influx.jmx
# 状態を見る
cd ~/loadlab && sudo docker compose ps
# 起動しなおす(2枚重ね+obsプロファイル。監視ツールもここで起動する)
sudo docker compose -f docker-compose.yml -f docker-compose.server.yml --profile obs up -d
# nginx の設定を変えたら「再読み込み」ではなく「作り直し」
sudo docker compose -f docker-compose.yml -f docker-compose.server.yml --profile obs up -d --force-recreate nginx
# 負荷中のCPUを見る
sudo docker stats --no-stream --format "{{.Name}} {{.CPUPerc}} {{.MemUsage}}"
# ログを見る
sudo docker compose logs --tail 50 app1
nginx の設定を変えたら reload ではなく作り直し
設定ファイルは1ファイルだけをコンテナにマウントしているため、
ファイルを削除して置き直すとマウントが切れます
(コンテナは消えた古いファイルを掴んだまま)。
nginx -s reload では反映されないので、
上の --force-recreate nginx を使ってください。
# GUI(作成・デバッグ専用)
C:\jmeter\apache-jmeter-5.6.3\bin\jmeter.bat
# CLI(計測はこちら)
C:\jmeter\apache-jmeter-5.6.3\bin\jmeter.bat -n -t plan.jmx -l result.jtl -e -o report
# プロパティを渡す
... -n -t plan.jmx -Jhost=localhost -Jport=8080 -Jthreads=30 -l result.jtl
テストプランで使えるプロパティ
| プロパティ | 意味 | 既定値 |
|---|---|---|
-Jhost | 対象のホスト名/IP | localhost |
-Jport | 対象のポート | 8080 |
-Jthreads | スレッド数(=人数) | プランによる |
-Jramp | Ramp-up 秒数 | プランによる |
-Jduration | 実行時間(秒)※04のみ | 120 |
-Jloops | 繰り返し回数 | プランによる |
-Jtesttitle | Grafana上での試験名 | stress |
-Jproduct | 取り合わせる商品ID ※05のみ | 7 |
練習台のスイッチ一覧
http://localhost:8080/admin から変更できます。負荷をかけている最中でも即座に反映されます。
| スイッチ | 既定 | 入れると何が起きるか |
|---|---|---|
n_plus_one | 0 | 商品一覧が1件ずつDBを引く非効率な作りになる(1+2N回の問い合わせ) |
cache | 0 | 商品一覧を10秒間おぼえておく |
review_index | 1 | 0にすると索引を実際に DROP する。商品詳細が約60倍遅くなる |
slow_payment_ms | 0 | 注文確定に外部決済API模擬の待ち時間を挟む(ミリ秒) |
error_rate | 0 | 全リクエストが指定%の確率で500を返す |
global_delay_ms | 0 | 全リクエストに一律の遅延を足す |
rate_limit | 0 | 1にすると /api/* にレート制限。超過は429 |
rate_limit_rps | 20 | レート制限のしきい値(毎秒) |
stock_mode | 0 | 1にすると在庫チェックが壊れた実装になる。在庫がマイナスになる |
cpu_burn | 0 | 商品一覧1回ごとにハッシュ計算を回してCPUを焼く |
curl.exe -s -X POST -H "x-admin-token: loadlab-admin" -H "content-type: application/json" -d "{\"n_plus_one\":1,\"cache\":0}" http://localhost:8080/api/admin/flags
curl.exe -s -H "x-admin-token: loadlab-admin" http://localhost:8080/api/admin/status
返ってくる JSON の主な項目です。
| 項目 | 意味 |
|---|---|
oversoldProducts | 在庫がマイナスになった商品の数。0以外なら事故 |
reviewIndexExists | 索引が存在するか |
runtime.pool | DBコネクションプールの状態(queuedが待ち行列) |
runtime.mysqlThreads | MySQLの接続数 |
runtime.lagMs | Nodeのイベントループ遅延。伸びていたらアプリのCPUが限界 |
学習用エンドポイント
思いどおりの挙動を再現したいときに使います。
| URL | 何が起きるか | 練習になること |
|---|---|---|
/api/slow?ms=2000 | 2秒待ってから返す | タイムアウト設定、継続時間のアサーション |
/api/flaky?rate=30 | 30%の確率で500 | 応答アサーション、エラー率の読み方 |
/api/heavy?n=500000 | CPUを焼く | アプリがCPU限界になる様子 |
/api/upload | ファイルを受け取る | multipart のリクエスト作成 |
/api/stream/orders/1 | 3秒かけて状況を流す(SSE) | 長時間接続の扱い |
/static/sample.png | 90KBの画像(nginxが直接配信) | 転送量と回線の限界 |
どのURLにも次の2つを付けられます(スイッチより優先されます)。
?_delay=500 | このリクエストだけ500ms遅らせる |
?_error=50 | このリクエストだけ50%の確率で失敗させる |
実測値の一覧
この教材で実際に測定した数値です。自分の結果と比べる基準にしてください。 環境が違えば絶対値は変わりますが、比率は同じ傾向になるはずです。
限界点さがし(専用サーバー・2コア・別PCから負荷)
| スレッド数 | スループット | 平均 | 最大 | エラー |
|---|---|---|---|---|
| 10 | 188.5 req/s | 47 ms | 2026 ms | 0% |
| 20 | 357.8 req/s | 49 ms | 1087 ms | 0% |
| 40 | 373.4 req/s | 94 ms | 1454 ms | 0% |
| 80 | 378.7 req/s | 186 ms | 5209 ms | 0% |
改善前後の比較
| 実験 | 前 | 後 |
|---|---|---|
| 索引の有無(商品詳細1回・同時実行1) | 索引なし 60〜124 ms | 索引あり 1〜2 ms |
| N+1 の解消(30スレッド60秒・ローカル) | 243.8 req/s / 平均112ms / 最大823ms | 329.5 req/s / 平均83ms / 最大443ms |
| N+1を「結合」で直そうとした失敗例 | 243 req/s | 135 req/s(悪化) |
| 負荷生成機の同居 vs 分離 | 同居 329.5 req/s | 分離 374.0 req/s |
ボトルネックの内訳(80スレッド時・実測)
| コンテナ | CPU | メモリ |
|---|---|---|
| mysql | 80.7% | 143.9 MiB / 768 MiB |
| app2 | 24.3% | 51.9 MiB / 384 MiB |
| app1 | 23.0% | 40.8 MiB / 384 MiB |
| nginx | 7.0% | 8.2 MiB / 128 MiB |
| redis | 4.2% | 8.2 MiB / 128 MiB |
| サンプラー | p95 | 原因 |
|---|---|---|
| 一覧 GET /api/products | 93 ms | MySQLのCPU |
| 詳細 GET /api/products/id | 57 ms | MySQLのCPU |
| 静的 GET /static/sample.png | 521 ms | 回線の帯域(nginxのCPUは7%) |
同時実行のバグ(在庫1個に30人)
| 正しい実装 | 壊れた実装 | |
|---|---|---|
| エラー率 | 32.22%(=正常な売り切れ) | 11.11% |
| 注文の成功数 | 1件 | 20件 |
| 在庫 | 0 | −19 |
その他
| データ投入 | 2,000ユーザー / 20,000商品 / 200,000レビューで 約4秒 |
| メモリ消費(対象側の合計ピーク) | 482 MB(うちMySQLが340MB) |
| メモリ消費(JMeter本体) | 490 MB(単体で一番重い) |
| エラー注入20%のとき | 実測 20.00%(ぴったり一致) |
日本語UI ↔ 英語UI 対訳
ネットの情報は英語UIで書かれていることが多いので、対応表を置いておきます。
| 日本語 | English |
|---|---|
| テスト計画 | Test Plan |
| スレッドグループ | Thread Group |
| スレッド数 | Number of Threads (users) |
| Ramp-up期間 | Ramp-up period |
| ループ回数 | Loop Count |
| サンプラー | Sampler |
| HTTPリクエスト | HTTP Request |
| HTTPリクエスト初期値 | HTTP Request Defaults |
| HTTPクッキーマネージャ | HTTP Cookie Manager |
| HTTPヘッダマネージャ | HTTP Header Manager |
| HTTPキャッシュマネージャ | HTTP Cache Manager |
| 設定エレメント | Config Element |
| 後処理 | Post Processors |
| 正規表現抽出 | Regular Expression Extractor |
| JSON抽出 | JSON Extractor |
| 境界抽出 | Boundary Extractor |
| アサーション | Assertions |
| 応答アサーション | Response Assertion |
| 継続時間のアサーション | Duration Assertion |
| タイマー | Timer |
| 一様乱数タイマー | Uniform Random Timer |
| リスナー | Listener |
| 結果を表示(ツリー) | View Results Tree |
| 集計レポート | Aggregate Report |
| 統計レポート | Summary Report |
| コントローラ | Logic Controller |
| トランザクションコントローラ | Transaction Controller |
| デバッグサンプラー | Debug Sampler |
よくある誤解
誤解1:スレッド数=同時アクセス数だと思っている
「考える時間」を入れていれば、30スレッドでも実際に同時に処理されているのは数件です。 スレッド数は「サイトを開いている人の数」、同時アクセス数は「その瞬間サーバーが処理している数」で別物です。 報告するときは「同時利用者30人」と書き、「同時アクセス30」とは書かないこと。
誤解2:平均応答時間で合否を判断する
平均は少数の極端な遅延を隠します。90%Line か 95%Line を基準にしてください。
誤解3:エラー率が下がったから改善した
この教材の在庫実験がまさにそれで、エラー率 32%→11% に「改善」した裏で 在庫がマイナス19になっていました。 性能の数字とデータの正しさは別に確認してください。
誤解4:数字が悪いのはサーバーのせい
GUIで実行していた、負荷生成機と同居していた、回線が細かった── 測定側が原因であることは非常に多いです。 サーバーのCPUが余っているのに遅いときは、まず自分を疑ってください。
誤解5:1回測れば十分
初回はキャッシュが冷えていて遅く出ます。同じ条件で2〜3回流し、2回目以降を採用してください。 改善前後を比べるときは順番を入れ替えても同じ結論になるかを確認すると確実です。
トラブル対処
| 症状 | 原因と対処 |
|---|---|
| エラー率が100% | 宛先が違う。HTTPリクエスト初期値のホスト・ポートを確認。curl.exe http://localhost:8080/healthz で先に疎通確認 |
| 403 が返る | CSRFトークンの相関に失敗。正規表現抽出のデフォルト値がそのまま入っていないか確認 |
| 401 が返る | JWTの抽出に失敗。JSON抽出の $.token と、ヘッダマネージャの Bearer ${token} を確認 |
| 409 が返る | 前回のカートや在庫が残っている。/api/admin/reset-data を実行 |
| 429 が返る | レート制限が有効になっている。rate_limit を 0 に戻す |
| 502 が返る | アプリが起動中か落ちている。docker compose ps と docker compose logs app1 |
変数が ${csrf} のまま送られる |
抽出エレメントの置き場所が違う。値を取り出したいリクエストの子に置く |
| アサーションが効かない | 置き場所を確認。スレッドグループ直下なら全リクエスト、特定リクエストの子ならそれだけ |
| 全員が同じユーザーになる | CSVの共有モードが「現在のスレッド」になっている。「すべてのスレッド」に変更 |
| HTMLレポートが生成されない | -o のフォルダが空でない。別名にするか中身を消す |
| JMeterがメモリ不足で落ちる | 結果ツリーを付けたままCLI実行している。CLIではリスナーを外す。または JVM_ARGS でヒープを増やす |
| Grafanaにグラフが出ない | 時間範囲を Last 15 minutes に。--profile obs で起動しているか確認 |
次に学ぶこと
この教材を終えたら、次はこのあたりです。難しい順に並べています。
| テーマ | 内容 | この教材で試せるか |
|---|---|---|
| 流量ベースの試験 | 人数ではなく「毎秒600件」を固定して測る。APIのSLA検証はこちら | できる。04のプランにある Constant Throughput Timer を有効にする |
| レート制限への対応 | 429が返ったときのリトライ設計 | できる。rate_limit を1にする |
| ファイルアップロード | multipart のリクエストを組む | できる。/api/upload に jmeter/data/upload.png を送る |
| HTTPS での計測 | TLSハンドシェイクの負荷、Keep-Aliveの重要性 | ドメインと証明書を用意すれば可能 |
| 長時間の耐久試験 | 数時間流してメモリリークや性能劣化を見る | できる。-Jduration=7200 など |
| CIへの組み込み | JTLを後処理して「p95が500msを超えたら失敗」と判定する | 自分で書く必要あり(JMeter自体に合否判定機能はない) |
| 分散実行 | 1台で負荷をかけきれないとき、jmeter-server を複数立てる | PCを複数用意すれば可能 |
最後に
負荷試験で一番大事なのは、道具の使い方より 「同じ条件で前後を測って、数字で語る」という姿勢です。 JMeter はそのための道具にすぎません。 測り方が雑なら、どんなに立派なグラフを出しても意味のある結論は出ません。