<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Load-Test on Tarragon</title><link>https://tarrragon.github.io/blog/tags/load-test/</link><description>Recent content in Load-Test on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 23 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/load-test/index.xml" rel="self" type="application/rss+xml"/><item><title>k6：Threshold CI Gate 與 Scenario 設計</title><link>https://tarrragon.github.io/blog/backend/06-reliability/vendors/k6/threshold-ci-gate-and-scenario-design/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/vendors/k6/threshold-ci-gate-and-scenario-design/</guid><description>&lt;h2 id="問題情境">問題情境&lt;/h2>
&lt;p>Load test 跑完會產生大量指標，但 CI pipeline 需要的是 pass/fail 訊號。若沒有 threshold 把指標轉成判讀結論，效能退化只能靠人工看 dashboard 發現，等到看見時通常已經累積數個版本。&lt;/p>
&lt;p>另一面，threshold 的判讀品質取決於 workload model 的真實度。用 &lt;code>--vus 10 --duration 30s&lt;/code> 跑出來的結果跟 production 流量結構差距太大時，threshold 通過也無法證明 production 安全。&lt;/p>
&lt;p>這篇處理兩個問題：怎麼設 threshold 讓 CI gate 可靠，怎麼設 scenario 讓 workload 接近真實。&lt;/p>
&lt;h2 id="threshold-設計">Threshold 設計&lt;/h2>
&lt;p>Threshold 的責任是把 load test 指標轉成 CI 的 pass/fail 訊號。k6 在所有 threshold 都通過時回傳 exit code 0，任一 threshold 失敗就回傳非零 — CI pipeline 直接用 exit code 判斷。&lt;/p>
&lt;h3 id="多指標-threshold">多指標 threshold&lt;/h3>
&lt;p>單一指標 threshold 容易漏風險。latency 正常但 error rate 偏高代表系統在丟請求；throughput 正常但 latency 偏高代表排隊開始堆積。完整的 threshold 至少涵蓋三個面向：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="nx">thresholds&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="nx">http_req_duration&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;p(95)&amp;lt;500&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;p(99)&amp;lt;1000&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">http_req_failed&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;rate&amp;lt;0.01&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nx">http_reqs&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;rate&amp;gt;100&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="p">};&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>latency threshold 用 percentile 而不是 average — average 會被長尾稀釋，p95/p99 更接近使用者感知的最差體驗。&lt;/p>
&lt;h3 id="門檻來源">門檻來源&lt;/h3>
&lt;p>Threshold 的門檻從 production baseline 出發。先從 observability 系統（Grafana / Datadog）取最近 7-30 天的 p95/p99 latency 與 error rate，加上可接受退化幅度（通常 10-20%）作為 threshold。門檻太緊會讓 CI 環境噪音觸發 false positive；門檻太寬會讓真退化滑過去。&lt;/p>
&lt;p>校準節奏：每月或每次重大架構變更後重新對齊 production baseline，避免 threshold 跟真實系統漂移。&lt;/p>
&lt;h3 id="path-level-threshold">Path-level threshold&lt;/h3>
&lt;p>不同 API path 的效能特徵不同。checkout 路徑的 latency 容忍度可能比 listing 路徑低很多。k6 的 group + tag 機制讓 threshold 可以按 path 設定：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">group&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="nx">from&lt;/span> &lt;span class="s1">&amp;#39;k6&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nx">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;checkout&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="c1">// checkout 請求
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="nx">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;listing&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1">// listing 請求
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="kr">const&lt;/span> &lt;span class="nx">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="nx">thresholds&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;http_req_duration{group:::checkout}&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;p(95)&amp;lt;300&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;http_req_duration{group:::listing}&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;p(95)&amp;lt;800&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="p">};&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>path-level threshold 讓 gate 的判讀粒度從「整體效能」細化到「關鍵路徑效能」。&lt;/p>
&lt;h2 id="scenario-設計">Scenario 設計&lt;/h2>
&lt;p>Scenario 的責任是讓壓測的流量結構接近 production。k6 提供五種 scenario executor，選擇取決於要控制什麼變量。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Executor&lt;/th>
 &lt;th>控制變量&lt;/th>
 &lt;th>適用場景&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>constant-vus&lt;/td>
 &lt;td>並發使用者數&lt;/td>
 &lt;td>簡單 smoke test&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ramping-vus&lt;/td>
 &lt;td>並發使用者數&lt;/td>
 &lt;td>階梯式升壓找 saturation&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>constant-arrival-rate&lt;/td>
 &lt;td>固定 RPS&lt;/td>
 &lt;td>CI regression（穩定輸入）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ramping-arrival-rate&lt;/td>
 &lt;td>變化 RPS&lt;/td>
 &lt;td>模擬 production peak/off-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>externally-controlled&lt;/td>
 &lt;td>外部 API&lt;/td>
 &lt;td>結合 production 流量 replay&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="executor-選擇判準">Executor 選擇判準&lt;/h3>
&lt;p>constant-vus 最簡單，但 throughput 會隨 response time 波動 — 伺服器變慢時 RPS 自動下降，掩蓋了真正的壓力。constant-arrival-rate 控制 RPS 穩定，能讓 threshold 的判讀基準一致，但需要設定足夠的 preAllocatedVUs 避免 k6 因為 VU 不足而主動降速。&lt;/p></description><content:encoded><![CDATA[<h2 id="問題情境">問題情境</h2>
<p>Load test 跑完會產生大量指標，但 CI pipeline 需要的是 pass/fail 訊號。若沒有 threshold 把指標轉成判讀結論，效能退化只能靠人工看 dashboard 發現，等到看見時通常已經累積數個版本。</p>
<p>另一面，threshold 的判讀品質取決於 workload model 的真實度。用 <code>--vus 10 --duration 30s</code> 跑出來的結果跟 production 流量結構差距太大時，threshold 通過也無法證明 production 安全。</p>
<p>這篇處理兩個問題：怎麼設 threshold 讓 CI gate 可靠，怎麼設 scenario 讓 workload 接近真實。</p>
<h2 id="threshold-設計">Threshold 設計</h2>
<p>Threshold 的責任是把 load test 指標轉成 CI 的 pass/fail 訊號。k6 在所有 threshold 都通過時回傳 exit code 0，任一 threshold 失敗就回傳非零 — CI pipeline 直接用 exit code 判斷。</p>
<h3 id="多指標-threshold">多指標 threshold</h3>
<p>單一指標 threshold 容易漏風險。latency 正常但 error rate 偏高代表系統在丟請求；throughput 正常但 latency 偏高代表排隊開始堆積。完整的 threshold 至少涵蓋三個面向：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">thresholds</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">http_req_duration</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;p(95)&lt;500&#39;</span><span class="p">,</span> <span class="s1">&#39;p(99)&lt;1000&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">http_req_failed</span><span class="o">:</span>   <span class="p">[</span><span class="s1">&#39;rate&lt;0.01&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">http_reqs</span><span class="o">:</span>         <span class="p">[</span><span class="s1">&#39;rate&gt;100&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>latency threshold 用 percentile 而不是 average — average 會被長尾稀釋，p95/p99 更接近使用者感知的最差體驗。</p>
<h3 id="門檻來源">門檻來源</h3>
<p>Threshold 的門檻從 production baseline 出發。先從 observability 系統（Grafana / Datadog）取最近 7-30 天的 p95/p99 latency 與 error rate，加上可接受退化幅度（通常 10-20%）作為 threshold。門檻太緊會讓 CI 環境噪音觸發 false positive；門檻太寬會讓真退化滑過去。</p>
<p>校準節奏：每月或每次重大架構變更後重新對齊 production baseline，避免 threshold 跟真實系統漂移。</p>
<h3 id="path-level-threshold">Path-level threshold</h3>
<p>不同 API path 的效能特徵不同。checkout 路徑的 latency 容忍度可能比 listing 路徑低很多。k6 的 group + tag 機制讓 threshold 可以按 path 設定：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">group</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;k6&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nx">group</span><span class="p">(</span><span class="s1">&#39;checkout&#39;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1">// checkout 請求
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="p">});</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="nx">group</span><span class="p">(</span><span class="s1">&#39;listing&#39;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1">// listing 請求
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>  <span class="p">});</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="nx">thresholds</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s1">&#39;http_req_duration{group:::checkout}&#39;</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;p(95)&lt;300&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s1">&#39;http_req_duration{group:::listing}&#39;</span><span class="o">:</span>  <span class="p">[</span><span class="s1">&#39;p(95)&lt;800&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>path-level threshold 讓 gate 的判讀粒度從「整體效能」細化到「關鍵路徑效能」。</p>
<h2 id="scenario-設計">Scenario 設計</h2>
<p>Scenario 的責任是讓壓測的流量結構接近 production。k6 提供五種 scenario executor，選擇取決於要控制什麼變量。</p>
<table>
  <thead>
      <tr>
          <th>Executor</th>
          <th>控制變量</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>constant-vus</td>
          <td>並發使用者數</td>
          <td>簡單 smoke test</td>
      </tr>
      <tr>
          <td>ramping-vus</td>
          <td>並發使用者數</td>
          <td>階梯式升壓找 saturation</td>
      </tr>
      <tr>
          <td>constant-arrival-rate</td>
          <td>固定 RPS</td>
          <td>CI regression（穩定輸入）</td>
      </tr>
      <tr>
          <td>ramping-arrival-rate</td>
          <td>變化 RPS</td>
          <td>模擬 production peak/off-peak</td>
      </tr>
      <tr>
          <td>externally-controlled</td>
          <td>外部 API</td>
          <td>結合 production 流量 replay</td>
      </tr>
  </tbody>
