🔒

Azure Container Apps 環境における ピアツーピア 暗号化

に公開

はじめに

この記事は、Azure Container Apps 環境 において ピアツーピア 暗号化 を構成して触ってみた記録です。

https://learn.microsoft.com/ja-jp/azure/container-apps/ingress-environment-configuration?tabs=azure-cli#peer-to-peer-encryption

手順

ドキュメントより引用

Azure Container Apps では、環境内のピアツーピア TLS 暗号化がサポートされています。 この機能を有効にすると、Azure Container Apps 環境スコープ内で有効なプライベート証明書を使用して、環境内のすべてのネットワーク トラフィックが暗号化されます。 これらの証明書は、Azure Container Apps によって自動的に管理されます。

Note 既定では、ピアツーピア暗号化は無効になっています。 アプリケーションに対してピアツーピア暗号化を有効にすると、高負荷のシナリオで応答の待機時間が長くなり、最大スループットが低下する可能性があります。

doc-p2p-diagram

1 受信 TLS トラフィックは、環境のエッジにあるイングレス プロキシで終了します。

2 環境内のイングレス プロキシとの間のトラフィックは、プライベート証明書で TLS 暗号化され、受信側で復号化されます。

3 アプリ A からアプリ B の FQDN への呼び出しは、まずエッジ イングレス プロキシに送信され、TLS で暗号化されます。

4 アプリ B のアプリ名を使用してアプリ A からアプリ B に対して行われた呼び出しは、アプリ B に直接送信され、TLS で暗号化されます。 アプリと Java コンポーネント間の呼び出しは、アプリ間通信と同じ方法で処理され、TLS で暗号化されます。

どういうことか。とりあえず試してみる。

portal-enable-p2p-encryption

"peerTrafficConfiguration": {
    "encryption": {
        "enabled": true
    }
},

このときアプリ側は何も変更していません。
すなわち、受信側の Nginx は 80 ポートで待ち受ける状態のままであり、アプリ自体は平文でリクエストを受け付けることになります。

また、画面上とプロパティでの変更がわかるだけで、ログなどからも P2P 暗号化が実行されていることは判断できません。

検証と考察

アプリケーションに対してピアツーピア暗号化を有効にすると、高負荷のシナリオで応答の待機時間が長くなり、最大スループットが低下する可能性があります。

との文章から実験してみました。

先に結論

P2P Encryption を有効化する場合、想定のパフォーマンスが得られるか、ユーザー影響がないかの確認は必須です。

Azure Container Apps の Peer-to-Peer (P2P) Encryption を OFF/ON で切り替え、同一条件(vegeta / 30s / timeout=60s / 1KB応答)で負荷試験を行い、成功率・p95レイテンシ・スループットを比較した。横軸は vegeta がレポートする実測の送信レート(rate)を用いる。1
結果として、レプリカ数を 1 に固定した場合、P2P ON は高負荷域で成功率が急低下し、スループットも大きく落ち込む挙動が確認できた(図1・図3)。一方で P2P OFF は同等の負荷条件でも成功率を維持できた。

success_rate
図1(成功率):P2P OFF/ON とレプリカ数(1 / 3)を変えたときの成功率比較。高負荷域での成功率低下は主に「P2P ON × レプリカ1」で顕著

注記:横軸は vegeta report の rate(実際に送れた送信レート)を使用。高負荷域ではクライアント側/サーバ側の要因で targetRate を維持できず、report の rate が低下することがあるため、比較の基準として rate を採用した。1

latency
図2(p95):p95レイテンシ比較(対数軸)。成功率が同等でも、P2P ON の方が p95 が増加する帯域があり、待機時間の増加として現れる。

注記:p95 グラフは値のレンジ(ms〜秒)差が大きいため、可視化のために対数軸を使用。

throughput
図3(スループット):実効スループット比較。成功率低下(503混在)が発生すると throughput も連動して下がる。

また、別途記事にする予定ですが、
P2P Encryption を実現するために、Ingress の Envoyt とは別に App 毎(レプリカ毎?)に Envoy Sidecar の存在することが推測されます。

検証

copilot との検証方針相談抜粋
  1. 測りたい「P2P のオーバーヘッド」と経路の選び方

