TT Lab
はじめる
学ぶ 学習パス コース

負荷テスト

ボトルネックの再現と除去

TT Labで続きを見る

このラボは本物のVM上で動きます

この箱はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが別に動き、systemdが実際にサービスを管理し、dockerは模倣ではなく本物のDockerエンジンです。docker runで起動したコンテナは実際にプロセスになり、docker execもdocker logsもそのまま動作します。

以前は、このラボはPodの中で動いていました。カーネルの権限をすべて下ろした箱なので、コンテナを起動するステップが塞がれていて、そのためイメージのアーカイブを直接展開してみるという回り道で学んでいました。もう回り道は必要ありません。

知っておくことが2つあります。

目標

ボトルネックをわざと作って再現し、階層ごとの仮説に絞り込み、取り除いたあと、改善幅を数字で確認して、目標のトラフィックを処理できるインスタンス数まで計算します。成果物は/root/lt3/の下に置きます。

なぜ重要なのか

ボトルネックを探すことは、勘ではなく、ラダーを順番にたどる作業です。ソケットバッファー → スレッドプールのキュー → コネクションプール待ち → DBのロック待ち → 処理。このラボが作るボトルネックは、そのうち最も手前の、つまり「一度に1つしか処理しない」という同時実行の制約です。このとき現れるサインが重要です。同時実行数を10倍に上げても、スループットはほぼそのままで、レイテンシだけが10倍に増えます。スループットが詰まると、代わりに待ち時間が増えるというこの関係を目で見たあとは、プロダクションでConnection is not available, request timed out after 30000msのようなログに出会ったとき、何を先に見るべきかがわかります。そして最後のステップで学ぶのは、測定値をそのまま使わないという原則です。測定された最大スループットの70%だけを使い、残りはヘッドルームとして残します。

準備

mkdir -p /root/lt3を実行しておいてください。使えるイメージは、あらかじめ取得してあるpython:3.12-alpine、nginx:1.27-alpine、alpine:3.20、busybox:1.36だけです。

ステップ

  1. /root/lt3/slow.pyを作成します。条件は、標準ライブラリhttp.serverのHTTPServerを使う(一度に1つずつ処理する)、各リクエストでtime.sleep(0.05)により50msの遅延を与える、レスポンス本文にslow-appという文字列を含める、の3つです。このスクリプトをpython:3.12-alpineイメージで実行して、コンテナ名lt-slow、ホストポート127.0.0.1:8086で起動します。curl http://127.0.0.1:8086/のレスポンスに、slow-appが見える必要があります。
  2. 同時実行数1で測定して、/root/lt3/slow-c1.txtに保存します。正常なら、p95は0.04秒以上、スループットは40 RPS以下と出ます(50msの遅延を直列に処理するので、理論上は約20 RPS)。
  3. 同時実行数10で測定して、/root/lt3/slow-c10.txtに保存します。確認する点: スループットが同時実行数1に対して2倍未満(ほぼそのまま)、p95は2倍以上に増加。スループットが詰まると、代わりに待ち時間が増えます。
  4. /root/lt3/hypothesis.mdに、ボトルネックの候補を3つ以上、-または1.で始まる項目として書きます。候補は互いに異なる階層である必要があります(同時実行/スレッド/ワーカー、CPU、コネクションプール、ロック競合、IO・ネットワークのうち、最低3つの階層)。各候補について、どう確認するかも一緒に書いてください。
  5. /root/lt3/fast.pyを作成します。条件は、ThreadingHTTPServer(またはThreadingMixIn)で同時処理ができる、遅延50msはそのまま維持する、レスポンス本文にfast-appを含める、の3つです。コンテナ名lt-fast、ホストポート127.0.0.1:8087で起動します。同時実行数10で測定して/root/lt3/fast-c10.txtに保存し、スループットがslow-c10.txtに対して3倍以上である必要があります。
  6. fastアプリを同時実行数1でも測定して/root/lt3/fast-c1.txtを作成したあと、/root/lt3/compare.csvに4行をまとめます。形式はapp,concurrency,rps,p95で、行はslow,1、slow,10、fast,1、fast,10です。値は、各結果ファイルから読んだものである必要があります。
  7. /root/lt3/capacity.txtに、4行を書きます。
    • target_rps=300(目標のピークトラフィック)
    • measured_rps=: compare.csvのfast/10行のrps
    • safe_rps=: 測定値の70%、小数点以下切り捨て(整数)
    • instances=: ceil(300 ÷ safe_rps)、つまり切り上げた整数
  8. /root/lt3/report.mdに、レポートを書きます。4つのセクションが必要です。ボトルネックの原因、証拠(測定値)、対処、容量の結論。本文に、改善前のスループット、改善後のスループット、必要なインスタンス数の3つの数字が、そのまま入っている必要があります。