</table>
<h3 id="executor-選擇判準">Executor 選擇判準</h3>
<p>constant-vus 最簡單，但 throughput 會隨 response time 波動 — 伺服器變慢時 RPS 自動下降，掩蓋了真正的壓力。constant-arrival-rate 控制 RPS 穩定，能讓 threshold 的判讀基準一致，但需要設定足夠的 preAllocatedVUs 避免 k6 因為 VU 不足而主動降速。</p>
<p>CI regression 測試建議用 constant-arrival-rate：輸入固定、輸出可比較、版本間的差異才有意義。</p>
<h3 id="production-traffic-shape-對齊">Production traffic shape 對齊</h3>
<p>用 ramping-arrival-rate 模擬 production 的流量形狀：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nx">scenarios</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">peak_simulation</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">      <span class="nx">executor</span><span class="o">:</span> <span class="s1">&#39;ramping-arrival-rate&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">      <span class="nx">startRate</span><span class="o">:</span> <span class="mi">50</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="nx">stages</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="p">{</span> <span class="nx">target</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span> <span class="nx">duration</span><span class="o">:</span> <span class="s1">&#39;2m&#39;</span> <span class="p">},</span>  <span class="c1">// ramp up
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>        <span class="p">{</span> <span class="nx">target</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span> <span class="nx">duration</span><span class="o">:</span> <span class="s1">&#39;5m&#39;</span> <span class="p">},</span>  <span class="c1">// sustain peak
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span>        <span class="p">{</span> <span class="nx">target</span><span class="o">:</span> <span class="mi">50</span><span class="p">,</span>  <span class="nx">duration</span><span class="o">:</span> <span class="s1">&#39;1m&#39;</span> <span class="p">},</span>  <span class="c1">// ramp down
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>      <span class="p">],</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="nx">preAllocatedVUs</span><span class="o">:</span> <span class="mi">300</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>流量形狀的參數（startRate / target / duration）從 production access log 的 peak 時段推算。Shopify 的 BFCM 準備流程把 game day 的 load test scenario 跟實際峰值形狀對齊 — 短時間爆量加高寫入比例需要特別設計 scenario 來覆蓋。</p>
<h3 id="cohort-模擬">Cohort 模擬</h3>
<p>Production 流量不是單一類型。用多 scenario 並行模擬不同 cohort：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nx">scenarios</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">read_traffic</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">      <span class="nx">executor</span><span class="o">:</span> <span class="s1">&#39;constant-arrival-rate&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">      <span class="nx">rate</span><span class="o">:</span> <span class="mi">150</span><span class="p">,</span> <span class="nx">exec</span><span class="o">:</span> <span class="s1">&#39;readFlow&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="nx">preAllocatedVUs</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="nx">duration</span><span class="o">:</span> <span class="s1">&#39;5m&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">write_traffic</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">      <span class="nx">executor</span><span class="o">:</span> <span class="s1">&#39;constant-arrival-rate&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <span class="nx">rate</span><span class="o">:</span> <span class="mi">30</span><span class="p">,</span> <span class="nx">exec</span><span class="o">:</span> <span class="s1">&#39;writeFlow&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">      <span class="nx">preAllocatedVUs</span><span class="o">:</span> <span class="mi">50</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">      <span class="nx">duration</span><span class="o">:</span> <span class="s1">&#39;5m&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">readFlow</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/* GET 請求 */</span> <span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">writeFlow</span><span class="p">()</span> <span class="p">{</span> <span class="cm">/* POST 請求 */</span> <span class="p">}</span></span></span></code></pre></div><p>讀寫比例從 production 的 access log 或 APM 資料推算。比例偏差會讓瓶頸位置失真 — 讀為主的模型抓不到寫入引起的 lock contention。</p>
<h3 id="資料驅動">資料驅動</h3>
<p>測試資料用 SharedArray 載入，避免每個 VU 各自載入造成記憶體浪費：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">SharedArray</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;k6/data&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kr">const</span> <span class="nx">users</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SharedArray</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">open</span><span class="p">(</span><span class="s1">&#39;./users.json&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p>資料來源可以是 production sample（脫敏後）或 synthetic generation。資料分佈需要接近 production — ID 範圍、key 分佈、payload 大小都會影響 query plan 與 cache 行為。</p>
<h2 id="ci-整合實務">CI 整合實務</h2>
<h3 id="fast-path每次-push">Fast path（每次 push）</h3>
<p>固定 scenario + 短 duration（30s-2min），用 constant-arrival-rate 做 regression 偵測。threshold 設在 production baseline + 10%。這一層的目的是快速攔住明顯退化，不需要模擬完整峰值。</p>
<h3 id="slow-pathmerge-gate">Slow path（merge gate）</h3>
<p>完整 scenario + 較長 duration（5-15min），包含多 cohort 與 ramping 模擬。threshold 涵蓋 path-level 指標。這一層的目的是深層驗證變更在接近真實壓力下的行為。</p>
<h3 id="結果留存">結果留存</h3>
<p>k6 結果預設輸出到 stdout。CI 整合時用 <code>--out</code> flag 把結果送到時序資料庫（InfluxDB / Prometheus Remote Write / Grafana Cloud k6），讓歷史趨勢可查詢。趨勢比較能偵測 threshold 內但持續惡化的 slow drift。</p>
<p>LinkedIn 的自動化壓測實踐把 load test 結果跟容量預測接在一起 — saturation point 隨時間的變化趨勢直接驅動擴容決策。</p>
<h2 id="邊界與陷阱">邊界與陷阱</h2>
<p><strong>Threshold variance</strong>：CI runner 的硬體差異（shared runner 的鄰居效應、network jitter、GC pause）會讓同一份 code 在不同 run 產生不同結果。控制方式：dedicated runner 消除鄰居效應、warmup iteration 丟棄前幾輪結果、多次 run 取中位數。若 variance 超過 threshold 的退化幅度，gate 判讀就不可信。</p>
<p><strong>門檻過寬或過緊</strong>：threshold 永遠通過代表 gate 形同虛設；threshold 頻繁 false positive 會讓團隊忽略 CI 結果。兩者都會讓 gate 失去判讀價值。校準的判準是：過去 30 天的 threshold 結果中，真正需要關注的退化是否都被攔住，同時 false positive 率低於 5%。</p>
<p><strong>Scenario 跟 production drift</strong>：production 的流量結構會隨產品演進改變。定期（每月或每次重大功能上線）用 access log 校準 scenario 的 RPS、cohort 比例與資料分佈，避免模型越跑越偏。</p>
<h2 id="整合路由">整合路由</h2>
<ul>
<li>上游概念：<a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load testing</a> 的 workload model 設計</li>
<li>下游能力：<a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate</a> 的 baseline 管理與退化定位</li>
<li>平行 vendor：<a href="/blog/backend/06-reliability/vendors/gatling/" data-link-title="Gatling" data-link-desc="JVM-based load test、Scala / Java / Kotlin DSL、強型別 scenario、HAR-driven recording">Gatling</a>、<a href="/blog/backend/06-reliability/vendors/locust/" data-link-title="Locust" data-link-desc="Python-based load test、distributed、易擴展">Locust</a>、<a href="/blog/backend/06-reliability/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="老牌 load test 工具、GUI &#43; plugins">JMeter</a></li>
<li>案例回寫：<a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify BFCM 容量治理</a>（game day load test 對齊峰值形狀）、<a href="/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">LinkedIn Automated Load Testing</a>（持續壓測驅動容量預測）</li>
</ul>
]]></content:encoded></item><item><title>k6</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/</guid><description>&lt;p>k6 的核心責任是把 workload model 轉成可重跑、可版本化、可接到 CI 的壓測 scenario。它適合 API、HTTP、gRPC、WebSocket 與 browser-style flow 的負載驗證，重點在用程式化腳本描述使用者行為、負載階段、threshold 與結果輸出。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>k6 是 Grafana Labs 旗下的 scriptable load testing 工具、2021 年被 Grafana 收購。產品線分兩層：&lt;em>k6 OSS&lt;/em>（Go 寫的 engine + JS API 描述 scenario、CLI 為主、output 可丟 Prometheus / InfluxDB / JSON / CSV）跟 &lt;em>Grafana Cloud k6&lt;/em>（前 k6 Cloud、SaaS 多 region runner + 結果保存 + 跟 Grafana Cloud dashboard / Loki / Tempo 同 plane）。底層 engine 是 Go、不是 JS — JS 只是 scenario 描述層、runtime 由 Go 跑、所以單機 VU 容量比 Python-based 工具高出一個量級。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter&lt;/a> 比、k6 走 &lt;em>code-first + CI-friendly&lt;/em>、JMeter 走 &lt;em>XML / GUI + plugin ecosystem&lt;/em>；JMeter 在 protocol 廣度（JDBC / LDAP / JMS / FTP）跟非工程團隊操作勝出、k6 在版控、PR review、artifact pipeline 勝出。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust&lt;/a> 比、k6 用 JS、Locust 用 Python；Locust 對 Python team 自然、但 Python GIL 讓單機 VU 容量受限、需多 worker、k6 單機可跑數千 VU。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling&lt;/a> 比、Gatling 走 JVM + Scala/Java/Kotlin DSL、適合 JVM-heavy 團隊；k6 的 threshold + Grafana ecosystem 整合在 release gate 場景更直接。&lt;/p>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>k6 適合把壓測納入工程流程。當團隊已經能描述 traffic shape、endpoint mix、arrival rate、think time 與 stop condition，k6 可以把這些模型寫成腳本，讓每次 release、capacity review 或 peak-event readiness 都能重跑同一組驗證。&lt;/p>
&lt;p>這個定位讓 k6 接到三個主章。它從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling&lt;/a> 接收流量模型，從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery&lt;/a> 接收 ramp-up 與 knee point 判讀，從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/production-validation/" data-link-title="9.10 Production-Side 驗證" data-link-desc="shadow traffic、dark launch、canary、production-like load test">9.10 Production-Side 驗證&lt;/a> 接收 canary、dark launch 或 production-like load test 的安全邊界。&lt;/p></description><content:encoded><![CDATA[<p>k6 的核心責任是把 workload model 轉成可重跑、可版本化、可接到 CI 的壓測 scenario。它適合 API、HTTP、gRPC、WebSocket 與 browser-style flow 的負載驗證，重點在用程式化腳本描述使用者行為、負載階段、threshold 與結果輸出。</p>
<h2 id="服務定位">服務定位</h2>
<p>k6 是 Grafana Labs 旗下的 scriptable load testing 工具、2021 年被 Grafana 收購。產品線分兩層：<em>k6 OSS</em>（Go 寫的 engine + JS API 描述 scenario、CLI 為主、output 可丟 Prometheus / InfluxDB / JSON / CSV）跟 <em>Grafana Cloud k6</em>（前 k6 Cloud、SaaS 多 region runner + 結果保存 + 跟 Grafana Cloud dashboard / Loki / Tempo 同 plane）。底層 engine 是 Go、不是 JS — JS 只是 scenario 描述層、runtime 由 Go 跑、所以單機 VU 容量比 Python-based 工具高出一個量級。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a> 比、k6 走 <em>code-first + CI-friendly</em>、JMeter 走 <em>XML / GUI + plugin ecosystem</em>；JMeter 在 protocol 廣度（JDBC / LDAP / JMS / FTP）跟非工程團隊操作勝出、k6 在版控、PR review、artifact pipeline 勝出。跟 <a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a> 比、k6 用 JS、Locust 用 Python；Locust 對 Python team 自然、但 Python GIL 讓單機 VU 容量受限、需多 worker、k6 單機可跑數千 VU。跟 <a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a> 比、Gatling 走 JVM + Scala/Java/Kotlin DSL、適合 JVM-heavy 團隊；k6 的 threshold + Grafana ecosystem 整合在 release gate 場景更直接。</p>
<h2 id="定位">定位</h2>
<p>k6 適合把壓測納入工程流程。當團隊已經能描述 traffic shape、endpoint mix、arrival rate、think time 與 stop condition，k6 可以把這些模型寫成腳本，讓每次 release、capacity review 或 peak-event readiness 都能重跑同一組驗證。</p>
<p>這個定位讓 k6 接到三個主章。它從 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> 接收流量模型，從 <a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a> 接收 ramp-up 與 knee point 判讀，從 <a href="/blog/backend/09-performance-capacity/production-validation/" data-link-title="9.10 Production-Side 驗證" data-link-desc="shadow traffic、dark launch、canary、production-like load test">9.10 Production-Side 驗證</a> 接收 canary、dark launch 或 production-like load test 的安全邊界。</p>
<h2 id="適用場景">適用場景</h2>
<p>API 壓測是 k6 最穩定的入口。Checkout、login、search、order query、payment callback mock 與 internal API 都可以用 scenario 表達，並用 threshold 把 latency、error rate 與 throughput 轉成 pass / fail 訊號。</p>
<p>CI performance gate 是 k6 的常見價值。團隊可以在 merge、nightly、pre-release 或 game day 前跑固定 baseline，觀察 p95 / p99、error rate、throughput 與 regression trend，再把結果交給 <a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 Performance Regression Gate</a>。</p>
<p>Peak readiness rehearsal 適合用 k6 表達階段式負載。活動前可以用 ramping arrival rate 模擬 T-90、T-30、T-7、T-1 與 T-0 的負載階段，並把結果回寫到 <a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="活動、季節性流量、推廣事件的 capacity readiness 流程">9.11 高峰事件準備</a>。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 k6 deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Scenario design</strong>：用 <code>executor: ramping-arrival-rate</code> 而非 <code>constant-vus</code>、把 RPS / arrival rate 設成 first-class、VU 由 engine 自動算；scenario 描述跟 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> 的 endpoint mix、think time、cohort 對得起來</li>
<li><strong>Threshold gate</strong>：<code>thresholds</code> 區塊明確寫 p95 / p99 / error rate / throughput、CI fail 條件清楚、不靠人眼看 summary 判斷 pass / fail</li>
<li><strong>Output 進 observability stack</strong>：<code>--out experimental-prometheus-rw</code> 把 metric remote-write 到 Prometheus、Grafana dashboard 接 k6 同 datasource、結果跟 target service 的 saturation metric 在同一張圖上看</li>
<li><strong>k6 Cloud vs CLI 邊界</strong>：本地 CLI 跑 baseline + CI、Grafana Cloud k6 跑跨 region / 大規模 / 結果 retention；不要把 CI gate 放 Cloud（成本 + 時間不對）、也不要本地單機硬跑 100k VU（runner 自身瓶頸假象）</li>
</ul>
<p>四件事任一缺失、就是 scenario 已經寫得不完整、threshold gate 失效、或 runner 觀測缺失。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>k6 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>腳本化</td>
          <td>scenario、threshold、setup / teardown 可版本化</td>
          <td>production traffic 抽樣與模型校正</td>
      </tr>
      <tr>
          <td>CI 友善</td>
          <td>CLI 與 artifact 容易接 pipeline</td>
          <td>長期趨勢儲存與 release gate 語意</td>
      </tr>
      <tr>
          <td>API 導向</td>
          <td>HTTP / gRPC / WebSocket 等常見 API 場景清楚</td>
          <td>複雜瀏覽器互動與端到端資料準備</td>
      </tr>
      <tr>
          <td>團隊學習成本</td>
          <td>JavaScript 腳本容易被多數 backend 團隊接手</td>
          <td>大型分散式 runner 與測試資料治理</td>
      </tr>
  </tbody>
</table>
<p>腳本化價值來自可重跑。一次性的壓測只能回答當天配置能撐多少；可版本化 scenario 可以回答 release 後容量曲線有沒有漂移，並讓退化調查回到同一份 workload model。</p>
<p>CI 友善價值來自交接成本低。壓測結果要能轉成 artifact、threshold、trend 與 gate decision，才會從「工程師手動跑工具」變成 release 流程的一部分。</p>
<p>API 導向價值來自後端路徑明確。k6 很適合 checkout API、search API、internal API 與 webhook receiver；如果主要問題是完整 browser UX、第三方真實支付或多裝置同步，文章要把資料準備、side effect 與環境隔離另外寫清楚。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>k6 和 JMeter 的主要差異是工作方式。k6 偏程式化腳本、CLI、CI artifact 與工程流程；JMeter 偏 GUI、protocol plugin、既有企業測試流程與非工程團隊協作。</p>
<p>k6 和 Gatling 的主要差異是生態與語言。k6 使用 JavaScript-style 腳本，Gatling 偏 JVM / Scala / Java / Kotlin 生態；團隊語言能力與既有 pipeline 會影響維護成本。</p>
<p>k6 和 Locust 的主要差異是團隊技能與模型表達。Locust 使用 Python，對 Python 團隊與 custom user behavior 很自然；k6 的 threshold、CLI 與雲端 / Grafana 生態讓 release gate 整合更直接。</p>
<p>k6 和 Vegeta 的主要差異是場景複雜度。Vegeta 適合簡單 HTTP load、CLI workflow 與快速 saturation 探測；k6 適合較完整的 multi-step scenario、threshold 與長期 baseline。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>k6</th>
          <th><a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a></th>
          <th><a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a></th>
          <th><a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Scenario 語言</td>
          <td>JavaScript（ES6+）</td>
          <td>XML（GUI 編輯）/ Groovy</td>
          <td>Python</td>
          <td>Scala / Java / Kotlin DSL</td>
      </tr>
      <tr>
          <td>Engine runtime</td>
          <td>Go</td>
          <td>JVM</td>
          <td>Python（gevent）</td>
          <td>JVM（Akka）</td>
      </tr>
      <tr>
          <td>單機 VU 容量</td>
          <td>高（thousands+）</td>
          <td>中（JVM heap-bound）</td>
          <td>中低（GIL、需 multi-worker）</td>
          <td>高（Akka actor）</td>
      </tr>
      <tr>
          <td>CI 友善度</td>
          <td>強 — CLI + threshold + JSON / Prometheus</td>
          <td>中 — 需 plugin / Jenkins integration</td>
          <td>中 — CLI 友善但 result reporting 較弱</td>
          <td>強 — CLI + HTML report + Maven/Gradle plugin</td>
      </tr>
      <tr>
          <td>Protocol 廣度</td>
          <td>HTTP / gRPC / WebSocket / Browser</td>
          <td>最廣（JDBC / LDAP / JMS / FTP / SMTP）</td>
          <td>HTTP 為主、其他靠 custom client</td>
          <td>HTTP / WebSocket / JMS / MQTT</td>
      </tr>
      <tr>
          <td>Browser test</td>
          <td>k6 Browser（Playwright-based）</td>
          <td>無原生（Selenium plugin）</td>
          <td>無原生</td>
          <td>無原生</td>
      </tr>
      <tr>
          <td>Distributed</td>
          <td>k6 Cloud / k6 Operator on k8s</td>
          <td>Master / Slave（運維重）</td>
          <td>Master / Worker</td>
          <td>Gatling Enterprise / FrontLine</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>API-first + CI gate + Grafana ecosystem</td>
          <td>企業 + protocol 多 + 非工程團隊</td>
          <td>Python team + custom user behavior</td>
          <td>JVM team + DSL 表達力</td>
      </tr>
  </tbody>