P2P 暗号化は Envoy↔Envoy 間の TLS(アプリ直前は HTTP のまま)。P2P の有無が効くのは、内部 Envoy 経路を通るときです。
よって、**比較対象は appname 経路(例: http://<APP_NAME>/)**がベスト。

外部FQDN や internal FQDN は Ingress Envoy(エッジ)経由かつ L7 終端が混ざるため、P2P の効果だけを純粋に切り出しにくいです。

もし外部の比較も載せたい場合は、「説明つき参考値」として別枠にしましょう

  1. フェアな A/B(OFF→ON)にするための前提合わせ
    アプリ/環境の固定化

固定 200 応答のエンドポイントを用意(例: /bench/1kb)。
アプリのリビジョン固定(トラフィック 100% を単一リビジョンに)。
レプリカ固定(minReplicas = maxReplicas。例:3→3 など)でオートスケールのゆらぎ排除。
CPU/メモリ要求/制限を明記(スロットリングで数字が割れます)。
**スケールトリガ(KEDA)**に関わるメトリクスが揺れないよう、全テストを短時間に連続で実施。
Dapr 無効(有効だと別オーバーヘッドが載ります)。
Warmup:本番計測の前に 10〜30 秒のウォームアップを必ず入れる。

クライアント(負荷側)の健全性

同一環境の別アプリから実行(ネットワーク経路を最短に)。
Keep-Alive 有効(-k)。
ulimit / sysctl を確認(ulimit -n, net.ipv4.ip_local_port_range, net.core.somaxconn など)。
複数クライアントの併用(例:3ポッド×ab で分散)か、wrk / vegeta の利用を検討。
タイムアウト(-s)や コネクション失敗の扱いを明記。
同一マシン内で “同時に” OFF/ON を打たない(リソース取り合い防止)。

宛先のアプリには 1KBの応答を返すエンドポイント を用意しました。

また、abHTTP/1.0 のみしか対応していないようなので、クライアント側は vegeta を使ってみます。

curl -L \
  https://github.com/tsenart/vegeta/releases/download/v12.12.0/vegeta_12.12.0_linux_amd64.tar.gz \
  -o vegeta.tar.gz

tar xfz vegeta.tar.gz

mv vegeta /usr/local/bin/vegeta

なお、クライアント側はレプリカ数1で固定とし、そのレプリカに接続したコンソールから vegeta を実行しています。

実験条件(比較の前提)

  • 負荷ツール:vegeta(rate固定、duration=30s、timeout=60s、connectionsは主に500)
  • 経路:環境内から http://node-app/...(アプリ名)でアクセス(P2P 影響が出る“環境内”経路)
  • 応答サイズ:約 1KB(bytes/1024)
  • スケール条件:min/max を 1固定と3固定で比較
  • P2P:OFF / ON を切り替え
  • 観測指標:Success率、503発生、throughput(実効RPS)、latency(p95/p99)、wait(攻撃後に処理が掃けるまでの時間)

Replica Count = 1 (min/max =1)

まずはターゲットレプリカ数は 1 で固定とします。

結果サマリ

A. **min=max=1(レプリカ1固定)**での OFF vs ON
✅ P2P OFF(安定)

rate=1000 でも Success 100% / 503 なし
throughput もほぼレートに追従(約 997 rps)
高負荷でも「処理しきれずに落ちる」挙動は出ていない(テールは伸びるが耐える)

❌ P2P ON(高負荷で不安定化)

rate=800 までは Success 100%
rate=850 で突然崩壊(Success 55%前後、503大量、waitが約29秒級に肥大化)
900/950/1000 でも 503が大量継続し、実効 throughput が大幅低下(数百rps)
node-app が 正常性プローブに応答できず再起動(=アプリ側も余力を失っていた)

👉 解釈:
レプリカ1固定では、P2P ON により「高負荷での余力」が削られ、待ち(wait/テールレイテンシ)が増え、ある点から 503 と再起動を伴って最大スループットが実質的に低下しました。
→ ドキュメントの「高負荷で待機時間増+最大スループット低下の可能性」を 強く裏付けます。

P2P Encription OFF

for r in 200 400 600 800 1000; do
  echo "== P2P OFF | rate=$r =="
  echo "GET http://node-app/misc/response/bytes/1024" \
  | vegeta attack -rate=$r -duration=30s -connections=500 -timeout=60s \
  | tee "p2pOFF_rate${r}.bin" \
  | vegeta report | tee "p2pOFF_rate${r}.txt"
  sleep 60
done
== P2P OFF | rate=200 ==
Requests      [total, rate, throughput]         6000, 200.03, 200.01
Duration      [total, attack, wait]             29.999s, 29.996s, 3.06ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.766ms, 15.155ms, 3.418ms, 5.017ms, 21.951ms, 392.743ms, 512.294ms
Bytes In      [total, mean]                     6288000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:6000
Error Set:
== P2P OFF | rate=400 ==
Requests      [total, rate, throughput]         12000, 400.03, 399.99
Duration      [total, attack, wait]             30.001s, 29.997s, 3.579ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.578ms, 30.881ms, 5.004ms, 38.575ms, 102.352ms, 597.963ms, 784.483ms
Bytes In      [total, mean]                     12576000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:12000
Error Set:
== P2P OFF | rate=600 ==
Requests      [total, rate, throughput]         18000, 600.04, 599.66
Duration      [total, attack, wait]             30.017s, 29.998s, 19.285ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.595ms, 70.526ms, 12.371ms, 100.657ms, 617.169ms, 797.208ms, 970.544ms
Bytes In      [total, mean]                     18864000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:18000
Error Set:
== P2P OFF | rate=800 ==
Requests      [total, rate, throughput]         23988, 797.92, 797.79
Duration      [total, attack, wait]             30.068s, 30.063s, 4.779ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.626ms, 110.843ms, 84.151ms, 123.449ms, 720.002ms, 901.831ms, 1.098s
Bytes In      [total, mean]                     25139424, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:23988
Error Set:
== P2P OFF | rate=1000 ==
Requests      [total, rate, throughput]         30000, 1000.03, 996.92
Duration      [total, attack, wait]             30.093s, 29.999s, 93.557ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.834ms, 229.28ms, 100.854ms, 744.124ms, 1.238s, 1.623s, 2.101s
Bytes In      [total, mean]                     31440000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:30000
Error Set:

P2P Encryption ON

for r in 200 400 600 800 1000; do
  echo "== P2P ON  | rate=$r =="
  echo "GET http://node-app/misc/response/bytes/1024" \
  | vegeta attack -rate=$r -duration=30s -connections=500 -timeout=60s \
  | tee "p2pON_rate${r}.bin" \
  | vegeta report | tee "p2pON_rate${r}.txt"
  sleep 60
done
== P2P ON  | rate=200 ==
Requests      [total, rate, throughput]         6000, 200.03, 200.02
Duration      [total, attack, wait]             29.998s, 29.995s, 2.118ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.509ms, 46.483ms, 2.343ms, 8.931ms, 527.538ms, 870.207ms, 1.016s
Bytes In      [total, mean]                     6288000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:6000
Error Set:
== P2P ON  | rate=400 ==
Requests      [total, rate, throughput]         12000, 400.03, 400.01
Duration      [total, attack, wait]             30s, 29.998s, 1.822ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.221ms, 50.724ms, 2.194ms, 33.662ms, 665.859ms, 858.394ms, 929.849ms
Bytes In      [total, mean]                     12576000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:12000
Error Set:
== P2P ON  | rate=600 ==
Requests      [total, rate, throughput]         18000, 600.04, 599.84
Duration      [total, attack, wait]             30.008s, 29.998s, 10.285ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.121ms, 62.491ms, 8.461ms, 60.879ms, 100.304ms, 1.304s, 1.599s
Bytes In      [total, mean]                     18864000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:18000
Error Set:
== P2P ON  | rate=800 ==
Requests      [total, rate, throughput]         24000, 800.06, 766.56
Duration      [total, attack, wait]             31.294s, 29.998s, 1.297s
Latencies     [min, mean, 50, 90, 95, 99, max]  1.22ms, 239.447ms, 22.184ms, 641.823ms, 1.56s, 3.506s, 3.998s
Bytes In      [total, mean]                     25141776, 1047.57
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           99.95%
Status Codes  [code:count]                      200:23989  503:11
Error Set:
503 Service Unavailable
== P2P ON  | rate=1000 ==
Requests      [total, rate, throughput]         30000, 1000.04, 275.89
Duration      [total, attack, wait]             1m3s, 29.999s, 33.244s
Latencies     [min, mean, 50, 90, 95, 99, max]  1.392ms, 1.882s, 1.361s, 2.726s, 3.786s, 24.156s, 55.745s
Bytes In      [total, mean]                     19779629, 659.32
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           58.16%
Status Codes  [code:count]                      200:17448  503:12552
Error Set:
503 Service Unavailable
Copilot による解説
  1. 結果サマリ(あなたの 1 回スイープ)
    ✅ P2P OFF(全レートで安定)

    Success 100%(200〜1000 全て 503 なし)
    Throughput ≈ Rate(200→200 / 400→400 / … / 1000→997)
    Rate を上げるほど レイテンシのテールが伸びる(p95/p99 が顕著に増加)

    ⚠️ P2P ON(800 から兆候、1000 で崩壊)

    200/400/600 は Success 100%
    800:Success 99.95%(503=11)、Throughput 766 rps、wait 1.297s
    1000:Success 58.16%(503=12552)、Throughput 275 rps、wait 33.244s(攻撃終了後も処理が掃けない)

    ON の 1000 は完全に 飽和して崩れた状態です。
    wait が 33 秒というのが決定的で、「30 秒間の入力に対して捌き切れず、キュー(処理待ち)が残っている」ことを示します。

  2. 重要ポイント:Throughput が落ちると “Bytes mean” も落ちる理由
    P2P ON の 1000 で Bytes In [mean] が 1048 → 659 に落ちていますよね。
    これは 503 応答が 200 応答より小さいため、成功率が下がるほど平均バイト数が減る、という挙動です。
    つまり ON の 1000 は **「処理できない→503 を返して早期に落としている」**状態で、単に遅いのではなく “拒否” が混ざっています。

  3. 「どこが限界か」を 2 種類の SLO で言語化(ブログ向け)
    ブログにするなら、同じデータでも **評価基準(SLO)**を置くと一気に説得力が増します。
    A) 成功率基準(例:Success ≥ 99.9%)

    P2P OFF:1000 まで OK(Success 100%)
    P2P ON:800 はギリOK(99.95%)、1000 は NG(58.16%)
    → P2P ON の安定上限は 800〜1000 の間

    B) レイテンシ基準も入れる(例:p95 < 1s)

    OFF 800 の p95 = 720ms → OK
    ON 800 の p95 = 1.56s → NG
    ON 600 の p95 = 100ms → OK

    → “成功率はOKでもレイテンシSLOではNG” という形で、
    P2P ON は 800 で既にテールが悪化している、と書けます。

  4. 何が起きているか(技術的な解釈)
    前提:min=max=1(単一レプリカ)、かつ P2P は Envoy↔Envoy の TLSを増やします。
    P2P ON で 800→1000 で崩れる理由として自然なのは:

    Envoy 間 TLS による CPU/暗号処理コスト増
    接続管理(TLS セッション確立・再利用・バッファ)が増えて プロキシ側が先に飽和
    飽和すると Envoy が upstream を守るために 503 を返し始める(いわゆる過負荷遮断)
    その結果、throughput が rate に追いつかず wait が伸びる(キューが掃けない)

    ここがポイントで、アプリは 80 のままでも、P2P ON で増えるコストは “プロキシ側” に乗ります。
    1 レプリカ固定だと、この追加コストがそのまま限界点低下として表れやすいです。

  5. 次にやると “結論が固まる” 追加実験(おすすめ順)
    ✅ (1) 境界を細かく刻む(最優先)
    いま分かったのは「ON は 800 は概ねOK、1000は崩壊」です。
    境界を確定させるために 850 / 900 / 950 を追加してください。

