リファレンス

リファレンス

行き詰まったとき、思い出せないときに開くページです。 コマンド、期待値、用語の対訳、よくある誤解をまとめてあります。

環境の切り替え

上のスイッチで「ローカル / サーバー」を切り替えると、 このページのコマンドが自動で書き換わります。実際に置換されるのは次の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 obs2枚重ね+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 の実行

この教材の 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
サーバー上で練習台を操作する(SSHしてから)
# 状態を見る
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 を使ってください。

ローカルに入れた JMeter で実行
# 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対象のホスト名/IPlocalhost
-Jport対象のポート8080
-Jthreadsスレッド数(=人数)プランによる
-JrampRamp-up 秒数プランによる
-Jduration実行時間(秒)※04のみ120
-Jloops繰り返し回数プランによる
-JtesttitleGrafana上での試験名stress
-Jproduct取り合わせる商品ID ※05のみ7

練習台のスイッチ一覧

http://localhost:8080/admin から変更できます。負荷をかけている最中でも即座に反映されます。

スイッチ既定入れると何が起きるか
n_plus_one0商品一覧が1件ずつDBを引く非効率な作りになる(1+2N回の問い合わせ)
cache0商品一覧を10秒間おぼえておく
review_index10にすると索引を実際に DROP する。商品詳細が約60倍遅くなる
slow_payment_ms0注文確定に外部決済API模擬の待ち時間を挟む(ミリ秒)
error_rate0全リクエストが指定%の確率で500を返す
global_delay_ms0全リクエストに一律の遅延を足す
rate_limit01にすると /api/* にレート制限。超過は429
rate_limit_rps20レート制限のしきい値(毎秒)
stock_mode01にすると在庫チェックが壊れた実装になる。在庫がマイナスになる
cpu_burn0商品一覧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.poolDBコネクションプールの状態(queuedが待ち行列)
runtime.mysqlThreadsMySQLの接続数
runtime.lagMsNodeのイベントループ遅延。伸びていたらアプリのCPUが限界

学習用エンドポイント

思いどおりの挙動を再現したいときに使います。

URL何が起きるか練習になること
/api/slow?ms=20002秒待ってから返すタイムアウト設定、継続時間のアサーション
/api/flaky?rate=3030%の確率で500応答アサーション、エラー率の読み方
/api/heavy?n=500000CPUを焼くアプリがCPU限界になる様子
/api/uploadファイルを受け取るmultipart のリクエスト作成
/api/stream/orders/13秒かけて状況を流す(SSE)長時間接続の扱い
/static/sample.png90KBの画像(nginxが直接配信)転送量と回線の限界

どのURLにも次の2つを付けられます(スイッチより優先されます)。

?_delay=500このリクエストだけ500ms遅らせる
?_error=50このリクエストだけ50%の確率で失敗させる

実測値の一覧

この教材で実際に測定した数値です。自分の結果と比べる基準にしてください。 環境が違えば絶対値は変わりますが、比率は同じ傾向になるはずです。

限界点さがし(専用サーバー・2コア・別PCから負荷)

スレッド数スループット平均最大エラー
10188.5 req/s47 ms2026 ms0%
20357.8 req/s49 ms1087 ms0%
40373.4 req/s94 ms1454 ms0%
80378.7 req/s186 ms5209 ms0%

改善前後の比較

実験
索引の有無(商品詳細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メモリ
mysql80.7%143.9 MiB / 768 MiB
app224.3%51.9 MiB / 384 MiB
app123.0%40.8 MiB / 384 MiB
nginx7.0%8.2 MiB / 128 MiB
redis4.2%8.2 MiB / 128 MiB
サンプラーp95原因
一覧 GET /api/products93 msMySQLのCPU
詳細 GET /api/products/id57 msMySQLのCPU
静的 GET /static/sample.png521 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 psdocker 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/uploadjmeter/data/upload.png を送る
HTTPS での計測 TLSハンドシェイクの負荷、Keep-Aliveの重要性 ドメインと証明書を用意すれば可能
長時間の耐久試験 数時間流してメモリリークや性能劣化を見る できる-Jduration=7200 など
CIへの組み込み JTLを後処理して「p95が500msを超えたら失敗」と判定する 自分で書く必要あり(JMeter自体に合否判定機能はない)
分散実行 1台で負荷をかけきれないとき、jmeter-server を複数立てる PCを複数用意すれば可能

最後に

負荷試験で一番大事なのは、道具の使い方より 「同じ条件で前後を測って、数字で語る」という姿勢です。 JMeter はそのための道具にすぎません。 測り方が雑なら、どんなに立派なグラフを出しても意味のある結論は出ません。