</table>
<p>選 k6 的核心訴求：API-first scenario + CI gate + Grafana / Prometheus ecosystem 已用、且團隊接受 JS DSL。Protocol 廣度需求大、走 JMeter；Python team、走 Locust；JVM-heavy、走 Gatling。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>k6 Browser</strong>：基於 Chromium + Playwright API、跑在 k6 同 scenario 內、可混 protocol-level 跟 browser-level load（前段 API call、後段真實 browser flow）。意義是「pure API load 跟 real user UX 在同一份 scenario」、不用維護兩套工具。但 browser VU 比 protocol VU 重幾十倍、runner cost 要重新算。</p>
<p><strong>xk6 extensions</strong>：用 Go 寫 k6 extension、補 protocol（Kafka / Redis / SQL / AMQP）或 output（custom backend）。<code>xk6 build</code> 生出客製 binary、organization 可維護自家 extension。意義是 k6 不只跑 HTTP — Kafka producer load / Redis hot-key probe 都能用同一個 scenario harness。</p>
<p><strong>Grafana Cloud k6（前 k6 Cloud）</strong>：SaaS 跑 multi-region runner、結果保存、跟 Grafana Cloud dashboard / Loki / Tempo / Prometheus 同 plane。適合 <em>跨 region 真實延遲驗證</em>、<em>大規模 distributed run</em>、<em>結果 retention + team share</em>。跟 Grafana Cloud 已用的團隊 ecosystem 一致；只用 OSS 的團隊走 k6 Operator on k8s。</p>
<p><strong>Distributed execution</strong>：自管 distributed 走 <a href="https://github.com/grafana/k6-operator">k6 Operator</a> on Kubernetes、scenario 拆 instance、結果 aggregate 到 output。意義是不需要 k6 Cloud 也能跑跨機器 load、但 runner pool 自管成本 + 結果 aggregation 自己處理。</p>
<p><strong>Output integration</strong>：<code>--out experimental-prometheus-rw</code> 直接 remote-write 到 Prometheus、Grafana dashboard 一張圖看 k6 client metric + target service saturation；<code>--out cloud</code> 上 Grafana Cloud k6；<code>--out json=...</code> 落地檔案給 CI artifact；<code>--out influxdb</code> 接 InfluxDB（legacy）。Loki 用來接 k6 console log、Tempo 用來接 k6 trace（若 scenario 帶 W3C trace context）。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>VU 跑不上去 / runner CPU 滿</strong>：scenario 寫了重 JS 邏輯（big JSON parse、複雜 regex、crypto）— 把 setup-once 邏輯搬 <code>setup()</code>、不要每 VU iteration 重算</li>
<li><strong>Resource throttling 假象</strong>：runner 機器 CPU / network bandwidth / file descriptor 自身瓶頸、target service 還沒到 saturation — 換大機 / 多 runner / 看 runner 自身 saturation metric 排除</li>
<li><strong>Threshold 設過嚴 / CI 一直 red</strong>：threshold 抄 production SLO 不留 budget — staging tenant 跑 5-10 次抓 baseline distribution、threshold 設 baseline + buffer、不是 SLO 直接搬</li>
<li><strong>p95 看起來好但 user 抱怨慢</strong>：scenario endpoint mix 跟 production traffic shape 不符 — 補 production endpoint distribution、按 weight 配 scenario、跟 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> 對齊</li>
<li><strong>Script logic 太重 / VU iteration 不穩</strong>：在 scenario 內做 token refresh / large payload 處理、iteration 時間漂移 — 用 <code>executor: ramping-arrival-rate</code> 鎖 RPS 而非 VU count、iteration 時間漂移由 engine 吸收</li>
<li><strong>結果無法回放 / 找不到 baseline</strong>：output 沒落 artifact、Grafana dashboard 沒存 time range — 每次 run 強制 <code>--out json</code> + tag scenario version + push 到 evidence package</li>
</ul>
<h2 id="操作成本">操作成本</h2>
<p>k6 的主要成本是 workload model 維護。腳本本身容易寫，真正的成本在 production endpoint mix、資料分布、tenant / region / user cohort、think time 與 peak shape 的持續校正。</p>
<p>Runner 成本會隨負載規模上升。單機 runner 適合小型 API baseline；跨 region、數十萬 RPS 或長時間 soak test 需要分散式 runner、網路成本、目標服務隔離與觀測儲存。</p>
<p>測試資料治理是高風險成本。Checkout、payment、order、email、notification 與 webhook 路徑都可能產生 side effect，因此 scenario 要明確定義 test tenant、idempotency key、mock boundary、cleanup 與 stop condition。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>k6 結果應回寫到 evidence package。最小欄位包括 scenario version、target environment、time range、VUs / arrival rate、threshold、p95 / p99、error rate、throughput、target service saturation metric、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>k6 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>k6 summary、JSON output、dashboard link</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>test start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>Grafana / Prometheus / APM 查詢連結</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>scenario coverage、test data freshness</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>production similarity、runner capacity</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未覆蓋 endpoint、未模擬第三方、資料偏差</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓 release gate 能判斷。k6 的 threshold pass 只是其中一個訊號；gate 還要看 target service 的 CPU、connection、DB latency、cache hit rate、queue lag 與 cloud cost。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>k6 目前在 09 案例庫中主要作為工具類承接點，案例主角仍是負載形狀與驗證節奏。它可回寫到 <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft 售票壓測</a> 的 pre-event load test 判讀、<a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 Prime Day readiness</a> 的 staged validation、<a href="/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/" data-link-title="9.C28 FanDuel：體育直播 &#43; 投注的雙重峰值" data-link-desc="FanDuel 3.5M MAU、Super Bowl 期間擴容 5-10 倍、用 AWS Local Zones &#43; Wavelength &#43; Outposts 處理 20&#43; 州的雙重峰值">9.C28 FanDuel 雙峰 workload</a> 的多模型壓測需求、<a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech FIFA World Cup readiness</a> 的 54000 TPS @ 25ms p95 驗證、以及 <a href="/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&#43; 個微服務承載 8 倍峰值流量、跨 200&#43; 城市">9.C7 Lyft 8x peak</a> 跨 100+ 微服務的獨立 threshold 設計。</p>
<p>這些案例提供的是負載形狀與工程節奏。k6 頁引用案例時，要把 case 轉成 workload model、ramp-up、threshold、runner 規模與 stop condition，並讓工具回到可替換的承載選項 — 例如 GR8 Tech 25ms p95 是 threshold pass / fail 的硬目標、Lyft 的「8x 是特定服務、不是全部 8x」要拆成 per-service scenario。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a></li>
<li>跨模組：<a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 Performance Regression Gate</a></li>
<li>官方：<a href="https://grafana.com/docs/k6/latest/">Grafana k6 documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Apache JMeter</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/jmeter/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/jmeter/</guid><description>&lt;p>JMeter 的核心責任是把多 protocol 測試與既有企業測試資產轉成可重跑的負載驗證。它適合 GUI 驅動、plugin 生態成熟、HTTP 之外還需要 JDBC、JMS、FTP、mail 或 legacy protocol 的團隊，重點在把測試流程保留成可審查、可交接、可在 non-GUI mode 跑的 artifact。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>JMeter 是 Apache Software Foundation 的 OSS load testing tool、Java 寫、用 XML 描述 thread group / sampler / listener 組成的 test plan（&lt;code>.jmx&lt;/code> 檔）、支援 GUI 與 CLI（non-GUI / headless）雙模式。它是業界最老牌、protocol 覆蓋最廣的壓測工具 — sampler 直接覆蓋 HTTP、JDBC、JMS、SOAP、FTP、SMTP、IMAP、TCP、JUnit、OS process 等。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6&lt;/a> 比、JMeter 走 &lt;em>GUI-driven + protocol 廣&lt;/em>、k6 走 &lt;em>code-first（JavaScript）+ HTTP 為主&lt;/em>；JMeter 適合 QA 團隊維護、k6 適合 dev / SRE 寫進 CI。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust&lt;/a> 比、JMeter 用 XML + plugin、Locust 用純 Python class、custom client 彈性 Locust 強但 protocol 內建支援 JMeter 廣。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling&lt;/a> 比、JMeter 偏 GUI / 多 protocol、Gatling 偏 JVM DSL（Scala / Java / Kotlin）+ async runtime、單機 throughput Gatling 較高但 protocol 廣度與既有資產承接 JMeter 勝。&lt;/p>
&lt;p>關鍵張力：&lt;em>GUI / protocol 廣度&lt;/em> ↔ &lt;em>單機 throughput / CI 友善度&lt;/em> 是選 JMeter 的根本取捨。GUI 適合 QA 團隊與跨角色協作、&lt;code>.jmx&lt;/code> 又有 plugin 生態與十多年累積；代價是 XML diff 難 review、GUI listener 吃記憶體、CI 整合相比 k6 / Gatling 多一層 packaging。&lt;/p>
&lt;p>JMeter 適合測試資產已經存在的組織。當團隊有大量 &lt;code>.jmx&lt;/code> 測試計畫、QA 團隊用 GUI 維護 scenario、或壓測需要跨 HTTP、JDBC、JMS 與其他 plugin protocol，JMeter 的價值在於承接組織流程，而不只是產生 HTTP 負載。這個定位讓 JMeter 接到 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/production-validation/" data-link-title="9.10 Production-Side 驗證" data-link-desc="shadow traffic、dark launch、canary、production-like load test">9.10 Production-Side 驗證&lt;/a>。它能支援 production-like test 的多系統 dependency，但 evidence package 要補上測試計畫版本、plugin 版本、runner 配置與結果保存方式。&lt;/p></description><content:encoded><![CDATA[<p>JMeter 的核心責任是把多 protocol 測試與既有企業測試資產轉成可重跑的負載驗證。它適合 GUI 驅動、plugin 生態成熟、HTTP 之外還需要 JDBC、JMS、FTP、mail 或 legacy protocol 的團隊，重點在把測試流程保留成可審查、可交接、可在 non-GUI mode 跑的 artifact。</p>
<h2 id="服務定位">服務定位</h2>
<p>JMeter 是 Apache Software Foundation 的 OSS load testing tool、Java 寫、用 XML 描述 thread group / sampler / listener 組成的 test plan（<code>.jmx</code> 檔）、支援 GUI 與 CLI（non-GUI / headless）雙模式。它是業界最老牌、protocol 覆蓋最廣的壓測工具 — sampler 直接覆蓋 HTTP、JDBC、JMS、SOAP、FTP、SMTP、IMAP、TCP、JUnit、OS process 等。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a> 比、JMeter 走 <em>GUI-driven + protocol 廣</em>、k6 走 <em>code-first（JavaScript）+ HTTP 為主</em>；JMeter 適合 QA 團隊維護、k6 適合 dev / SRE 寫進 CI。跟 <a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a> 比、JMeter 用 XML + plugin、Locust 用純 Python class、custom client 彈性 Locust 強但 protocol 內建支援 JMeter 廣。跟 <a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a> 比、JMeter 偏 GUI / 多 protocol、Gatling 偏 JVM DSL（Scala / Java / Kotlin）+ async runtime、單機 throughput Gatling 較高但 protocol 廣度與既有資產承接 JMeter 勝。</p>
<p>關鍵張力：<em>GUI / protocol 廣度</em> ↔ <em>單機 throughput / CI 友善度</em> 是選 JMeter 的根本取捨。GUI 適合 QA 團隊與跨角色協作、<code>.jmx</code> 又有 plugin 生態與十多年累積；代價是 XML diff 難 review、GUI listener 吃記憶體、CI 整合相比 k6 / Gatling 多一層 packaging。</p>
<p>JMeter 適合測試資產已經存在的組織。當團隊有大量 <code>.jmx</code> 測試計畫、QA 團隊用 GUI 維護 scenario、或壓測需要跨 HTTP、JDBC、JMS 與其他 plugin protocol，JMeter 的價值在於承接組織流程，而不只是產生 HTTP 負載。這個定位讓 JMeter 接到 <a href="/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型</a> 與 <a href="/blog/backend/09-performance-capacity/production-validation/" data-link-title="9.10 Production-Side 驗證" data-link-desc="shadow traffic、dark launch、canary、production-like load test">9.10 Production-Side 驗證</a>。它能支援 production-like test 的多系統 dependency，但 evidence package 要補上測試計畫版本、plugin 版本、runner 配置與結果保存方式。</p>
<h2 id="適用場景">適用場景</h2>
<p>多 protocol 壓測是 JMeter 的主要入口。企業服務常同時需要測 HTTP API、JDBC query、JMS queue、FTP 或 mail flow，JMeter 的 sampler 與 plugin 生態能讓同一份測試計畫覆蓋多種 dependency。</p>
<p>GUI 協作適合非純工程團隊。QA、測試中心或受監管環境常需要可視化測試設計、審核與交接，JMeter 的 GUI 能降低跨角色溝通成本。</p>
<p>Legacy 測試資產適合保留 JMeter。既有 <code>.jmx</code> 檔案、listener、plugin 與報表流程如果已經運作多年，重寫到 k6、Gatling 或 Locust 的機會成本要用維護收益抵銷。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 JMeter deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Thread group 設計</strong>：thread count / ramp-up / loop count / duration 是否反映真實流量模型、有沒有用 <em>Stepping Thread Group</em>（plugin）或 <em>Concurrency Thread Group</em> 控制 arrival rate、不是把 thread 當「user」直接綁</li>
<li><strong>Listener 配置</strong>：GUI listener（View Results Tree / Aggregate Report / Graph）只在 design / debug 階段開、正式跑必須改 <em>Simple Data Writer</em> 輸出 JTL、結果分析交給離線 HTML report 或外部 Grafana</li>
<li><strong>Distributed mode 設定</strong>：單機 thread 上限約 3000-5000（受 JVM heap 與 thread context switch 限制）、超過要走 <em>master + slave</em>（remote engine）；slave 機器 plugin / JMeter version / JVM 參數要跟 master 一致、否則結果不可信</li>
<li><strong>GUI vs CLI 模式區分</strong>：GUI 是 design / debug only、production load 一律走 <code>jmeter -n -t plan.jmx -l result.jtl</code>；GUI 跑大規模測試會把 listener 拉爆記憶體、結果反而失真</li>
</ul>
<p>四件事任一缺、就是 <a href="/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型</a> 邊界的待補項目。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>JMeter 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>多 protocol</td>
          <td>sampler 與 plugin 覆蓋廣</td>
          <td>plugin 版本治理與測試環境一致性</td>
      </tr>
      <tr>
          <td>GUI 協作</td>
          <td>非工程角色可讀可改</td>
          <td>code review、diff 與版本控制紀律</td>
      </tr>
      <tr>
          <td>既有資產</td>
          <td><code>.jmx</code>、listener、報表可延續</td>
          <td>scenario cleanup 與 artifact 標準化</td>
      </tr>
      <tr>
          <td>分散式執行</td>
          <td>remote engine 可擴負載</td>
          <td>runner sizing、網路瓶頸與結果合併</td>
      </tr>
  </tbody>