for r in 200 600 800 850 900 950 1000; do
  echo "== P2P ON | rate=$r =="
  echo "GET http://node-app/misc/response/bytes/1024" \
  | vegeta attack -rate=$r -duration=30s -connections=500 -timeout=60s \
  | tee "p2pON_rate${r}.bin" \
  | vegeta report | tee "p2pON_rate${r}.txt"
  sleep 60
done
== P2P ON | rate=200 ==
Requests      [total, rate, throughput]         6000, 200.03, 200.02
Duration      [total, attack, wait]             29.997s, 29.995s, 1.969ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.328ms, 31.974ms, 2.001ms, 4.39ms, 284.911ms, 680.216ms, 957.357ms
Bytes In      [total, mean]                     6288000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:6000
Error Set:
== P2P ON | rate=600 ==
Requests      [total, rate, throughput]         18000, 600.04, 599.80
Duration      [total, attack, wait]             30.01s, 29.998s, 12.36ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.181ms, 52.625ms, 9.887ms, 78.406ms, 100.362ms, 1.092s, 1.248s
Bytes In      [total, mean]                     18864000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:18000
Error Set:
== P2P ON | rate=800 ==
Requests      [total, rate, throughput]         24000, 800.04, 797.67
Duration      [total, attack, wait]             30.088s, 29.999s, 89.092ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.364ms, 150.07ms, 34.79ms, 111.632ms, 930.548ms, 2.707s, 3.296s
Bytes In      [total, mean]                     25152000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:24000
Error Set:
== P2P ON | rate=850 ==
Requests      [total, rate, throughput]         25500, 850.01, 239.54
Duration      [total, attack, wait]             58.964s, 30s, 28.964s
Latencies     [min, mean, 50, 90, 95, 99, max]  3.867ms, 2.096s, 1.306s, 2.591s, 3.353s, 27.707s, 56.266s
Bytes In      [total, mean]                     16156565, 633.59
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           55.39%
Status Codes  [code:count]                      200:14124  503:11376
Error Set:
503 Service Unavailable
== P2P ON | rate=900 ==
Requests      [total, rate, throughput]         27000, 900.03, 448.65
Duration      [total, attack, wait]             36.18s, 29.999s, 6.181s
Latencies     [min, mean, 50, 90, 95, 99, max]  1.193ms, 1.868s, 1.173s, 2.26s, 2.861s, 27.918s, 29.997s
Bytes In      [total, mean]                     18296461, 677.65
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           60.12%
Status Codes  [code:count]                      200:16232  503:10768
Error Set:
503 Service Unavailable
== P2P ON | rate=950 ==
Requests      [total, rate, throughput]         28500, 950.02, 322.34
Duration      [total, attack, wait]             52.672s, 29.999s, 22.672s
Latencies     [min, mean, 50, 90, 95, 99, max]  2.146ms, 2.07s, 1.492s, 2.644s, 3.266s, 25.839s, 49.761s
Bytes In      [total, mean]                     19176087, 672.85
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           59.57%
Status Codes  [code:count]                      200:16978  503:11522
Error Set:
503 Service Unavailable
== P2P ON | rate=1000 ==
Requests      [total, rate, throughput]         29978, 999.05, 398.59
Duration      [total, attack, wait]             42.382s, 30.007s, 12.375s
Latencies     [min, mean, 50, 90, 95, 99, max]  1.222ms, 1.831s, 1.291s, 2.444s, 3.151s, 25.173s, 29.946s
Bytes In      [total, mean]                     19267961, 642.74
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           56.35%
Status Codes  [code:count]                      200:16893  503:13085
Error Set:
503 Service Unavailable