参考

遅いアプリケーションを起動する

/root/lt3/slow.pyを作成します。条件は、標準ライブラリhttp.serverのHTTPServerを使う(一度に1つずつ処理する)、各リクエストでtime.sleep(0.05)により50msの遅延を与える、レスポンス本文にslow-appという文字列を含める、の3つです。このスクリプトをpython:3.12-alpineイメージで実行して、コンテナ名lt-slow、ホストポート127.0.0.1:8086で起動します。curl http://127.0.0.1:8086/のレスポンスに、slow-appが見える必要があります。

/root/lt3/slow.pyは、リクエストごとに人為的な遅延を入れて、一度に1つずつしか処理しないサーバーである必要があります。レスポンス本文にslow-appという文字列が入っている必要があり、コンテナ名はlt-slow、ホストポートは8086です。

同時実行数1で基準を測定する

同時実行数1で測定して、/root/lt3/slow-c1.txtに保存します。正常なら、p95は0.04秒以上、スループットは40 RPS以下と出ます(50msの遅延を直列に処理するので、理論上は約20 RPS)。

/root/lt3/slow-c1.txtに保存します。50msの遅延を入れたなら、p95は最低でも40ms以上、スループットは1秒あたり20件前後が出れば正常です。値がおかしければ、対象のポートから確認してください。

同時実行数を10倍に上げてみる

同時実行数10で測定して、/root/lt3/slow-c10.txtに保存します。確認する点: スループットが同時実行数1に対して2倍未満(ほぼそのまま)、p95は2倍以上に増加。スループットが詰まると、代わりに待ち時間が増えます。

/root/lt3/slow-c10.txtに保存します。直列処理なら、スループットはほぼそのままで、代わりに待ち時間が増えます。この2つが同時に観察されれば、ボトルネックが同時実行にあるというサインです。

ボトルネックの仮説を立てる

/root/lt3/hypothesis.mdに、ボトルネックの候補を3つ以上、-または1.で始まる項目として書きます。候補は互いに異なる階層である必要があります(同時実行/スレッド/ワーカー、CPU、コネクションプール、ロック競合、IO・ネットワークのうち、最低3つの階層)。各候補について、どう確認するかも一緒に書いてください。

/root/lt3/hypothesis.mdに、互いに異なる階層の候補3つと、それぞれの確認方法を書きます。リクエストの処理経路のキューを順番にたどれば、候補が重なりません。確認方法のない仮説は、仮説ではありません。

同時処理に変える

/root/lt3/fast.pyを作成します。条件は、ThreadingHTTPServer(またはThreadingMixIn)で同時処理ができる、遅延50msはそのまま維持する、レスポンス本文にfast-appを含める、の3つです。コンテナ名lt-fast、ホストポート127.0.0.1:8087で起動します。同時実行数10で測定して/root/lt3/fast-c10.txtに保存し、スループットがslow-c10.txtに対して3倍以上である必要があります。

/root/lt3/fast.pyは、リクエストを同時に処理できる必要があります。遅延そのものは、そのままにしてください。遅延を減らすのではなく、待ちをなくすことが、今回の改善の核心です。コンテナ名はlt-fast、ポートは8087です。

改善前後の比較表

fastアプリを同時実行数1でも測定して/root/lt3/fast-c1.txtを作成したあと、/root/lt3/compare.csvに4行をまとめます。形式はapp,concurrency,rps,p95で、行はslow,1、slow,10、fast,1、fast,10です。値は、各結果ファイルから読んだものである必要があります。

fastアプリも、同時実行数1でもう1回測定したあと、/root/lt3/compare.csvに4行をまとめます。値は、各結果ファイルから読んだものである必要があります。

必要なインスタンス数を計算する

/root/lt3/capacity.txtに、4行を書きます。

/root/lt3/capacity.txtに、目標・測定・安全なスループットと、インスタンス数を書きます。測定された最大値をそのまま使うとヘッドルームがなく、インスタンス数は切り上げる必要があります。

分析レポートを書く

/root/lt3/report.mdに、レポートを書きます。4つのセクションが必要です。ボトルネックの原因、証拠(測定値)、対処、容量の結論。本文に、改善前のスループット、改善後のスループット、必要なインスタンス数の3つの数字が、そのまま入っている必要があります。

/root/lt3/report.mdに、原因・証拠・対処・容量の結論を書きます。証拠は文章ではなく数字である必要があるので、前のステップで出た値を、本文にそのまま書いてください。