</table>
<p>多 protocol 價值來自 dependency coverage。當 workload model 包含 database、queue、file transfer 或 legacy endpoint，JMeter 可以把不同 dependency 的壓力放在同一個測試計畫中觀察。</p>
<p>GUI 協作價值來自跨角色可見性。這個優點會帶來版本控制成本，因為 XML diff 不容易 review；團隊要補上 naming、folder structure、parameterization 與 review checklist。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>JMeter 和 k6 的主要差異是 workflow。JMeter 偏 GUI、plugin 與既有企業流程；k6 偏 code-first、CLI、threshold 與 CI artifact。</p>
<p>JMeter 和 Gatling 的主要差異是 scenario 表達。JMeter 用 test plan、thread group、sampler 與 listener 組裝；Gatling 用 JVM DSL 描述 simulation，較適合工程團隊維護複雜 flow。</p>
<p>JMeter 和 Locust 的主要差異是自訂能力。JMeter 依賴 plugin 與 sampler，Locust 可以直接用 Python library 實作 custom client；如果 protocol 特別特殊，Python 團隊可能更適合 Locust。</p>
<p>JMeter 和 Vegeta 的主要差異是複雜度。Vegeta 適合快速 HTTP saturation probe；JMeter 適合多步驟、多 dependency 與可交接測試計畫。</p>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>JMeter</th>
          <th>k6</th>
          <th>Locust</th>
          <th>Gatling</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>描述語言</td>
          <td>XML（<code>.jmx</code>）+ GUI</td>
          <td>JavaScript</td>
          <td>Python（class-based）</td>
          <td>Scala / Java / Kotlin DSL</td>
      </tr>
      <tr>
          <td>Protocol 覆蓋</td>
          <td>HTTP/JDBC/JMS/SOAP/FTP/SMTP/TCP</td>
          <td>HTTP/WebSocket/gRPC</td>
          <td>HTTP + 任何 Python lib custom</td>
          <td>HTTP/JMS/MQTT</td>
      </tr>
      <tr>
          <td>單機 throughput</td>
          <td>中（thread-per-user）</td>
          <td>高（Go goroutine）</td>
          <td>中（gevent / async）</td>
          <td>高（Akka async）</td>
      </tr>
      <tr>
          <td>Runtime model</td>
          <td>JVM thread</td>
          <td>Go runtime</td>
          <td>Python gevent</td>
          <td>JVM async actor</td>
      </tr>
      <tr>
          <td>CI 友善度</td>
          <td>需 packaging <code>.jmx</code> + plugin</td>
          <td>強 — 單一 JS file + CLI</td>
          <td>強 — pip + Python file</td>
          <td>強 — sbt / Maven + Scala file</td>
      </tr>
      <tr>
          <td>GUI</td>
          <td>完整 GUI（design / debug）</td>
          <td>無（CLI only）</td>
          <td>Web UI（runtime monitoring）</td>
          <td>無（HTML report only）</td>
      </tr>
      <tr>
          <td>Distributed</td>
          <td>Master + Slave（remote engine）</td>
          <td>k6 Cloud / Operator</td>
          <td>Master + Worker</td>
          <td>Gatling Enterprise / FrontLine</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>Enterprise QA + 多 protocol</td>
          <td>Dev / SRE + HTTP-heavy + CI</td>
          <td>Python 團隊 + custom protocol</td>
          <td>JVM 團隊 + 複雜 scenario</td>
      </tr>
  </tbody>