Replica Count = 3 (min/max =3)

結果サマリ

✅ 200〜1000 rps 領域(成功率はどちらも 100%)

P2P OFF:rate=1000 で Success 100%、p95 ≈ 105ms、p99 ≈ 506ms(max ≈ 798ms)
P2P ON:rate=1000 で Success 100%、p95 ≈ 515ms、p99 ≈ 1529ms(max ≈ 2095ms)

→ 成功率/throughput は同等なのに、P2P ON は p95 が約 4.9倍(1000 rps 時)で、テールが明確に悪化しています。
✅ より高負荷(1500〜2400 指定付近)での傾向

この領域では、vegeta の「実際に送れた rate」が落ちたり(クライアント側も影響)、throughput が 約 1.1k〜1.35k rps あたりで頭打ちになっています(OFF/ONともに似た水準)。
ただし、待機時間(レイテンシ)は秒台(p95 2〜3s)へ上がりやすい。
P2P ON では一部条件で 503 が混じることもありました(例:2000で503が少数発生)。

❌ さらに攻めた領域(2500 指定)

P2P ON:Success 68%・503大量(11981)で崩壊
P2P OFF:この run では Success 100% を維持(ただし throughput は 1087 rps まで落ち、遅延は上昇)

→ “最大スループット低下” というより ON の方が高負荷域で不安定化(503が出やすい) が見えます。

