<?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>Saturation on Tarragon</title><link>https://tarrragon.github.io/blog/tags/saturation/</link><description>Recent content in Saturation on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 12 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/saturation/index.xml" rel="self" type="application/rss+xml"/><item><title>9.4 Saturation Discovery</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/saturation-discovery/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/saturation-discovery/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Saturation discovery 的責任是把「系統能撐多少」這個問題變成可量化答案。沒有 saturation 量測時、容量規劃只能猜；有 saturation 量測之後、能說「在當前配置下、p99 &amp;lt; 100ms 的條件下、能撐 X RPS、headroom Y%」。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-theory/" data-link-title="9.1 壓測理論與系統行為" data-link-desc="Little&amp;#39;s Law、queueing theory、USL、saturation curve 在容量規劃中的角色">9.1 壓測理論&lt;/a> 的關係：9.1 預測 saturation curve 的形狀（linear → knee → cliff）、9.4 用實測找出 &lt;em>本服務&lt;/em> 的曲線具體位置。理論告訴我們 knee 存在、實測告訴我們它在哪裡。&lt;/p>
&lt;p>本章不深入工具操作（&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;em>方法論&lt;/em> — 怎麼設計 ramp-up、怎麼判斷 knee、怎麼把結果文件化讓後續決策可用。&lt;/p>
&lt;h2 id="saturation-的精確定義">Saturation 的精確定義&lt;/h2>
&lt;p>容量規劃裡 saturation 不是「系統當機」、是「系統 &lt;em>進入 latency 指數成長區&lt;/em>」。這個區分很重要 — 系統 &lt;em>看起來&lt;/em> 還在跑、其實已經不可預測。&lt;/p>
&lt;p>技術上 saturation 對應 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/saturation-point/" data-link-title="Saturation Point" data-link-desc="說明系統從線性穩態進入 latency 指數成長區的關鍵流量點">queueing theory 的 knee point&lt;/a>：utilization 超過某個臨界（M/M/c 通常 70-80%）、平均 queue length 從線性轉成指數成長。latency 是 queue length 的線性函數、所以也跟著指數成長。&lt;/p>
&lt;p>實務上把 saturation 分三段：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>linear region&lt;/strong>（utilization &amp;lt; 50%）：latency 平穩、加流量幾乎不影響&lt;/li>
&lt;li>&lt;strong>knee region&lt;/strong>（utilization 50-80%）：latency 開始上升、但還可接受&lt;/li>
&lt;li>&lt;strong>cliff region&lt;/strong>（utilization &amp;gt; 80%）：latency 不可預測、可能 timeout / cascade failure&lt;/li>
&lt;/ul>
&lt;p>健康系統運轉在 linear 後半段或 knee 前段（utilization 50-70%）、留出 headroom 應付 burst。autoscaler 的 target metric 通常訂在 60-70%、是這條曲線推導出的安全位置。&lt;/p>
&lt;h2 id="ramp-up-測試方法">Ramp-up 測試方法&lt;/h2>
&lt;p>要找出 saturation 點、必須跑 &lt;em>ramp-up 測試&lt;/em> — 不能固定一個壓力值。&lt;/p>
&lt;p>&lt;strong>單點壓測的問題&lt;/strong>：跑「2000 RPS 連續 10 分鐘」、看 latency 100ms、結論「能撐 2000 RPS」 — 但不知道 1500 跟 2500 RPS 是什麼樣。可能 1500 也是 100ms（離 knee 還很遠）、可能 2500 直接崩（已經在 cliff）。&lt;/p>
&lt;p>&lt;strong>Ramp-up 流程&lt;/strong>：從基線開始、按倍數加壓（1x / 2x / 4x / 8x &amp;hellip;）。每個壓力 level 維持 5-10 分鐘、觀察 latency / throughput / resource utilization 的穩態（不是 transient）。紀錄每個 level 的 percentile 分布。&lt;/p>
&lt;p>&lt;strong>Knee 出現的訊號&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>throughput 從線性成長轉成 sub-linear（加壓但 throughput 不再等比成長）&lt;/li>
&lt;li>latency p50 還算穩、但 p99 / p999 開始飆&lt;/li>
&lt;li>resource saturation queue 開始堆積（不只 utilization 上升）&lt;/li>
&lt;li>error rate 仍接近 0（cliff 才會 error 飆）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cliff 出現的訊號&lt;/strong>：throughput 開始下降（加壓反而越來越慢）、latency p99 變成 timeout、error rate 飆升、retry storm 出現。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Saturation discovery 的責任是把「系統能撐多少」這個問題變成可量化答案。沒有 saturation 量測時、容量規劃只能猜；有 saturation 量測之後、能說「在當前配置下、p99 &lt; 100ms 的條件下、能撐 X RPS、headroom Y%」。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/performance-theory/" data-link-title="9.1 壓測理論與系統行為" data-link-desc="Little&#39;s Law、queueing theory、USL、saturation curve 在容量規劃中的角色">9.1 壓測理論</a> 的關係：9.1 預測 saturation curve 的形狀（linear → knee → cliff）、9.4 用實測找出 <em>本服務</em> 的曲線具體位置。理論告訴我們 knee 存在、實測告訴我們它在哪裡。</p>
<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> 處理工具）、聚焦在 <em>方法論</em> — 怎麼設計 ramp-up、怎麼判斷 knee、怎麼把結果文件化讓後續決策可用。</p>
<h2 id="saturation-的精確定義">Saturation 的精確定義</h2>
<p>容量規劃裡 saturation 不是「系統當機」、是「系統 <em>進入 latency 指數成長區</em>」。這個區分很重要 — 系統 <em>看起來</em> 還在跑、其實已經不可預測。</p>
<p>技術上 saturation 對應 <a href="/blog/backend/knowledge-cards/saturation-point/" data-link-title="Saturation Point" data-link-desc="說明系統從線性穩態進入 latency 指數成長區的關鍵流量點">queueing theory 的 knee point</a>：utilization 超過某個臨界（M/M/c 通常 70-80%）、平均 queue length 從線性轉成指數成長。latency 是 queue length 的線性函數、所以也跟著指數成長。</p>
<p>實務上把 saturation 分三段：</p>
<ul>
<li><strong>linear region</strong>（utilization &lt; 50%）：latency 平穩、加流量幾乎不影響</li>
<li><strong>knee region</strong>（utilization 50-80%）：latency 開始上升、但還可接受</li>
<li><strong>cliff region</strong>（utilization &gt; 80%）：latency 不可預測、可能 timeout / cascade failure</li>
</ul>
<p>健康系統運轉在 linear 後半段或 knee 前段（utilization 50-70%）、留出 headroom 應付 burst。autoscaler 的 target metric 通常訂在 60-70%、是這條曲線推導出的安全位置。</p>
<h2 id="ramp-up-測試方法">Ramp-up 測試方法</h2>
<p>要找出 saturation 點、必須跑 <em>ramp-up 測試</em> — 不能固定一個壓力值。</p>
<p><strong>單點壓測的問題</strong>：跑「2000 RPS 連續 10 分鐘」、看 latency 100ms、結論「能撐 2000 RPS」 — 但不知道 1500 跟 2500 RPS 是什麼樣。可能 1500 也是 100ms（離 knee 還很遠）、可能 2500 直接崩（已經在 cliff）。</p>
<p><strong>Ramp-up 流程</strong>：從基線開始、按倍數加壓（1x / 2x / 4x / 8x &hellip;）。每個壓力 level 維持 5-10 分鐘、觀察 latency / throughput / resource utilization 的穩態（不是 transient）。紀錄每個 level 的 percentile 分布。</p>
<p><strong>Knee 出現的訊號</strong>：</p>
<ul>
<li>throughput 從線性成長轉成 sub-linear（加壓但 throughput 不再等比成長）</li>
<li>latency p50 還算穩、但 p99 / p999 開始飆</li>
<li>resource saturation queue 開始堆積（不只 utilization 上升）</li>
<li>error rate 仍接近 0（cliff 才會 error 飆）</li>
</ul>
<p><strong>Cliff 出現的訊號</strong>：throughput 開始下降（加壓反而越來越慢）、latency p99 變成 timeout、error rate 飆升、retry storm 出現。</p>
<p>對應案例：<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 台">Tixcraft 用 10K t2.micro 壓測</a> 找 DynamoDB 從 20 IOPS 到 135K 的擴展曲線、知道 knee 在哪。</p>
<h2 id="resource-saturation-的六個維度">Resource saturation 的六個維度</h2>
<p>每次 ramp-up 都要同時觀察六個維度的 resource saturation、找出哪個 <em>先 saturate</em>。</p>
<p><strong>CPU</strong>：utilization 100% <em>不一定</em> 等於 saturation。要看 load average 跟 run queue。utilization 80% 但 run queue 不斷增長 → 已 saturate；utilization 100% 但 run queue 空 → 還能撐（單純 CPU bound）。</p>
<p><strong>Memory</strong>：not OOM 即可？不夠。GC pause（Java、Go）、swap（Linux）、cache eviction 都是隱性 saturation。記憶體不直接 OOM 但 GC 飆 → 已影響 <a href="/blog/backend/knowledge-cards/tail-latency/" data-link-title="Tail Latency" data-link-desc="說明 p99 / p999 等長尾延遲為何比平均延遲更能反映 saturation">tail latency</a>。</p>
<p><strong>Disk I/O</strong>：要看三個維度：throughput（MB/s）、IOPS（operations/sec）、queue depth。雲端 SSD 通常先 IOPS bound、不是 throughput；本機 NVMe 可能先 throughput bound。</p>
<p><strong>Network</strong>：bandwidth（Gbps）、packets per second、connection count。雲端 instance 通常有 PPS limit、超過會 silent drop、不是顯式錯誤。</p>
<p><strong>Connection pool</strong>：DB / cache / external API 的連線數。這是 <em>最常見的隱性 bottleneck</em>。pool size 訂 100、實際在用 95 → utilization 看似還好、其實已經 saturate（剩下的 request 在等 connection）。</p>
<p><strong>External API quota</strong>：第三方 rate limit（Stripe、Twilio、Slack API）。這個維度的 saturation 看不到 <em>本系統</em> 的訊號、要看 <em>對方 API 的 429 error rate</em>。</p>
<p>對應案例：<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%">Lemino RDB connection limit</a> — connection 是 RDB 的 saturation 點、CPU 跟 RAM 都還沒到。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/use-method/" data-link-title="USE Method" data-link-desc="Brendan Gregg 提出的資源層 Utilization / Saturation / Errors 三維度量測法">USE Method 卡片</a>。</p>
<h2 id="hot-partition-的隱性-saturation">Hot partition 的隱性 saturation</h2>
<p>對分散式 KV / OLTP（DynamoDB、Cosmos DB、Bigtable、Cassandra）、saturation 還有另一個維度：<a href="/blog/backend/knowledge-cards/hot-partition/" data-link-title="Hot Partition" data-link-desc="說明分散式 KV / OLTP 中、單一 partition 流量遠超其他的容量問題">hot partition</a>。</p>
<p>名義容量 = 每 partition 上限 × partition 數量。partition key 分布不均 → 名義容量達不到。整體 utilization 看起來 20% → 系統還能撐？不一定。最熱 partition 已經 100%、其他 partition 0%、整體平均才 20%、但加流量會打在最熱 partition、立即 throttle。</p>
<p><strong>識別 hot partition 的訊號</strong>：</p>
<ul>
<li>throughput 上不去、但 average resource utilization 低</li>
<li>某些 key 的 request latency 飆、其他 key 正常</li>
<li>DynamoDB throttling event 出現（即使 capacity 還沒滿）</li>
</ul>
<p><strong>處理方法</strong>：</p>
<ul>
<li>composite key（event_id + user_id_hash）</li>
<li>write sharding（event_id + random_suffix）</li>
<li>time-bucket（event_id + minute）</li>
<li>用 cache 吸收 hot key（DAX、ElastiCache）</li>
</ul>
<p>對應案例：<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% 可用性的廣告事件量測">Amazon Ads 9000 萬 RPS</a> — partition 設計均勻時可以撐 sustained 高吞吐；<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 台">Tixcraft 售票</a> — 同一場演唱會（event_id）天然容易 hot、必須用 composite key 分散。</p>
<h2 id="long-tail-latency-的-saturation">Long-tail latency 的 saturation</h2>
<p>p50 / p95 / p99 / p999 在 saturate 時表現可能完全不同。</p>
<p>p50（中位數）對 GC pause、retry storm、tail latency 不敏感 — 大部分 request 沒事、p50 看不到。
p99（百分之 1）對 connection contention 開始敏感、能早期看到 saturation。
p999（千分之 1）對 GC stop-the-world、leader election、retry storm 敏感、是長尾的最強訊號。</p>
<p>純看 average / p50 會誤判 saturation 還沒到。SLO 通常訂 p99（讓 99% 用戶體驗良好）、internal critical 系統可訂 p99.9（5 個 9 的可用性對應 5 個 9 的 latency 期待）。</p>
<p>對應案例：<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 以下">Tubi p99 &lt; 10ms</a> — ML 系統的 user-perceived latency 是 <em>最後完成的 inference</em>、p50 快沒用；<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 而不是自動擴容 — 延遲敏感型負載的容量取捨">Coinbase sub-ms</a> — RAFT 系統的 p999 通常比 p99 高一個量級。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/tail-latency/" data-link-title="Tail Latency" data-link-desc="說明 p99 / p999 等長尾延遲為何比平均延遲更能反映 saturation">Tail Latency 卡片</a>。</p>
<h2 id="saturation-文件化容量地圖">Saturation 文件化：容量地圖</h2>
<p>Saturation discovery 跑完之後、產出 <em>容量地圖</em> — 不是一個數字、是一張表。</p>
<p>容量地圖至少要回答：</p>
<ul>
<li>在 X 配置下（instance count、type、network）</li>
<li>SLO 條件 Y 下（p99 &lt; N ms、error rate &lt; M%）</li>
<li>能撐 Z RPS（含分解到不同 endpoint）</li>
<li>knee 在哪（什麼條件下進入 cliff）</li>
<li>第一個 saturate 的 resource 是什麼</li>
</ul>
<p>紀錄 <em>測試時間</em> 跟 <em>軟硬體版本</em>：硬體 / 軟體版本變動後、saturation 點可能位移、舊地圖不能套用。</p>
<p>加入 release gate：每次重大改動後 re-test、確認 knee 沒往不好的方向移。這層自動化跟 <a href="/blog/backend/09-performance-capacity/improvement-loop/" data-link-title="9.9 Performance Improvement Loop" data-link-desc="壓測 → profile → fix → re-test → release gate 的閉環">9.9 Improvement Loop</a> 對接。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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></td>
          <td>DynamoDB IOPS 20 → 135K 的擴展曲線量測</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>partition 均勻時的線性擴展</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>connection limit 是 RDB 的 saturation 點</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>p99 &lt; 10ms saturation 條件比平均嚴格</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/performance-theory/" data-link-title="9.1 壓測理論與系統行為" data-link-desc="Little&#39;s Law、queueing theory、USL、saturation curve 在容量規劃中的角色">9.1 壓測理論</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></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>（找到 knee 之後、定位是哪個 resource）</li>
<li>下游：<a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>（用 knee 算 headroom）</li>
<li>跨模組：<a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a>（量測訊號）</li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/saturation-point/" data-link-title="Saturation Point" data-link-desc="說明系統從線性穩態進入 latency 指數成長區的關鍵流量點">Saturation Point</a></li>
<li><a href="/blog/backend/knowledge-cards/use-method/" data-link-title="USE Method" data-link-desc="Brendan Gregg 提出的資源層 Utilization / Saturation / Errors 三維度量測法">USE Method</a></li>
<li><a href="/blog/backend/knowledge-cards/tail-latency/" data-link-title="Tail Latency" data-link-desc="說明 p99 / p999 等長尾延遲為何比平均延遲更能反映 saturation">Tail Latency</a></li>
<li><a href="/blog/backend/knowledge-cards/hot-partition/" data-link-title="Hot Partition" data-link-desc="說明分散式 KV / OLTP 中、單一 partition 流量遠超其他的容量問題">Hot Partition</a></li>
</ul>
]]></content:encoded></item></channel></rss>