</table>
<h2 id="操作成本">操作成本</h2>
<p>JMeter 的主要成本是測試計畫治理。<code>.jmx</code> 檔案可以累積大量 listener、debug sampler、hard-coded variable 與過期 assertion，長期不整理會讓壓測結果失去可追溯性。</p>
<p>Runner 成本來自 JVM 與 listener。GUI listener 適合開發階段觀察，不適合大規模壓測；正式測試要使用 non-GUI mode，把結果輸出成 JTL、HTML report 或外部 metrics。</p>
<p>Plugin 成本來自版本漂移。不同 runner、不同工程師機器或 CI image 的 plugin 版本如果不一致，同一份測試計畫可能產生不同結果，因此要把 plugin 清單、JMeter 版本與 container image 固定下來。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>JMeter 結果應回寫到 evidence package。最小欄位包括 test plan version、JMeter version、plugin list、runner topology、thread group 設定、ramp-up、duration、p95 / p99、error rate、throughput、target saturation metric 與 known gap。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>JMeter 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td><code>.jmx</code>、JTL、HTML report、dashboard link</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>test start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>APM / Prometheus / DB / queue 查詢連結</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>test plan version、plugin version</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>runner topology、production similarity</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未覆蓋 protocol、資料偏差、listener overhead</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓結果可審查。JMeter 測試計畫常由多人維護，gate decision 要能追到哪一版 <code>.jmx</code>、哪一組 runner、哪一批測試資料與哪一個目標環境。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>JMeter Plugins 生態</strong>：<a href="https://jmeter-plugins.org/">jmeter-plugins.org</a> 社群維護的 plugin 集合補齊原版 JMeter 的不足 — <em>Custom Thread Groups</em>（Stepping / Ultimate / Concurrency / Arrivals）讓 thread schedule 反映真實 arrival rate、<em>PerfMon</em> 抓 remote server CPU / memory、<em>Throughput Shaping Timer</em> 直接以 RPS 為目標而非 thread count、<em>Dummy Sampler</em> 拿來 mock dependency。Plugin Manager 統一安裝、CI image 要把 plugin 清單固定（<code>PluginsManagerCMD.sh install &lt;plugins&gt;</code>）避免漂移。</p>
<p><strong>BlazeMeter Cloud / Distributed execution</strong>：自建 distributed mode（master + slave 跨多 VM）成本高 — slave 機器要同 JMeter 版本、同 plugin、同 JVM 參數、RMI port 開通、結果回傳網路足夠。<a href="https://www.blazemeter.com/">BlazeMeter</a>（Perforce / 前 CA）是 JMeter SaaS、直接吃 <code>.jmx</code> 跑 cloud-scale 壓測、附 geo-distributed runner、適合短期 spike 測試不想自建 distributed cluster 的團隊。trade-off 是 vendor lock-in 跟 per-test 計費 — 長期高頻測試自建較划算。</p>
<p><strong>Distributed mode 細節</strong>：master 機器發 control plane（thread group 配置、test plan 分發）、slave 跑 thread 並回傳 sample 結果。瓶頸常出在 <em>master 收結果</em>（RMI / 自訂 protocol），不是 slave 跑不動 — 大規模測試應該關掉 GUI listener、用 <em>Backend Listener</em> 把 metric 即時推到外部時序資料庫、master 只收彙整指標而非每個 sample。同步要點：所有 slave 用同一份 <code>.jmx</code> 與 test data CSV，CSV 不能依賴 master local path。</p>
<p><strong>Backend Listener + Grafana 整合</strong>：JMeter 原生 <em>Backend Listener</em> 支援 InfluxDB / Graphite / Elasticsearch、把 active thread / response time / hit / error 即時推出去、Grafana 配 <a href="https://grafana.com/grafana/dashboards/5496-apache-jmeter-dashboard/">official JMeter dashboard</a> 即時看 throughput / latency curve。這個組合取代 GUI listener、是 distributed mode 的標準觀測方式 — listener overhead 從 master 移到外部時序系統、master 不再被 GUI 拉爆。配合 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">4 observability</a> 的時序資料庫已有時、JMeter metric 進同一個 Grafana、跟 application 端的 latency / error 並列、加速 <a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 Performance Regression Gate</a> 的對照判讀。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>GUI 模式吃記憶體爆 / OOM</strong>：GUI listener（View Results Tree / Graph）會把所有 sample 留在 heap、跑大規模就 OutOfMemoryError — 設計階段才開 GUI、正式跑切 <code>jmeter -n</code> non-GUI、listener 用 Simple Data Writer 寫 JTL 而非 in-memory aggregate</li>
<li><strong>Listener 拖累 throughput / 結果失真</strong>：太多 listener 同時開、每個 sample 都被多個 listener 處理、JMeter 自身成為瓶頸 — 正式測試只留 Simple Data Writer + Backend Listener、結果分析離線跑 <code>jmeter -g result.jtl -o report/</code> 產 HTML</li>
<li><strong>Thread group 計算錯 / 真實流量對不上</strong>：把 thread 當「user」直接設、忽略 think time + ramp-up、結果壓出來的是 thread 全速跑而非業務流量 — 改用 Concurrency Thread Group 或 Throughput Shaping Timer 直接以 RPS 為目標、配 Constant Timer 模擬 think time</li>
<li><strong>Distributed mode 結果跟單機對不上</strong>：slave 機器 plugin / JMeter version / JVM heap 不一致、或 CSV 路徑只存在 master — 把 slave 環境 container 化（同 Docker image）、CSV 隨 <code>.jmx</code> 一起分發、<code>--remote-start</code> 統一啟動</li>
<li><strong><code>.jmx</code> XML diff 不可 review / merge conflict 多</strong>：多人同時改測試計畫、GUI 改完 XML 結構大變 — 拆 fragment（Test Fragment + Module Controller）、scenario 分檔、parameterization 走外部 CSV / properties、PR review 看截圖 + 跑結果而非 raw XML diff</li>
<li><strong>Plugin 版本漂移 / CI 結果不可重現</strong>：dev 機器 plugin 跟 CI image 不同版 — 固定 plugin manifest、CI image 用 <code>PluginsManagerCMD.sh install-for-jmx plan.jmx</code> 從 plan 自動安裝、版本鎖到 image tag</li>
<li><strong>HTTPS / TLS 連線數爆炸</strong>：JMeter 預設每 thread 一個 TLS handshake、large thread count 把 server TLS 拖垮、結果反而測到 TLS 不是 app — 開 <em>HTTP Cache Manager</em> 跟 <em>KeepAlive</em>、必要時調 <code>httpclient4.idletimeout</code></li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>JMeter 在 09 案例庫中適合作為 enterprise load test 承接點。它可回寫到 <a href="/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &#43; 傳統伺服器當慢速消費者、承受 100K&#43; 同時選位 &#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft 售票壓測</a> 的 pre-event validation、<a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow ticketing</a> 的售票流量模型、<a href="/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/" data-link-title="9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood" data-link-desc="Amazon 自家服務在 Prime Day 2025 的峰值數字 — 一年一次可預期峰值的容量設計參考">9.C1 Prime Day readiness</a> 的 staged validation、<a href="/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL 1860 萬同時觀看</a> 的全球直播 pre-event rehearsal、以及 <a href="/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/" data-link-title="9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升" data-link-desc="Standard Chartered 銀行遷移到 Aurora 後吞吐量提升 10 倍至 4000 TPS、跨 7 個受監管市場">9.C14 Standard Chartered</a> 跨 7 個受監管市場的 Aurora 4000 TPS 容量驗證。</p>
<p>這些案例提供的是複雜業務流程與活動前驗證節奏。JMeter 頁引用案例時，要把 case 轉成 thread group、ramp-up、data set、dependency sampler 與 result artifact，並讓負載數字回到業務流程判讀 — 例如 Hotstar 的「集中地理區 CDN 壓力」要在 JMeter 用 per-region thread group 模擬、不是把全球流量塞進單一 runner。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/production-validation/" data-link-title="9.10 Production-Side 驗證" data-link-desc="shadow traffic、dark launch、canary、production-like load test">9.10 Production-Side 驗證</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a>、<a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a>、<a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a></li>
<li>跨模組：<a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 Performance Regression Gate</a></li>
<li>官方：<a href="https://jmeter.apache.org/usermanual/index.html">Apache JMeter documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Gatling</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/gatling/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/gatling/</guid><description>&lt;p>Gatling 的核心責任是把複雜使用者流程寫成可維護的 JVM simulation。它適合 JVM 生態團隊、強型別 DSL、HTTP / WebSocket / JMS / MQTT 等 scenario，以及需要把 injection profile、assertion、report 與 CI pipeline 綁在一起的壓測流程。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Gatling 是 &lt;em>Scala-origin / 現以 Java DSL 為主流&lt;/em> 的 load testing 工具、跑在 JVM、async / non-blocking engine（基於 Akka / Netty）讓單一 injector node 就能驅動高 RPS。它跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust&lt;/a> 的核心差異在 &lt;em>語言生態 + engine efficiency + scenario 表達力&lt;/em>、壓出負載的能力都具備：&lt;/p>
&lt;ul>
&lt;li>vs k6 — k6 走 Go runtime + JavaScript scripting、CLI / Grafana 生態友善；Gatling 走 JVM + Java/Scala/Kotlin DSL、適合既有 JVM 工具鏈與強型別 review&lt;/li>
&lt;li>vs JMeter — JMeter 走 GUI / XML test plan、適合非工程角色協作；Gatling 走 code-first、適合 PR / build pipeline / refactor 工作流&lt;/li>
&lt;li>vs Locust — Locust 走 Python coroutine、scripting 自由度高；Gatling 走 DSL + injection profile、scenario 結構化程度更高&lt;/li>
&lt;li>engine efficiency — async / non-blocking model 讓 Gatling 在單機可推到數萬 RPS、JMeter thread-per-user 在同等資源下 throughput 較低&lt;/li>
&lt;/ul>
&lt;p>產品線分兩層：&lt;em>Gatling OSS&lt;/em>（開源 simulation runner + HTML report）與 &lt;em>Gatling Enterprise&lt;/em>（前身 FrontLine、加上 distributed injector、cluster orchestration、live monitoring、long-term result storage、role-based access）。OSS 適合單機 baseline / CI smoke、Enterprise 適合 cross-region distributed / 大型活動前壓測 / 結果長期治理。&lt;/p>
&lt;h2 id="最短判讀路徑">最短判讀路徑&lt;/h2>
&lt;p>判斷 Gatling 在壓測流程裡是否健康、最少看四件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Scala DSL vs Java DSL 版本&lt;/strong>：Gatling 3.7+（2022）正式加 Java DSL、2024 後新專案多走 Java DSL；舊 Scala simulation 仍可跑、但團隊要決定 &lt;em>維持 Scala 還是漸進改寫 Java&lt;/em>、避免雙語言治理&lt;/li>
&lt;li>&lt;strong>Injection profile 設計&lt;/strong>：simulation 是否明確區分 &lt;em>open model&lt;/em>（&lt;code>rampUsersPerSec&lt;/code> / &lt;code>constantUsersPerSec&lt;/code>、模擬真實 arrival）vs &lt;em>closed model&lt;/em>（&lt;code>atOnceUsers&lt;/code> / &lt;code>rampUsers&lt;/code>、模擬 fixed user pool），對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling&lt;/a> 的 traffic shape&lt;/li>
&lt;li>&lt;strong>Assertion gate&lt;/strong>：simulation 是否有 &lt;code>assertions { global.responseTime.percentile3.lt(500) }&lt;/code> 這類 hard gate、CI 跑完直接 fail build；沒 assertion 的 simulation 只是壓測、不是 release gate&lt;/li>
&lt;li>&lt;strong>Enterprise vs OSS 邊界&lt;/strong>：是否清楚知道哪些能力只 Enterprise 有（distributed injector / multi-region / long-term result storage / live dashboard）、避免用 OSS 拼湊 Enterprise 級需求&lt;/li>
&lt;/ul>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>Gatling 適合 code-first 且 JVM 能力強的團隊。當 workload model 需要多步驟 flow、資料 feeder、條件分支、session state 與明確 injection profile，Gatling 能用 simulation 把這些行為寫成工程 artifact。&lt;/p></description><content:encoded><![CDATA[<p>Gatling 的核心責任是把複雜使用者流程寫成可維護的 JVM simulation。它適合 JVM 生態團隊、強型別 DSL、HTTP / WebSocket / JMS / MQTT 等 scenario，以及需要把 injection profile、assertion、report 與 CI pipeline 綁在一起的壓測流程。</p>
<h2 id="服務定位">服務定位</h2>
<p>Gatling 是 <em>Scala-origin / 現以 Java DSL 為主流</em> 的 load testing 工具、跑在 JVM、async / non-blocking engine（基於 Akka / Netty）讓單一 injector node 就能驅動高 RPS。它跟 <a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a> / <a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a> / <a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a> 的核心差異在 <em>語言生態 + engine efficiency + scenario 表達力</em>、壓出負載的能力都具備：</p>
<ul>
<li>vs k6 — k6 走 Go runtime + JavaScript scripting、CLI / Grafana 生態友善；Gatling 走 JVM + Java/Scala/Kotlin DSL、適合既有 JVM 工具鏈與強型別 review</li>
<li>vs JMeter — JMeter 走 GUI / XML test plan、適合非工程角色協作；Gatling 走 code-first、適合 PR / build pipeline / refactor 工作流</li>
<li>vs Locust — Locust 走 Python coroutine、scripting 自由度高；Gatling 走 DSL + injection profile、scenario 結構化程度更高</li>
<li>engine efficiency — async / non-blocking model 讓 Gatling 在單機可推到數萬 RPS、JMeter thread-per-user 在同等資源下 throughput 較低</li>
</ul>
<p>產品線分兩層：<em>Gatling OSS</em>（開源 simulation runner + HTML report）與 <em>Gatling Enterprise</em>（前身 FrontLine、加上 distributed injector、cluster orchestration、live monitoring、long-term result storage、role-based access）。OSS 適合單機 baseline / CI smoke、Enterprise 適合 cross-region distributed / 大型活動前壓測 / 結果長期治理。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Gatling 在壓測流程裡是否健康、最少看四件事：</p>
<ul>
<li><strong>Scala DSL vs Java DSL 版本</strong>：Gatling 3.7+（2022）正式加 Java DSL、2024 後新專案多走 Java DSL；舊 Scala simulation 仍可跑、但團隊要決定 <em>維持 Scala 還是漸進改寫 Java</em>、避免雙語言治理</li>
<li><strong>Injection profile 設計</strong>：simulation 是否明確區分 <em>open model</em>（<code>rampUsersPerSec</code> / <code>constantUsersPerSec</code>、模擬真實 arrival）vs <em>closed model</em>（<code>atOnceUsers</code> / <code>rampUsers</code>、模擬 fixed user pool），對應 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> 的 traffic shape</li>
<li><strong>Assertion gate</strong>：simulation 是否有 <code>assertions { global.responseTime.percentile3.lt(500) }</code> 這類 hard gate、CI 跑完直接 fail build；沒 assertion 的 simulation 只是壓測、不是 release gate</li>
<li><strong>Enterprise vs OSS 邊界</strong>：是否清楚知道哪些能力只 Enterprise 有（distributed injector / multi-region / long-term result storage / live dashboard）、避免用 OSS 拼湊 Enterprise 級需求</li>
</ul>
<h2 id="定位">定位</h2>
<p>Gatling 適合 code-first 且 JVM 能力強的團隊。當 workload model 需要多步驟 flow、資料 feeder、條件分支、session state 與明確 injection profile，Gatling 能用 simulation 把這些行為寫成工程 artifact。</p>
<p>這個定位讓 Gatling 接到 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> 與 <a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a>。它的價值在於把 traffic shape 寫進 injection profile，讓 ramp-up、constant users、stress peak 與 soak test 都能被版本化。</p>
<h2 id="適用場景">適用場景</h2>
<p>JVM 團隊適合用 Gatling 承接壓測。Java、Scala 或 Kotlin 團隊能把 simulation 當成一般程式碼 review，並用既有 build、dependency、CI 與 artifact 流程維護。</p>
<p>複雜 scenario 適合用 Gatling 表達。登入、搜尋、加入購物車、checkout、payment mock、order query 這類 multi-step flow 可以用 session 與 feeder 管理資料。</p>
<p>高品質 report 適合 release review。Gatling 的 report 能幫 reviewer 看到 response time distribution、request group、error 與 injection profile，適合在 release gate 中保留可讀證據。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Gatling 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>JVM DSL</td>
          <td>simulation 可 code review</td>
          <td>Scala / Java / Kotlin 維護能力</td>
      </tr>
      <tr>
          <td>Injection profile</td>
          <td>負載階段可精準表達</td>
          <td>production traffic shape 校正</td>
      </tr>
      <tr>
          <td>Session / feeder</td>
          <td>多步驟資料與狀態容易管理</td>
          <td>測試資料治理與敏感資料遮罩</td>
      </tr>
      <tr>
          <td>Report</td>
          <td>release review 可讀性高</td>
          <td>長期趨勢儲存與 cross-run comparison</td>
      </tr>
  </tbody>
</table>
<p>JVM DSL 價值來自可維護性。壓測 scenario 如果需要被長期 review、重構、抽 helper 或接 build pipeline，Gatling 的 code-first workflow 會比 GUI test plan 更適合工程團隊。</p>
<p>Injection profile 價值來自負載形狀精準。團隊可以把 steady load、spike、ramp、open model 與 closed model 放到 simulation 中，讓 <a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a> 的 knee point 判讀更可重現。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>Gatling 和 k6 的主要差異是語言與生態。Gatling 適合 JVM 團隊與強型別 simulation；k6 適合 JavaScript-style scripting、CLI workflow 與 Grafana 生態。</p>
<p>Gatling 和 JMeter 的主要差異是維護模式。Gatling 偏 code review、build pipeline 與 simulation abstraction；JMeter 偏 GUI、plugin 與跨角色測試資產。</p>
<p>Gatling 和 Locust 的主要差異是自訂語言。Locust 適合 Python 團隊與任意 Python client；Gatling 適合 JVM 團隊與 report / injection profile 的結構化壓測。</p>
<p>Gatling 和 Vegeta 的主要差異是 scenario 深度。Vegeta 適合快速 HTTP pressure test；Gatling 適合需要 session、feeder、assertion 與多 request group 的長期測試。</p>
<h2 id="操作成本">操作成本</h2>
<p>Gatling 的主要成本是 JVM 團隊能力。非 JVM 團隊要承擔語言、build tool、dependency 與 simulation pattern 的學習成本；這個成本只有在 scenario 複雜度夠高時才划算。</p>
<p>測試資料成本來自 feeder 與 session。多步驟 flow 需要 account、cart、order、token、region 與 tenant 資料，資料過期或分布偏差會讓壓測結果失真。</p>
<p>Enterprise / distributed 成本要提前評估。單機 Gatling 適合中小型 baseline；跨 region、大型活動前驗證或長時間 soak test 需要 runner topology、結果集中與雲端成本治理。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Gatling 結果應回寫到 evidence package。最小欄位包括 simulation version、injection profile、feeder source、target environment、assertion、response time distribution、error rate、throughput、target service saturation metric、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Gatling 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>simulation code、HTML report、dashboard link</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>test start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>APM / metrics / logs 查詢連結</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>feeder freshness、scenario coverage</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>production similarity、runner capacity</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未覆蓋 flow、資料偏差、下游 mock 限制</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓 simulation 可回放。Reviewer 要能從 report 回到 injection profile、scenario code、feeder 與目標環境，才有辦法判斷一次壓測是容量訊號還是測試設計偏差。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Gatling</th>
          <th>k6</th>
          <th>JMeter</th>
          <th>Locust</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>語言 / DSL</td>
          <td>Java / Kotlin / Scala DSL（JVM）</td>
          <td>JavaScript（Go runtime）</td>
          <td>GUI / XML test plan（JVM）</td>
          <td>Python（coroutine / gevent）</td>
      </tr>
      <tr>
          <td>Engine model</td>
          <td>Async / non-blocking（Akka + Netty）</td>
          <td>Async（Go goroutine）</td>
          <td>Thread-per-user（同步）</td>
          <td>Async coroutine</td>
      </tr>
      <tr>
          <td>單機 RPS 上限</td>
          <td>高（數萬 RPS）</td>
          <td>高（數萬 RPS）</td>
          <td>中（thread overhead）</td>
          <td>中（GIL + coroutine）</td>
      </tr>
      <tr>
          <td>Scenario 表達力</td>
          <td>強（session / feeder / 條件分支內建）</td>
          <td>中（JS function 自寫）</td>
          <td>中（GUI 拖拉 + listener）</td>
          <td>中（Python class + task）</td>
      </tr>
      <tr>
          <td>Report quality</td>
          <td>高（HTML report 內建、distribution / group 詳細）</td>
          <td>中（CLI 摘要 + Grafana 串接）</td>
          <td>中（GUI listener、不適合 headless）</td>
          <td>中（web UI 即時、無 historical）</td>
      </tr>
      <tr>
          <td>CI integration</td>
          <td>強（Maven / Gradle / sbt + assertion gate）</td>
          <td>強（CLI + JSON output）</td>
          <td>中（CLI mode 可、但 GUI-first）</td>
          <td>強（CLI + Python ecosystem）</td>
      </tr>
      <tr>
          <td>Distributed</td>
          <td>OSS 自建 / Enterprise 內建</td>
          <td>k6 Cloud / OSS 自建</td>
          <td>自建（master-slave）</td>
          <td>自建（master-worker）</td>
      </tr>
      <tr>
          <td>商業版本</td>
          <td>Gatling Enterprise（前 FrontLine）</td>
          <td>Grafana Cloud k6</td>
          <td>無（純 OSS）</td>
          <td>無（純 OSS）</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>JVM 團隊、複雜 scenario、release gate、高 RPS efficiency</td>
          <td>全棧團隊、CLI workflow、Grafana 生態</td>
          <td>跨角色團隊、legacy test plan、protocol 多樣</td>
          <td>Python 團隊、自訂 client、輕量 setup</td>
      </tr>
  </tbody>
</table>
<p>選 Gatling 的核心訴求：<em>JVM 團隊 + 複雜 scenario（session / feeder / 多 group）+ 高 RPS 單機效率 + HTML report 作為 release gate 證據</em>。Java DSL 在 2024 後降低了 Scala 學習門檻、讓 Java/Kotlin 後端團隊不必再為了壓測導入 Scala。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Gatling Enterprise（前 FrontLine）</strong>：商業版加 <em>distributed injector cluster</em>（跨 region / 跨 cloud 推大型負載）、<em>live monitoring dashboard</em>（real-time RPS / response time 趨勢、不用等 simulation 結束看 HTML）、<em>long-term result storage</em>（cross-run comparison、retention policy）、<em>role-based access</em>（QA / dev / SRE 不同權限）。對只跑單機 baseline 的團隊 OSS 已夠；要跑黑五 / 春晚級活動前壓測或多 region 同時施壓、需要 Enterprise 或自建 distributed topology。</p>
<p><strong>Java DSL 取代 Scala 成主流（2022-2024）</strong>：Gatling 3.7（2022）正式釋出 Java DSL、3.9+ 文件 Java / Kotlin / Scala 三語並列、2024 後新教學多以 Java 為主。對 Java 後端團隊降低 onboarding 成本、但要注意 <em>Gatling 2.x → 3.x</em> 的 Scala syntax 不向後相容（<code>scenario</code> builder、<code>http</code> config、<code>feed</code> 用法都改寫）— 舊 simulation 升級時等於改寫一遍。</p>
<p><strong>Distributed execution（OSS）</strong>：OSS 沒有內建 cluster orchestration、要靠 <em>multiple injector + result aggregation</em>：每台 injector 跑同一份 simulation（按 user count 切割）、結束後把 <code>simulation.log</code> 蒐集到一處用 <code>gatling.sh</code> 重跑 report stage。常見補位是用 Kubernetes Job + 共享 PVC、或直接走 Gatling Enterprise。</p>
<p><strong>HTML report 與 release gate</strong>：simulation 跑完自動產 HTML report、含 <em>response time percentile distribution</em>（mean / p50 / p95 / p99 / max）、<em>per-request-group breakdown</em>、<em>active users over time</em>、<em>error log</em>。release gate 的標準做法是：CI job 跑 simulation → assertion gate fail 直接 break build → HTML report 存成 build artifact 供 reviewer 翻查、配合 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">Evidence Package</a> 治理。</p>
<p><strong>CI integration 模式</strong>：Jenkins / GitLab CI / GitHub Actions 都靠 <code>mvn gatling:test</code> / <code>gradle gatlingRun</code> / <code>sbt gatling:test</code> 入口、CI 設定 <em>baseline simulation</em>（每 PR 跑、catch regression）+ <em>release simulation</em>（release branch / nightly 跑、長時間 soak）。staging environment 跑壓測時要隔離噪音來源（其他 QA 流量 / cron job）、否則 RPS 數字會被污染。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Scala learning curve 拖累進度</strong>：團隊沒人會 Scala、被 implicit / case class / pattern match 卡住 — 改用 Java DSL（3.7+）或 Kotlin DSL、保留 Gatling 表達力但去除 Scala 學習成本</li>
<li><strong>Gatling 2.x → 3.x 升級 simulation 全紅</strong>：<code>bootstrap</code> import path / <code>scenario</code> builder API / <code>feed</code> 語法都變了 — 走 <em>新專案直接 3.x、舊專案維持 2.x</em> 雙軌、或安排專門 sprint 改寫、避免邊跑邊踩雷</li>
<li><strong>JVM heap OOM / GC pause 拖慢 RPS</strong>：高 RPS 下 default heap 不夠、Young Gen GC 頻繁 — 調 <code>-Xmx4G -Xms4G</code>、用 G1GC / ZGC、監控 injector 的 GC log 跟 CPU、不是只看 target service</li>
<li><strong>Injection profile 設計錯導致誤判 saturation</strong>：用 <code>atOnceUsers(1000)</code> 壓 closed model 但實際 traffic 是 open arrival、結果 knee point 找錯 — 看 production traffic shape、open model 用 <code>constantUsersPerSec</code> / <code>rampUsersPerSec</code>、closed model 才用 <code>atOnceUsers</code></li>
<li><strong>Single injector node 撞 client-side bottleneck</strong>：injector CPU / network / file descriptor / source port 用滿、看起來 target saturate 其實是 injector saturate — 監控 injector resource、scale out 成 distributed 或走 Enterprise</li>
<li><strong>Feeder data 過期 / 分布偏差</strong>：用同一份 <code>users.csv</code> 反覆壓、cache hit rate 失真、production 看不到的 cache miss 路徑沒被測 — feeder 走 <code>random</code> / <code>shuffle</code>、定期 regenerate、覆蓋 long-tail key</li>
<li><strong>HTML report 看起來綠但 production 出事</strong>：assertion gate 只設 average response time、p99 / error rate 沒設、release 後尖峰時段才爆 — assertion 要明確設 p95 / p99 + error rate threshold、不只看 mean</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>Gatling 適合回寫多步驟與多負載模型案例。它可接 <a href="/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/" data-link-title="9.C28 FanDuel：體育直播 &#43; 投注的雙重峰值" data-link-desc="FanDuel 3.5M MAU、Super Bowl 期間擴容 5-10 倍、用 AWS Local Zones &#43; Wavelength &#43; Outposts 處理 20&#43; 州的雙重峰值">9.C28 FanDuel 雙峰 workload</a> 的直播與投注雙模型、<a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek waiting room</a> 的 token / admission flow、<a href="/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/" data-link-title="9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化" data-link-desc="BookMyShow 從 15 年自建 analytics 遷移到 AWS modern data architecture、4 個月完成、分析成本下降 80%">9.C17 BookMyShow ticketing</a> 的售票流程壓力、<a href="/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/" data-link-title="9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本" data-link-desc="DraftKings 用 Aurora MySQL 跑體育博彩金融帳本、Super Bowl 流量 &#43;50% 不影響延遲">9.C4 DraftKings Aurora 金融帳本</a> 的「比賽期讀爆量 + payout 時寫爆量」雙峰錯位，以及 <a href="/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech</a> 的「投注 / 結算 / 賠率更新」三類請求 group 的 injection profile。</p>
<p>這些案例的重點是 scenario 與 injection profile。Gatling 頁引用案例時，要把業務流程拆成 request group、session state、feeder、assertion 與 stop condition — 例如 DraftKings 雙峰錯位要寫成兩個 scenario 平行注入、各自有獨立 assertion budget。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a>、<a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a>、<a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a></li>
<li>官方：<a href="https://docs.gatling.io/">Gatling documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Locust</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/locust/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/locust/</guid><description>&lt;p>Locust 的核心責任是用 Python 表達高度自訂的使用者行為與 protocol client。它適合 Python 團隊、需要自訂 client、需要 distributed worker、或 scenario 邏輯比工具內建 sampler 更複雜的壓測流程。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Locust 適合把壓測寫成一般 Python 程式。當 workload model 需要呼叫 internal SDK、特殊 protocol、複雜資料準備、狀態機、隨機行為或自訂 client、Locust 可以直接使用 Python 生態來表達。底層架構是 &lt;em>master + worker&lt;/em> 分散式 swarm、worker 之間用 Gevent green-thread（非 OS thread）模擬大量並發 user、master 負責 spawn rate、aggregation 跟 Web UI。&lt;/p>
&lt;p>這個定位讓 Locust 接到 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程&lt;/a>。它能把特殊 client 與下游 dependency 放進同一個 user behavior、但也要求團隊處理 runner、資料與可重現性。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6&lt;/a>（JS / Go runtime）比、Locust 用 Python 換到 &lt;em>自訂能力與生態相容&lt;/em>、但代價是單 worker capacity 低、CPU bound 容易先打到自己。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter&lt;/a>（GUI / XML）比、Locust 偏 &lt;em>code-first 工程團隊&lt;/em>、scenario 直接走 Git review、不靠 GUI plugin 拼裝。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling&lt;/a>（Scala DSL）比、Locust 換到 &lt;em>Python team 友善 + 既有 domain library 重用&lt;/em>、但失去 JVM injection profile 的精細度與報表內建。&lt;/p>
&lt;p>關鍵張力：&lt;em>Python 表達力&lt;/em> ↔ &lt;em>runner 效能上限&lt;/em>。Python team 想 reuse domain library、staging fixture、API client 寫壓測腳本時 Locust 是首選；但要心裡有數 &lt;em>單 worker RPS 上限不高&lt;/em>、超過幾千 RPS 就要靠 worker scale-out、不是調 Locust 本身。&lt;/p>
&lt;h2 id="適用場景">適用場景&lt;/h2>
&lt;p>Python 團隊適合用 Locust 長期維護壓測。既有 domain library、API client、fixture、資料產生器與驗證 helper 都可以被壓測腳本重用。&lt;/p>
&lt;p>自訂 protocol 適合用 Locust。HTTP 之外、如果服務需要 gRPC、WebSocket、binary protocol、message broker client 或自家 SDK、Locust 可以直接接 Python library。&lt;/p>
&lt;p>Distributed load 適合用 Locust worker 擴展。當單機 Python runner 遇到 CPU 或 connection bottleneck、可以用 master / worker 拆開負載產生能力。&lt;/p></description><content:encoded><![CDATA[<p>Locust 的核心責任是用 Python 表達高度自訂的使用者行為與 protocol client。它適合 Python 團隊、需要自訂 client、需要 distributed worker、或 scenario 邏輯比工具內建 sampler 更複雜的壓測流程。</p>
<h2 id="服務定位">服務定位</h2>
<p>Locust 適合把壓測寫成一般 Python 程式。當 workload model 需要呼叫 internal SDK、特殊 protocol、複雜資料準備、狀態機、隨機行為或自訂 client、Locust 可以直接使用 Python 生態來表達。底層架構是 <em>master + worker</em> 分散式 swarm、worker 之間用 Gevent green-thread（非 OS thread）模擬大量並發 user、master 負責 spawn rate、aggregation 跟 Web UI。</p>
<p>這個定位讓 Locust 接到 <a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a> 與 <a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a>。它能把特殊 client 與下游 dependency 放進同一個 user behavior、但也要求團隊處理 runner、資料與可重現性。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a>（JS / Go runtime）比、Locust 用 Python 換到 <em>自訂能力與生態相容</em>、但代價是單 worker capacity 低、CPU bound 容易先打到自己。跟 <a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a>（GUI / XML）比、Locust 偏 <em>code-first 工程團隊</em>、scenario 直接走 Git review、不靠 GUI plugin 拼裝。跟 <a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a>（Scala DSL）比、Locust 換到 <em>Python team 友善 + 既有 domain library 重用</em>、但失去 JVM injection profile 的精細度與報表內建。</p>
<p>關鍵張力：<em>Python 表達力</em> ↔ <em>runner 效能上限</em>。Python team 想 reuse domain library、staging fixture、API client 寫壓測腳本時 Locust 是首選；但要心裡有數 <em>單 worker RPS 上限不高</em>、超過幾千 RPS 就要靠 worker scale-out、不是調 Locust 本身。</p>
<h2 id="適用場景">適用場景</h2>
<p>Python 團隊適合用 Locust 長期維護壓測。既有 domain library、API client、fixture、資料產生器與驗證 helper 都可以被壓測腳本重用。</p>
<p>自訂 protocol 適合用 Locust。HTTP 之外、如果服務需要 gRPC、WebSocket、binary protocol、message broker client 或自家 SDK、Locust 可以直接接 Python library。</p>
<p>Distributed load 適合用 Locust worker 擴展。當單機 Python runner 遇到 CPU 或 connection bottleneck、可以用 master / worker 拆開負載產生能力。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本頁、讀者能判斷：</p>
<ol>
<li>Locust 在壓測 stack 中承擔哪一段（user behavior modeling / load generation / distributed swarm）、哪些要外接（<a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">Prometheus / Grafana</a> 觀測 worker 自身、APM 看目標 saturation）</li>
<li>User class / task weight / on_start lifecycle 的 ownership 設計（誰寫 locustfile、誰 review、誰調 spawn rate）</li>
<li>Distributed master-worker 部署的容量規劃（單 worker user 上限、worker 數量計算、target RPS 對應 worker count）</li>
<li>何時用 Locust、何時走 k6 / JMeter / Gatling 的取捨</li>
</ol>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Locust 壓測是否健康、最少看四件事：</p>
<ul>
<li><strong>User class 設計</strong>：每個 <code>HttpUser</code> / <code>User</code> subclass 是不是一個明確的 <em>persona</em>（mobile user / API client / admin user）、<code>wait_time</code> 是否反映真實使用者間隔（不是 0 拼最大 RPS、是 <code>between(1, 5)</code> 模擬 think time）、user state 是否在 instance 內封閉</li>
<li><strong>Task 比例</strong>：<code>@task(weight)</code> 數字是否對應 production traffic mix（80% read / 15% write / 5% admin、不是每個 endpoint 等比例）、weight 是否走版控 review</li>
<li><strong>on_start lifecycle</strong>：login / token fetch / session bootstrap 是否寫在 <code>on_start</code>（每個 user 一次）、不是寫在 <code>@task</code> 裡（每個 request 都重做）— 寫錯位置會讓 auth endpoint 變成主要 traffic</li>
<li><strong>Distributed master-worker</strong>：worker 數量是否夠（單 worker 跑幾千 user 後 CPU 會先打死、不是目標服務先死）、master 是否獨立機器（master 也跑 user 時 aggregation 跟 Web UI 會卡）、<code>--expect-workers</code> 是否設、worker sync drift 是否觀察</li>
</ul>
<p>四件事任一缺失、就是壓測證據可信度的待補項目。</p>
<h2 id="日常操作與決策形狀">日常操作與決策形狀</h2>
<p><strong>locustfile 結構</strong>：locustfile.py 是 Python module、定義 <code>User</code> / <code>HttpUser</code> subclass、每個 user 有 <code>wait_time</code>、若干 <code>@task(weight)</code> method、<code>on_start</code> / <code>on_stop</code> lifecycle hook。執行用 <code>locust -f locustfile.py --host=https://target</code> 起 Web UI、或 <code>locust --headless -u 1000 -r 100 -t 10m</code> 在 CI 跑無 UI 模式。locustfile 應該走 Git review、不是 GUI 改完就跑。</p>
<p><strong>Task weight / wait_time 設計</strong>：weight 是 <em>相對權重</em>、不是百分比 —<code>@task(8)</code> + <code>@task(2)</code> 等於 80% / 20%。<code>wait_time = between(1, 5)</code> 在每個 task 之間等 1-5 秒、模擬 think time；若要拚最大 RPS 用 <code>constant(0)</code>、但同時要意識到這就不是 user behavior 模型、是 <em>throughput probe</em>。</p>
<p><strong>on_start vs @task 的邊界</strong>：<code>on_start(self)</code> 每個 user instance 啟動時跑一次、適合做 login、token fetch、cache warm、fixture lookup；<code>@task</code> 是 user 行為主迴圈、每次選一個 task 跑。把 login 寫在 <code>@task</code> 是常見錯誤、會讓 IdP 變成主壓力來源、不是目標 API。</p>
<p><strong>Gevent-based concurrency</strong>：Locust 用 <a href="https://www.gevent.org/">gevent</a> 的 green-thread 模擬大量 concurrent user、不是 OS thread。意義是單 worker 可以跑幾千個 <em>user</em>、但 CPU bound 工作（JSON serialization、加密、本地計算）會 <em>blocking</em> 整個 worker 的 event loop。<code>gevent.monkey.patch_all()</code> 要在 import 第一行、否則 socket / time / ssl 不會被 patch、blocking call 會卡死 swarm。</p>
<p><strong>Distributed master-worker</strong>：單機到極限時開 distributed — <code>locust --master</code> 起 master、<code>locust --worker --master-host=master.example.com</code> 起 worker。Master 負責 Web UI、spawn rate 控制、result aggregation、stat 收集；worker 負責跑 user。Master 不該跑 user（會跟 aggregation 搶 CPU、stat 失真）。worker 數量計算：先單 worker 拉到 CPU 80% 看能撐多少 user、目標 user 數除這個值 + 20% buffer。</p>
<p><strong>Custom load shape</strong>：除了固定 <code>-u 1000</code>、Locust 支援 <code>LoadTestShape</code> subclass 寫 <em>時間軸負載曲線</em> — spike test（瞬間 0 → 5000 user）、ramp test（線性爬升）、wave test（週期性高低交替）、step test（階梯式增加）。<code>tick()</code> method 每秒回傳 <code>(user_count, spawn_rate)</code>。用 custom shape 才能模擬 <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek waiting room</a> 那種 ticket drop 瞬間衝擊。</p>
<p><strong>Prometheus exporter / 觀測</strong>：Locust 內建 stat 只是 in-memory 的 p50 / p95 / p99 / RPS、結束就消失。長期觀測接 <a href="https://github.com/ContainerSolutions/locust_exporter">locust-prometheus-exporter</a>（或 <code>--csv result.csv</code> 自己抓）、把 metric 推到 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">Prometheus</a> + Grafana。<strong>worker 自身的 CPU / memory / network</strong> 一定要同時觀測、不然分不出是目標 saturation 還是 worker 已死。</p>
<p><strong>Locust Cloud（managed SaaS）</strong>：2024 後 Locust 推官方 <a href="https://docs.locust.cloud/">Locust Cloud</a>、託管 master + worker + result storage、付費換 ops 成本。自管 master-worker 對 CI / staging 是合理的；production 等級的 scale test（10k+ concurrent user）跑一次要拉幾十台 worker、用 Cloud 省 infra ops 是合理 trade-off。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Locust</th>
          <th>k6</th>
          <th>JMeter</th>
          <th>Gatling</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>腳本語言</td>
          <td>Python（generic）</td>
          <td>JavaScript (k6 runtime)</td>
          <td>XML / GUI / Groovy</td>
          <td>Scala DSL（也支援 Java / Kotlin）</td>
      </tr>
      <tr>
          <td>Runtime</td>
          <td>Python + Gevent green-thread</td>
          <td>Go-based、單 binary、低 overhead</td>
          <td>JVM、heavy</td>
          <td>JVM、async actor model</td>
      </tr>
      <tr>
          <td>單 worker capacity</td>
          <td>中低（Python overhead、千級 user）</td>
          <td>高（Go runtime、萬級 VU 單機）</td>
          <td>中（JVM tuning 後可用）</td>
          <td>高（Akka actor、效能好）</td>
      </tr>
      <tr>
          <td>Distributed mode</td>
          <td>內建 master-worker</td>
          <td>內建 k6 Cloud / k6 Operator</td>
          <td>內建 master-slave</td>
          <td>Gatling Enterprise（前 FrontLine）</td>
      </tr>
      <tr>
          <td>User behavior 彈性</td>
          <td>高 — 一般 Python、任意 library</td>
          <td>中 — JS 但 k6 runtime 受限</td>
          <td>中 — GUI 拼裝 + plugin</td>
          <td>中高 — Scala DSL 表達 simulation</td>
      </tr>
      <tr>
          <td>Custom protocol</td>
          <td>強 — 接任何 Python library</td>
          <td>強 — 有 gRPC / WS / Kafka extension</td>
          <td>強但繁瑣 — plugin 生態廣</td>
          <td>中 — 主要 HTTP / WS</td>
      </tr>
      <tr>
          <td>CI / headless</td>
          <td><code>--headless</code> 支援</td>
          <td>CI-first design</td>
          <td>non-GUI mode 支援</td>
          <td>內建支援</td>
      </tr>
      <tr>
          <td>Report / UI</td>
          <td>Web UI 即時 + CSV 匯出</td>
          <td>k6 Cloud / Grafana / 簡 stdout</td>
          <td>GUI listener / HTML report</td>
          <td>HTML report 內建、視覺豐富</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>緩（Python team）/ 陡（非 Python）</td>
          <td>中 — JS-style scripting</td>
          <td>緩（GUI）/ 陡（深度 tuning）</td>
          <td>陡 — Scala 語法</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>Python team + 自訂 behavior / client</td>
          <td>DevOps + CI / 標準 HTTP / 高 RPS 單機</td>
          <td>非工程角色協作 / legacy enterprise</td>
          <td>JVM team + 精細 injection profile</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>低 — Python 腳本可移植</td>
          <td>中 — k6 runtime 綁定</td>
          <td>中 — XML jmx 不易他移</td>
          <td>中 — Scala DSL 綁定</td>
      </tr>
  </tbody>
</table>
<p>選 Locust 的核心訴求：<em>Python team + custom user behavior + 既有 domain library 重用</em>、且能投入 worker scale-out 預算（單 worker capacity 低、要靠分散式補）+ scenario 走 Git review 不靠 GUI。標準 HTTP 高 RPS 單機壓測直接走 k6 更快、非工程角色協作壓測走 JMeter、JVM team 精細模擬走 Gatling。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Distributed Locust 的 master-worker swarm</strong>：production scale test 通常需要 10-100 個 worker。實作要點：worker 之間 <em>不要</em> 共享 state、shared resource 由 master 統一發（用 <a href="https://zeromq.org/">zeromq</a> message bus）；worker 加入 / 離開時 user 會 redistribute、避免 user index 當 unique key；worker 跨 region 跑時 <em>latency 來自 worker → target 不只是 target 內部</em>、要在 worker 本身的 region 對齊。</p>
<p><strong>Custom load shape（spike / wave / step）</strong>：<code>LoadTestShape.tick(self)</code> return <code>(user_count, spawn_rate)</code> tuple 每秒被叫一次。Spike test：前 60 秒 0 user、第 61 秒瞬間衝 5000、模擬 <a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek waiting room</a> 的 admission storm。Wave test：sine wave 在 1000-3000 user 之間振盪、測 autoscaling 反應速度。Step test：每 5 分鐘加 1000 user、觀察哪一階開始降級。custom shape 是 Locust 比 k6 強的點之一。</p>
<p><strong>跟 Prometheus exporter 整合</strong>：locust-prometheus-exporter 把 Locust stat 推到 Prometheus / Grafana、做長期 baseline、跨 test 比較、p99 退化偵測。實務上要在 dashboard 同時放 <em>Locust 內部 stat</em> + <em>worker host metric</em> + <em>目標服務 APM</em>、三層 stack 起來才能判讀是 runner 還是目標 saturation。</p>
<p><strong>Locust Cloud（managed SaaS）</strong>：2024+ 官方 SaaS、託管 master + worker + result + dashboard。trade-off：自管適合 CI / staging / 內網壓測（target 跑在內網時 Cloud 連不到）；Cloud 適合大規模一次性 scale test（拉 50 worker 跑 2 小時、跑完即停、不想自己 infra ops）。</p>
<h2 id="操作成本">操作成本</h2>
<p>Locust 的主要成本是 runner overhead 與分散式治理。Python runner 的效能上限要用 worker scale-out 解決；壓測結論要同時檢查目標服務 saturation 與 worker 本身 CPU、connection、network 是否已成瓶頸。</p>
<p>腳本工程成本來自自由度。Python 可以很快寫出複雜行為、也容易把測試資料、randomness、side effect、sleep 與 exception handling 寫散；團隊要維持 scenario structure、fixture、logging 與 artifact 標準。</p>
<p>自訂 client 成本來自校正。使用 SDK 或 custom protocol client 時、要確認 client retry、timeout、connection pool 與 serialization 行為是否接近 production、避免 runner 模擬出不存在的壓力形狀。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Worker CPU 100% 但目標服務閒</strong>：Python runner 先死、不是 target saturation — 加 worker 數量、或檢查 task 裡有沒有 CPU bound 的本地計算（大 JSON parse、加密、本地 fixture 生成）擠掉 event loop</li>
<li><strong>Gevent monkey-patch gotcha</strong>：<code>requests</code> / <code>psycopg2</code> / 自家 SDK 在第三方 library 內部 blocking call、整個 worker 卡住 — <code>gevent.monkey.patch_all()</code> 一定要寫在 import 第一行；無法 patch 的 C extension（如 native MySQL driver）改用 gevent-friendly client</li>
<li><strong>RPS 達不到目標 / 看起來像 target 慢</strong>：實際是 worker connection pool 耗盡、或 worker 本身網卡飽和 — 觀測 worker 本身的 TCP socket 數、netstat ESTABLISHED、network throughput；不要直接 blame target</li>
<li><strong>Distributed sync drift</strong>：worker 之間 user count 不平均、aggregation 顯示 RPS 抖動 — <code>--expect-workers=N</code> 確認 master 等所有 worker join 才開測；worker 跨 region 時 message bus latency 也會影響 sync</li>
<li><strong>on_start 在 @task 裡跑</strong>：壓測啟動瞬間打爆 auth endpoint、看到 IdP latency 飆高以為是 target — 把 login / token fetch 移到 <code>on_start</code>、每個 user 只做一次</li>
<li><strong>wait_time = 0 拼最大 RPS 結果結論奇怪</strong>：這已經不是 user behavior 是 throughput probe、p99 跟 production 對不上 — 改成 <code>between(1, 5)</code> 模擬 think time 或寫 custom shape</li>
<li><strong>Web UI 卡 / master CPU 100%</strong>：master 同時在跑 user + aggregation — <code>locust --master</code> 跟 worker 拆機器、master 不跑 user</li>
</ul>
<h2 id="何時改走其他服務">何時改走其他服務</h2>
<table>
  <thead>
      <tr>
          <th>需求形狀</th>
          <th>改走</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>標準 HTTP / 高 RPS 單機 / CI-first</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a></td>
      </tr>
      <tr>
          <td>非工程角色協作 / GUI 拼裝</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a></td>
      </tr>
      <tr>
          <td>JVM team / 精細 injection profile</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a></td>
      </tr>
      <tr>
          <td>極簡 HTTP probe / 命令列 one-shot</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/vegeta/" data-link-title="Vegeta" data-link-desc="用簡潔 CLI 與固定 rate HTTP attack 快速探測 latency、throughput 與 saturation 的效能工程工具">Vegeta</a></td>
      </tr>
      <tr>
          <td>Production traffic replay / shadow</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay</a> / <a href="/blog/backend/09-performance-capacity/vendors/service-mesh-mirroring/" data-link-title="Service Mesh Mirroring" data-link-desc="用 sidecar / proxy 層 mirror production traffic 到新版本或 shadow service 的 production validation 方式">Service Mesh Mirroring</a></td>
      </tr>
      <tr>
          <td>壓測結果回寫到效能工程 lifecycle</td>
          <td><a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a>、<a href="/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型</a></td>
      </tr>
  </tbody>
</table>
<h2 id="不在本頁內的主題">不在本頁內的主題</h2>
<ul>
<li>locustfile 完整語法 reference、<code>User</code> 跟 <code>HttpUser</code> 的 attribute 細節</li>
<li>Locust Cloud 計費跟 quota 細節（看官方 docs）</li>
<li>gevent 跟 asyncio 的取捨（Locust 選了 gevent、不在本頁討論替代）</li>
<li>壓測證據怎麼歸檔（看 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">9.7 evidence package</a> 通則）</li>
</ul>
<h2 id="evidence-package">Evidence Package</h2>
<p>Locust 結果應回寫到 evidence package。最小欄位包括 locustfile version、user class、task weight、spawn rate、worker count、client library version、target environment、p95 / p99、error rate、throughput、target saturation metric、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Locust 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>locustfile、CSV / JSON result、dashboard link</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>test start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>APM / metrics / logs 查詢連結</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>user behavior coverage、fixture freshness</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>worker capacity、client realism</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>worker bottleneck、custom client 偏差、資料偏差</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是區分目標瓶頸與 runner 瓶頸。Locust 分散式測試要同時保存 worker 數量、worker 資源、spawn rate 與 client behavior、讓 reviewer 知道壓力是否真的打到目標服務。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>Locust 適合回寫需要高度自訂 user behavior 的案例。它可接 <a href="/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/" data-link-title="9.C28 FanDuel：體育直播 &#43; 投注的雙重峰值" data-link-desc="FanDuel 3.5M MAU、Super Bowl 期間擴容 5-10 倍、用 AWS Local Zones &#43; Wavelength &#43; Outposts 處理 20&#43; 州的雙重峰值">9.C28 FanDuel 雙峰 workload</a> 的投注行為模型、<a href="/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16 SeatGeek waiting room</a> 的 admission / token flow、<a href="/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26 PayPay mobile payment messaging</a> 的外部推送與下游 quota 模擬、<a href="/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/" data-link-title="9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量" data-link-desc="Pokémon GO 上線時實際流量達原始預估 50 倍、Google CRE 怎麼即時補容量">9.C8 Niantic Pokémon GO 50x surge</a> 的玩家移動 + 互動混合行為、以及 <a href="/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/" data-link-title="9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發" data-link-desc="Zoom 在 2020 年 COVID 爆發時、日活從 1000 萬衝到 3 億、用 DynamoDB 撐住會議後端">9.C18 Zoom COVID 30x surge</a> 的會議建立 / 加入 / 離開行為混合。</p>
<p>這些案例的重點是 domain behavior。Locust 頁引用案例時、要把 case 轉成 user class、task weight、custom client、downstream mock 與 worker capacity、再把總 RPS 放回這些行為條件下判讀 — 例如 Pokémon GO 玩家行為跟一般 web user 完全不同（持續 GPS 上報 + 偶發互動）、不能直接用 HTTP RPS 衡量；SeatGeek waiting room 要寫 <code>LoadTestShape</code> 模擬 ticket drop 瞬間衝擊、不是穩態 RPS。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/workload-modeling/" data-link-title="9.2 Workload Modeling" data-link-desc="把 production traffic shape 翻成可重播的壓測模型">9.2 Workload Modeling</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/load-test-tooling/" data-link-title="9.3 壓測工具選型" data-link-desc="k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的工程選型">9.3 壓測工具選型</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a>、<a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a>、<a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a>、<a href="/blog/backend/09-performance-capacity/vendors/vegeta/" data-link-title="Vegeta" data-link-desc="用簡潔 CLI 與固定 rate HTTP attack 快速探測 latency、throughput 與 saturation 的效能工程工具">Vegeta</a></li>
<li>跨類：<a href="/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay</a>（production traffic replay 替代 synthetic load）</li>
<li>跨模組：<a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">4 Observability</a>（worker 自身 + 目標 APM 雙觀測）</li>
<li>官方：<a href="https://docs.locust.io/">Locust documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Vegeta</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vegeta/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vegeta/</guid><description>&lt;p>Vegeta 的核心責任是用簡潔 CLI 對 HTTP endpoint 產生固定 rate 負載，快速探測 latency、throughput、error rate 與 saturation。它適合單一 endpoint、少量 header / body 變化、快速 baseline、incident 後驗證與工程師本機或 CI 中的輕量壓測。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Vegeta 是 Go 寫的 HTTP load testing CLI，核心模型是 &lt;em>constant rate attack&lt;/em>：指定「每秒 N 個 request」就持續打 N rps、不會因 server 變慢就降速，跟「fire-and-wait」型工具（hey / wrk 預設 closed-loop）行為差異很大。constant rate 是 &lt;em>open-loop&lt;/em> 模型 — 模擬真實流量「不會因服務慢而減少」的行為、所以 saturation 點才會明確浮現。&lt;/p>
&lt;p>Vegeta 是 Unix philosophy CLI：targets 從 stdin 讀（可以 pipe 進複雜 generator）、binary report 從 stdout 出（可以 pipe 進 &lt;code>vegeta report&lt;/code> / &lt;code>vegeta plot&lt;/code> / &lt;code>vegeta encode&lt;/code>）。這個設計讓 Vegeta 容易跟 shell pipeline / CI script 接合、但同時也決定它不適合表達多步驟 session。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6&lt;/a> 比、Vegeta 走 &lt;em>CLI-first + open-loop constant rate&lt;/em>、k6 走 &lt;em>JS scenario + threshold + CI artifact&lt;/em>。Vegeta 適合「我要對這個 URL 打 200 rps 60 秒」的一次性壓測、k6 適合「我有 3 種 user journey、各占 40/30/30%、跑 ramp-up profile」的可維護 scenario。跟 hey 比、Vegeta 的 constant rate 是真的 open-loop、hey 的 &lt;code>-q&lt;/code> 是 per-worker rate（worker 變慢整體就降速）— 探測 saturation 時 Vegeta 比較誠實。跟 wrk / wrk2 比、Vegeta 沒有 LuaJIT 那麼極致的單機壓測效能、但 binary report + &lt;code>vegeta plot&lt;/code> + targets pipe 對日常工程師工作流更友善。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本頁、讀者能判斷：&lt;/p>
&lt;ol>
&lt;li>何時用 Vegeta、何時走 k6 / hey / wrk / Gatling / Locust 的取捨&lt;/li>
&lt;li>constant rate attack 的設計意涵（open-loop vs closed-loop、為什麼這對 saturation discovery 重要）&lt;/li>
&lt;li>target file / rate / duration / report 四件套的 baseline workflow 跟 evidence package 對應&lt;/li>
&lt;li>排錯時的常見陷阱：runner 端 TCP socket exhaust、open file limit、constant rate 跟 target server 限速 disconnect&lt;/li>
&lt;/ol>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>Vegeta 適合快速回答「這個 endpoint 在某個 rate 下表現如何」。當團隊需要先找出大概 knee point、驗證一個修補是否降低 latency、或在 CI 裡跑小型 performance smoke test，Vegeta 的 CLI workflow 很直接。&lt;/p></description><content:encoded><![CDATA[<p>Vegeta 的核心責任是用簡潔 CLI 對 HTTP endpoint 產生固定 rate 負載，快速探測 latency、throughput、error rate 與 saturation。它適合單一 endpoint、少量 header / body 變化、快速 baseline、incident 後驗證與工程師本機或 CI 中的輕量壓測。</p>
<h2 id="服務定位">服務定位</h2>
<p>Vegeta 是 Go 寫的 HTTP load testing CLI，核心模型是 <em>constant rate attack</em>：指定「每秒 N 個 request」就持續打 N rps、不會因 server 變慢就降速，跟「fire-and-wait」型工具（hey / wrk 預設 closed-loop）行為差異很大。constant rate 是 <em>open-loop</em> 模型 — 模擬真實流量「不會因服務慢而減少」的行為、所以 saturation 點才會明確浮現。</p>
<p>Vegeta 是 Unix philosophy CLI：targets 從 stdin 讀（可以 pipe 進複雜 generator）、binary report 從 stdout 出（可以 pipe 進 <code>vegeta report</code> / <code>vegeta plot</code> / <code>vegeta encode</code>）。這個設計讓 Vegeta 容易跟 shell pipeline / CI script 接合、但同時也決定它不適合表達多步驟 session。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a> 比、Vegeta 走 <em>CLI-first + open-loop constant rate</em>、k6 走 <em>JS scenario + threshold + CI artifact</em>。Vegeta 適合「我要對這個 URL 打 200 rps 60 秒」的一次性壓測、k6 適合「我有 3 種 user journey、各占 40/30/30%、跑 ramp-up profile」的可維護 scenario。跟 hey 比、Vegeta 的 constant rate 是真的 open-loop、hey 的 <code>-q</code> 是 per-worker rate（worker 變慢整體就降速）— 探測 saturation 時 Vegeta 比較誠實。跟 wrk / wrk2 比、Vegeta 沒有 LuaJIT 那麼極致的單機壓測效能、但 binary report + <code>vegeta plot</code> + targets pipe 對日常工程師工作流更友善。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本頁、讀者能判斷：</p>
<ol>
<li>何時用 Vegeta、何時走 k6 / hey / wrk / Gatling / Locust 的取捨</li>
<li>constant rate attack 的設計意涵（open-loop vs closed-loop、為什麼這對 saturation discovery 重要）</li>
<li>target file / rate / duration / report 四件套的 baseline workflow 跟 evidence package 對應</li>
<li>排錯時的常見陷阱：runner 端 TCP socket exhaust、open file limit、constant rate 跟 target server 限速 disconnect</li>
</ol>
<h2 id="定位">定位</h2>
<p>Vegeta 適合快速回答「這個 endpoint 在某個 rate 下表現如何」。當團隊需要先找出大概 knee point、驗證一個修補是否降低 latency、或在 CI 裡跑小型 performance smoke test，Vegeta 的 CLI workflow 很直接。</p>
<p>這個定位讓 Vegeta 接到 <a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a> 與 <a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a>。它提供的是快速壓力探針，後續若要表達複雜 workload model，通常要轉向 k6、Gatling、Locust 或 JMeter。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷一次 Vegeta 壓測是否有效、最少看四件事：</p>
<ul>
<li><strong>Target 描述完整性</strong>：targets file 是否包含 method / URL / headers / body、是否反映真實 request shape（含 auth header、content-type、representative payload size），缺一就會讓壓測結果偏離正式環境</li>
<li><strong>Rate model 設計</strong>：選的是 constant rate（<code>-rate=200/s</code>）還是 ramp（用多段 attack pipe），constant rate 適合 saturation probe、ramp-up 要 wrap script 自己 stage、Vegeta 沒有原生 ramp profile</li>
<li><strong>Report 解讀</strong>：<code>vegeta report</code> 給 mean / p50 / p95 / p99 / max latency + success rate + throughput，重點看 <em>p99 跟 max 的距離</em> 與 <em>requested rate vs actual throughput</em> 是否 disconnect — disconnect 表示 server / runner 端有人在限速</li>
<li><strong>Duration vs warm-up</strong>：短 duration（&lt; 30s）容易吃到 JIT / cache / connection pool warm-up 噪音，baseline 壓測 duration 至少 60s、且第一段 result 要 discard，否則 p99 會被前 5s 拉高</li>
</ul>
<h2 id="適用場景">適用場景</h2>
<p>單 endpoint saturation probe 是 Vegeta 的主要入口。工程師可以對 login、search、read API、feature flag endpoint 或 internal health-like endpoint 施加固定 rate，觀察 p95 / p99 與 error rate 何時開始上升。</p>
<p>Regression smoke test 適合用 Vegeta。CI 或 pre-release 可以用短時間固定 rate 測試，確認 hot path 沒有明顯退化，再把更完整的 scenario 交給 k6、Gatling 或 Locust。</p>
<p>Incident 後修補驗證適合用 Vegeta。當事故根因是某個 endpoint 的 query、cache miss、lock contention 或 timeout，修補後可以用相同 request set 重跑，快速比較 latency distribution。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Vegeta 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CLI 簡潔</td>
          <td>本機、CI、shell workflow 容易接</td>
          <td>長期報表與 artifact 標準化</td>
      </tr>
      <tr>
          <td>固定 rate</td>
          <td>探測 rate / latency 關係清楚</td>
          <td>複雜使用者行為與 arrival pattern</td>
      </tr>
      <tr>
          <td>HTTP 導向</td>
          <td>API hot path 快速驗證</td>
          <td>非 HTTP protocol 與 multi-step flow</td>
      </tr>
      <tr>
          <td>快速 probe</td>
          <td>適合 smoke test 與修補驗證</td>
          <td>完整 workload model 與資料治理</td>
      </tr>
  </tbody>
</table>
<p>CLI 簡潔價值來自低摩擦。當問題還在定位階段，工程師可以很快產生可重跑 command 與 target file，先取得 baseline，再決定是否需要完整壓測平台。</p>
<p>固定 rate 價值來自可比較。用相同 request set、rate、duration 與 target environment 重跑，可以讓修補前後的 latency distribution 有清楚對照。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>Vegeta 和 k6 的主要差異是 scenario 深度。Vegeta 適合固定 rate HTTP probe；k6 適合多步驟 scenario、threshold、CI artifact 與 browser-style flow。</p>
<p>Vegeta 和 JMeter 的主要差異是工具重量。Vegeta 適合快速 CLI；JMeter 適合 GUI、多 protocol、plugin 與企業測試資產。</p>
<p>Vegeta 和 Gatling 的主要差異是長期維護模式。Vegeta 用 command / target file 保持簡單；Gatling 用 simulation 維護複雜 flow 與 injection profile。</p>
<p>Vegeta 和 Locust 的主要差異是自訂能力。Locust 適合 Python user behavior 與 custom client；Vegeta 適合 HTTP endpoint 的直接壓力測量。</p>
<h2 id="操作成本">操作成本</h2>
<p>Vegeta 的主要成本是 workload coverage 有限。它能快速測 endpoint，但多步驟 session、資料依賴、payment mock、queue side effect 與 realistic user journey 需要額外工具或腳本補上。</p>
<p>Artifact 成本來自命令可追溯性。每次測試要保存 rate、duration、targets、headers、body、環境、版本與結果檔；否則快速 probe 很容易變成不可比較的一次性觀察。</p>
<p>Runner 成本通常較低，但仍要檢查本機瓶頸。高 rate 測試時，產生負載的機器也可能先被 CPU、network、file descriptor 或 connection limit 卡住。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Vegeta 結果應回寫到 evidence package。最小欄位包括 command、target file hash、rate、duration、workers、target environment、p95 / p99、max latency、error rate、throughput、target saturation metric、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Vegeta 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>command、targets file、binary result、report</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>test start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>APM / metrics / logs 查詢連結</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>target set freshness、header / body correctness</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>runner capacity、endpoint representativeness</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未覆蓋多步驟 flow、資料偏差、runner limit</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓快速測試可以比較。Vegeta 的結果通常很短，反而更需要保存 command 與 target set，讓下一次修補驗證能跑同一組條件。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Vegeta</th>
          <th>k6</th>
          <th>hey</th>
          <th>wrk / wrk2</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>負載模型</td>
          <td>Open-loop constant rate（rps 不隨 latency 降）</td>
          <td>Open-loop（k6 default）/ closed-loop（VU mode）</td>
          <td>Per-worker rate（closed-loop 傾向）</td>
          <td>wrk closed-loop / wrk2 open-loop</td>
      </tr>
      <tr>
          <td>Scenario 深度</td>
          <td>單 endpoint pipe target、多 endpoint 需 script</td>
          <td>JS script、多步驟、staging / threshold / SLO 內建</td>
          <td>單一 URL CLI flag</td>
          <td>Lua script 可寫複雜邏輯但 idiom 較陡</td>
      </tr>
      <tr>
          <td>輸出形式</td>
          <td>Binary stream + <code>vegeta report/plot/encode</code></td>
          <td>stdout summary + JSON + 內建 dashboard</td>
          <td>stdout 文字 summary</td>
          <td>stdout 文字 summary、HdrHistogram</td>
      </tr>
      <tr>
          <td>CI 整合</td>
          <td>用 shell 包、自寫 threshold gate</td>
          <td>內建 threshold / exit code、CI artifact 標準化</td>
          <td>簡單 smoke、無 threshold</td>
          <td>需自寫 wrapper</td>
      </tr>
      <tr>
          <td>學習成本</td>
          <td>低 — 幾個 flag 就上手</td>
          <td>中 — 要寫 JS scenario</td>
          <td>極低 — 一行 CLI</td>
          <td>中 — Lua 加 HdrHistogram 概念</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>修補驗證、CI smoke、saturation probe</td>
          <td>完整壓測平台、SLO gate、多 scenario</td>
          <td>一次性 ad-hoc 探測</td>
          <td>極致單機壓測效能、低 overhead 量測</td>
      </tr>
  </tbody>
</table>
<p>選 Vegeta 的核心訴求：<em>工程師本機 / CI smoke / 修補驗證 / saturation probe</em> 都要快速可重跑、且結果要可以保存比較；不需要完整 scenario 模型也不需要 GUI 報表。若團隊需要完整 user journey、threshold / SLO gate、長期 trend dashboard，直接走 <a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a> 或 <a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a>。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Reporting 多輸出 format</strong>：<code>vegeta report</code> 預設 text summary、加 <code>-type=hist[0,10ms,50ms,100ms,500ms]</code> 給 latency bucket histogram、<code>-type=json</code> 給機器可讀 result、<code>vegeta plot</code> 出 HTML latency chart、<code>vegeta encode -to=csv</code> 轉成可進 spreadsheet / dashboard 的 CSV。binary result 檔可重複 decode 成不同 format，不用重跑壓測。修補驗證的標準作法是保留 <code>results.bin</code>、之後可隨時 re-render report。</p>
<p><strong>Pipe attack workflow</strong>：Vegeta 的 stdin/stdout 都是 stream — 可以用 shell pipe 串接 <code>jq</code> 動態產 targets（<code>jq -r '.urls[] | &quot;GET &quot; + .'</code>）、用 <code>vegeta attack | tee results.bin | vegeta report</code> 同時寫檔跟即時看 summary、用 <code>cat results-old.bin results-new.bin | vegeta report</code> 比較兩次結果。這個設計讓 Vegeta 跟 incident drill / chaos test script 容易接合 — 修補 deploy 完跑一次 attack、result 直接 commit 進 git 當 evidence。</p>
<p><strong>CI integration pattern</strong>：CI 裡 Vegeta 沒有 k6 那種內建 threshold，要自寫 gate — <code>vegeta report -type=json results.bin | jq '.latencies.p99'</code> 出 p99、bash 比較 budget、超標 exit 非零。把 <code>targets.txt</code> + <code>attack.sh</code> + <code>expected-budget.json</code> commit 進 repo、CI artifact 上傳 <code>results.bin</code> + <code>plot.html</code>，下次 regression 時可以 diff。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Requested rate 跟 actual throughput disconnect（要 200rps 實際只跑 80rps）</strong>：runner 端先飽和、不是 server 飽和 — 看 <code>vegeta attack</code> stderr 是否報 <code>socket: too many open files</code>、檢查 <code>ulimit -n</code>（生產壓測 runner 至少設 65535）；或 server 端有限速 / rate limit / connection cap 把 request reject 在 TCP 層、Vegeta 看不到完整 response 就被卡</li>
<li><strong>TCP socket exhaust（runner 端）</strong>：constant rate 模型下、若 server 回應慢、connection 會堆積、<code>TIME_WAIT</code> socket 爆 ephemeral port range — 用 <code>-keepalive=true</code>（預設）並調 <code>net.ipv4.tcp_tw_reuse=1</code>、或加 <code>-connections=N</code> 限制 connection pool 上限避免無限堆 socket</li>
<li><strong>p99 / max latency 異常高、但 server-side metrics 看不到</strong>：runner 端 GC pause / CPU steal / network jitter 把 latency 量測污染 — 把 runner 移到跟 target 同 placement group / same AZ、確認 runner CPU 沒被其他 process 搶、duration 拉長到 5min 讓 outlier 變稀釋</li>
<li><strong>Success rate 100% 但 server 已經爆</strong>：targets 沒帶 auth header / 打到 LB 而非 backend、所有 request 在前面就 200 / cache hit、server 根本沒收到壓力 — 檢查 target server access log 的 request count 跟 Vegeta requested rate 是否對得上</li>
<li><strong>短時間壓測結果不穩定（同 command 跑兩次差很多）</strong>：duration 太短（&lt; 30s）、warm-up 噪音占比太高 — 至少 60s、第一段 5-10s discard、若 endpoint 有 lazy initialization（cache / connection pool / JIT compile）先跑一段 warm-up attack 再正式量</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>Vegeta 適合回寫單 endpoint hot path 與修補驗證案例。它可接 <a href="/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/" data-link-title="9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計" data-link-desc="為什麼 Coinbase 國際交易所選 Cluster Placement Group &#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase ultra-low latency</a> 的 sub-millisecond latency distribution 判讀、<a href="/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/" data-link-title="9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99" data-link-desc="Tubi 把 ML 推薦的 feature store 從 ScyllaDB 遷到 ElastiCache for Redis、99 百分位延遲降到 10ms 以下">9.C25 Tubi feature store</a> 的 p99 &lt; 10ms lookup 驗證、<a href="/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/" data-link-title="9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端" data-link-desc="Lemino 用 DynamoDB &#43; AWS Media Services 撐 30 channels live &#43; 5M MAU、工程工時下降 90%">9.C29 Lemino connection limit</a> 的 RDB bottleneck 探測、<a href="/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/" data-link-title="9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎" data-link-desc="Tinder 用 Amazon ElastiCache for Valkey 提供配對引擎所需的次毫秒延遲快取層">9.C6 Tinder ElastiCache</a> 的次毫秒 cache lookup 驗證，以及 <a href="/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/" data-link-title="9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測" data-link-desc="Amazon Ads 在 DynamoDB 上跑 9000 萬 reads/sec &#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads DynamoDB</a> 的 hot partition 探測。</p>
<p>這些案例的重點是快速定位與比較。Vegeta 頁引用案例時，要把 case 轉成 endpoint、rate、duration、latency budget、target saturation metric 與 runner limit — 例如 Coinbase 的 sub-ms 目標要求 Vegeta runner 必須跟 target 同 placement group、否則 runner 自身的網路 jitter 會吃掉觀測精度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/bottleneck-localization/" data-link-title="9.5 瓶頸定位流程" data-link-desc="從 app 到 DB / cache / broker / 第三方 quota 的逐層瓶頸定位">9.5 瓶頸定位流程</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a>、<a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a>、<a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a></li>
<li>跨模組：<a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 Performance Regression Gate</a></li>
<li>官方：<a href="https://github.com/tsenart/vegeta">Vegeta documentation</a></li>
</ul>
]]></content:encoded></item></channel></rss>