P2P Encryption OFF

for r in 200 600 800 850 900 950 1000 1500 2000 2400 2500; do
  echo "== P2P OFF min=max=3 | rate=$r =="
  echo "GET http://node-app/misc/response/bytes/1024" \
  | vegeta attack -rate=$r -duration=30s -connections=500 -timeout=60s \
  | tee "p2pOFF_rate${r}.bin" \
  | vegeta report | tee "p2pOFF_rate${r}.txt"
  sleep 60
done
== P2P OFF min=max=3 | rate=200 ==
Requests      [total, rate, throughput]         6000, 200.03, 200.01
Duration      [total, attack, wait]             29.998s, 29.996s, 2.371ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.357ms, 6.418ms, 2.035ms, 2.757ms, 4.046ms, 201.721ms, 564.908ms
Bytes In      [total, mean]                     6288000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:6000
Error Set:
== P2P OFF min=max=3 | rate=600 ==
Requests      [total, rate, throughput]         18000, 600.04, 599.87
Duration      [total, attack, wait]             30.007s, 29.998s, 8.594ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.157ms, 32.638ms, 4.636ms, 49.246ms, 103.094ms, 716.37ms, 1.082s
Bytes In      [total, mean]                     18864000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:18000
Error Set:
== P2P OFF min=max=3 | rate=800 ==
Requests      [total, rate, throughput]         24000, 800.06, 799.93
Duration      [total, attack, wait]             30.003s, 29.998s, 4.649ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.128ms, 40.898ms, 7.512ms, 99.742ms, 110.912ms, 601.832ms, 877.693ms
Bytes In      [total, mean]                     25152000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:24000
Error Set:
== P2P OFF min=max=3 | rate=850 ==
Requests      [total, rate, throughput]         25501, 850.03, 847.41
Duration      [total, attack, wait]             30.093s, 30s, 92.923ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.081ms, 29.081ms, 6.831ms, 95.4ms, 100.578ms, 537.216ms, 883.05ms
Bytes In      [total, mean]                     26725048, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:25501
Error Set:
== P2P OFF min=max=3 | rate=900 ==
Requests      [total, rate, throughput]         27000, 900.00, 897.22
Duration      [total, attack, wait]             30.093s, 30s, 92.892ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.084ms, 25.92ms, 7.207ms, 97.762ms, 100.415ms, 328.1ms, 804.031ms
Bytes In      [total, mean]                     28296000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:27000
Error Set:
== P2P OFF min=max=3 | rate=950 ==
Requests      [total, rate, throughput]         28500, 950.04, 947.07
Duration      [total, attack, wait]             30.093s, 29.999s, 94.14ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.113ms, 55.121ms, 8.411ms, 100.912ms, 170.275ms, 697.727ms, 981.401ms
Bytes In      [total, mean]                     29868000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:28500
Error Set:
== P2P OFF min=max=3 | rate=1000 ==
Requests      [total, rate, throughput]         30000, 1000.05, 997.00
Duration      [total, attack, wait]             30.09s, 29.998s, 92.047ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.082ms, 50.246ms, 9.33ms, 101.234ms, 104.625ms, 505.986ms, 797.85ms
Bytes In      [total, mean]                     31440000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:30000
Error Set:
== P2P OFF min=max=3 | rate=1500 ==
Requests      [total, rate, throughput]         37579, 1189.40, 1156.45
Duration      [total, attack, wait]             32.495s, 31.595s, 900.036ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.34ms, 973.954ms, 708.491ms, 2.135s, 2.614s, 3.364s, 4.791s
Bytes In      [total, mean]                     39382792, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:37579
Error Set:
== P2P OFF min=max=3 | rate=2000 ==
Requests      [total, rate, throughput]         38487, 1270.10, 1245.34
Duration      [total, attack, wait]             30.905s, 30.302s, 602.641ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.284ms, 1.113s, 890.641ms, 2.364s, 2.932s, 3.562s, 5.798s
Bytes In      [total, mean]                     40334376, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:38487
Error Set:
== P2P OFF min=max=3 | rate=2400 ==
Requests      [total, rate, throughput]         40387, 1341.76, 1328.76
Duration      [total, attack, wait]             30.394s, 30.1s, 294.453ms
Latencies     [min, mean, 50, 90, 95, 99, max]  2.432ms, 676.901ms, 501.361ms, 1.399s, 1.75s, 2.098s, 2.79s
Bytes In      [total, mean]                     42325576, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:40387
Error Set:
== P2P OFF min=max=3 | rate=2500 ==
Requests      [total, rate, throughput]         33580, 1115.89, 1087.04
Duration      [total, attack, wait]             30.891s, 30.093s, 798.55ms
Latencies     [min, mean, 50, 90, 95, 99, max]  3.475ms, 1.223s, 1.117s, 2.288s, 2.481s, 3.143s, 3.803s
Bytes In      [total, mean]                     35191840, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:33580
Error Set:

P2P Encryption ON

for r in 200 600 800 850 900 950 1000 1500 2000 2400 2500; do
  echo "== P2P ON | rate=$r =="
  echo "GET http://node-app/misc/response/bytes/1024" \
  | vegeta attack -rate=$r -duration=30s -connections=500 -timeout=60s \
  | tee "p2pON_rate${r}.bin" \
  | vegeta report | tee "p2pON_rate${r}.txt"
  sleep 60
done
== P2P ON | rate=200 ==
Requests      [total, rate, throughput]         6000, 200.03, 200.01
Duration      [total, attack, wait]             29.998s, 29.995s, 2.844ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.612ms, 8.508ms, 2.654ms, 3.633ms, 5.296ms, 240.284ms, 608.754ms
Bytes In      [total, mean]                     6288000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:6000
Error Set:
== P2P ON | rate=600 ==
Requests      [total, rate, throughput]         18000, 600.04, 599.95
Duration      [total, attack, wait]             30.003s, 29.998s, 4.758ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.243ms, 57.934ms, 5.802ms, 83.229ms, 526.809ms, 990.908ms, 1.3s
Bytes In      [total, mean]                     18864000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:18000
Error Set:
== P2P ON | rate=800 ==
Requests      [total, rate, throughput]         24000, 800.05, 799.97
Duration      [total, attack, wait]             30.001s, 29.998s, 3.269ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.298ms, 75.065ms, 8.319ms, 101.431ms, 560.743ms, 1.055s, 1.602s
Bytes In      [total, mean]                     25152000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:24000
Error Set:
== P2P ON | rate=850 ==
Requests      [total, rate, throughput]         25500, 850.03, 849.75
Duration      [total, attack, wait]             30.009s, 29.999s, 9.792ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.267ms, 52.311ms, 8.321ms, 100.336ms, 113.035ms, 807.267ms, 1.109s
Bytes In      [total, mean]                     26724000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:25500
Error Set:
== P2P ON | rate=900 ==
Requests      [total, rate, throughput]         27000, 900.03, 897.45
Duration      [total, attack, wait]             30.085s, 29.999s, 86.409ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.206ms, 74.297ms, 9.377ms, 102.474ms, 429.619ms, 983.735ms, 1.686s
Bytes In      [total, mean]                     28296000, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:27000
Error Set:
== P2P ON | rate=950 ==
Requests      [total, rate, throughput]         28501, 950.04, 949.95
Duration      [total, attack, wait]             30.003s, 30s, 2.653ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.219ms, 81.67ms, 11.426ms, 102.866ms, 316.991ms, 909.911ms, 1.303s
Bytes In      [total, mean]                     29869048, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:28501
Error Set:
== P2P ON | rate=1000 ==
Requests      [total, rate, throughput]         29998, 996.83, 996.43
Duration      [total, attack, wait]             30.105s, 30.094s, 11.984ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.309ms, 118.728ms, 90.398ms, 200.712ms, 515.415ms, 1.529s, 2.095s
Bytes In      [total, mean]                     31437904, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:29998
Error Set:
== P2P ON | rate=1500 ==
Requests      [total, rate, throughput]         40889, 1357.73, 1260.32
Duration      [total, attack, wait]             32.418s, 30.116s, 2.302s
Latencies     [min, mean, 50, 90, 95, 99, max]  1.85ms, 1.005s, 799.669ms, 1.998s, 2.609s, 3.765s, 5.598s
Bytes In      [total, mean]                     42821791, 1047.27
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           99.92%
Status Codes  [code:count]                      200:40857  503:32
Error Set:
503 Service Unavailable
== P2P ON | rate=2000 ==
Requests      [total, rate, throughput]         41945, 1393.75, 1313.92
Duration      [total, attack, wait]             31.899s, 30.095s, 1.804s
Latencies     [min, mean, 50, 90, 95, 99, max]  3.713ms, 1.07s, 800.896ms, 2.31s, 2.818s, 3.881s, 6.6s
Bytes In      [total, mean]                     43928472, 1047.29
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           99.92%
Status Codes  [code:count]                      200:41913  503:32
Error Set:
503 Service Unavailable
== P2P ON | rate=2400 ==
Requests      [total, rate, throughput]         41468, 1368.63, 1346.62
Duration      [total, attack, wait]             30.794s, 30.299s, 495.113ms
Latencies     [min, mean, 50, 90, 95, 99, max]  1.737ms, 740.83ms, 502.925ms, 1.698s, 2.003s, 2.94s, 4.301s
Bytes In      [total, mean]                     43458464, 1048.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:41468
Error Set:
== P2P ON | rate=2500 ==
Requests      [total, rate, throughput]         38041, 1231.05, 748.77
Duration      [total, attack, wait]             34.804s, 30.901s, 3.903s
Latencies     [min, mean, 50, 90, 95, 99, max]  1.837ms, 3.395s, 2.791s, 6.644s, 8.089s, 14.574s, 24.303s
Bytes In      [total, mean]                     28689921, 754.18
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           68.51%
Status Codes  [code:count]                      200:26060  503:11981
Error Set:
503 Service Unavailable
Copilot による解説抜粋

⚠️ 1500〜2400 指定では「指定レートを維持できず、実効1.3k付近で頭打ち」
あなたの出力(要点):

-rate=1500 → report上 rate=1357.73, throughput=1260.32、p95=2.6s、503=32
-rate=2000 → report上 rate=1393.75, throughput=1313.92、p95=2.8s、503=32
-rate=2400 → report上 rate=1368.63, throughput=1346.62、p95=2.0s、503=0

ここで大事なのは、1500/2000/2400 と指定を上げても、実測の “rate(送信レート)” が約 1.36〜1.39k に張り付いている点です。
= サーバというより、往復遅延や接続制約により、クライアントがそれ以上の一定レート送信を維持できていない可能性が高いです(or サービスがその辺りで飽和してレイテンシが増え、結果として送れなくなる)。
この領域では “最大処理能力(キャパ)”が約 1.35k rps 付近に見える、というのが読み取りになります。

for c in 500 1000 2000; do
  echo "== P2P ON min=max=3 | targetRate=2000 | connections=$c =="
  echo "GET http://node-app/misc/response/bytes/1024" \
  | vegeta attack -rate=2000 -duration=30s -connections=$c -timeout=60s \
  | tee "p2pON_r2000_c${c}.bin" \
  | vegeta report | tee "p2pON_r2000_c${c}.txt"
  sleep 90
done
== P2P ON min=max=3 | targetRate=2000 | connections=500 ==
Requests      [total, rate, throughput]         40958, 1364.03, 1331.12
Duration      [total, attack, wait]             30.735s, 30.027s, 707.731ms
Latencies     [min, mean, 50, 90, 95, 99, max]  3.418ms, 901.432ms, 701.628ms, 1.799s, 2.21s, 3.309s, 5.5s
Bytes In      [total, mean]                     42881020, 1046.95
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           99.89%
Status Codes  [code:count]                      200:40912  503:46
Error Set:
503 Service Unavailable
== P2P ON min=max=3 | targetRate=2000 | connections=1000 ==
Requests      [total, rate, throughput]         39042, 1301.57, 1223.63
Duration      [total, attack, wait]             31.901s, 29.996s, 1.905s
Latencies     [min, mean, 50, 90, 95, 99, max]  2.516ms, 2.107s, 1.812s, 4.237s, 4.91s, 6.04s, 8.098s
Bytes In      [total, mean]                     40909490, 1047.83
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           99.98%
Status Codes  [code:count]                      200:39035  503:7
Error Set:
503 Service Unavailable
== P2P ON min=max=3 | targetRate=2000 | connections=2000 ==
Requests      [total, rate, throughput]         48916, 1620.05, 407.52
Duration      [total, attack, wait]             49.696s, 30.194s, 19.502s
Latencies     [min, mean, 50, 90, 95, 99, max]  3.615ms, 2.879s, 2.195s, 4.513s, 6.281s, 21.668s, 39.709s
Bytes In      [total, mean]                     24531446, 501.50
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           41.40%
Status Codes  [code:count]                      200:20252  503:28664
Error Set:
503 Service Unavailable

503 の要因

vegeta encode p2pON_r2000_c2000.bin | grep '"code":503' | head -n 10
{"attack":"","seq":1254,"code":503,"timestamp":"2026-01-19T06:59:52.700218003Z","latency":1290149538,"bytes_out":0,"bytes_in":114,"error":"503 Service Unavailable","body":"dXBzdHJlYW0gY29ubmVjdCBlcnJvciBvciBkaXNjb25uZWN0L3Jlc2V0IGJlZm9yZSBoZWFkZXJzLiByZXRyaWVkIGFuZCB0aGUgbGF0ZXN0IHJlc2V0IHJlYXNvbjogY29ubmVjdGlvbiB0aW1lb3V0","method":"GET","url":"http://node-app/misc/response/bytes/1024","headers":{"Content-Length":["114"],"Content-Type":["text/plain"],"Date":["Mon, 19 Jan 2026 06:59:53 GMT"]}}

body を base64 decode すると

upstream connect error or disconnect/reset before headers. retried and the latest reset reason: connection timeout

Discussion