<?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>Performance on Tarragon</title><link>https://tarrragon.github.io/blog/tags/performance/</link><description>Recent content in Performance on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sat, 20 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/performance/index.xml" rel="self" type="application/rss+xml"/><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>9.1 壓測理論與系統行為</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-theory/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-theory/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>壓測理論的角色是讓「加機器能不能解決」這個問題從直覺變成可推導。沒有理論基礎時、容量決策容易陷入「跑壓測 → 看數字 → 加機器」的盲試循環；有理論之後、可以從「現在的延遲 / 吞吐 / 並發量」反推「瓶頸在哪個資源、加什麼有效」。&lt;/p>
&lt;p>本章是 9.2-9.12 的共同基礎。後續章節的 workload modeling、saturation discovery、capacity planning、SLO 都會回引本章的數學工具。讀者可以把這章當作「容量規劃的最小詞彙表」、其他章節是這些詞彙的應用情境。&lt;/p>
&lt;p>本章不深入推導公式、聚焦在 &lt;em>工程意義&lt;/em>。讀完之後讀者能回答：為什麼系統在 80% utilization 就該擴、為什麼加機器會邊際效益遞減、為什麼 sub-ms 延遲需求會反推架構選擇。&lt;/p>
&lt;h2 id="littles-law穩態系統的最小數學工具">Little&amp;rsquo;s Law：穩態系統的最小數學工具&lt;/h2>
&lt;p>Little&amp;rsquo;s Law 用一條等式 L = λW 把三個變數綁在一起：L 是系統內平均並發數、λ 是請求到達率、W 是請求平均逗留時間。這個關係在 &lt;em>穩態&lt;/em>（流量已穩定、不在 warmup 階段）必然成立、不需要假設特定分布或服務模式。&lt;/p>
&lt;p>工程上最有價值的用法是「反推」。給定預期 RPS λ = 1000 跟 SLO latency 上限 W = 200ms、能算出系統最大穩態並發 L = 1000 × 0.2 = 200。這個 200 直接對應「connection pool size」「thread pool size」「async worker count」這類容量參數 — 訂得比 200 小、系統撐不住預期流量；訂得比 200 大太多、資源浪費。&lt;/p>
&lt;p>反向也成立。當 connection pool 卡死在某個 size L、latency budget W 已訂、能算出可支撐的 RPS。這個算法在 capacity planning 階段比 ramp-up 壓測更快、可以先用 Little&amp;rsquo;s Law 篩掉明顯撐不住的配置、再用壓測驗證剩下的候選。&lt;/p>
&lt;p>對應案例：&lt;a href="https://tarrragon.github.io/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 &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">Coinbase sub-ms&lt;/a> 把 W 訂在 sub-millisecond、所有架構選擇都從這個 W 反推；&lt;a href="https://tarrragon.github.io/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 ML p99 &amp;lt; 10ms&lt;/a> 從 W 反推 feature lookup 必須 cache hit 路徑、不能回到持久 store。&lt;/p>
&lt;p>詳見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/little-law/" data-link-title="Little&amp;#39;s Law" data-link-desc="說明系統內並發數、到達率與逗留時間三者的數學關係">Little&amp;rsquo;s Law 卡片&lt;/a>。&lt;/p>
&lt;h2 id="queueing-theory為什麼-80-利用率就是-knee">Queueing Theory：為什麼 80% 利用率就是 knee&lt;/h2>
&lt;p>排隊論（M/M/c 模型）解釋了一個常見直覺：「系統在 50% utilization 看似還很閒、80% 就該擴、90% 已經太晚」。這個直覺不是經驗法則、是 &lt;em>數學必然&lt;/em>。&lt;/p>
&lt;p>M/M/c 系統的平均 queue length 跟 utilization 之間是非線性關係。當 utilization 從 50% 漲到 70%、queue length 約增加 2-3 倍；從 70% 漲到 90%、queue length 增加 10 倍以上。latency 跟 queue length 成正比（Little&amp;rsquo;s Law 又出現）、所以 latency 也呈現同樣的指數成長。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>壓測理論的角色是讓「加機器能不能解決」這個問題從直覺變成可推導。沒有理論基礎時、容量決策容易陷入「跑壓測 → 看數字 → 加機器」的盲試循環；有理論之後、可以從「現在的延遲 / 吞吐 / 並發量」反推「瓶頸在哪個資源、加什麼有效」。</p>
<p>本章是 9.2-9.12 的共同基礎。後續章節的 workload modeling、saturation discovery、capacity planning、SLO 都會回引本章的數學工具。讀者可以把這章當作「容量規劃的最小詞彙表」、其他章節是這些詞彙的應用情境。</p>
<p>本章不深入推導公式、聚焦在 <em>工程意義</em>。讀完之後讀者能回答：為什麼系統在 80% utilization 就該擴、為什麼加機器會邊際效益遞減、為什麼 sub-ms 延遲需求會反推架構選擇。</p>
<h2 id="littles-law穩態系統的最小數學工具">Little&rsquo;s Law：穩態系統的最小數學工具</h2>
<p>Little&rsquo;s Law 用一條等式 L = λW 把三個變數綁在一起：L 是系統內平均並發數、λ 是請求到達率、W 是請求平均逗留時間。這個關係在 <em>穩態</em>（流量已穩定、不在 warmup 階段）必然成立、不需要假設特定分布或服務模式。</p>
<p>工程上最有價值的用法是「反推」。給定預期 RPS λ = 1000 跟 SLO latency 上限 W = 200ms、能算出系統最大穩態並發 L = 1000 × 0.2 = 200。這個 200 直接對應「connection pool size」「thread pool size」「async worker count」這類容量參數 — 訂得比 200 小、系統撐不住預期流量；訂得比 200 大太多、資源浪費。</p>
<p>反向也成立。當 connection pool 卡死在某個 size L、latency budget W 已訂、能算出可支撐的 RPS。這個算法在 capacity planning 階段比 ramp-up 壓測更快、可以先用 Little&rsquo;s Law 篩掉明顯撐不住的配置、再用壓測驗證剩下的候選。</p>
<p>對應案例：<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> 把 W 訂在 sub-millisecond、所有架構選擇都從這個 W 反推；<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 ML p99 &lt; 10ms</a> 從 W 反推 feature lookup 必須 cache hit 路徑、不能回到持久 store。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/little-law/" data-link-title="Little&#39;s Law" data-link-desc="說明系統內並發數、到達率與逗留時間三者的數學關係">Little&rsquo;s Law 卡片</a>。</p>
<h2 id="queueing-theory為什麼-80-利用率就是-knee">Queueing Theory：為什麼 80% 利用率就是 knee</h2>
<p>排隊論（M/M/c 模型）解釋了一個常見直覺：「系統在 50% utilization 看似還很閒、80% 就該擴、90% 已經太晚」。這個直覺不是經驗法則、是 <em>數學必然</em>。</p>
<p>M/M/c 系統的平均 queue length 跟 utilization 之間是非線性關係。當 utilization 從 50% 漲到 70%、queue length 約增加 2-3 倍；從 70% 漲到 90%、queue length 增加 10 倍以上。latency 跟 queue length 成正比（Little&rsquo;s Law 又出現）、所以 latency 也呈現同樣的指數成長。</p>
<p>工程意義：健康系統運轉在 50-70% utilization、超過 80% 就接近 knee、超過 90% 進入不可預測區。「為什麼明明還沒滿就 saturate」的答案就在這條曲線。autoscaler 的 target metric 通常訂在 60-70%、是 queueing theory 推導出的安全邊界、不是工程師憑感覺。</p>
<p>多 server 模型（M/M/c）比單 server（M/M/1）有顯著容量優勢：c 個 server 的有效容量遠超 1 個 server 容量 × c。這也解釋了為什麼水平擴容（多開幾個 instance）通常比垂直擴容（單機加 CPU）划算 — 不只是規模、是 queue 行為的本質差異。</p>
<p>對應案例：<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 體育博彩峰值流量">GR8 Tech 25ms p95</a> 把 p95 維持在 25ms 同時撐 54K TPS、靠的是 <em>永遠不讓系統進入 knee</em>、AI 預測讓擴容窗口縮短到 reaction time 內。</p>
<h2 id="universal-scalability-law擴容會邊際失效">Universal Scalability Law：擴容會邊際失效</h2>
<p>USL（Neil Gunther 提出）的公式 throughput(N) = N / (1 + α(N-1) + βN(N-1)) 解釋了「為什麼加機器到某個點之後 throughput 反而下降」。兩個常數 α 跟 β 描述系統的擴展限制：</p>
<ul>
<li>α 是必須序列化的部分（Amdahl&rsquo;s Law 的對應）。distributed lock、coordinator、單一 leader DB 都是 α 來源。α 越大、線性擴容越早 plateau。</li>
<li>β 是節點間互相通訊的成本（crosstalk）。cache invalidation broadcast、consensus <a href="/blog/backend/knowledge-cards/quorum/" data-link-title="Quorum" data-link-desc="分散式系統以多數節點同意作為提交或讀取有效性的門檻">quorum</a>、cross-region replication 都是 β。β 比 α 更危險、會讓 throughput 在 N 大到某點後 <em>反向下降</em>。</li>
</ul>
<p>工程上 α 比較好處理 — 把序列化部分拆細、用 partition 切分、用 sharded coordinator。β 比較難 — 通訊本質就需要協調、降低 β 通常要重新設計分散式協議（例如 Spanner 用 TrueTime 把跨節點交易的協調成本降低）。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">Spanner 線性擴展到 10 億 req/sec</a> — TrueTime API 讓跨地區交易的 β 降到可接受、達成傳統 OLTP 做不到的線性；<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 RAFT consensus</a> — RAFT 的 quorum 通訊讓 β 不可降、所以 <em>選擇不橫向擴</em>、改用 z1d + Cluster Placement Group 榨單機。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/universal-scalability-law/" data-link-title="Universal Scalability Law (USL)" data-link-desc="說明系統擴容到一定規模後吞吐反而下降的數學模型">USL 卡片</a>。</p>
<h2 id="saturation-curvelinear--knee--cliff">Saturation Curve：linear → knee → cliff</h2>
<p>實際系統的 latency vs throughput 曲線分三段。第一段是 linear region — utilization 低、latency 平穩、加流量幾乎不影響 latency。第二段是 knee — utilization 接近 80%、latency 開始指數成長、再加流量會明顯變慢。第三段是 cliff — 系統進入不穩定區、latency 不可預測、可能 timeout、可能 cascade failure。</p>
<p>容量規劃的關鍵概念是 <em>knee point = 設計容量上限</em>。健康系統運轉在 knee 以下 50-70%、留出 headroom 應付 burst 跟 forecast 誤差。沒有量過 knee 的系統等於「不知道距離崩潰多遠」 — 平日看起來穩、實際隨時可能因為一個小 spike 進入 cliff。</p>
<p>不同 system 的 knee 位置差異很大。stateless service 通常 knee 在 80% CPU；DB 因為 lock contention、knee 可能在 60% utilization；broker / queue 因為 disk I/O bottleneck、knee 可能在 50%。容量規劃時不能一概而論、必須個別量測。</p>
<p>每次重大改動後必須 re-test knee。新增功能、改 ORM、升級 library、調 GC tuning、改 cache 策略 — 任何一個都可能讓 knee 往不好的方向移。</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 DynamoDB IOPS 20 → 135K</a> — partition 設計均勻時 saturation point 可以推到極遠（6750x 擴展）；<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 key 均勻、不靠 vendor 神話。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/saturation-point/" data-link-title="Saturation Point" data-link-desc="說明系統從線性穩態進入 latency 指數成長區的關鍵流量點">Saturation Point 卡片</a>。</p>
<h2 id="反推從業務-kpi-到系統參數">反推：從業務 KPI 到系統參數</h2>
<p>理論工具的真正價值在「反推」 — 不是先設計系統再量測 saturate 多少、是 <em>先訂業務目標再反推系統參數</em>。這層思維把容量規劃從 reactive（撐到撐不住才擴）變成 proactive（按業務需求預先配置）。</p>
<p>反推流程通常從 latency budget 開始（詳見 <a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a>）：</p>
<ol>
<li>從 user-perceived end-to-end latency（例如 p99 500ms）開始</li>
<li>拆到每個 stage（網路、CDN、application、cache、DB、第三方）的 latency 配額</li>
<li>配額決定每個 stage 的設計選擇 — DB 配 50ms → 不能跨 region、application 配 100ms → 不能多層 microservice hop</li>
<li>配額 + 預期 RPS → Little&rsquo;s Law 算每個 stage 的並發</li>
<li>並發 → 每個 stage 的容量需求 → 實例數 / connection pool size / cache size</li>
</ol>
<p>反推失敗的常見徵兆：算出來的某個 stage 容量超過 vendor 提供的上限（例如「需要 50 萬 DynamoDB RCU」可能超過單一 table partition 上限）、或某個 stage latency 配額過短（例如 cross-AZ 網路至少 1-2ms、配 0.5ms 不可能達成）。這時要回頭調整 SLO 或重新設計架構。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/latency-budget/" data-link-title="Latency Budget" data-link-desc="把 user-perceived latency 拆到每個 stage 的配額、反推架構選擇">Latency Budget 卡片</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/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</a></td>
          <td>sub-ms latency 反推所有架構選擇</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></td>
          <td>TrueTime 降低 β 達成線性擴展</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>ML p99 &lt; 10ms 的 stage latency 配額</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>
  </tbody>
</table>
<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>（把模型量化成 production traffic）</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>（實測 knee point）</li>
<li>跨章節：<a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a>（latency budget 拆解）</li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/little-law/" data-link-title="Little&#39;s Law" data-link-desc="說明系統內並發數、到達率與逗留時間三者的數學關係">Little&rsquo;s Law</a></li>
<li><a href="/blog/backend/knowledge-cards/universal-scalability-law/" data-link-title="Universal Scalability Law (USL)" data-link-desc="說明系統擴容到一定規模後吞吐反而下降的數學模型">Universal Scalability Law</a></li>
<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/latency-budget/" data-link-title="Latency Budget" data-link-desc="把 user-perceived latency 拆到每個 stage 的配額、反推架構選擇">Latency Budget</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>
</ul>
]]></content:encoded></item><item><title>9.C1 AWS Prime Day 2025：可預期極端峰值的 dogfood</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/aws-prime-day-extreme-scale-2025/</guid><description>&lt;p>這個案例的核心責任是提供「極端可預期峰值」的容量設計參考點。Prime Day 是 Amazon 每年最大的單一行銷事件、發生時間提前數月公告、所有相依服務都能進入準備階段、是最接近「教科書版本的容量規劃」的真實場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>2025 年 Prime Day 期間 AWS 主要服務的峰值數字（引自 &lt;a href="https://aws.amazon.com/blogs/aws/aws-services-scale-to-new-heights-for-prime-day-2025-key-metrics-and-milestones/">AWS News Blog&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>服務&lt;/th>
 &lt;th>峰值&lt;/th>
 &lt;th>年增率&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Amazon SQS&lt;/td>
 &lt;td>1.66 億訊息 / 秒（新紀錄）&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS Lambda&lt;/td>
 &lt;td>每日 1.7 兆次呼叫&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon API Gateway&lt;/td>
 &lt;td>1 兆次內部請求&lt;/td>
 &lt;td>+30%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon DynamoDB&lt;/td>
 &lt;td>1.51 億 RPS、毫秒級回應&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon ElastiCache&lt;/td>
 &lt;td>每日 1.5 quadrillion 請求&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon CloudFront&lt;/td>
 &lt;td>3 兆次 HTTP 請求&lt;/td>
 &lt;td>+43%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon Kinesis Streams&lt;/td>
 &lt;td>8.07 億 records / 秒峰值&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon EBS&lt;/td>
 &lt;td>20.3 兆次 I/O&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon Aurora&lt;/td>
 &lt;td>5000 億次 transaction&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon SageMaker AI&lt;/td>
 &lt;td>6260 億次推論請求&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Amazon ECS on Fargate&lt;/td>
 &lt;td>每日 1840 萬個 task&lt;/td>
 &lt;td>+77%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS FIS（混沌實驗）&lt;/td>
 &lt;td>6800+ 次彈性測試&lt;/td>
 &lt;td>8 倍於 2024&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>基礎設施層面：AWS Graviton 處理器承擔超過 40% 的 EC2 compute、部署超過 87,000 顆 Inferentia / Trainium AI 晶片、AWS Outposts 對機器人下達 5.24 億條指令（年增 160%）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Prime Day 是「可預期極端峰值」的標竿。它的容量問題不是「會不會撐住」、而是「準備到什麼程度才划算」。對應主章問題節點：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Capacity Planning&lt;/strong>（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6&lt;/a>）：年度活動的容量計算可以用歷史 baseline × 預期成長 × headroom 三項相乘、但 Prime Day 規模下、每一項的不確定性放大都會變成數百萬美金成本差異。Amazon 公開的年增率（API Gateway +30%、CloudFront +43%、ECS on Fargate +77%）顯示連 Amazon 自己每年的成長預測都不能直線外推。&lt;/li>
&lt;li>&lt;strong>Performance Observability&lt;/strong>（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8&lt;/a>）：DynamoDB 「1.51 億 RPS、毫秒級回應」這種敘述同時包含吞吐與延遲、是 production-grade 容量地圖的最小單位。只說吞吐不說延伸分布、容量資訊不完整。&lt;/li>
&lt;li>&lt;strong>Improvement Loop&lt;/strong>（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9&lt;/a>）：FIS 混沌實驗 8 倍於 2024 顯示 Amazon 把「在 Prime Day 之前主動製造失敗」當成必修課、不是事後檢討。這層投資跟容量規劃同等重要。&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>這個案例可以抽出三個跨平台可重用的工程做法。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>把可預期峰值寫進服務級 SLO&lt;/strong>：Prime Day 在 SQS / Lambda / DynamoDB / Aurora 都建立了內部 SLO baseline、平日跑在 baseline 之下、峰值是擴張到「設計容量」而不是「實驗容量」。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 直接對齊。&lt;/li>
&lt;li>&lt;strong>pre-scaling + scheduled capacity&lt;/strong>：CloudFront 43%、API Gateway 30% 的年增率都是 &lt;em>提前算進&lt;/em> 容量計畫、不是當天 reactive 擴容。對應 EC2 Auto Scaling 的 &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">predictive / scheduled scaling&lt;/a> 模式。&lt;/li>
&lt;li>&lt;strong>事前主動製造失敗、不靠當天 reactive&lt;/strong>：FIS 8x 成長代表「在 Prime Day 之前 6800 次 chaos test」、把驗證成本前置到容量規劃階段。這條跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">06.4 Chaos Testing&lt;/a> 形成閉環 — 06 講失敗模式驗證、09 講容量地圖、兩者在 Prime Day 級別的事件上必須一起做。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP 的 Compute Engine MIG + Predictive Autoscaler、Azure 的 VM Scale Sets + Predictive Autoscale、Kubernetes 生態的 KEDA + Karpenter 都可以實作同樣的 pre-scaling 策略。差異是 vendor 整合度、不是工程概念。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「極端可預期峰值」的容量設計參考點。Prime Day 是 Amazon 每年最大的單一行銷事件、發生時間提前數月公告、所有相依服務都能進入準備階段、是最接近「教科書版本的容量規劃」的真實場景。</p>
<h2 id="觀察">觀察</h2>
<p>2025 年 Prime Day 期間 AWS 主要服務的峰值數字（引自 <a href="https://aws.amazon.com/blogs/aws/aws-services-scale-to-new-heights-for-prime-day-2025-key-metrics-and-milestones/">AWS News Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>服務</th>
          <th>峰值</th>
          <th>年增率</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Amazon SQS</td>
          <td>1.66 億訊息 / 秒（新紀錄）</td>
          <td>-</td>
      </tr>
      <tr>
          <td>AWS Lambda</td>
          <td>每日 1.7 兆次呼叫</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon API Gateway</td>
          <td>1 兆次內部請求</td>
          <td>+30%</td>
      </tr>
      <tr>
          <td>Amazon DynamoDB</td>
          <td>1.51 億 RPS、毫秒級回應</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon ElastiCache</td>
          <td>每日 1.5 quadrillion 請求</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon CloudFront</td>
          <td>3 兆次 HTTP 請求</td>
          <td>+43%</td>
      </tr>
      <tr>
          <td>Amazon Kinesis Streams</td>
          <td>8.07 億 records / 秒峰值</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon EBS</td>
          <td>20.3 兆次 I/O</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon Aurora</td>
          <td>5000 億次 transaction</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon SageMaker AI</td>
          <td>6260 億次推論請求</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Amazon ECS on Fargate</td>
          <td>每日 1840 萬個 task</td>
          <td>+77%</td>
      </tr>
      <tr>
          <td>AWS FIS（混沌實驗）</td>
          <td>6800+ 次彈性測試</td>
          <td>8 倍於 2024</td>
      </tr>
  </tbody>
</table>
<p>基礎設施層面：AWS Graviton 處理器承擔超過 40% 的 EC2 compute、部署超過 87,000 顆 Inferentia / Trainium AI 晶片、AWS Outposts 對機器人下達 5.24 億條指令（年增 160%）。</p>
<h2 id="判讀">判讀</h2>
<p>Prime Day 是「可預期極端峰值」的標竿。它的容量問題不是「會不會撐住」、而是「準備到什麼程度才划算」。對應主章問題節點：</p>
<ol>
<li><strong>Capacity Planning</strong>（<a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6</a>）：年度活動的容量計算可以用歷史 baseline × 預期成長 × headroom 三項相乘、但 Prime Day 規模下、每一項的不確定性放大都會變成數百萬美金成本差異。Amazon 公開的年增率（API Gateway +30%、CloudFront +43%、ECS on Fargate +77%）顯示連 Amazon 自己每年的成長預測都不能直線外推。</li>
<li><strong>Performance Observability</strong>（<a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8</a>）：DynamoDB 「1.51 億 RPS、毫秒級回應」這種敘述同時包含吞吐與延遲、是 production-grade 容量地圖的最小單位。只說吞吐不說延伸分布、容量資訊不完整。</li>
<li><strong>Improvement Loop</strong>（<a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9</a>）：FIS 混沌實驗 8 倍於 2024 顯示 Amazon 把「在 Prime Day 之前主動製造失敗」當成必修課、不是事後檢討。這層投資跟容量規劃同等重要。</li>
</ol>
<h2 id="策略">策略</h2>
<p>這個案例可以抽出三個跨平台可重用的工程做法。</p>
<ol>
<li><strong>把可預期峰值寫進服務級 SLO</strong>：Prime Day 在 SQS / Lambda / DynamoDB / Aurora 都建立了內部 SLO baseline、平日跑在 baseline 之下、峰值是擴張到「設計容量」而不是「實驗容量」。這跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 直接對齊。</li>
<li><strong>pre-scaling + scheduled capacity</strong>：CloudFront 43%、API Gateway 30% 的年增率都是 <em>提前算進</em> 容量計畫、不是當天 reactive 擴容。對應 EC2 Auto Scaling 的 <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">predictive / scheduled scaling</a> 模式。</li>
<li><strong>事前主動製造失敗、不靠當天 reactive</strong>：FIS 8x 成長代表「在 Prime Day 之前 6800 次 chaos test」、把驗證成本前置到容量規劃階段。這條跟 <a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">06.4 Chaos Testing</a> 形成閉環 — 06 講失敗模式驗證、09 講容量地圖、兩者在 Prime Day 級別的事件上必須一起做。</li>
</ol>
<p>跨平台等效：GCP 的 Compute Engine MIG + Predictive Autoscaler、Azure 的 VM Scale Sets + Predictive Autoscale、Kubernetes 生態的 KEDA + Karpenter 都可以實作同樣的 pre-scaling 策略。差異是 vendor 整合度、不是工程概念。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃年度活動容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>想設計可預期峰值的 SLO → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO 與 Error Budget 政策</a></li>
<li>想做事前混沌驗證 → <a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">06.4 Chaos Testing</a> + <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">06.22 Steady State Definition</a></li>
<li>對照不同形狀的峰值 → <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>（事件型不可預期峰值）/ <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</a>（無峰值低延遲）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/aws/aws-services-scale-to-new-heights-for-prime-day-2025-key-metrics-and-milestones/">AWS services scale to new heights for Prime Day 2025: key metrics and milestones</a></li>
<li><a href="https://aws.amazon.com/blogs/industries/conquering-peak-retail-events-with-aws/">Conquering Peak Retail Events with AWS</a></li>
<li><a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">Predictive scaling for Amazon EC2 Auto Scaling</a></li>
</ul>
]]></content:encoded></item><item><title>Active Parameter</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/active-parameter/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/active-parameter/</guid><description>&lt;p>Active parameter 的核心概念是「&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/moe/" data-link-title="Mixture of Experts (MoE)" data-link-desc="把 transformer 的 FFN 層拆成多個專家、每 token 只啟用少數、總參數大但每 token 計算量小的架構">MoE&lt;/a> 模型每生成一個 token 實際參與 forward pass 的參數量」。跟模型總參數量是兩個獨立指標：&lt;strong>總參數&lt;/strong>影響記憶體需求（要全部載入）、&lt;strong>active parameter&lt;/strong> 影響推論速度上限（每 token 走的計算量）。Dense 模型的 active parameter 等於總參數；MoE 模型的 active parameter 通常只有總參數的 10% ~ 20%。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>模型命名中的 active parameter 線索：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>命名範例&lt;/th>
 &lt;th>解讀&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>Qwen3-30B-A3B&lt;/code>&lt;/td>
 &lt;td>30B 總參數、A3B 表示 active 約 3B&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>Mixtral-8x7B&lt;/code>&lt;/td>
 &lt;td>8 個 7B expert、每 token top-2 啟用 ≈ 14B active（含 shared）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>Llama-3.3-70B&lt;/code>&lt;/td>
 &lt;td>Dense、active = total = 70B&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>DeepSeek-V3&lt;/code>&lt;/td>
 &lt;td>671B 總參數、active 約 37B（依官方文件）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>模型在不同維度的影響：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>受影響因素&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>記憶體需求&lt;/td>
 &lt;td>總參數 × 每權重 bytes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生字速度上限&lt;/td>
 &lt;td>active parameter × 每 token 讀取量 / &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/memory-bandwidth/" data-link-title="Memory Bandwidth" data-link-desc="記憶體每秒能讀寫多少 bytes：決定本地 LLM 生字速度的真正瓶頸">memory bandwidth&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>模型能力（社群常見回報）&lt;/td>
 &lt;td>較強相關於總參數、但 active parameter 是底線&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：active parameter 跟模型能力的關係是社群常見回報、不是嚴格定理；具體模型在 coding / reasoning / 對話等任務的表現依訓練資料、RLHF、prompt 風格變化、需以 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/swe-bench/" data-link-title="SWE-bench" data-link-desc="用真實 GitHub issue 量化 LLM coding 能力的 benchmark">SWE-bench&lt;/a> 等公開 benchmark 跟自己工作流校準。&lt;/p>&lt;/blockquote>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>理解 active parameter 後可以解釋兩個現象：為什麼 30B MoE 跟 30B Dense 在同硬體下生字速度差很多（前者每 token 只走 3B active）、為什麼 MoE 模型能力對應的「等價 Dense 大小」不是簡單線性（社群常見回報接近總參數的 60% ~ 80% 等價 Dense 能力、但 case-by-case）。&lt;/p>
&lt;p>選 MoE 模型時、active parameter 是速度判讀軸、總參數是記憶體判讀軸、能力判讀靠自己工作流的 benchmark；不要直接拿「30B」跟 Dense 30B 作能力對等。&lt;/p></description><content:encoded><![CDATA[<p>Active parameter 的核心概念是「<a href="/blog/llm/knowledge-cards/moe/" data-link-title="Mixture of Experts (MoE)" data-link-desc="把 transformer 的 FFN 層拆成多個專家、每 token 只啟用少數、總參數大但每 token 計算量小的架構">MoE</a> 模型每生成一個 token 實際參與 forward pass 的參數量」。跟模型總參數量是兩個獨立指標：<strong>總參數</strong>影響記憶體需求（要全部載入）、<strong>active parameter</strong> 影響推論速度上限（每 token 走的計算量）。Dense 模型的 active parameter 等於總參數；MoE 模型的 active parameter 通常只有總參數的 10% ~ 20%。</p>
<h2 id="概念位置">概念位置</h2>
<p>模型命名中的 active parameter 線索：</p>
<table>
  <thead>
      <tr>
          <th>命名範例</th>
          <th>解讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Qwen3-30B-A3B</code></td>
          <td>30B 總參數、A3B 表示 active 約 3B</td>
      </tr>
      <tr>
          <td><code>Mixtral-8x7B</code></td>
          <td>8 個 7B expert、每 token top-2 啟用 ≈ 14B active（含 shared）</td>
      </tr>
      <tr>
          <td><code>Llama-3.3-70B</code></td>
          <td>Dense、active = total = 70B</td>
      </tr>
      <tr>
          <td><code>DeepSeek-V3</code></td>
          <td>671B 總參數、active 約 37B（依官方文件）</td>
      </tr>
  </tbody>
</table>
<p>模型在不同維度的影響：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>受影響因素</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>記憶體需求</td>
          <td>總參數 × 每權重 bytes</td>
      </tr>
      <tr>
          <td>生字速度上限</td>
          <td>active parameter × 每 token 讀取量 / <a href="/blog/llm/knowledge-cards/memory-bandwidth/" data-link-title="Memory Bandwidth" data-link-desc="記憶體每秒能讀寫多少 bytes：決定本地 LLM 生字速度的真正瓶頸">memory bandwidth</a></td>
      </tr>
      <tr>
          <td>模型能力（社群常見回報）</td>
          <td>較強相關於總參數、但 active parameter 是底線</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>事實查核註</strong>：active parameter 跟模型能力的關係是社群常見回報、不是嚴格定理；具體模型在 coding / reasoning / 對話等任務的表現依訓練資料、RLHF、prompt 風格變化、需以 <a href="/blog/llm/knowledge-cards/swe-bench/" data-link-title="SWE-bench" data-link-desc="用真實 GitHub issue 量化 LLM coding 能力的 benchmark">SWE-bench</a> 等公開 benchmark 跟自己工作流校準。</p></blockquote>
<h2 id="設計責任">設計責任</h2>
<p>理解 active parameter 後可以解釋兩個現象：為什麼 30B MoE 跟 30B Dense 在同硬體下生字速度差很多（前者每 token 只走 3B active）、為什麼 MoE 模型能力對應的「等價 Dense 大小」不是簡單線性（社群常見回報接近總參數的 60% ~ 80% 等價 Dense 能力、但 case-by-case）。</p>
<p>選 MoE 模型時、active parameter 是速度判讀軸、總參數是記憶體判讀軸、能力判讀靠自己工作流的 benchmark；不要直接拿「30B」跟 Dense 30B 作能力對等。</p>
]]></content:encoded></item><item><title>Flash Attention</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/flash-attention/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/flash-attention/</guid><description>&lt;p>Flash Attention 的核心概念是「重新組織 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">Attention&lt;/a> 計算的順序、把中間結果留在 GPU 高速 cache、減少對 GPU memory 的讀寫往返」。它不改變 attention 的數學定義（輸出跟原始實作在浮點誤差範圍內一致）、但實作層面對長 context 推論吞吐有明顯提升、且是部分 &lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">KV cache 量化&lt;/a> 組合在 llama.cpp 上的必要前置。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Flash Attention 在推論架構中的角色：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">推論時的 attention 計算：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ├── 原始實作：Q · K^T 整個算完、寫進 memory、再讀出來做 softmax、再算 · V
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> │ └── 多次 memory 讀寫、長 context 下 IO 成為瓶頸
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> └── Flash Attention：用 tiling 把計算切塊、中間結果留在 SRAM / register
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> └── 減少 memory 讀寫、長 context 加速明顯&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>跟 attention 變體的關係：&lt;/p>
&lt;ul>
&lt;li>Flash Attention 是&lt;strong>實作層&lt;/strong>的優化、跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">MHA / GQA / MLA&lt;/a> 等&lt;strong>架構層&lt;/strong>變體是兩個獨立維度。&lt;/li>
&lt;li>不同變體都能搭配 Flash Attention 的實作技巧。&lt;/li>
&lt;/ul>
&lt;p>在 llama.cpp 中的旗標：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">llama-server -fa &lt;span class="c1"># 啟用 flash attention&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># 或&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">llama-server --flash-attn&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：Flash Attention 的版本演進快（Flash Attention 1 / 2 / 3）、不同推論引擎的支援度依版本變化。具體限制（如「V cache Q4 量化要 -fa 才能啟用」）依 llama.cpp 版本變動、引用前以 &lt;code>llama-server --help&lt;/code> 跟 release notes 為準。&lt;/p>&lt;/blockquote>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>理解 Flash Attention 後可以解釋兩個現象：為什麼啟用 &lt;code>-fa&lt;/code> 後長 context 推論速度提升明顯（IO bound 變 compute bound）、為什麼部分 KV cache 量化組合（如 V=Q4_0）在 llama.cpp 上需要 flash attention 才能跑（實作層面的耦合）。&lt;/p>
&lt;p>工程實務上、啟用 flash attention 通常沒副作用（數學上等價、品質不變）、是 PC 場景長 context 推論的預設啟用旗標。詳見 &lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">5.2 KV cache 量化策略&lt;/a> 的 flash attention 段落。&lt;/p></description><content:encoded><![CDATA[<p>Flash Attention 的核心概念是「重新組織 <a href="/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">Attention</a> 計算的順序、把中間結果留在 GPU 高速 cache、減少對 GPU memory 的讀寫往返」。它不改變 attention 的數學定義（輸出跟原始實作在浮點誤差範圍內一致）、但實作層面對長 context 推論吞吐有明顯提升、且是部分 <a href="/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">KV cache 量化</a> 組合在 llama.cpp 上的必要前置。</p>
<h2 id="概念位置">概念位置</h2>
<p>Flash Attention 在推論架構中的角色：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">推論時的 attention 計算：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ├── 原始實作：Q · K^T 整個算完、寫進 memory、再讀出來做 softmax、再算 · V
</span></span><span class="line"><span class="ln">3</span><span class="cl">  │     └── 多次 memory 讀寫、長 context 下 IO 成為瓶頸
</span></span><span class="line"><span class="ln">4</span><span class="cl">  └── Flash Attention：用 tiling 把計算切塊、中間結果留在 SRAM / register
</span></span><span class="line"><span class="ln">5</span><span class="cl">        └── 減少 memory 讀寫、長 context 加速明顯</span></span></code></pre></div><p>跟 attention 變體的關係：</p>
<ul>
<li>Flash Attention 是<strong>實作層</strong>的優化、跟 <a href="/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">MHA / GQA / MLA</a> 等<strong>架構層</strong>變體是兩個獨立維度。</li>
<li>不同變體都能搭配 Flash Attention 的實作技巧。</li>
</ul>
<p>在 llama.cpp 中的旗標：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">llama-server -fa  <span class="c1"># 啟用 flash attention</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 或</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">llama-server --flash-attn</span></span></code></pre></div><blockquote>
<p><strong>事實查核註</strong>：Flash Attention 的版本演進快（Flash Attention 1 / 2 / 3）、不同推論引擎的支援度依版本變化。具體限制（如「V cache Q4 量化要 -fa 才能啟用」）依 llama.cpp 版本變動、引用前以 <code>llama-server --help</code> 跟 release notes 為準。</p></blockquote>
<h2 id="設計責任">設計責任</h2>
<p>理解 Flash Attention 後可以解釋兩個現象：為什麼啟用 <code>-fa</code> 後長 context 推論速度提升明顯（IO bound 變 compute bound）、為什麼部分 KV cache 量化組合（如 V=Q4_0）在 llama.cpp 上需要 flash attention 才能跑（實作層面的耦合）。</p>
<p>工程實務上、啟用 flash attention 通常沒副作用（數學上等價、品質不變）、是 PC 場景長 context 推論的預設啟用旗標。詳見 <a href="/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">5.2 KV cache 量化策略</a> 的 flash attention 段落。</p>
]]></content:encoded></item><item><title>0.1 為什麼 LLM 生字慢</title><link>https://tarrragon.github.io/blog/llm/00-foundations/why-llm-feels-slow/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/00-foundations/why-llm-feels-slow/</guid><description>&lt;p>LLM 生字慢的核心原因有兩個：&lt;strong>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/autoregressive/" data-link-title="Autoregressive" data-link-desc="LLM 一次生成一個 token、把已生成內容作為下一次輸入的架構">自回歸架構&lt;/a>&lt;/strong>（autoregressive）讓模型一次生一個 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token&lt;/a>、&lt;strong>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/memory-bandwidth/" data-link-title="Memory Bandwidth" data-link-desc="記憶體每秒能讀寫多少 bytes：決定本地 LLM 生字速度的真正瓶頸">記憶體頻寬&lt;/a>瓶頸&lt;/strong>讓 Apple Silicon 在算力之外有一個獨立的速度上限。這兩個瓶頸結合起來、才能解釋為什麼 32GB Mac 跑 31B 模型約 30 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/tokens-per-second/" data-link-title="Tokens Per Second" data-link-desc="LLM 每秒能生成幾個 token：生字速度的標準量化指標">tok/s&lt;/a>、而資料中心的 H100 跑同樣模型能到 200 tok/s。&lt;/p>
&lt;p>理解這個機制不只是為了知識本身。後續所有加速技巧（&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/speculative-decoding/" data-link-title="Speculative Decoding" data-link-desc="用小模型猜未來 token、大模型並行驗證的加速技巧">speculative decoding&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/mtp/" data-link-title="Multi-Token Prediction (MTP)" data-link-desc="Google 為 Gemma 系列釋出的 speculative decoding 工程化實作">MTP&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/quantization/" data-link-title="Quantization" data-link-desc="用較少 bits 表示模型權重：壓縮記憶體佔用、加快生字速度，代價是少量品質衰減">量化&lt;/a>）都是在攻擊這兩個瓶頸的不同部分；不懂瓶頸在哪，看到「2x 加速」「3x 加速」這種廣告詞就無從判讀。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後，你應該能回答：&lt;/p>
&lt;ol>
&lt;li>為什麼 LLM 採用「一個 token 接一個 token」的生成方式、而非整段一次生出？&lt;/li>
&lt;li>為什麼 Apple Silicon 的「&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/unified-memory/" data-link-title="Unified Memory Architecture" data-link-desc="Apple Silicon 讓 CPU / GPU / NE 共用同一塊記憶體：跑大模型的優勢來源">統一記憶體&lt;/a>」對 LLM 推論是優勢？&lt;/li>
&lt;li>為什麼&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/quantization/" data-link-title="Quantization" data-link-desc="用較少 bits 表示模型權重：壓縮記憶體佔用、加快生字速度，代價是少量品質衰減">模型量化&lt;/a>能加速、而非只是省記憶體？&lt;/li>
&lt;li>為什麼長 prompt 的&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">首字延遲&lt;/a>特別有感？&lt;/li>
&lt;/ol>
&lt;h2 id="自回歸架構一次只能吐一個-token">自回歸架構：一次只能吐一個 token&lt;/h2>
&lt;p>自回歸的核心概念是「下一個 token 的生成需要前面所有 token 的結果」。模型每生成一個 token，都要把目前已有的 token 序列（你的 prompt + 它已經生成的部分）重新丟進神經網路算一次，得到下一個 token 的機率分佈，挑一個輸出，然後重複。&lt;/p>
&lt;p>舉個具體例子。當你輸入 &lt;code>寫一個 Python function 計算費氏數列&lt;/code>，模型生成回答的過程大致是：&lt;/p>
&lt;ol>
&lt;li>把 prompt 丟進模型，產出第一個 token，例如 &lt;code>def&lt;/code>。&lt;/li>
&lt;li>把 prompt + &lt;code>def&lt;/code> 丟進模型，產出 &lt;code>fib&lt;/code>。&lt;/li>
&lt;li>把 prompt + &lt;code>def fib&lt;/code> 丟進模型，產出 &lt;code>(&lt;/code>。&lt;/li>
&lt;li>一直重複到模型決定產出結束 token。&lt;/li>
&lt;/ol>
&lt;p>每一步都要跑一次完整的神經網路 forward pass（神經網路把輸入資料從第一層算到最後一層、產出輸出的單次計算）。這就是為什麼回答長度直接影響等待時間、跟雲端旗艦模型一樣；差別只是雲端每個 forward pass 跑得更快。&lt;/p>
&lt;p>陷阱是把自回歸跟 streaming 混淆。Streaming 只是把已產出的 token 即時顯示在畫面上，看起來「邊想邊說」；模型內部該跑幾次 forward pass 就是幾次，streaming 不會加速生成本身。&lt;/p>
&lt;h2 id="記憶體頻寬apple-silicon-真正的瓶頸">記憶體頻寬：Apple Silicon 真正的瓶頸&lt;/h2>
&lt;p>LLM 推論的瓶頸幾乎一定落在記憶體頻寬、而不是算力。原因是每生成一個 token 都要把整個模型的權重從記憶體讀到處理器一次；模型有多大、每秒能讀多少 GB、就決定了每秒能吐幾個 token。每生一個 token 都要把整份權重讀過一次、所以「每秒能讀完幾份權重」就是「每秒能吐幾個 token」。&lt;/p>
&lt;p>模型大小的換算規則很簡單：bf16 每個權重佔 2 bytes、Q4 量化後每個權重約 0.5 byte。所以：&lt;/p>
&lt;ul>
&lt;li>Gemma 4 31B 的 bf16 權重約 62GB（31B × 2 bytes）、Q4 量化後約 18GB。&lt;/li>
&lt;li>M4 Max 的記憶體頻寬約 546 GB/s、M2 Pro 約 200 GB/s。&lt;/li>
&lt;li>理論上限 = 頻寬 / 模型大小。M4 Max 跑 Q4 量化 31B 模型、理論上限約 546 / 18 ≈ 30 tok/s。&lt;/li>
&lt;/ul>
&lt;p>實際數字會比理論上限低 30 ~ 50%（attention 機制的 KV cache 也要讀寫、有些運算需要中間結果），所以 M4 Max 跑 Q4 31B 大約落在 20 ~ 25 tok/s。這個推導讓你看到任何「在 Mac 上跑 70B 模型很快」的說法時，可以直接用頻寬算一下合不合理。&lt;/p></description><content:encoded><![CDATA[<p>LLM 生字慢的核心原因有兩個：<strong><a href="/blog/llm/knowledge-cards/autoregressive/" data-link-title="Autoregressive" data-link-desc="LLM 一次生成一個 token、把已生成內容作為下一次輸入的架構">自回歸架構</a></strong>（autoregressive）讓模型一次生一個 <a href="/blog/llm/knowledge-cards/token/" data-link-title="Token" data-link-desc="LLM 處理文字時的最小單位：介於字元與單字之間">token</a>、<strong><a href="/blog/llm/knowledge-cards/memory-bandwidth/" data-link-title="Memory Bandwidth" data-link-desc="記憶體每秒能讀寫多少 bytes：決定本地 LLM 生字速度的真正瓶頸">記憶體頻寬</a>瓶頸</strong>讓 Apple Silicon 在算力之外有一個獨立的速度上限。這兩個瓶頸結合起來、才能解釋為什麼 32GB Mac 跑 31B 模型約 30 <a href="/blog/llm/knowledge-cards/tokens-per-second/" data-link-title="Tokens Per Second" data-link-desc="LLM 每秒能生成幾個 token：生字速度的標準量化指標">tok/s</a>、而資料中心的 H100 跑同樣模型能到 200 tok/s。</p>
<p>理解這個機制不只是為了知識本身。後續所有加速技巧（<a href="/blog/llm/knowledge-cards/speculative-decoding/" data-link-title="Speculative Decoding" data-link-desc="用小模型猜未來 token、大模型並行驗證的加速技巧">speculative decoding</a>、<a href="/blog/llm/knowledge-cards/mtp/" data-link-title="Multi-Token Prediction (MTP)" data-link-desc="Google 為 Gemma 系列釋出的 speculative decoding 工程化實作">MTP</a>、<a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a>、<a href="/blog/llm/knowledge-cards/quantization/" data-link-title="Quantization" data-link-desc="用較少 bits 表示模型權重：壓縮記憶體佔用、加快生字速度，代價是少量品質衰減">量化</a>）都是在攻擊這兩個瓶頸的不同部分；不懂瓶頸在哪，看到「2x 加速」「3x 加速」這種廣告詞就無從判讀。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後，你應該能回答：</p>
<ol>
<li>為什麼 LLM 採用「一個 token 接一個 token」的生成方式、而非整段一次生出？</li>
<li>為什麼 Apple Silicon 的「<a href="/blog/llm/knowledge-cards/unified-memory/" data-link-title="Unified Memory Architecture" data-link-desc="Apple Silicon 讓 CPU / GPU / NE 共用同一塊記憶體：跑大模型的優勢來源">統一記憶體</a>」對 LLM 推論是優勢？</li>
<li>為什麼<a href="/blog/llm/knowledge-cards/quantization/" data-link-title="Quantization" data-link-desc="用較少 bits 表示模型權重：壓縮記憶體佔用、加快生字速度，代價是少量品質衰減">模型量化</a>能加速、而非只是省記憶體？</li>
<li>為什麼長 prompt 的<a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">首字延遲</a>特別有感？</li>
</ol>
<h2 id="自回歸架構一次只能吐一個-token">自回歸架構：一次只能吐一個 token</h2>
<p>自回歸的核心概念是「下一個 token 的生成需要前面所有 token 的結果」。模型每生成一個 token，都要把目前已有的 token 序列（你的 prompt + 它已經生成的部分）重新丟進神經網路算一次，得到下一個 token 的機率分佈，挑一個輸出，然後重複。</p>
<p>舉個具體例子。當你輸入 <code>寫一個 Python function 計算費氏數列</code>，模型生成回答的過程大致是：</p>
<ol>
<li>把 prompt 丟進模型，產出第一個 token，例如 <code>def</code>。</li>
<li>把 prompt + <code>def</code> 丟進模型，產出 <code>fib</code>。</li>
<li>把 prompt + <code>def fib</code> 丟進模型，產出 <code>(</code>。</li>
<li>一直重複到模型決定產出結束 token。</li>
</ol>
<p>每一步都要跑一次完整的神經網路 forward pass（神經網路把輸入資料從第一層算到最後一層、產出輸出的單次計算）。這就是為什麼回答長度直接影響等待時間、跟雲端旗艦模型一樣；差別只是雲端每個 forward pass 跑得更快。</p>
<p>陷阱是把自回歸跟 streaming 混淆。Streaming 只是把已產出的 token 即時顯示在畫面上，看起來「邊想邊說」；模型內部該跑幾次 forward pass 就是幾次，streaming 不會加速生成本身。</p>
<h2 id="記憶體頻寬apple-silicon-真正的瓶頸">記憶體頻寬：Apple Silicon 真正的瓶頸</h2>
<p>LLM 推論的瓶頸幾乎一定落在記憶體頻寬、而不是算力。原因是每生成一個 token 都要把整個模型的權重從記憶體讀到處理器一次；模型有多大、每秒能讀多少 GB、就決定了每秒能吐幾個 token。每生一個 token 都要把整份權重讀過一次、所以「每秒能讀完幾份權重」就是「每秒能吐幾個 token」。</p>
<p>模型大小的換算規則很簡單：bf16 每個權重佔 2 bytes、Q4 量化後每個權重約 0.5 byte。所以：</p>
<ul>
<li>Gemma 4 31B 的 bf16 權重約 62GB（31B × 2 bytes）、Q4 量化後約 18GB。</li>
<li>M4 Max 的記憶體頻寬約 546 GB/s、M2 Pro 約 200 GB/s。</li>
<li>理論上限 = 頻寬 / 模型大小。M4 Max 跑 Q4 量化 31B 模型、理論上限約 546 / 18 ≈ 30 tok/s。</li>
</ul>
<p>實際數字會比理論上限低 30 ~ 50%（attention 機制的 KV cache 也要讀寫、有些運算需要中間結果），所以 M4 Max 跑 Q4 31B 大約落在 20 ~ 25 tok/s。這個推導讓你看到任何「在 Mac 上跑 70B 模型很快」的說法時，可以直接用頻寬算一下合不合理。</p>
<p>Apple Silicon 的**<a href="/blog/llm/knowledge-cards/unified-memory/" data-link-title="Unified Memory Architecture" data-link-desc="Apple Silicon 讓 CPU / GPU / NE 共用同一塊記憶體：跑大模型的優勢來源">統一記憶體</a>**（Unified Memory Architecture, UMA）讓 CPU、GPU、Neural Engine 共用同一塊記憶體、省下跨 PCIe 搬資料的成本。傳統 PC + NVIDIA GPU 的記憶體分成系統記憶體跟 VRAM；模型權重要放進 VRAM 才能用 GPU 跑、跨 PCIe 搬資料的速度成本很高。Mac 的 64GB 統一記憶體可以幾乎全部給模型用（扣掉系統保留部分）、同等價位的 PC 通常只有 12GB ~ 24GB VRAM。</p>
<p>這就是為什麼 Mac 在「跑得動多大的模型」上佔優勢，但在「跑多快」上輸給 H100。H100 的 HBM 頻寬約 3,300 GB/s，是 M4 Max 的 6 倍。能跑得動 vs 跑得快，是兩件事。</p>
<h2 id="量化用精度換頻寬">量化：用精度換頻寬</h2>
<p>量化的核心是把模型權重從 16-bit float 壓成 4-bit、5-bit、8-bit integer。權重數量不變，但每個權重佔的 bytes 變少；模型總大小變小，每秒能讀過的權重變多，生字速度直接變快。</p>
<p>常見量化等級：</p>
<table>
  <thead>
      <tr>
          <th>量化</th>
          <th>每權重 bits</th>
          <th>相對 bf16 大小</th>
          <th>品質衰減</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>bf16</td>
          <td>16</td>
          <td>1x</td>
          <td>無（基準）</td>
          <td>開發、評估、有大量記憶體</td>
      </tr>
      <tr>
          <td>Q8</td>
          <td>8</td>
          <td>0.5x</td>
          <td>幾乎不可察覺</td>
          <td>32GB+ Mac、品質敏感任務</td>
      </tr>
      <tr>
          <td>Q5_K</td>
          <td>5</td>
          <td>0.31x</td>
          <td>輕微</td>
          <td>24GB Mac、日常使用</td>
      </tr>
      <tr>
          <td>Q4_K</td>
          <td>4</td>
          <td>0.25x</td>
          <td>可察覺但實用</td>
          <td>16 ~ 24GB Mac、最常用甜蜜點</td>
      </tr>
      <tr>
          <td>Q3</td>
          <td>3</td>
          <td>0.19x</td>
          <td>明顯、coding 任務 hallucination 上升</td>
          <td>記憶體緊張時的權宜選擇、coding 慎用</td>
      </tr>
  </tbody>
</table>
<p>接近真實的選擇：</p>
<ul>
<li>32GB Mac 跑 31B 模型：選 Q4_K，記憶體佔用 ~ 18GB，留 14GB 給系統與 IDE。</li>
<li>24GB Mac 跑 14B 模型：選 Q5_K 或 Q4_K，看任務品質要求。</li>
<li>16GB Mac 跑 7B 模型：選 Q4_K，是現實上界。</li>
</ul>
<p>陷阱是把量化等級拉到極限以塞下更大模型。Coding 任務上 Q3 的 31B 模型常輸給 Q5 的 14B 模型；模型「夠大」跟「夠好」是兩件事、選 model size 時先看任務通過率、再用量化等級調記憶體。後續 <a href="/blog/llm/01-local-llm-services/model-selection-priority/" data-link-title="1.4 寫 code 場景的模型選型優先順序" data-link-desc="Gemma 4 31B MTP → Qwen3-Coder 30B → Qwen3 14B → gpt-oss 20B 的取捨與適用情境">模型選型章節</a> 會展開這個取捨。</p>
<h2 id="kv-cache-與長-prompt-痛點">KV cache 與長 prompt 痛點</h2>
<p><a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a>（key-value cache）把 attention 機制每個 token 產生的中間結果暫存、後續 token 生成時直接讀 cache 跳過重算、讓「已經算過的 prompt」省下重複跑 forward pass。</p>
<p>但 KV cache 有兩個性質會放大長 prompt 的痛點：</p>
<ol>
<li><strong>首次處理 prompt 時要完整算過一次</strong>、這個階段稱為 <strong><a href="/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill</a></strong>。10K token 的 prompt 在本地可能需要 30 ~ 90 秒才 prefill 完、這 30 ~ 90 秒就是 <a href="/blog/llm/knowledge-cards/ttft/" data-link-title="TTFT" data-link-desc="Time To First Token：送出 prompt 到第一個 token 出現的等待時間">TTFT</a> 的主要來源。</li>
<li><strong>KV cache 本身佔記憶體</strong>：長 <a href="/blog/llm/knowledge-cards/context-window/" data-link-title="Context Window" data-link-desc="模型一次能處理的最大 token 數量：prompt 加生成的總和上限">context</a> 跑下來、KV cache 可能比模型權重還大、會擠壓可用記憶體。</li>
</ol>
<p>這就是為什麼 coding agent 場景（塞整個 repo 進 prompt）在本地特別痛：每次都要重新 prefill，每次都等 30 ~ 90 秒。oMLX 這類特化伺服器就是針對這個痛點，用 paged SSD KV cache 把已 prefill 過的 context 存到 SSD，下次同樣的 prompt 前綴可以直接讀 cache，把 TTFT 從 30 ~ 90 秒降到 1 ~ 3 秒。詳見 <a href="/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">0.4 MLX / MTP / oMLX</a>。</p>
<h2 id="speculative-decoding-與-mtp">Speculative decoding 與 MTP</h2>
<p>既然瓶頸是「每生一個 token 都要讀一次完整模型權重」、那能否一次生多個 token？<a href="/blog/llm/knowledge-cards/speculative-decoding/" data-link-title="Speculative Decoding" data-link-desc="用小模型猜未來 token、大模型並行驗證的加速技巧">speculative decoding</a>（推測解碼）就是這個想法的具體實作。</p>
<p>機制大致是：</p>
<ol>
<li>用一個小模型（<a href="/blog/llm/knowledge-cards/drafter-model/" data-link-title="Drafter Model" data-link-desc="speculative decoding 中用來快速猜未來 token 的小模型">drafter</a>、例如 1B 參數）快速猜未來 N 個 token。</li>
<li>把這 N 個 token 一次餵給大模型（target、例如 31B 參數）、讓大模型並行驗證每個位置的機率分佈。</li>
<li>大模型保留認同的前綴、從第一個拒絕點之後重新生成。</li>
</ol>
<p>這個機制能加速的關鍵是「大模型的驗證可以並行」。一次 forward pass 驗證 N 個 token 的時間，跟驗證 1 個 token 的時間差不多（因為瓶頸是讀權重，不是算力）。如果接受率高，等於一次 forward pass 產出多個 token。</p>
<p>寫 code 場景特別適合 speculative decoding、因為 code 有大量可預測 pattern（縮排、括號、常見變數名、import 語句）、小模型猜對的接受率高。Google 為 Gemma 4 釋出官方 drafter、官方數據在 coding 任務有 2 ~ 3 倍加速；接受率低的任務（純創意寫作、隨機字串生成）加速幅度可能降到 1.5 倍左右、加速倍數跟任務 pattern 強相關。</p>
<p><strong><a href="/blog/llm/knowledge-cards/mtp/" data-link-title="Multi-Token Prediction (MTP)" data-link-desc="Google 為 Gemma 系列釋出的 speculative decoding 工程化實作">Multi-Token Prediction</a></strong>（MTP）是這個概念的具體實作、本質是 speculative decoding 的工程化版本。下一章 <a href="/blog/llm/00-foundations/mlx-mtp-omlx/" data-link-title="0.4 MLX / MTP / oMLX 的區別" data-link-desc="三個常被混為一談的術語：framework、加速技巧、特化 server，疊加而非互斥">0.4 MLX / MTP / oMLX</a> 會把 MTP 跟其他容易混淆的術語放在一起對照。</p>
<h2 id="何時這套推導失準">何時這套推導失準</h2>
<p>「頻寬決定生字速度」是 dense 模型 + 單請求情境下的乾淨推導。實務上有三類情境會讓這個公式失準、解讀效能數字時要對應調整：</p>
<ol>
<li><strong>MoE 模型（Mixture of Experts）</strong>：每個 token 只啟用部分專家層、實際讀的權重遠小於總權重。例如 Mixtral 8x7B 名義 46B 參數、但每個 token 只啟用約 12B、速度上限要用「啟用權重」算、不是總權重。判讀 MoE 模型在 PC 獨立 GPU 上的部署細節見 <a href="/blog/llm/knowledge-cards/moe-cpu-offload/" data-link-title="MoE CPU 卸載" data-link-desc="把 Mixture-of-Experts 模型不活躍的專家層權重放在系統 RAM、用到再走 PCIe 拉回 GPU、讓有限 VRAM 跑得了更大模型">MoE CPU 卸載</a>。</li>
<li><strong>多請求 batching</strong>：資料中心級推論伺服器把多請求 batch 一起跑、權重讀一次處理 N 個 token、攤平頻寬成本。本章開頭舉的「H100 跑 200 tok/s」是 batch=1 的單 user 數字、production 場景 batch=32 時單 user 看到的速度更接近 50 tok/s、但 total throughput 翻 N 倍。詳見 <a href="/blog/llm/knowledge-cards/batching/" data-link-title="Batching" data-link-desc="多 request 一起跑、攤平 model load 成本：production LLM inference 的核心優化、決定 throughput vs latency 取捨">batching 卡片</a>。</li>
<li><strong>Speculative decoding 接受率變動</strong>：MTP / drafter 的加速幅度跟任務 pattern 強相關、coding 任務的 2 ~ 3 倍無法直接 carryover 到創意寫作、看 benchmark 數字時要追問「跑的是哪類任務」。</li>
</ol>
<p>判讀效能數字時的反射動作：先問「dense 還是 MoE」「batch 多少」「任務 pattern 強弱」、再決定能不能套頻寬公式。</p>
<h2 id="下一章">下一章</h2>
<p>下一章：<a href="/blog/llm/00-foundations/three-layer-architecture/" data-link-title="0.2 介面 / 伺服器 / 模型三層架構" data-link-desc="把任何本地 LLM 工具放回正確的層級，用三層心智模型看懂工具關係">0.2 三層架構</a>，把任何本地 LLM 工具放回正確的層級。</p>
]]></content:encoded></item><item><title>Sentry 深入</title><link>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/sentry-deep-dive/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/sentry-deep-dive/</guid><description>&lt;blockquote>
&lt;p>&lt;strong>跟 Backend 04 的分工&lt;/strong>：本文從 client-side 使用角度說明 Sentry 的 error tracking、performance monitoring 與 session replay — SDK 怎麼埋、error 怎麼分群、release 怎麼追蹤。Server-side 平台治理（告警路由整合、SLI 指標設計、self-hosted vs SaaS 成本治理、跟 OTel 的整合）見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Backend 04 Sentry vendor page&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;p>Sentry 的核心是 error tracking — 自動捕獲未處理的例外、提供 stack trace、自動分群（grouping）相同 root cause 的 error。在 error tracking 的基礎上，Sentry 擴展了 performance monitoring（transaction / span）和 session replay（重播使用者操作）。&lt;/p>
&lt;h2 id="error-tracking">Error tracking&lt;/h2>
&lt;p>Sentry 的 error tracking 架構有三個層次：SDK 端的自動捕獲、server 端的 issue grouping 和 UI 端的 issue management。&lt;/p>
&lt;h3 id="自動捕獲">自動捕獲&lt;/h3>
&lt;p>Sentry SDK 在各平台註冊全域錯誤處理器（和&lt;a href="https://tarrragon.github.io/blog/monitoring/03-sdk-design/auto-intercept/" data-link-title="自動攔截機制" data-link-desc="JS window.onerror / Flutter FlutterError.onError / Python sys.excepthook — 各平台攔截未捕獲例外的機制和限制">模組三 自動攔截&lt;/a>的機制相同）。捕獲到例外後，SDK 收集 stack trace、breadcrumbs（最近的使用者操作）、device context（OS / browser / device model）和自訂 tags，打包成 event 送到 Sentry server。&lt;/p>
&lt;h3 id="issue-grouping">Issue grouping&lt;/h3>
&lt;p>Sentry server 收到 error event 後，用 fingerprinting 演算法判斷這個 error 是否和已有的 issue 相同。預設的 fingerprinting 基於 stack trace 的 frame — 如果兩個 error 的 stack trace 指向同一個位置，歸入同一個 issue。&lt;/p>
&lt;p>自訂 fingerprint 讓開發者控制 grouping 邏輯。例如：不同使用者觸發的同一個 API error 可能有不同的 stack trace（因為 call site 不同），但 root cause 相同 — 自訂 fingerprint 把它們歸入同一個 issue。&lt;/p>
&lt;h3 id="issue-management">Issue management&lt;/h3>
&lt;p>每個 issue 有狀態（unresolved / resolved / ignored）、指派（誰負責修復）、趨勢（這個 issue 的發生頻率是上升還是下降）。Sentry 的 UI 提供 issue 列表、趨勢圖、影響範圍（影響多少使用者）。&lt;/p>
&lt;h2 id="performance-monitoring">Performance monitoring&lt;/h2>
&lt;p>Sentry 的 performance monitoring 用 transaction 和 span 模型（和 OpenTelemetry 的 trace / span 概念相同）。&lt;/p>
&lt;p>Transaction 代表一個完整的操作（頁面載入、API 請求處理）。Span 是 transaction 內的子操作（database query、外部 API 呼叫）。Transaction 和 span 的 duration 構成操作的時間分佈。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><strong>跟 Backend 04 的分工</strong>：本文從 client-side 使用角度說明 Sentry 的 error tracking、performance monitoring 與 session replay — SDK 怎麼埋、error 怎麼分群、release 怎麼追蹤。Server-side 平台治理（告警路由整合、SLI 指標設計、self-hosted vs SaaS 成本治理、跟 OTel 的整合）見 <a href="/blog/backend/04-observability/vendors/sentry/" data-link-title="Sentry" data-link-desc="Error tracking 主流、APM / Profiling / Session Replay 擴展">Backend 04 Sentry vendor page</a>。</p></blockquote>
<p>Sentry 的核心是 error tracking — 自動捕獲未處理的例外、提供 stack trace、自動分群（grouping）相同 root cause 的 error。在 error tracking 的基礎上，Sentry 擴展了 performance monitoring（transaction / span）和 session replay（重播使用者操作）。</p>
<h2 id="error-tracking">Error tracking</h2>
<p>Sentry 的 error tracking 架構有三個層次：SDK 端的自動捕獲、server 端的 issue grouping 和 UI 端的 issue management。</p>
<h3 id="自動捕獲">自動捕獲</h3>
<p>Sentry SDK 在各平台註冊全域錯誤處理器（和<a href="/blog/monitoring/03-sdk-design/auto-intercept/" data-link-title="自動攔截機制" data-link-desc="JS window.onerror / Flutter FlutterError.onError / Python sys.excepthook — 各平台攔截未捕獲例外的機制和限制">模組三 自動攔截</a>的機制相同）。捕獲到例外後，SDK 收集 stack trace、breadcrumbs（最近的使用者操作）、device context（OS / browser / device model）和自訂 tags，打包成 event 送到 Sentry server。</p>
<h3 id="issue-grouping">Issue grouping</h3>
<p>Sentry server 收到 error event 後，用 fingerprinting 演算法判斷這個 error 是否和已有的 issue 相同。預設的 fingerprinting 基於 stack trace 的 frame — 如果兩個 error 的 stack trace 指向同一個位置，歸入同一個 issue。</p>
<p>自訂 fingerprint 讓開發者控制 grouping 邏輯。例如：不同使用者觸發的同一個 API error 可能有不同的 stack trace（因為 call site 不同），但 root cause 相同 — 自訂 fingerprint 把它們歸入同一個 issue。</p>
<h3 id="issue-management">Issue management</h3>
<p>每個 issue 有狀態（unresolved / resolved / ignored）、指派（誰負責修復）、趨勢（這個 issue 的發生頻率是上升還是下降）。Sentry 的 UI 提供 issue 列表、趨勢圖、影響範圍（影響多少使用者）。</p>
<h2 id="performance-monitoring">Performance monitoring</h2>
<p>Sentry 的 performance monitoring 用 transaction 和 span 模型（和 OpenTelemetry 的 trace / span 概念相同）。</p>
<p>Transaction 代表一個完整的操作（頁面載入、API 請求處理）。Span 是 transaction 內的子操作（database query、外部 API 呼叫）。Transaction 和 span 的 duration 構成操作的時間分佈。</p>
<p>Performance monitoring 的價值是發現「慢」的問題 — P95 回應時間超過閾值、特定 span 佔了 transaction 80% 的時間。和 error tracking 互補：error 告訴你「什麼壞了」，performance 告訴你「什麼慢了」。</p>
<h2 id="session-replay">Session replay</h2>
<p>Session replay 錄製使用者的操作過程 — DOM 變化、滑鼠移動、點擊事件 — 在 Sentry UI 中重播。開發者可以看到「使用者在觸發 error 之前做了什麼操作」。</p>
<p>Session replay 的實作是 DOM snapshot + mutation recording。記錄的是 DOM 結構的變化（非螢幕錄影），在重播時重建 DOM。資料量比錄影小很多，但仍然是所有 Sentry 功能中資料量最大的。</p>
<p>隱私考量：session replay 會看到使用者輸入的內容（除非做 masking）。Sentry 提供 privacy configuration 控制哪些元素被 mask（輸入框、敏感資料區域）。</p>
<h2 id="自架方案和-sentry-的差距">自架方案和 Sentry 的差距</h2>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>自架方案</th>
          <th>Sentry</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Error 捕獲</td>
          <td>SDK 自動攔截</td>
          <td>SDK 自動攔截（相同）</td>
      </tr>
      <tr>
          <td>Issue grouping</td>
          <td>手動 grep 分群</td>
          <td>自動 fingerprinting + 自訂規則</td>
      </tr>
      <tr>
          <td>趨勢分析</td>
          <td>手動計數</td>
          <td>自動趨勢圖 + 告警</td>
      </tr>
      <tr>
          <td>Performance</td>
          <td>metric 事件 + 手動分析</td>
          <td>Transaction / span + 自動 P95</td>
      </tr>
      <tr>
          <td>Session replay</td>
          <td>無</td>
          <td>DOM recording + 重播 UI</td>
      </tr>
  </tbody>
</table>
<p>Sentry 的核心價值在 issue grouping 和趨勢分析 — 把大量 error event 歸類成可管理的 issue 列表，自動追蹤每個 issue 的趨勢。自架方案用 grep 做不到自動 grouping。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Firebase 的整合方案 → <a href="/blog/monitoring/06-commercial-comparison/firebase-suite/" data-link-title="Firebase 套件" data-link-desc="Crashlytics &#43; Analytics &#43; Remote Config 的整合 — Firebase 把 error tracking 和行為分析拆成獨立產品的設計取捨">Firebase 套件</a></li>
<li>Datadog 的全棧 APM → <a href="/blog/monitoring/06-commercial-comparison/datadog-rum/" data-link-title="Datadog RUM" data-link-desc="全棧 APM 的 client-side 觀點 — client action 到 server trace 的完整鏈路追蹤">Datadog RUM</a></li>
<li>自架 vs 商業的判斷 → <a href="/blog/monitoring/06-commercial-comparison/self-hosted-vs-commercial/" data-link-title="自架 vs 商業的判斷決策表" data-link-desc="使用者數、網路範圍、功能需求、合規要求四個維度判斷該自架還是用商業方案">自架 vs 商業的判斷決策表</a></li>
<li>自架方案的 error fingerprint 實作 → <a href="/blog/monitoring/04-collector/error-fingerprint/" data-link-title="Error Fingerprint 與去重分群" data-link-desc="把大量 error 事件歸組成可管理的 issue 列表 — fingerprint 演算法、message normalization、error_groups 表設計、自架方案的務實邊界">Error Fingerprint 與去重分群</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>9.2 Workload Modeling</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/workload-modeling/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/workload-modeling/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Workload modeling 的角色是讓壓測結果有意義。如果壓測模型跟 production traffic shape 不一致、壓測通過不代表 production 撐得住。這一層的工作不是「製造大量請求」、而是「製造跟 production 一樣形狀的請求」。&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 提供推導工具、9.2 把工具的輸入（流量參數）量化。沒有 workload model、Little&amp;rsquo;s Law 的 λ 跟 W 都是猜。&lt;/p>
&lt;p>本章的核心問題：production traffic 不是「N RPS」這麼簡單。它有時間分布、地理分布、操作分布、cohort 分布、burst pattern。每個維度都會影響系統行為。一個只測「總 RPS」的壓測通過了、production 還是可能因為某個 cohort 集中或某個 burst pattern 出事。&lt;/p>
&lt;h2 id="traffic-shape-的五個維度">Traffic shape 的五個維度&lt;/h2>
&lt;p>Production traffic shape 至少要量五個維度才算 model 完整。&lt;/p>
&lt;p>&lt;strong>平均吞吐 vs 峰值&lt;/strong>：peak/avg ratio 是工程意義最大的單一指標。1.5x 的 peak/avg 代表流量相對平緩、容量規劃可以接近 average peak；3-5x 的 peak/avg 代表 bursty 流量、必須按 peak 規劃、平日大幅 over-provision。對應案例：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">ASOS Black Friday 24h 1.67 億 / 峰值 3500 RPS&lt;/a> 峰均比約 1.81x 屬於相對溫和；&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">Tixcraft 5 分鐘賣完&lt;/a> 是另一極端。&lt;/p>
&lt;p>&lt;strong>時間分布&lt;/strong>：日內（早晚通勤）、週內（週末活躍）、月內（月初發薪）、季內（節慶）、年內（活動）。不同尺度的週期都要記錄、用於 forecast 跟 pre-scaling 決策。&lt;/p>
&lt;p>&lt;strong>用戶分布&lt;/strong>：geographic（哪個 region 多）、device（mobile vs desktop）、tier（free / paid / VIP）。同樣 RPS、不同分布可能造成完全不同系統行為 — VIP 用戶可能跑更複雜 query、mobile 用戶可能更多 retry、跨 region 用戶可能更多 cross-zone latency。&lt;/p>
&lt;p>&lt;strong>操作分布&lt;/strong>：read vs write 比、不同 endpoint 的 mix。一個系統 90% read 跟 50% read 的容量設計完全不同 — read-heavy 可以 cache、write-heavy 必須關注 storage IOPS。&lt;/p>
&lt;p>&lt;strong>Cohort 與 burst pattern&lt;/strong>：同一秒的請求不一定均勻 — bursty arrival 比 Poisson arrival 對系統更殘酷。突發 burst 來源：promo 推播、KOL 推廣、新片發布、新聞事件。&lt;/p>
&lt;p>對應案例：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &amp;#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">GR8 Tech 賽事高潮 burst&lt;/a> — 賽事「進球瞬間」 burst 比平均流量高 10-50 倍；&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&amp;#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&amp;#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">Disney+ 新片發布&lt;/a> — 同片瞬間集中、cohort 高度集中。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Workload modeling 的角色是讓壓測結果有意義。如果壓測模型跟 production traffic shape 不一致、壓測通過不代表 production 撐得住。這一層的工作不是「製造大量請求」、而是「製造跟 production 一樣形狀的請求」。</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 提供推導工具、9.2 把工具的輸入（流量參數）量化。沒有 workload model、Little&rsquo;s Law 的 λ 跟 W 都是猜。</p>
<p>本章的核心問題：production traffic 不是「N RPS」這麼簡單。它有時間分布、地理分布、操作分布、cohort 分布、burst pattern。每個維度都會影響系統行為。一個只測「總 RPS」的壓測通過了、production 還是可能因為某個 cohort 集中或某個 burst pattern 出事。</p>
<h2 id="traffic-shape-的五個維度">Traffic shape 的五個維度</h2>
<p>Production traffic shape 至少要量五個維度才算 model 完整。</p>
<p><strong>平均吞吐 vs 峰值</strong>：peak/avg ratio 是工程意義最大的單一指標。1.5x 的 peak/avg 代表流量相對平緩、容量規劃可以接近 average peak；3-5x 的 peak/avg 代表 bursty 流量、必須按 peak 規劃、平日大幅 over-provision。對應案例：<a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">ASOS Black Friday 24h 1.67 億 / 峰值 3500 RPS</a> 峰均比約 1.81x 屬於相對溫和；<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 5 分鐘賣完</a> 是另一極端。</p>
<p><strong>時間分布</strong>：日內（早晚通勤）、週內（週末活躍）、月內（月初發薪）、季內（節慶）、年內（活動）。不同尺度的週期都要記錄、用於 forecast 跟 pre-scaling 決策。</p>
<p><strong>用戶分布</strong>：geographic（哪個 region 多）、device（mobile vs desktop）、tier（free / paid / VIP）。同樣 RPS、不同分布可能造成完全不同系統行為 — VIP 用戶可能跑更複雜 query、mobile 用戶可能更多 retry、跨 region 用戶可能更多 cross-zone latency。</p>
<p><strong>操作分布</strong>：read vs write 比、不同 endpoint 的 mix。一個系統 90% read 跟 50% read 的容量設計完全不同 — read-heavy 可以 cache、write-heavy 必須關注 storage IOPS。</p>
<p><strong>Cohort 與 burst pattern</strong>：同一秒的請求不一定均勻 — bursty arrival 比 Poisson arrival 對系統更殘酷。突發 burst 來源：promo 推播、KOL 推廣、新片發布、新聞事件。</p>
<p>對應案例：<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 體育博彩峰值流量">GR8 Tech 賽事高潮 burst</a> — 賽事「進球瞬間」 burst 比平均流量高 10-50 倍；<a href="/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">Disney+ 新片發布</a> — 同片瞬間集中、cohort 高度集中。</p>
<h2 id="從-production-log-抽-workload-model">從 production log 抽 workload model</h2>
<p>實務上 workload model 不能憑空寫、要從 production data 抽。流程通常分四步：</p>
<p><strong>第一步：data 蒐集</strong>。從 access log、APM trace、metric 系統取得 production traffic 樣本。要 sampling（不是全量）、避免影響 production；要包含 <em>至少一個完整 weekly cycle</em>（含週末、含峰谷）；要按 endpoint / per-tenant 分組。</p>
<p><strong>第二步：分組統計</strong>。對每組（per endpoint、per tier、per region）計算 percentile（p50 / p95 / p99）、arrival pattern（Poisson、bursty、scheduled）、payload size 分布。輸出是「workload profile」 — 比單一數字更接近 reality。</p>
<p><strong>第三步：序列重播</strong>。複製一段 production traffic 的時間序列、保留 inter-arrival timing（不只是 RPS 平均、是 <em>每秒幾個</em>）。這層讓 burst 在壓測重現、不只是「平均壓力均勻分布」。</p>
<p><strong>第四步：脫敏處理</strong>。PII（user_id、phone、address）必須匿名化或替換 — 否則壓測環境變成 PII 洩漏點。常見做法：hash + salt + 確保結果 cardinality 跟 production 一致。</p>
<p>production log 通常缺寫入 payload（log 只記 metadata、不記 request body）、要從 application metric 或 schema sample 補。schema sample 用「distinct value 抽樣」、不是「random」 — 確保壓測涵蓋常見 value pattern。</p>
<h2 id="synthetic-load-vs-production-replay">Synthetic load vs production replay</h2>
<p>兩種主要壓測方式各有取捨。</p>
<p><strong>Synthetic load</strong>：手寫腳本、明確控制每個請求的 shape。優點是好複現、可以針對特定情境設計（例如「測登入失敗 retry」）；缺點是容易脫離 production reality、寫腳本的人會無意識套用自己的偏見。</p>
<p><strong>Production traffic replay</strong>：用 GoReplay、Istio mirror、AWS VPC Traffic Mirroring 等工具把 production traffic 複製到測試環境。優點是 <em>最貼近真實</em>、自動帶上 burst 跟 cohort；缺點是消耗 production 下游資源（要算進容量規劃）、PII / 合規處理複雜、replay 環境的下游 mock 不容易做。</p>
<p><strong>混合模式</strong>：常態壓測用 synthetic（cheap、可控）、release candidate 驗證用 production replay（真實）、debug 特定 incident 用 <em>特定時段</em> 的 replay。三種工具在不同階段用、不是二選一。</p>
<p>對應案例：<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; 州的雙重峰值">FanDuel 雙峰需要兩個 workload model 並行</a> — 直播 model（CDN heavy、長 session）跟投注 model（低延遲、burst at goal）必須分開壓測、不能合成一個。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/workload-model/" data-link-title="Workload Model" data-link-desc="描述 production traffic 形狀的可重播模型 — 容量規劃跟壓測的共同輸入">Workload Model 卡片</a> 跟 <a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic 卡片</a>。</p>
<h2 id="模型驗證怎麼知道模型像-production">模型驗證：怎麼知道模型像 production</h2>
<p>寫了 workload model 之後、怎麼驗證它真的「像 production」？方法是 <em>跑壓測 同時 對比 production metrics</em>。</p>
<p>驗證指標包含：throughput pattern（總 RPS、各 endpoint mix）、latency 分布（p50 / p95 / p99 對比）、resource utilization（CPU / memory / network 行為）、error rate 與 retry pattern。</p>
<p>兩個可能的偏差結果：</p>
<ul>
<li><strong>模型撐不住但 production 撐得住</strong> → 模型太苛刻、可能高估了流量或操作複雜度。usually fine、調整模型參數即可。</li>
<li><strong>模型撐得住但 production 撐不住</strong> → 模型不足、漏了某個維度。dangerous、需要回到 data 蒐集階段找漏掉的 pattern。</li>
</ul>
<p>對應案例：<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 撐住會議後端">Zoom 30x COVID surge</a> — 之前的 workload model 完全不能用、必須 reset baseline 重新從 post-COVID 流量抽 model；<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> — 用實際售票場景重播驗證、不是 synthetic 數字。</p>
<h2 id="模型維護定期-review">模型維護：定期 review</h2>
<p>Workload model 不是一次抽完就永久有效。業務變化會讓模型過時、過時的模型導出的容量規劃會失準。</p>
<p>需要 re-抽 model 的訊號：</p>
<ul>
<li>新功能上線改變 user journey（例如新增 video upload、user 行為變寫多）</li>
<li>新市場進入改變 cohort 分布（例如進入印度市場、mobile share 大幅增加）</li>
<li>行銷活動改變 burst pattern（例如新增 push notification、burst 集中度上升）</li>
<li>用戶習慣轉變（例如 work-from-home 讓週末跟平日流量比變化）</li>
</ul>
<p>維護節奏建議每季 review 一次、重大產品改動立即 re-抽。每次 re-抽要 <em>跟前一版對比</em>、量化變化幅度、決定哪些容量計畫要重新評估。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS Black Friday</a></td>
          <td>持續高峰型 workload（峰均比 1.81x）</td>
      </tr>
      <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>flash-sale 形狀（5 分鐘賣完）</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>100+ 微服務各自 workload model（不能用單一）</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>3 億 / 天的峰均比預估</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>雙峰必須兩個 model 並行</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></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>（用什麼工具實作 model）</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>（用 model 跑 ramp-up）</li>
<li>跨模組：<a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a>（production log 來源）</li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/workload-model/" data-link-title="Workload Model" data-link-desc="描述 production traffic 形狀的可重播模型 — 容量規劃跟壓測的共同輸入">Workload Model</a></li>
<li><a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic</a></li>
<li><a href="/blog/backend/knowledge-cards/growth-curve/" data-link-title="Growth Curve" data-link-desc="說明用戶 / 流量隨時間成長的五種典型形狀、影響容量規劃方法">Growth Curve</a></li>
<li><a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast</a></li>
</ul>
]]></content:encoded></item><item><title>9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/</guid><description>&lt;p>這個案例的核心責任是說明「事件型不可預期峰值」的工程做法。體育博彩流量的形狀跟 Prime Day 不同 — 峰值會在賽事的特定瞬間（進球、最後一分鐘）爆量、單一賽事內可能有多次脈衝、跨賽事的時間點難以提前數月排程。GR8 Tech 在 2022 FIFA World Cup 期間達到零停機營運、是這類負載形狀的有效參考。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>GR8 Tech 從本地基礎設施遷移到 AWS、重建為微服務架構後的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/gr8-tech-case-study/">GR8 Tech case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>遷移前狀況&lt;/th>
 &lt;th>遷移後峰值&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>投注延遲&lt;/td>
 &lt;td>賽事高峰期額外延遲 2-3 秒&lt;/td>
 &lt;td>25 ms p95&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結算吞吐&lt;/td>
 &lt;td>（未公開）&lt;/td>
 &lt;td>每分鐘 100 萬次投注結算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>交易吞吐&lt;/td>
 &lt;td>（未公開）&lt;/td>
 &lt;td>54000 TPS @ 25ms p95&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同時在線&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>200,000+ 同時使用者&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>投注吞吐&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>每分鐘 80,000 次體育投注&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>99.95% uptime&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本彈性&lt;/td>
 &lt;td>固定預配置&lt;/td>
 &lt;td>需求降低時成本下降 25%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon EKS（Kubernetes 容器編排、跨雲端與本地）、Amazon EC2（compute）、Amazon S3 與 Amazon EBS（儲存）、AWS Auto Scaling 結合 &lt;strong>GR8 Tech 自家 AI 預測模型&lt;/strong>、AWS Infrastructure Event Management（重大賽事支援）。&lt;/p>
&lt;p>擴展範圍：「Scaled to 15 markets using AWS」。事件覆蓋：2022 FIFA World Cup 期間零停機。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>GR8 Tech 的工程做法揭露三個事件型峰值的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>不可預期 ≠ 不可預測&lt;/strong>：賽事「何時開打」是已知的（schedule 提前公告）、「賽事內何時爆量」是未知的（進球、加時、最後一分鐘）。AI 預測模型不是預測「會不會有峰值」、而是預測「峰值在 60 秒內可能多大」、把擴容窗口縮短到反應時間之內。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的「預測時間尺度」軸。&lt;/li>
&lt;li>&lt;strong>延遲是業務指標、不是技術指標&lt;/strong>：「2-3 秒額外延遲」直接造成「投注失敗、客戶流失」。25ms p95 是收入 KPI 而不是 SLO 漂亮數字。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性&lt;/a> 把 latency 翻成業務 metric 的責任。&lt;/li>
&lt;li>&lt;strong>微服務 + 容器編排是擴容粒度的前置&lt;/strong>：遷移前的單體系統「擴容」只能複製整套系統、成本曲線陡峭。EKS 拆解後可以針對熱點服務（投注引擎、結算引擎）獨立擴容、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的逐層定位直接對齊。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：54000 TPS @ 25ms 是 &lt;em>公開的成功數字&lt;/em>、不是「永遠都這樣」的承諾。AI 預測模型必然有預測誤差、AWS Infrastructure Event Management 也是事件型服務、不是平台預設。這類案例適合作為「目標可達性」的存在證明、不適合直接套用為自家服務的容量假設。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>把賽事 schedule 灌進 capacity forecast&lt;/strong>：在事件已知的條件下、預先把 baseline 拉高、避免 AI 模型在零起跑時擴容。對應 EC2 Auto Scaling 的 &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html">scheduled scaling&lt;/a> + predictive scaling 雙模。&lt;/li>
&lt;li>&lt;strong>AI 模型輸入要包含領域訊號&lt;/strong>：通用 ML autoscaler 用 CPU / latency 預測、領域 autoscaler 還會用 &lt;em>賽事重要性&lt;/em>、&lt;em>投注量歷史曲線&lt;/em>、&lt;em>下注玩家集中度&lt;/em> 等業務訊號。這層讓擴容時機從反應式變成預測式。&lt;/li>
&lt;li>&lt;strong>熱點服務獨立擴容、不是整體擴容&lt;/strong>：投注引擎跟結算引擎的峰值時間不一致（投注集中在賽前 + 比賽中、結算集中在賽後）、單獨擴容比整體擴容省 25%+ 成本。&lt;/li>
&lt;li>&lt;strong>AWS Infrastructure Event Management 等廠商支援服務&lt;/strong>：在年度重大事件可以申請（World Cup、Olympic、Black Friday 等）、提供 pre-scaling 與專屬監控通道。這在 GCP / Azure 也有對等服務（GCP Customer Care Premium、Azure Event Management Support）。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP GKE + Vertical Pod Autoscaler + 自家 ML 預測、Azure AKS + KEDA + Azure ML 預測、自建 Kubernetes + Karpenter + Prometheus 推導模型都可以實作同樣的「預測 + 擴容」模式。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「事件型不可預期峰值」的工程做法。體育博彩流量的形狀跟 Prime Day 不同 — 峰值會在賽事的特定瞬間（進球、最後一分鐘）爆量、單一賽事內可能有多次脈衝、跨賽事的時間點難以提前數月排程。GR8 Tech 在 2022 FIFA World Cup 期間達到零停機營運、是這類負載形狀的有效參考。</p>
<h2 id="觀察">觀察</h2>
<p>GR8 Tech 從本地基礎設施遷移到 AWS、重建為微服務架構後的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/gr8-tech-case-study/">GR8 Tech case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>遷移前狀況</th>
          <th>遷移後峰值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>投注延遲</td>
          <td>賽事高峰期額外延遲 2-3 秒</td>
          <td>25 ms p95</td>
      </tr>
      <tr>
          <td>結算吞吐</td>
          <td>（未公開）</td>
          <td>每分鐘 100 萬次投注結算</td>
      </tr>
      <tr>
          <td>交易吞吐</td>
          <td>（未公開）</td>
          <td>54000 TPS @ 25ms p95</td>
      </tr>
      <tr>
          <td>同時在線</td>
          <td>-</td>
          <td>200,000+ 同時使用者</td>
      </tr>
      <tr>
          <td>投注吞吐</td>
          <td>-</td>
          <td>每分鐘 80,000 次體育投注</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>-</td>
          <td>99.95% uptime</td>
      </tr>
      <tr>
          <td>成本彈性</td>
          <td>固定預配置</td>
          <td>需求降低時成本下降 25%</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon EKS（Kubernetes 容器編排、跨雲端與本地）、Amazon EC2（compute）、Amazon S3 與 Amazon EBS（儲存）、AWS Auto Scaling 結合 <strong>GR8 Tech 自家 AI 預測模型</strong>、AWS Infrastructure Event Management（重大賽事支援）。</p>
<p>擴展範圍：「Scaled to 15 markets using AWS」。事件覆蓋：2022 FIFA World Cup 期間零停機。</p>
<h2 id="判讀">判讀</h2>
<p>GR8 Tech 的工程做法揭露三個事件型峰值的判讀重點。</p>
<ol>
<li><strong>不可預期 ≠ 不可預測</strong>：賽事「何時開打」是已知的（schedule 提前公告）、「賽事內何時爆量」是未知的（進球、加時、最後一分鐘）。AI 預測模型不是預測「會不會有峰值」、而是預測「峰值在 60 秒內可能多大」、把擴容窗口縮短到反應時間之內。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的「預測時間尺度」軸。</li>
<li><strong>延遲是業務指標、不是技術指標</strong>：「2-3 秒額外延遲」直接造成「投注失敗、客戶流失」。25ms p95 是收入 KPI 而不是 SLO 漂亮數字。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a> 把 latency 翻成業務 metric 的責任。</li>
<li><strong>微服務 + 容器編排是擴容粒度的前置</strong>：遷移前的單體系統「擴容」只能複製整套系統、成本曲線陡峭。EKS 拆解後可以針對熱點服務（投注引擎、結算引擎）獨立擴容、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的逐層定位直接對齊。</li>
</ol>
<p>需要警惕的判讀盲點：54000 TPS @ 25ms 是 <em>公開的成功數字</em>、不是「永遠都這樣」的承諾。AI 預測模型必然有預測誤差、AWS Infrastructure Event Management 也是事件型服務、不是平台預設。這類案例適合作為「目標可達性」的存在證明、不適合直接套用為自家服務的容量假設。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>把賽事 schedule 灌進 capacity forecast</strong>：在事件已知的條件下、預先把 baseline 拉高、避免 AI 模型在零起跑時擴容。對應 EC2 Auto Scaling 的 <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html">scheduled scaling</a> + predictive scaling 雙模。</li>
<li><strong>AI 模型輸入要包含領域訊號</strong>：通用 ML autoscaler 用 CPU / latency 預測、領域 autoscaler 還會用 <em>賽事重要性</em>、<em>投注量歷史曲線</em>、<em>下注玩家集中度</em> 等業務訊號。這層讓擴容時機從反應式變成預測式。</li>
<li><strong>熱點服務獨立擴容、不是整體擴容</strong>：投注引擎跟結算引擎的峰值時間不一致（投注集中在賽前 + 比賽中、結算集中在賽後）、單獨擴容比整體擴容省 25%+ 成本。</li>
<li><strong>AWS Infrastructure Event Management 等廠商支援服務</strong>：在年度重大事件可以申請（World Cup、Olympic、Black Friday 等）、提供 pre-scaling 與專屬監控通道。這在 GCP / Azure 也有對等服務（GCP Customer Care Premium、Azure Event Management Support）。</li>
</ol>
<p>跨平台等效：GCP GKE + Vertical Pod Autoscaler + 自家 ML 預測、Azure AKS + KEDA + Azure ML 預測、自建 Kubernetes + Karpenter + Prometheus 推導模型都可以實作同樣的「預測 + 擴容」模式。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想做事件型峰值的容量預測 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想用 AI / ML 做預測式擴容 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a></li>
<li>想拆解微服務以便獨立擴容 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>對照不同形狀的峰值 → <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 AWS Prime Day</a>（可預期極端峰值）/ <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</a>（無峰值低延遲）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/gr8-tech-case-study/">GR8 Tech Achieves High Performance and Scalability with Data Center Migration to AWS</a></li>
<li><a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html">Predictive scaling for Amazon EC2 Auto Scaling</a></li>
<li><a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html">Scheduled scaling for Amazon EC2 Auto Scaling</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>9.3 壓測工具選型</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/load-test-tooling/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/load-test-tooling/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>壓測工具選型的核心不是「哪個工具最強」、是「哪個工具最貼合本團隊的 workload model 表達能力跟 CI 整合需求」。沒有絕對最好的工具、只有最匹配當前場景的工具。&lt;/p>
&lt;p>跟 &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> 的關係：9.2 定義 workload 長什麼樣、9.3 找能複製這個樣子的工具。工具選對、壓測結果可信；工具選錯、壓測結果誤導。&lt;/p>
&lt;p>本章不是工具教學、是 &lt;em>選型維度&lt;/em> + 主流工具的 &lt;em>適用情境&lt;/em>。讀者讀完後能回答「我現在這個 workload 該用哪個工具」、而不是「哪個工具最快」。&lt;/p>
&lt;h2 id="六個選型維度">六個選型維度&lt;/h2>
&lt;p>選工具時要按六個維度評估、不能只看「能不能跑 HTTP GET」。&lt;/p>
&lt;p>&lt;strong>腳本表達能力&lt;/strong>：能不能寫複雜 user journey（登入 → 瀏覽 → 加購物車 → 結帳）、不只是單一 HTTP request。複雜系統的壓測通常是 user journey 級別、單一 endpoint 壓測只能找絕對極限、找不到 cross-endpoint contention。&lt;/p>
&lt;p>&lt;strong>協議支援&lt;/strong>：HTTP / WebSocket / gRPC / TCP / 自家二進位協議。WebSocket 跟 gRPC 是現代後端常見、傳統工具（JMeter、wrk）可能要 plugin 補。&lt;/p>
&lt;p>&lt;strong>規模能力&lt;/strong>：單機可以發多少 RPS、能不能分散式擴容。本機 wrk 可發 10K-50K RPS；分散式 Locust 可發 1M+ RPS。決定因素：CPU 效率、async I/O 模型、是否單機 bound。&lt;/p>
&lt;p>&lt;strong>CI 整合&lt;/strong>：能不能在 PR 上跑 lightweight perf check、結果能不能機器可讀（JSON / Prometheus exposition）、能不能跟 baseline diff。沒有 CI 整合的工具只能做「事件型壓測」、無法做 continuous perf governance。&lt;/p>
&lt;p>&lt;strong>結果分析&lt;/strong>：原生 dashboard（k6 Cloud、Gatling Enterprise）/ Prometheus + Grafana 整合 / 純文字輸出。要看結果分發、團隊成員能不能輕鬆查詢歷史。&lt;/p>
&lt;p>&lt;strong>學習曲線&lt;/strong>：腳本語言（JavaScript / Scala / Python / Go）、團隊熟悉度。工具好但團隊不會用、會變成 1-2 個工程師的孤島技能、流失時整套廢掉。&lt;/p>
&lt;h2 id="主流開源工具對照">主流開源工具對照&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>腳本&lt;/th>
 &lt;th>規模&lt;/th>
 &lt;th>學習曲線&lt;/th>
 &lt;th>適用情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>k6&lt;/td>
 &lt;td>JS&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>低-中&lt;/td>
 &lt;td>複雜 user journey + CI 整合、現代工具首選&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JMeter&lt;/td>
 &lt;td>XML/GUI&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>中-高&lt;/td>
 &lt;td>企業已有流程、protocol 廣、reluctant 改&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Gatling&lt;/td>
 &lt;td>Scala&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>報表精美、Scala 學習門檻&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Locust&lt;/td>
 &lt;td>Python&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>複雜邏輯、Python 生態、單機 throughput 受限&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Vegeta&lt;/td>
 &lt;td>CLI&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>CLI driven、quick HTTP 壓測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>wrk/wrk2&lt;/td>
 &lt;td>C&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>單機極限 RPS、saturation discovery 用&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>k6&lt;/strong> 是過去 5 年崛起的綜合首選。JavaScript 腳本（前端工程師也能寫）、原生 dashboard、Prometheus exposition、CI 友善。Grafana 收購後生態加速。缺點：複雜 stateful 場景（DB connection pool 共享）需要繞 workaround。&lt;/p>
&lt;p>&lt;strong>JMeter&lt;/strong> 是企業常見的 incumbent。協議支援廣（含 LDAP、JDBC、JMS）、有 GUI 編輯器。缺點：腳本是 XML、版本控制困難；GUI 主要用來生成腳本、實際跑壓測還是要 headless。已經在用的團隊建議繼續、新團隊不必特意選它。&lt;/p>
&lt;p>&lt;strong>Gatling&lt;/strong> 高 throughput 純 async、性能優秀、報表精美。缺點：Scala / Kotlin DSL 學習曲線陡、新版本（11+）改了 DSL 不向後相容。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>壓測工具選型的核心不是「哪個工具最強」、是「哪個工具最貼合本團隊的 workload model 表達能力跟 CI 整合需求」。沒有絕對最好的工具、只有最匹配當前場景的工具。</p>
<p>跟 <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> 的關係：9.2 定義 workload 長什麼樣、9.3 找能複製這個樣子的工具。工具選對、壓測結果可信；工具選錯、壓測結果誤導。</p>
<p>本章不是工具教學、是 <em>選型維度</em> + 主流工具的 <em>適用情境</em>。讀者讀完後能回答「我現在這個 workload 該用哪個工具」、而不是「哪個工具最快」。</p>
<h2 id="六個選型維度">六個選型維度</h2>
<p>選工具時要按六個維度評估、不能只看「能不能跑 HTTP GET」。</p>
<p><strong>腳本表達能力</strong>：能不能寫複雜 user journey（登入 → 瀏覽 → 加購物車 → 結帳）、不只是單一 HTTP request。複雜系統的壓測通常是 user journey 級別、單一 endpoint 壓測只能找絕對極限、找不到 cross-endpoint contention。</p>
<p><strong>協議支援</strong>：HTTP / WebSocket / gRPC / TCP / 自家二進位協議。WebSocket 跟 gRPC 是現代後端常見、傳統工具（JMeter、wrk）可能要 plugin 補。</p>
<p><strong>規模能力</strong>：單機可以發多少 RPS、能不能分散式擴容。本機 wrk 可發 10K-50K RPS；分散式 Locust 可發 1M+ RPS。決定因素：CPU 效率、async I/O 模型、是否單機 bound。</p>
<p><strong>CI 整合</strong>：能不能在 PR 上跑 lightweight perf check、結果能不能機器可讀（JSON / Prometheus exposition）、能不能跟 baseline diff。沒有 CI 整合的工具只能做「事件型壓測」、無法做 continuous perf governance。</p>
<p><strong>結果分析</strong>：原生 dashboard（k6 Cloud、Gatling Enterprise）/ Prometheus + Grafana 整合 / 純文字輸出。要看結果分發、團隊成員能不能輕鬆查詢歷史。</p>
<p><strong>學習曲線</strong>：腳本語言（JavaScript / Scala / Python / Go）、團隊熟悉度。工具好但團隊不會用、會變成 1-2 個工程師的孤島技能、流失時整套廢掉。</p>
<h2 id="主流開源工具對照">主流開源工具對照</h2>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>腳本</th>
          <th>規模</th>
          <th>學習曲線</th>
          <th>適用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>k6</td>
          <td>JS</td>
          <td>中</td>
          <td>低-中</td>
          <td>複雜 user journey + CI 整合、現代工具首選</td>
      </tr>
      <tr>
          <td>JMeter</td>
          <td>XML/GUI</td>
          <td>中</td>
          <td>中-高</td>
          <td>企業已有流程、protocol 廣、reluctant 改</td>
      </tr>
      <tr>
          <td>Gatling</td>
          <td>Scala</td>
          <td>高</td>
          <td>高</td>
          <td>報表精美、Scala 學習門檻</td>
      </tr>
      <tr>
          <td>Locust</td>
          <td>Python</td>
          <td>高</td>
          <td>中</td>
          <td>複雜邏輯、Python 生態、單機 throughput 受限</td>
      </tr>
      <tr>
          <td>Vegeta</td>
          <td>CLI</td>
          <td>中</td>
          <td>低</td>
          <td>CLI driven、quick HTTP 壓測</td>
      </tr>
      <tr>
          <td>wrk/wrk2</td>
          <td>C</td>
          <td>高</td>
          <td>低</td>
          <td>單機極限 RPS、saturation discovery 用</td>
      </tr>
  </tbody>
</table>
<p><strong>k6</strong> 是過去 5 年崛起的綜合首選。JavaScript 腳本（前端工程師也能寫）、原生 dashboard、Prometheus exposition、CI 友善。Grafana 收購後生態加速。缺點：複雜 stateful 場景（DB connection pool 共享）需要繞 workaround。</p>
<p><strong>JMeter</strong> 是企業常見的 incumbent。協議支援廣（含 LDAP、JDBC、JMS）、有 GUI 編輯器。缺點：腳本是 XML、版本控制困難；GUI 主要用來生成腳本、實際跑壓測還是要 headless。已經在用的團隊建議繼續、新團隊不必特意選它。</p>
<p><strong>Gatling</strong> 高 throughput 純 async、性能優秀、報表精美。缺點：Scala / Kotlin DSL 學習曲線陡、新版本（11+）改了 DSL 不向後相容。</p>
<p><strong>Locust</strong> 是 Python 生態的選擇、特別適合複雜業務邏輯（用 Python 寫 user journey 自然）。分散式部署原生支援。缺點：Python 單線程 throughput 受限、要靠分散式擴容。</p>
<p><strong>Vegeta</strong> 跟 <strong>wrk</strong> 是「quick check」工具、用於單一 endpoint 的極限測試。不適合複雜場景、適合 saturation discovery 第一輪「找這個服務的天花板」。</p>
<h2 id="production-traffic-replay-工具">Production traffic replay 工具</h2>
<p>當需要複製 <em>真實 production traffic</em> 的壓測場景時、需要另一類工具。</p>
<p><strong>GoReplay</strong> 是最常用的開源 traffic replay 工具。在 production server 上 tcpdump-based 捕獲 HTTP traffic、可以 store 到 file 或 stream 到 staging 環境。優點：開源、無 vendor lock-in；缺點：HTTP only、加密流量要拿到 key 才能用。</p>
<p><strong>Service mesh shadow（Istio / Linkerd mirror）</strong>：mesh 層 mirror traffic 到 staging service。優點：mesh 已部署的話 zero infra cost、加密 traffic 也能 mirror。缺點：需要 service mesh 已落地。</p>
<p><strong>AWS VPC Traffic Mirroring</strong>：底層網路層 mirror、application 完全無感。優點：最低 invasion；缺點：AWS only、加密 traffic 要另外處理。</p>
<p><strong>Diffy（Twitter / X 開源、已 deprecated 但概念仍有效）</strong>：dual-write 同時打到舊 / 新版本、比對結果。適合驗證「新版本是否邏輯正確」、不是純壓測。</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> — 用分散式 EC2 跑 synthetic load 模擬 100K 同時搶票；<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 服務">SeatGeek Virtual Waiting Room</a> — token 配發邏輯通常用 dual-write 驗證新舊版本一致。</p>
<h2 id="雲端-managed-壓測服務">雲端 managed 壓測服務</h2>
<p>當不想養 load test infrastructure、想 ad-hoc 跑大規模壓測時、用 managed service。</p>
<p><strong>AWS Distributed Load Testing</strong>：CloudFormation 起 Fargate cluster 跑 JMeter 或 Taurus、報表寫到 S3。優點：一鍵部署、Fargate 計費；缺點：JMeter-based、不是現代 k6 風格。</p>
<p><strong>Grafana k6 Cloud</strong>：託管 k6、跨地理 distributed 壓測（從多個 region 同時發流量）。優點：地理分散原生、跟 Grafana 整合無縫；缺點：vendor cost。</p>
<p><strong>Azure Load Testing</strong>：Azure 原生、整合 Application Insights。優點：Azure 用戶無縫；缺點：相對較新、生態還在補。</p>
<p><strong>GCP 沒有 first-party managed load testing</strong>：要靠 Marketplace 方案或自管 Locust on GKE。</p>
<h2 id="工具選型決策樹">工具選型決策樹</h2>
<p>落地時的快速決策：</p>
<ul>
<li>想快速驗證單一 API 極限 → wrk / Vegeta</li>
<li>想寫複雜 user journey + CI 整合 + JavaScript 團隊 → <strong>k6</strong>（新項目首選）</li>
<li>企業已有 JMeter 流程、不想換 → JMeter（接受 XML / GUI 複雜度）</li>
<li>大規模分散式 + Python 生態 → Locust</li>
<li>報表給管理層看、Scala 團隊 → Gatling</li>
<li>想複製真實 production traffic → GoReplay 或 service mesh shadow</li>
<li>想 ad-hoc 雲端大規模壓測 → 對應雲商的 managed load test</li>
</ul>
<h2 id="常見反模式">常見反模式</h2>
<ul>
<li><strong>只測單一 API、不測 user journey</strong>：找不到 cross-endpoint contention、找不到 session state 累積</li>
<li><strong>壓測機跟被測機在同一網段</strong>：網路延遲被低估、p99 比 production 樂觀</li>
<li><strong>壓測時 throttle 自己的工具</strong>：結果不是被測系統的極限、是工具自己的極限</li>
<li><strong>結果報表只看平均</strong>：<a href="/blog/backend/knowledge-cards/tail-latency/" data-link-title="Tail Latency" data-link-desc="說明 p99 / p999 等長尾延遲為何比平均延遲更能反映 saturation">tail latency</a> 看不到、p99 退化被掩蓋</li>
<li><strong>壓測環境跟 production hardware 不一致</strong>：CPU 型號、network、disk IOPS 差很大、結果不可外推</li>
<li><strong>沒驗證 model</strong>：跑了壓測但沒對比 production metrics、不知道 model 是否貼近 reality</li>
</ul>
<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>10,000 台 t2.micro 跑分散式壓測（$130 / 小時）</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>ML p99 &lt; 10ms 壓測必須帶 latency distribution</td>
      </tr>
  </tbody>
</table>
<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/saturation-discovery/" data-link-title="9.4 Saturation Discovery" data-link-desc="找出 throughput plateau 與 latency knee 的方法">9.4 Saturation Discovery</a>（用工具找 knee）</li>
<li>下游：<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>（CI 整合）</li>
<li>跨模組：<a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">06.1 CI Pipeline</a>（壓測在 CI 的位置）</li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/load-test/" data-link-title="Load Test" data-link-desc="說明在預期流量下驗證容量、延遲與降級策略的測試">Load Test</a></li>
<li><a href="/blog/backend/knowledge-cards/workload-model/" data-link-title="Workload Model" data-link-desc="描述 production traffic 形狀的可重播模型 — 容量規劃跟壓測的共同輸入">Workload Model</a></li>
<li><a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic</a></li>
<li><a href="/blog/backend/knowledge-cards/saturation-point/" data-link-title="Saturation Point" data-link-desc="說明系統從線性穩態進入 latency 指數成長區的關鍵流量點">Saturation Point</a></li>
</ul>
]]></content:encoded></item><item><title>9.C3 Coinbase International Exchange：超低延遲交易的逆向容量設計</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-ultra-low-latency-exchange-2023/</guid><description>&lt;p>這個案例的核心責任是揭示「無明顯峰值但延遲就是收入」這類負載的容量設計、跟前兩個案例形成對照。金融交易不靠峰值定義成敗、靠每個交易的延遲穩定性 — 多 1ms 延遲在套利策略下可能直接吃掉整筆交易的利潤。Coinbase International Exchange 為這類負載做了一系列「反主流」的取捨：固定佈署、不啟用自動擴容、強制節點實體靠近。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Coinbase 在 2023-05 推出國際交易所、上線後關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/coinbase-cryptocurrency-exchange-case-study/">Coinbase Case Study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>吞吐量&lt;/td>
 &lt;td>100,000 messages/sec（擴容後）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲目標&lt;/td>
 &lt;td>sub-millisecond（次毫秒級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>累計交易額&lt;/td>
 &lt;td>上線以來超過 150 億美元&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>24/7、受監管的交易平台&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Amazon EC2 z1d 實例&lt;/strong>：高頻 CPU + NVMe 本地儲存、針對單執行緒效能最佳化&lt;/li>
&lt;li>&lt;strong>EC2 Cluster Placement Groups&lt;/strong>：強制把節點集中到單一機架附近、最小化 node-to-node 網路延遲&lt;/li>
&lt;li>&lt;strong>Amazon Aurora&lt;/strong>：高速 transaction lookup 的關聯式資料庫&lt;/li>
&lt;li>「Built from the ground up, using Cloud Native principles」（沒有複用既有交易所程式碼）&lt;/li>
&lt;li>內部使用 &lt;strong>RAFT consensus&lt;/strong> 維持交易順序&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這個案例最值得讀的地方、是它「沒有做」的事比「做了」的事更有教學價值。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>沒有用 Auto Scaling&lt;/strong>：交易撮合引擎用 RAFT consensus 維持嚴格順序、節點數量是 consensus 一部分、不能臨時增加。容量規劃完全是 &lt;em>pre-provision&lt;/em>、不是 reactive。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 必須區分「可水平擴容服務」跟「不可水平擴容服務」、後者的容量公式只有 headroom × peak、沒有 elastic 補救。&lt;/li>
&lt;li>&lt;strong>沒有用通用 EC2 實例&lt;/strong>：z1d 是 AWS 針對「高頻 CPU + NVMe」設計的特化實例、犧牲了通用性換取單核效能。這層選擇隱含一個容量規劃決策：&lt;em>單機效能上限&lt;/em> 直接決定 &lt;em>系統理論吞吐上限&lt;/em>、橫向擴容不能超過 RAFT 節點數限制、那麼縱向就必須榨乾。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 必須先判斷瓶頸屬「可分散」還是「不可分散」。&lt;/li>
&lt;li>&lt;strong>沒有用多區域分散&lt;/strong>：Cluster Placement Group 把節點壓到同一可用區內、犧牲了 region failover 速度、換取 node-to-node 網路延遲。這跟「高可用性」的常見直覺相反、是「延遲敏感型負載的容量設計優先於可靠性設計」的一個範例。&lt;/li>
&lt;li>&lt;strong>延遲是設計輸入、不是設計結果&lt;/strong>：sub-millisecond 是先訂目標、再反推所有架構選擇的結果、壓測只是驗證手段。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為&lt;/a> 中 Little&amp;rsquo;s Law 的反向應用 — 給定延遲目標 + 吞吐目標、反推 concurrency 上限 + 每個 stage 的 latency budget。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：「sub-millisecond latency 達成」這類陳述通常指 &lt;em>p50 或 p90&lt;/em>、不一定是 p99 或 p999。長尾延遲在 RAFT 系統下可能比平均高一個數量級（leader election、replication lag）。讀案例時要注意延遲分布 vs 平均值的差別。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>延遲敏感型服務先做 latency budget 反推&lt;/strong>：給每個 stage（網路、CPU、磁碟、序列化、共識）一個 latency 配額、總和等於 SLO 上限。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a>。&lt;/li>
&lt;li>&lt;strong>單機效能榨乾優先於橫向擴容&lt;/strong>：當 consensus / ordered processing 限制了水平擴容時、單機選型（CPU 頻率、NUMA locality、NVMe）變成主要槓桿。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 把 saturation 點推得越遠。&lt;/li>
&lt;li>&lt;strong>拓樸感知的部署策略&lt;/strong>：Cluster Placement Group 是 AWS 名稱、概念是「網路拓樸感知的工作負載放置」。GCP 有 Compact Placement Policy、Azure 有 Proximity Placement Groups、自建 Kubernetes 有 Pod Topology Spread Constraints + Node Affinity。&lt;/li>
&lt;li>&lt;strong>接受「不可彈性」是有意識決策、不是失敗&lt;/strong>：很多服務不該全部都自動擴容。設計時要區分「需要 elastic 的 stateless 邊緣」跟「必須 pre-provision 的有狀態核心」、容量規劃也要兩條腿。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：所有主流雲端都有對應的高頻 CPU 實例（GCP C2 / Azure HBv 系列）、placement policy 與本地 NVMe 儲存。自建環境可以用 SR-IOV + RDMA + NUMA pinning 達成更極致的版本。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是揭示「無明顯峰值但延遲就是收入」這類負載的容量設計、跟前兩個案例形成對照。金融交易不靠峰值定義成敗、靠每個交易的延遲穩定性 — 多 1ms 延遲在套利策略下可能直接吃掉整筆交易的利潤。Coinbase International Exchange 為這類負載做了一系列「反主流」的取捨：固定佈署、不啟用自動擴容、強制節點實體靠近。</p>
<h2 id="觀察">觀察</h2>
<p>Coinbase 在 2023-05 推出國際交易所、上線後關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/coinbase-cryptocurrency-exchange-case-study/">Coinbase Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>吞吐量</td>
          <td>100,000 messages/sec（擴容後）</td>
      </tr>
      <tr>
          <td>延遲目標</td>
          <td>sub-millisecond（次毫秒級）</td>
      </tr>
      <tr>
          <td>累計交易額</td>
          <td>上線以來超過 150 億美元</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>24/7、受監管的交易平台</td>
      </tr>
  </tbody>
</table>
<p>服務組合：</p>
<ul>
<li><strong>Amazon EC2 z1d 實例</strong>：高頻 CPU + NVMe 本地儲存、針對單執行緒效能最佳化</li>
<li><strong>EC2 Cluster Placement Groups</strong>：強制把節點集中到單一機架附近、最小化 node-to-node 網路延遲</li>
<li><strong>Amazon Aurora</strong>：高速 transaction lookup 的關聯式資料庫</li>
<li>「Built from the ground up, using Cloud Native principles」（沒有複用既有交易所程式碼）</li>
<li>內部使用 <strong>RAFT consensus</strong> 維持交易順序</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>這個案例最值得讀的地方、是它「沒有做」的事比「做了」的事更有教學價值。</p>
<ol>
<li><strong>沒有用 Auto Scaling</strong>：交易撮合引擎用 RAFT consensus 維持嚴格順序、節點數量是 consensus 一部分、不能臨時增加。容量規劃完全是 <em>pre-provision</em>、不是 reactive。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 必須區分「可水平擴容服務」跟「不可水平擴容服務」、後者的容量公式只有 headroom × peak、沒有 elastic 補救。</li>
<li><strong>沒有用通用 EC2 實例</strong>：z1d 是 AWS 針對「高頻 CPU + NVMe」設計的特化實例、犧牲了通用性換取單核效能。這層選擇隱含一個容量規劃決策：<em>單機效能上限</em> 直接決定 <em>系統理論吞吐上限</em>、橫向擴容不能超過 RAFT 節點數限制、那麼縱向就必須榨乾。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 必須先判斷瓶頸屬「可分散」還是「不可分散」。</li>
<li><strong>沒有用多區域分散</strong>：Cluster Placement Group 把節點壓到同一可用區內、犧牲了 region failover 速度、換取 node-to-node 網路延遲。這跟「高可用性」的常見直覺相反、是「延遲敏感型負載的容量設計優先於可靠性設計」的一個範例。</li>
<li><strong>延遲是設計輸入、不是設計結果</strong>：sub-millisecond 是先訂目標、再反推所有架構選擇的結果、壓測只是驗證手段。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a> 中 Little&rsquo;s Law 的反向應用 — 給定延遲目標 + 吞吐目標、反推 concurrency 上限 + 每個 stage 的 latency budget。</li>
</ol>
<p>需要警惕的判讀盲點：「sub-millisecond latency 達成」這類陳述通常指 <em>p50 或 p90</em>、不一定是 p99 或 p999。長尾延遲在 RAFT 系統下可能比平均高一個數量級（leader election、replication lag）。讀案例時要注意延遲分布 vs 平均值的差別。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>延遲敏感型服務先做 latency budget 反推</strong>：給每個 stage（網路、CPU、磁碟、序列化、共識）一個 latency 配額、總和等於 SLO 上限。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a>。</li>
<li><strong>單機效能榨乾優先於橫向擴容</strong>：當 consensus / ordered processing 限制了水平擴容時、單機選型（CPU 頻率、NUMA locality、NVMe）變成主要槓桿。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 把 saturation 點推得越遠。</li>
<li><strong>拓樸感知的部署策略</strong>：Cluster Placement Group 是 AWS 名稱、概念是「網路拓樸感知的工作負載放置」。GCP 有 Compact Placement Policy、Azure 有 Proximity Placement Groups、自建 Kubernetes 有 Pod Topology Spread Constraints + Node Affinity。</li>
<li><strong>接受「不可彈性」是有意識決策、不是失敗</strong>：很多服務不該全部都自動擴容。設計時要區分「需要 elastic 的 stateless 邊緣」跟「必須 pre-provision 的有狀態核心」、容量規劃也要兩條腿。</li>
</ol>
<p>跨平台等效：所有主流雲端都有對應的高頻 CPU 實例（GCP C2 / Azure HBv 系列）、placement policy 與本地 NVMe 儲存。自建環境可以用 SR-IOV + RDMA + NUMA pinning 達成更極致的版本。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計延遲敏感型服務的容量地圖 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想搞清楚哪些服務該水平擴容、哪些不該 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a></li>
<li>想做 latency budget 反推 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a></li>
<li>對照不同形狀的負載 → <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 AWS Prime Day</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</a>（事件型不可預期峰值）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/coinbase-cryptocurrency-exchange-case-study/">Coinbase Launches an Ultra-Low-Latency Cryptocurrency Exchange on AWS</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/coinbase-migration-case-study/">Coinbase Scales 50% Faster, Cuts Costs 62% with AWS</a></li>
<li><a href="https://aws.amazon.com/video/watch/a413043e5cb/">Ultra-Low-Latency Crypto Exchange on AWS（video）</a></li>
</ul>
]]></content:encoded></item><item><title>8.3 Dropbox：從 Python 遷移到 Go</title><link>https://tarrragon.github.io/blog/go/08-case-studies/dropbox/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/dropbox/</guid><description>&lt;p>Dropbox 的案例是最典型的「性能關鍵服務遷移」故事之一。官方案例直接寫到，他們把 performance-critical backends 從 Python 轉到 Go，以獲得更好的 concurrency support 與更快的執行速度。&lt;/p>
&lt;h2 id="你應該看什麼">你應該看什麼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://go.dev/solutions/dropbox">Dropbox - Open sourcing our Go libraries&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼&lt;/h2>
&lt;ol>
&lt;li>Go 很常被選在 Python 已經不夠用的後端邊界。&lt;/li>
&lt;li>併發支援通常是遷移的重要原因之一。&lt;/li>
&lt;li>遷移通常先把性能最敏感的部分換成 Go，逐步擴展。&lt;/li>
&lt;/ol>
&lt;h2 id="可對照的公開原始碼">可對照的公開原始碼&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/dropbox/godropbox">dropbox/godropbox&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/dropbox/dropbox-api-spec">dropbox/dropbox-api-spec&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Dropbox 的公開 Go libraries 與 API spec 很適合對照閱讀。你會看到一個大公司如何把 Go 用在可重用工具與服務邊界上。&lt;/p></description><content:encoded><![CDATA[<p>Dropbox 的案例是最典型的「性能關鍵服務遷移」故事之一。官方案例直接寫到，他們把 performance-critical backends 從 Python 轉到 Go，以獲得更好的 concurrency support 與更快的執行速度。</p>
<h2 id="你應該看什麼">你應該看什麼</h2>
<ul>
<li><a href="https://go.dev/solutions/dropbox">Dropbox - Open sourcing our Go libraries</a></li>
</ul>
<h2 id="這個案例告訴我們什麼">這個案例告訴我們什麼</h2>
<ol>
<li>Go 很常被選在 Python 已經不夠用的後端邊界。</li>
<li>併發支援通常是遷移的重要原因之一。</li>
<li>遷移通常先把性能最敏感的部分換成 Go，逐步擴展。</li>
</ol>
<h2 id="可對照的公開原始碼">可對照的公開原始碼</h2>
<ul>
<li><a href="https://github.com/dropbox/godropbox">dropbox/godropbox</a></li>
<li><a href="https://github.com/dropbox/dropbox-api-spec">dropbox/dropbox-api-spec</a></li>
</ul>
<p>Dropbox 的公開 Go libraries 與 API spec 很適合對照閱讀。你會看到一個大公司如何把 Go 用在可重用工具與服務邊界上。</p>
]]></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>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><item><title>9.C4 DraftKings：Aurora 撐 100 萬 ops/min 的體育博彩金融帳本</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/draftkings-aurora-financial-ledger/</guid><description>&lt;p>這個案例的核心責任是說明「transactional 金融系統」如何在不可預期峰值下維持低延遲。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &amp;#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech&lt;/a> 對比 — GR8 Tech 走「微服務 + AI 預測擴容」、DraftKings 走「Aurora 單一資料庫服務支撐多 DB cluster」、兩條路徑都解決同類業務問題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>DraftKings 帳本系統的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/draftkings-aurora-case-study/">DraftKings case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>客戶數&lt;/td>
 &lt;td>310 萬 unique customers / month (Q2 2024)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值操作&lt;/td>
 &lt;td>100 萬 ops / 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>讀延遲&lt;/td>
 &lt;td>&amp;lt; 1 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫延遲&lt;/td>
 &lt;td>6 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Replication lag&lt;/td>
 &lt;td>從 30 秒降到 10-30 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database 數量&lt;/td>
 &lt;td>200 個 individual databases&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Super Bowl 流量&lt;/td>
 &lt;td>比賽季開幕高 +50%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon Aurora MySQL-Compatible、Aurora Replicas（讀寫分流）、Aurora I/O-Optimized（2023-05 推出）、Aurora Database Cloning（測試環境）、跨三個 AZ 儲存複製。&lt;/p>
&lt;p>關鍵負載形狀：「write workloads spike up significantly around payout events, but opening the app during the game also activates a lot of balance queries」— 比賽進行時是讀爆量、payout event 時是寫爆量、雙峰錯位。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>DraftKings 的工程選擇揭露三個 OLTP 容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>200 個獨立資料庫 = sharding 預先做好&lt;/strong>：按業務切 200 個 cluster、用巨型 cluster 撐全部在這個規模行不通。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 把「單機極限」改成「shard 極限」、每個 shard 的容量規劃變成獨立問題。&lt;/li>
&lt;li>&lt;strong>Replication lag 30 秒 → 10-30 ms&lt;/strong>：這個改善不只是「快」、而是讓 read-after-write 變得可預測。Aurora 的 storage layer 多 AZ 複製是這個 lag 改善的主因。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 replication lag 影響 transaction boundary 設計。&lt;/li>
&lt;li>&lt;strong>Super Bowl +50% 「no sweat」&lt;/strong>：這句話的工程意義是 &lt;em>提前做好容量規劃&lt;/em>、不是「Aurora 神奇」。寫 workload 預期可能 + 50%、整個 system headroom 預留至少 50%、加上 read replica 動態加減、才能讓 50% 增幅變成「不流汗」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的 headroom budget 與 event-driven scheduled scaling。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：100 萬 ops / 分鐘 = ~17K ops / 秒、跨 200 個 databases 平均下來每個 DB 約 80 ops / 秒。這不是「單一 DB 撐 100 萬 ops」、而是「200 shard 加總 100 萬」。讀案例時要看「峰值是分散到多少 shard」、不只看總數。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「transactional 金融系統」如何在不可預期峰值下維持低延遲。跟 <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> 對比 — GR8 Tech 走「微服務 + AI 預測擴容」、DraftKings 走「Aurora 單一資料庫服務支撐多 DB cluster」、兩條路徑都解決同類業務問題。</p>
<h2 id="觀察">觀察</h2>
<p>DraftKings 帳本系統的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/draftkings-aurora-case-study/">DraftKings case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶數</td>
          <td>310 萬 unique customers / month (Q2 2024)</td>
      </tr>
      <tr>
          <td>峰值操作</td>
          <td>100 萬 ops / 分鐘</td>
      </tr>
      <tr>
          <td>讀延遲</td>
          <td>&lt; 1 ms</td>
      </tr>
      <tr>
          <td>寫延遲</td>
          <td>6 ms</td>
      </tr>
      <tr>
          <td>Replication lag</td>
          <td>從 30 秒降到 10-30 ms</td>
      </tr>
      <tr>
          <td>Database 數量</td>
          <td>200 個 individual databases</td>
      </tr>
      <tr>
          <td>Super Bowl 流量</td>
          <td>比賽季開幕高 +50%</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon Aurora MySQL-Compatible、Aurora Replicas（讀寫分流）、Aurora I/O-Optimized（2023-05 推出）、Aurora Database Cloning（測試環境）、跨三個 AZ 儲存複製。</p>
<p>關鍵負載形狀：「write workloads spike up significantly around payout events, but opening the app during the game also activates a lot of balance queries」— 比賽進行時是讀爆量、payout event 時是寫爆量、雙峰錯位。</p>
<h2 id="判讀">判讀</h2>
<p>DraftKings 的工程選擇揭露三個 OLTP 容量設計重點。</p>
<ol>
<li><strong>200 個獨立資料庫 = sharding 預先做好</strong>：按業務切 200 個 cluster、用巨型 cluster 撐全部在這個規模行不通。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 把「單機極限」改成「shard 極限」、每個 shard 的容量規劃變成獨立問題。</li>
<li><strong>Replication lag 30 秒 → 10-30 ms</strong>：這個改善不只是「快」、而是讓 read-after-write 變得可預測。Aurora 的 storage layer 多 AZ 複製是這個 lag 改善的主因。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 replication lag 影響 transaction boundary 設計。</li>
<li><strong>Super Bowl +50% 「no sweat」</strong>：這句話的工程意義是 <em>提前做好容量規劃</em>、不是「Aurora 神奇」。寫 workload 預期可能 + 50%、整個 system headroom 預留至少 50%、加上 read replica 動態加減、才能讓 50% 增幅變成「不流汗」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的 headroom budget 與 event-driven scheduled scaling。</li>
</ol>
<p>需要警惕：100 萬 ops / 分鐘 = ~17K ops / 秒、跨 200 個 databases 平均下來每個 DB 約 80 ops / 秒。這不是「單一 DB 撐 100 萬 ops」、而是「200 shard 加總 100 萬」。讀案例時要看「峰值是分散到多少 shard」、不只看總數。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>按業務切 OLTP cluster、不要一個 DB 撐全部</strong>：DraftKings 200 個 databases 顯示「業務切片」是 OLTP 擴容的前置。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema design 與 partition 決策。</li>
<li><strong>讀寫分流是 OLTP 容量規劃的基線</strong>：6ms 寫 vs &lt;1ms 讀的差距、加上 read replica、是 OLTP 擴容最基本的兩個槓桿。</li>
<li><strong>事件型峰值預測寫進 baseline</strong>：Super Bowl 是已知事件、+50% 是歷史經驗、所以可以提前 pre-scale。事件未知（突發新聞、KOL 推廣）的情況才需要 AI 預測（對照 <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>）。</li>
</ol>
<p>跨平台等效：GCP Cloud SQL + read replica / Spanner、Azure Database for PostgreSQL + read replica、自建 PostgreSQL + Patroni + pgbouncer 都可以實作對等架構。Aurora 的差異是 storage layer 對 replica 的 lag 改善。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 OLTP 高峰容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想搞清楚事件型 vs 突發型峰值 → <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> 對照</li>
<li>想做 read replica 容量設計 → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>想理解 replication lag 對 transaction boundary 的影響 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想理解 6 寫 / 4 讀 quorum 跟 200 cluster fleet 治理 → <a href="/blog/backend/01-database/vendors/aurora/storage-architecture/" data-link-title="Aurora Storage Architecture：quorum-based 分散式 log 與韌性即性能設計" data-link-desc="Aurora storage / compute 分離、6-way 跨 AZ replication、4-of-6 write / 3-of-6 read quorum、韌性投資自動 amortize 成 read 性能、DraftKings 6ms 寫 / &lt;1ms 讀 production reference">Aurora 儲存層架構</a></li>
<li>想規劃 read replica scaling 與 reader endpoint 路由 → <a href="/blog/backend/01-database/vendors/aurora/read-replica-scaling/" data-link-title="Aurora Read Replica Scaling：15 replica 上限、lag profile、headroom 預留與 fleet 治理" data-link-desc="Aurora 15 replica 上限、共享 storage 為什麼能養大量 replica、事件型容量分級表、DraftKings headroom 預留判讀、FanDuel 雙 SLO 並行、fleet 治理 3 條 driver（business sharding / microservice / 合規）">Aurora read replica scaling</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/draftkings-aurora-case-study/">DraftKings Scales Its Financial Ledger with Amazon Aurora</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-aurora-i-o-optimized-database-storage-configuration/">Aurora I/O-Optimized announcement</a></li>
</ul>
]]></content:encoded></item><item><title>4.4 sync.RWMutex：保護共享狀態</title><link>https://tarrragon.github.io/blog/go/04-concurrency/rwmutex/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/04-concurrency/rwmutex/</guid><description>&lt;p>&lt;code>sync.RWMutex&lt;/code> 是 Go 用來保護共享狀態的讀寫鎖。它的核心用途是允許多個讀取者同時讀取，但寫入者必須獨占資料，避免 goroutine 同時讀寫 map、slice 或 struct 時產生資料競爭。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>理解 data race 的風險&lt;/li>
&lt;li>區分 &lt;code>Mutex&lt;/code> 與 &lt;code>RWMutex&lt;/code>&lt;/li>
&lt;li>用 &lt;code>RLock&lt;/code> / &lt;code>RUnlock&lt;/code> 保護讀取&lt;/li>
&lt;li>用 &lt;code>Lock&lt;/code> / &lt;code>Unlock&lt;/code> 保護寫入&lt;/li>
&lt;li>避免回傳內部 map 或 slice 破壞鎖邊界&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察共享-map-不能被多個-goroutine-無保護地讀寫">【觀察】共享 map 不能被多個 goroutine 無保護地讀寫&lt;/h2>
&lt;p>共享狀態的核心規則是：只要多個 goroutine 可能同時讀寫同一份資料，就必須用同步機制保護。以下程式同時讀寫 map，存在 data race：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">UserRepository&lt;/span> &lt;span class="kd">struct&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">users&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">User&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="nx">User&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">user&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;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">bool&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">10&lt;/span>&lt;span class="cl"> &lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&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 class="k">return&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果 &lt;code>Set&lt;/code> 和 &lt;code>Get&lt;/code> 從不同 goroutine 同時執行，map 可能被同時讀寫。Go 的 map 不保證這種情境安全。&lt;/p>
&lt;h2 id="判讀rwmutex-區分讀取與寫入">【判讀】RWMutex 區分讀取與寫入&lt;/h2>
&lt;p>&lt;code>RWMutex&lt;/code> 的核心規則是：讀取使用 &lt;code>RLock&lt;/code>，寫入使用 &lt;code>Lock&lt;/code>；多個讀取可並行，寫入會排他。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">UserRepository&lt;/span> &lt;span class="kd">struct&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">users&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">User&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">mu&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">RWMutex&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="nx">User&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="k">defer&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">user&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="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">bool&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">13&lt;/span>&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RLock&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="k">defer&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RUnlock&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="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">id&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="k">return&lt;/span> &lt;span class="nx">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&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>&lt;code>Set&lt;/code> 修改 map，所以用 &lt;code>Lock&lt;/code>。&lt;code>Get&lt;/code> 只讀 map，所以用 &lt;code>RLock&lt;/code>。&lt;/p>
&lt;h2 id="策略鎖保護的是資料不變式">【策略】鎖保護的是資料不變式&lt;/h2>
&lt;p>鎖範圍的核心規則是：鎖要包住所有需要一致觀察或一致修改的資料。鎖的邊界應涵蓋完整不變式，慢速 I/O、網路呼叫與和共享資料無關的計算則應放在鎖外。&lt;/p>
&lt;p>例如，這個更新同時修改兩個欄位，兩個欄位要在同一把鎖內更新：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">user&lt;/span> &lt;span class="nx">User&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">2&lt;/span>&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Lock&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="k">defer&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unlock&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>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ID&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">user&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="o">++&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>如果 &lt;code>users&lt;/code> 和 &lt;code>count&lt;/code> 分開鎖，讀者可能看到 map 已更新但 count 還沒更新的中間狀態。&lt;/p>
&lt;h2 id="執行回傳資料時要保留-copy-boundary">【執行】回傳資料時要保留 copy boundary&lt;/h2>
&lt;p>鎖邊界的核心規則是：鎖只能保護鎖內操作；回傳內部 map 會讓呼叫者在鎖外修改資料，破壞 repository 對狀態的控制權。&lt;/p>
&lt;p>不安全做法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Users&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">User&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">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RLock&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="k">defer&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RUnlock&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="k">return&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>安全做法是回傳複製：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">UserRepository&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Users&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">User&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">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RLock&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="k">defer&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">mu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">RUnlock&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="nx">result&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nx">User&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&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="k">for&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">user&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">users&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">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">user&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">result&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;/code>&lt;/pre>&lt;/div>&lt;p>呼叫者拿到的是複本，不能繞過 &lt;code>UserRepository&lt;/code> 修改內部狀態。&lt;/p>
&lt;h2 id="mutex-還是-rwmutex">Mutex 還是 RWMutex？&lt;/h2>
&lt;p>選擇鎖的核心規則是：讀多寫少且讀操作可並行時用 &lt;code>RWMutex&lt;/code>；不確定時先用 &lt;code>Mutex&lt;/code>，設計更簡單。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>鎖&lt;/th>
 &lt;th>適合情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>sync.Mutex&lt;/code>&lt;/td>
 &lt;td>狀態小、讀寫都簡單、沒有明顯讀多寫少&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>sync.RWMutex&lt;/code>&lt;/td>
 &lt;td>讀取頻繁、寫入較少、讀操作可安全並行&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;code>sync.Mutex&lt;/code> 的核心優勢是簡單。若狀態很小、讀寫都很快，或讀寫比例尚未明確，先使用 &lt;code>Mutex&lt;/code> 通常更容易維護。它讓每次存取都走同一條鎖路徑，讀者也比較容易確認資料何時被保護。&lt;/p>
&lt;p>&lt;code>sync.RWMutex&lt;/code> 的核心優勢是讀多寫少時可以讓多個讀取並行。它適合像 in-memory cache、狀態查詢 repository 或連線註冊表這類讀取頻繁的資料結構。使用它時，寫入仍然要用 &lt;code>Lock&lt;/code>，因為 &lt;code>RLock&lt;/code> 只適合保護純讀取。&lt;/p>
&lt;p>鎖選擇的判斷重點是資料不變式與讀寫比例。若讀取本身會組裝複雜資料、需要複製大型 map，或很快就會呼叫外部 I/O，&lt;code>RWMutex&lt;/code> 帶來的並行讀取收益可能被複雜度抵消。&lt;/p>
&lt;h2 id="替代方案什麼時候不用-rwmutex">替代方案：什麼時候不用 RWMutex&lt;/h2>
&lt;p>&lt;code>RWMutex&lt;/code> 不是共享狀態保護的唯一選擇。三類替代方案各有適用條件：&lt;/p></description><content:encoded><![CDATA[<p><code>sync.RWMutex</code> 是 Go 用來保護共享狀態的讀寫鎖。它的核心用途是允許多個讀取者同時讀取，但寫入者必須獨占資料，避免 goroutine 同時讀寫 map、slice 或 struct 時產生資料競爭。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>理解 data race 的風險</li>
<li>區分 <code>Mutex</code> 與 <code>RWMutex</code></li>
<li>用 <code>RLock</code> / <code>RUnlock</code> 保護讀取</li>
<li>用 <code>Lock</code> / <code>Unlock</code> 保護寫入</li>
<li>避免回傳內部 map 或 slice 破壞鎖邊界</li>
</ol>
<hr>
<h2 id="觀察共享-map-不能被多個-goroutine-無保護地讀寫">【觀察】共享 map 不能被多個 goroutine 無保護地讀寫</h2>
<p>共享狀態的核心規則是：只要多個 goroutine 可能同時讀寫同一份資料，就必須用同步機制保護。以下程式同時讀寫 map，存在 data race：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">UserRepository</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">users</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="nx">User</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">UserRepository</span><span class="p">)</span> <span class="nf">Set</span><span class="p">(</span><span class="nx">id</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">user</span> <span class="nx">User</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="p">=</span> <span class="nx">user</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">UserRepository</span><span class="p">)</span> <span class="nf">Get</span><span class="p">(</span><span class="nx">id</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">User</span><span class="p">,</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nx">user</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="nx">user</span><span class="p">,</span> <span class="nx">ok</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>如果 <code>Set</code> 和 <code>Get</code> 從不同 goroutine 同時執行，map 可能被同時讀寫。Go 的 map 不保證這種情境安全。</p>
<h2 id="判讀rwmutex-區分讀取與寫入">【判讀】RWMutex 區分讀取與寫入</h2>
<p><code>RWMutex</code> 的核心規則是：讀取使用 <code>RLock</code>，寫入使用 <code>Lock</code>；多個讀取可並行，寫入會排他。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">UserRepository</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">users</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="nx">User</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">mu</span>    <span class="nx">sync</span><span class="p">.</span><span class="nx">RWMutex</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">UserRepository</span><span class="p">)</span> <span class="nf">Set</span><span class="p">(</span><span class="nx">id</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">user</span> <span class="nx">User</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">defer</span> <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="p">=</span> <span class="nx">user</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="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">UserRepository</span><span class="p">)</span> <span class="nf">Get</span><span class="p">(</span><span class="nx">id</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="nx">User</span><span class="p">,</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RLock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">defer</span> <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RUnlock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nx">user</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">r</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="nx">user</span><span class="p">,</span> <span class="nx">ok</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>Set</code> 修改 map，所以用 <code>Lock</code>。<code>Get</code> 只讀 map，所以用 <code>RLock</code>。</p>
<h2 id="策略鎖保護的是資料不變式">【策略】鎖保護的是資料不變式</h2>
<p>鎖範圍的核心規則是：鎖要包住所有需要一致觀察或一致修改的資料。鎖的邊界應涵蓋完整不變式，慢速 I/O、網路呼叫與和共享資料無關的計算則應放在鎖外。</p>
<p>例如，這個更新同時修改兩個欄位，兩個欄位要在同一把鎖內更新：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">UserRepository</span><span class="p">)</span> <span class="nf">Add</span><span class="p">(</span><span class="nx">user</span> <span class="nx">User</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">defer</span> <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">users</span><span class="p">[</span><span class="nx">user</span><span class="p">.</span><span class="nx">ID</span><span class="p">]</span> <span class="p">=</span> <span class="nx">user</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">count</span><span class="o">++</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>如果 <code>users</code> 和 <code>count</code> 分開鎖，讀者可能看到 map 已更新但 count 還沒更新的中間狀態。</p>
<h2 id="執行回傳資料時要保留-copy-boundary">【執行】回傳資料時要保留 copy boundary</h2>
<p>鎖邊界的核心規則是：鎖只能保護鎖內操作；回傳內部 map 會讓呼叫者在鎖外修改資料，破壞 repository 對狀態的控制權。</p>
<p>不安全做法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">UserRepository</span><span class="p">)</span> <span class="nf">Users</span><span class="p">()</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="nx">User</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RLock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">defer</span> <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RUnlock</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">r</span><span class="p">.</span><span class="nx">users</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>安全做法是回傳複製：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="o">*</span><span class="nx">UserRepository</span><span class="p">)</span> <span class="nf">Users</span><span class="p">()</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="nx">User</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RLock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">defer</span> <span class="nx">r</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RUnlock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nx">result</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="nx">User</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">users</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">user</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">r</span><span class="p">.</span><span class="nx">users</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nx">result</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="p">=</span> <span class="nx">user</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="k">return</span> <span class="nx">result</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>呼叫者拿到的是複本，不能繞過 <code>UserRepository</code> 修改內部狀態。</p>
<h2 id="mutex-還是-rwmutex">Mutex 還是 RWMutex？</h2>
<p>選擇鎖的核心規則是：讀多寫少且讀操作可並行時用 <code>RWMutex</code>；不確定時先用 <code>Mutex</code>，設計更簡單。</p>
<table>
  <thead>
      <tr>
          <th>鎖</th>
          <th>適合情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>sync.Mutex</code></td>
          <td>狀態小、讀寫都簡單、沒有明顯讀多寫少</td>
      </tr>
      <tr>
          <td><code>sync.RWMutex</code></td>
          <td>讀取頻繁、寫入較少、讀操作可安全並行</td>
      </tr>
  </tbody>
</table>
<p><code>sync.Mutex</code> 的核心優勢是簡單。若狀態很小、讀寫都很快，或讀寫比例尚未明確，先使用 <code>Mutex</code> 通常更容易維護。它讓每次存取都走同一條鎖路徑，讀者也比較容易確認資料何時被保護。</p>
<p><code>sync.RWMutex</code> 的核心優勢是讀多寫少時可以讓多個讀取並行。它適合像 in-memory cache、狀態查詢 repository 或連線註冊表這類讀取頻繁的資料結構。使用它時，寫入仍然要用 <code>Lock</code>，因為 <code>RLock</code> 只適合保護純讀取。</p>
<p>鎖選擇的判斷重點是資料不變式與讀寫比例。若讀取本身會組裝複雜資料、需要複製大型 map，或很快就會呼叫外部 I/O，<code>RWMutex</code> 帶來的並行讀取收益可能被複雜度抵消。</p>
<h2 id="替代方案什麼時候不用-rwmutex">替代方案：什麼時候不用 RWMutex</h2>
<p><code>RWMutex</code> 不是共享狀態保護的唯一選擇。三類替代方案各有適用條件：</p>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>適用情境</th>
          <th>跟 RWMutex 對比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>sync.Map</code></td>
          <td>key 集合大、entries 異步增減、讀寫分散在不同 key</td>
          <td>內建讀寫並行、無全域鎖；但語意不同（無 size、無 range 一致性）</td>
      </tr>
      <tr>
          <td><code>sync/atomic</code></td>
          <td>單一純量（counter、flag、pointer）</td>
          <td>無鎖、最快；但只能保護單一值、不能保護結構不變式</td>
      </tr>
      <tr>
          <td>Channel-based coordination</td>
          <td>狀態由單一 owner goroutine 持有、其他 goroutine 透過 channel 傳訊息</td>
          <td>用 ownership 取代 sharing；適合 producer / consumer pattern、見 <a href="../channel/">4.2 channel</a></td>
      </tr>
  </tbody>
</table>
<p>判別準則：</p>
<ul>
<li>保護<strong>多欄位不變式</strong>（如 <code>users</code> + <code>count</code> 同步）→ <code>RWMutex</code> 或 <code>Mutex</code></li>
<li>保護<strong>單一純量</strong>且操作可表達為 atomic op（CAS、increment）→ <code>sync/atomic</code></li>
<li>保護<strong>大量獨立 key</strong> 且無跨 key 不變式 → <code>sync.Map</code></li>
<li>狀態可由<strong>單一 owner</strong> 持有、外部用訊息驅動 → channel-based、見 <a href="../channel/">4.2</a> / <a href="../backpressure/">4.5 backpressure</a></li>
</ul>
<p>選錯方案的代價：用 <code>sync/atomic</code> 保護需要不變式的多欄位 → silent atomicity violation；用 <code>sync.Map</code> 期待 range 一致性 → 拿到 inconsistent snapshot；用 channel 處理需要嚴格 ordering 的 fan-in → 順序錯亂。</p>
<h2 id="rwmutex-不解的問題">RWMutex 不解的問題</h2>
<p><code>RWMutex</code> 解的是 <strong>data race</strong>（多 goroutine 同時讀寫同一份資料的 visible race）。下列問題<strong>不在 <code>RWMutex</code> 防護範圍</strong>、必須由其他機制處理：</p>
<table>
  <thead>
      <tr>
          <th>不防的問題</th>
          <th>為什麼不解</th>
          <th>該用什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Deadlock</td>
          <td>多把鎖的鎖順序不一致、<code>RWMutex</code> 沒有偵測能力</td>
          <td>鎖排序協議、<code>go test -race</code> 並非 deadlock detector</td>
      </tr>
      <tr>
          <td>Starvation</td>
          <td>RWMutex 設計上 reader 多時 writer 可能長期等不到（Go 實作有部分 fairness 保護）</td>
          <td>量測 lock 等待時間、讀多時切 channel-based 或 sharded 鎖</td>
      </tr>
      <tr>
          <td>Lock contention scaling</td>
          <td>goroutine 增多時、單把鎖的競爭成本可能 dominate；<code>RWMutex</code> 多核 scalability 弱</td>
          <td>sharded lock、sync.Map、無鎖結構</td>
      </tr>
      <tr>
          <td>Context cancellation</td>
          <td>reader 已經 hold RLock 時、context 取消不會強制釋放；reader 必須主動 check ctx</td>
          <td>lock 內快進快出、長操作放鎖外、check ctx</td>
      </tr>
      <tr>
          <td>Atomicity violation</td>
          <td>把多步操作拆到多次 Lock/Unlock 中間、其他 goroutine 可能看到中間狀態</td>
          <td>拉大鎖範圍、或改 transaction-like API</td>
      </tr>
      <tr>
          <td>Memory ordering（跨鎖）</td>
          <td>RWMutex 只保證鎖內 happens-before、跨鎖讀寫的 ordering 沒保證</td>
          <td>用 channel 傳遞 ordering、或 atomic load/store</td>
      </tr>
  </tbody>
</table>
<p>判讀訊號：</p>
<ul>
<li><code>go test -race</code> pass、production 仍偶發資料異常 → 可能 atomicity violation 或 ordering bug、不是 data race</li>
<li>多核 CPU 加倍但 throughput 不增 → lock contention dominate、考慮 shard</li>
<li>p99 latency 在高 concurrency 下爆炸 → reader 排隊或 starvation、查 lock 等待 metric</li>
<li>shutdown 時 goroutine 不退 → reader hold RLock + 未 check ctx、補 context 檢查</li>
</ul>
<h2 id="context-dependencescale-改變策略">Context dependence：scale 改變策略</h2>
<p><code>RWMutex</code> 的有效性會隨 deployment 條件變化：</p>
<ul>
<li><strong>Map 大小</strong>：copy 成本隨 entries 線性增長、1k entries 廉價、1M entries 每次 copy 都是 GC pressure 來源；大 map 改 <code>sync.Map</code> 或 sharded</li>
<li><strong>讀寫比例</strong>：90% 讀以下、<code>RWMutex</code> 收益不顯著、<code>Mutex</code> 簡單；讀寫接近時 RWMutex 的內部 atomic 操作成本可能反而比 Mutex 慢</li>
<li><strong>Goroutine 數量</strong>：少（&lt; 10）時 contention 微、多（&gt; 1000）時 RWMutex 不適合、要 shard 或換 lock-free 結構</li>
<li><strong>持鎖時間</strong>：鎖內 microsecond 級 OK、毫秒級會堆隊；鎖內絕不做 I/O / 網路呼叫</li>
</ul>
<h2 id="選擇-rwmutex-前先問四件事">選擇 RWMutex 前先問四件事</h2>
<p><code>RWMutex</code> 只解 data race subset——不解 deadlock / starvation / atomicity violation / context cancellation / 多核 contention scaling。狀態可表達為 atomic op、單 owner channel、或大量獨立 key 時、<code>sync/atomic</code> / channel-based / <code>sync.Map</code> 通常更合適。選擇前先問：「不變式跨幾個欄位？讀寫比例？goroutine 數量？持鎖時間？」</p>
]]></content:encoded></item><item><title>4.4 單例與快取模式</title><link>https://tarrragon.github.io/blog/python/04-oop/singleton-cache/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/04-oop/singleton-cache/</guid><description>&lt;p>在某些情況下，我們需要控制物件的建立次數或快取計算結果以提升效能。本章介紹 Hook 系統中使用的快取模式。&lt;/p>
&lt;h2 id="模組級快取">模組級快取&lt;/h2>
&lt;p>Python 模組是天然的單例——模組只會被載入一次。利用這個特性，可以實作簡單的快取。&lt;/p>
&lt;h3 id="實際範例配置快取">實際範例：配置快取&lt;/h3>
&lt;p>來自 &lt;code>.claude/lib/config_loader.py&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&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="c1"># 模組級快取變數&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">_quality_rules_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 載入代理人配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用模組級快取，避免重複讀取檔案。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 檢查快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&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="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">clear_config_cache&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> 清除配置快取
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> 用於測試或配置熱更新。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用方式">使用方式&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 第一次呼叫：從檔案載入&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n">config1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 第二次呼叫：直接返回快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">config2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># config1 is config2 # True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">clear_config_cache&lt;/span>&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 class="n">config3&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 重新從檔案載入&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="為什麼使用這個模式">為什麼使用這個模式？&lt;/h2>
&lt;h3 id="效能考量">效能考量&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 沒有快取：每次都讀取檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_config_slow&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;config.yaml&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&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="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># I/O 操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_config_fast&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="k">global&lt;/span> &lt;span class="n">_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&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="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;config.yaml&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&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 class="n">_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="一致性">一致性&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 確保所有地方使用相同的配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">config_a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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="n">config_b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 修改 config_a 會影響 config_b（因為是同一個物件）&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="函式裝飾器快取">函式裝飾器快取&lt;/h2>
&lt;h3 id="functoolslru_cache">@functools.lru_cache&lt;/h3>
&lt;p>Python 標準庫提供的快取裝飾器：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">functools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">lru_cache&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="nd">@lru_cache&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">maxsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">128&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="k">def&lt;/span> &lt;span class="nf">expensive_computation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&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="s2">&amp;#34;&amp;#34;&amp;#34;計算結果會被快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Computing for &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">...&amp;#34;&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="k">return&lt;/span> &lt;span class="nb">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&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>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">result1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">expensive_computation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 印出 &amp;#34;Computing for 1000...&amp;#34;&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="c1"># 第二次呼叫：直接返回快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">result2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">expensive_computation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 不印出任何東西&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 清除快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="n">expensive_computation&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cache_clear&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="functoolscache-python-39">@functools.cache (Python 3.9+)&lt;/h3>
&lt;p>無大小限制的快取：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">functools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cache&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="nd">@cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">fibonacci&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&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="k">if&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&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="k">return&lt;/span> &lt;span class="n">n&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">fibonacci&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">fibonacci&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">2&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>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">fibonacci&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 瞬間完成&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="手動實作快取">手動實作快取&lt;/h2>
&lt;h3 id="字典快取">字典快取&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&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="s2">&amp;#34;&amp;#34;&amp;#34;取得使用者資料，使用快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">user_id&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">_cache&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="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fetch_from_database&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&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="k">return&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">user_id&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">invalidate_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&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="s2">&amp;#34;&amp;#34;&amp;#34;使特定使用者的快取失效&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">clear_all_cache&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&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="s2">&amp;#34;&amp;#34;&amp;#34;清除所有快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="帶過期時間的快取">帶過期時間的快取&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">time&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&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"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">_cache_time&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&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"> 6&lt;/span>&lt;span class="cl">&lt;span class="n">CACHE_TTL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">300&lt;/span> &lt;span class="c1"># 5 分鐘&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_with_ttl&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;取得快取，檢查是否過期&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">key&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">_cache&lt;/span>&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 class="k">if&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">_cache_time&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">CACHE_TTL&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&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="k">else&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="c1"># 快取過期&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">del&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&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="k">del&lt;/span> &lt;span class="n">_cache_time&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&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="k">return&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">set_with_ttl&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;設定快取&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache_time&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="單例模式">單例模式&lt;/h2>
&lt;p>當確實需要單例時的實作方式：&lt;/p></description><content:encoded><![CDATA[<p>在某些情況下，我們需要控制物件的建立次數或快取計算結果以提升效能。本章介紹 Hook 系統中使用的快取模式。</p>
<h2 id="模組級快取">模組級快取</h2>
<p>Python 模組是天然的單例——模組只會被載入一次。利用這個特性，可以實作簡單的快取。</p>
<h3 id="實際範例配置快取">實際範例：配置快取</h3>
<p>來自 <code>.claude/lib/config_loader.py</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</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="c1"># 模組級快取變數</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">_agents_config_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">_quality_rules_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    載入代理人配置
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    使用模組級快取，避免重複讀取檔案。
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># 檢查快取</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">if</span> <span class="n">_agents_config_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">return</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">def</span> <span class="nf">load_quality_rules</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">global</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">if</span> <span class="n">_quality_rules_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">_get_default_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">return</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">def</span> <span class="nf">clear_config_cache</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">    清除配置快取
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">    用於測試或配置熱更新。
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span><span class="p">,</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="kc">None</span></span></span></code></pre></div><h3 id="使用方式">使用方式</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 第一次呼叫：從檔案載入</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">config1</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 第二次呼叫：直接返回快取</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">config2</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># config1 is config2  # True</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 需要重新載入時</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">clear_config_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">config3</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>  <span class="c1"># 重新從檔案載入</span></span></span></code></pre></div><h2 id="為什麼使用這個模式">為什麼使用這個模式？</h2>
<h3 id="效能考量">效能考量</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 沒有快取：每次都讀取檔案</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">load_config_slow</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;config.yaml&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</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="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>  <span class="c1"># I/O 操作</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 有快取：只讀取一次</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">load_config_fast</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">global</span> <span class="n">_cache</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">if</span> <span class="n">_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;config.yaml&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">_cache</span> <span class="o">=</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="n">_cache</span></span></span></code></pre></div><h3 id="一致性">一致性</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 確保所有地方使用相同的配置</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">config_a</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">config_b</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 修改 config_a 會影響 config_b（因為是同一個物件）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 這可能是優點也可能是缺點，取決於使用場景</span></span></span></code></pre></div><h2 id="函式裝飾器快取">函式裝飾器快取</h2>
<h3 id="functoolslru_cache">@functools.lru_cache</h3>
<p>Python 標準庫提供的快取裝飾器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</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="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">expensive_computation</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;計算結果會被快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Computing for </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2">...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 第一次呼叫：執行計算</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">result1</span> <span class="o">=</span> <span class="n">expensive_computation</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span>  <span class="c1"># 印出 &#34;Computing for 1000...&#34;</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="c1"># 第二次呼叫：直接返回快取</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">result2</span> <span class="o">=</span> <span class="n">expensive_computation</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span>  <span class="c1"># 不印出任何東西</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 清除快取</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">expensive_computation</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span></span></span></code></pre></div><h3 id="functoolscache-python-39">@functools.cache (Python 3.9+)</h3>
<p>無大小限制的快取：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">cache</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="nd">@cache</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="n">n</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">fibonacci</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fibonacci</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 快取讓遞迴變得高效</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">fibonacci</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>  <span class="c1"># 瞬間完成</span></span></span></code></pre></div><h2 id="手動實作快取">手動實作快取</h2>
<h3 id="字典快取">字典快取</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</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="k">def</span> <span class="nf">get_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;取得使用者資料，使用快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">if</span> <span class="n">user_id</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">_cache</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">fetch_from_database</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">_cache</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">invalidate_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使特定使用者的快取失效&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">_cache</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">clear_all_cache</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;清除所有快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span></span></span></code></pre></div><h3 id="帶過期時間的快取">帶過期時間的快取</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">_cache</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">_cache_time</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">CACHE_TTL</span> <span class="o">=</span> <span class="mi">300</span>  <span class="c1"># 5 分鐘</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">get_with_ttl</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;取得快取，檢查是否過期&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">if</span> <span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">_cache_time</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">CACHE_TTL</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="k">return</span> <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="c1"># 快取過期</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">del</span> <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="k">del</span> <span class="n">_cache_time</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">set_with_ttl</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;設定快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">_cache_time</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span></span></span></code></pre></div><h2 id="單例模式">單例模式</h2>
<p>當確實需要單例時的實作方式：</p>
<h3 id="使用模組最簡單">使用模組（最簡單）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># singleton.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">_Singleton</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">instance</span> <span class="o">=</span> <span class="n">_Singleton</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kn">from</span> <span class="nn">singleton</span> <span class="kn">import</span> <span class="n">instance</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">instance</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="mi">42</span></span></span></code></pre></div><h3 id="使用類別裝飾器">使用類別裝飾器</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">singleton</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">instances</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">get_instance</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">if</span> <span class="bp">cls</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="n">instances</span><span class="p">[</span><span class="bp">cls</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="n">instances</span><span class="p">[</span><span class="bp">cls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">get_instance</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nd">@singleton</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">Database</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Connecting to database...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">db1</span> <span class="o">=</span> <span class="n">Database</span><span class="p">()</span>  <span class="c1"># 印出 &#34;Connecting...&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">db2</span> <span class="o">=</span> <span class="n">Database</span><span class="p">()</span>  <span class="c1"># 不印出（返回同一個實例）</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">db1</span> <span class="ow">is</span> <span class="n">db2</span>  <span class="c1"># True</span></span></span></code></pre></div><h3 id="使用-new">使用 <strong>new</strong></h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">Singleton</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">_instance</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">if</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_instance</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="bp">cls</span><span class="o">.</span><span class="n">_instance</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_instance</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">s1</span> <span class="o">=</span> <span class="n">Singleton</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">s2</span> <span class="o">=</span> <span class="n">Singleton</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">s1</span> <span class="ow">is</span> <span class="n">s2</span>  <span class="c1"># True</span></span></span></code></pre></div><h2 id="hook-系統的實際應用">Hook 系統的實際應用</h2>
<h3 id="配置載入器的設計">配置載入器的設計</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># config_loader.py</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 私有快取</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">_agents_config_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    載入代理人配置
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    特點：
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    1. 使用模組級快取
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    2. 支援預設配置
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    3. 提供清除快取的方法
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">if</span> <span class="n">_agents_config_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="c1"># 返回預設配置</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">return</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="k">def</span> <span class="nf">_get_default_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="s2">&#34;&#34;&#34;預設配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;known_agents&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="s2">&#34;basil-hook-architect&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="s2">&#34;thyme-documentation-integrator&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="s2">&#34;agent_dispatch_rules&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="s2">&#34;Hook 開發&#34;</span><span class="p">:</span> <span class="s2">&#34;basil-hook-architect&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="p">}</span></span></span></code></pre></div><h2 id="測試快取程式碼">測試快取程式碼</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</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="k">class</span> <span class="nc">TestConfigLoader</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># 每個測試前清除快取</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">clear_config_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="c1"># 每個測試後清除快取</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">clear_config_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">test_config_is_cached</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試配置被快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">config1</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">config2</span> <span class="o">=</span> <span class="n">load_agents_config</span><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="c1"># 應該是同一個物件</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIs</span><span class="p">(</span><span class="n">config1</span><span class="p">,</span> <span class="n">config2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">test_clear_cache_works</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試清除快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">config1</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">clear_config_cache</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">config2</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 應該是不同的物件</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIsNot</span><span class="p">(</span><span class="n">config1</span><span class="p">,</span> <span class="n">config2</span><span class="p">)</span></span></span></code></pre></div><h2 id="最佳實踐">最佳實踐</h2>
<h3 id="1-提供清除快取的方法">1. 提供清除快取的方法</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 好：可以清除快取</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">clear_config_cache</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">global</span> <span class="n">_cache</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">_cache</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 不好：無法重新載入</span></span></span></code></pre></div><h3 id="2-考慮執行緒安全">2. 考慮執行緒安全</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</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="n">_lock</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">_cache</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">get_cached_value</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">global</span> <span class="n">_cache</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">if</span> <span class="n">_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="c1"># 雙重檢查</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="k">if</span> <span class="n">_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">                <span class="n">_cache</span> <span class="o">=</span> <span class="n">expensive_computation</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="n">_cache</span></span></span></code></pre></div><h3 id="3-文件化快取行為">3. 文件化快取行為</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">load_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s2">    載入配置
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s2">        結果會被快取，後續呼叫返回同一個物件。
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="s2">        使用 clear_config_cache() 可重新載入。
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>模組級快取和 <code>@lru_cache</code> 有什麼區別？</li>
<li>為什麼 <code>clear_config_cache()</code> 很重要？</li>
<li>在多執行緒環境下，模組級快取可能有什麼問題？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>使用 <code>@lru_cache</code> 實作一個帶快取的 API 呼叫函式</li>
<li>實作一個帶 TTL（存活時間）的快取</li>
<li>為現有的快取添加執行緒安全保護</li>
</ol>
<h2 id="延伸閱讀進階系列">延伸閱讀（進階系列）</h2>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">進階設計模式</a> - 更多設計模式的深入探討</li>
<li><a href="/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理</a> - 進階快取策略</li>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">實戰效能優化：LRU 快取</a> - lru_cache 的實際應用</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python/04-oop/factory/" data-link-title="4.3 工廠模式" data-link-desc="動態建立物件">工廠模式</a></em>
<em>下一模組：<a href="/blog/python/05-error-testing/" data-link-title="模組五：錯誤處理與測試" data-link-desc="穩健程式碼的基石：異常處理和單元測試">錯誤處理與測試</a></em></p>
]]></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><item><title>9.5 瓶頸定位流程</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/bottleneck-localization/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/bottleneck-localization/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>瓶頸定位的責任是回答「為什麼擴 app 沒用」這類問題。當 &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> 找到 knee point 之後、下一步是知道 &lt;em>哪個 resource&lt;/em> 先 saturate。沒有定位、容量規劃只能 &lt;em>全部翻倍&lt;/em>；有定位、可以 &lt;em>精準加在瓶頸層&lt;/em>。&lt;/p>
&lt;p>跟其他章節的關係：跟 9.4 是姊妹章（9.4 找出 knee、9.5 定位 knee 的成因）、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性&lt;/a> 互補（9.8 訊號治理、9.5 用訊號做定位）。&lt;/p>
&lt;p>本章不深入工具操作、聚焦在 &lt;em>方法論&lt;/em> — 怎麼按層次定位、怎麼避免常見誤判、怎麼區分可分散 vs 不可分散瓶頸。&lt;/p>
&lt;h2 id="use-methodresource-oriented-觀察">USE method：resource-oriented 觀察&lt;/h2>
&lt;p>Brendan Gregg 的 USE method 提供逐層定位的最小框架：對每個資源、量三個維度。&lt;/p>
&lt;p>&lt;strong>Utilization&lt;/strong>：資源使用率 0-100%。CPU 70%、memory 60%、disk 40% 這類數字。
&lt;strong>Saturation&lt;/strong>：資源排隊量（queue depth）。CPU run queue length、memory swap rate、disk I/O wait queue、connection pool wait count。
&lt;strong>Errors&lt;/strong>：資源層錯誤。CPU page fault、memory OOM、disk I/O error、network packet drop、connection refused。&lt;/p>
&lt;p>對每個資源（CPU / RAM / disk / network / DB connection / cache connection / file descriptor）逐一檢查。&lt;em>第一個出現 saturation 上升的資源是 bottleneck&lt;/em>、不是 utilization 最高的那個。&lt;/p>
&lt;p>USE 跟 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/red-method/" data-link-title="RED Method" data-link-desc="Tom Wilkie 提出的請求層 Rate / Errors / Duration 三維度量測法">RED method&lt;/a>（rate / errors / duration）互補：USE 看「哪個資源頂不住」、RED 看「哪個 endpoint 表現變差」。容量規劃通常先用 USE 找瓶頸、再用 RED 看影響面。&lt;/p>
&lt;p>詳見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/use-method/" data-link-title="USE Method" data-link-desc="Brendan Gregg 提出的資源層 Utilization / Saturation / Errors 三維度量測法">USE Method 卡片&lt;/a>。&lt;/p>
&lt;h2 id="逐層定位流程">逐層定位流程&lt;/h2>
&lt;p>從 application 層往下查、按依賴鏈逐層檢查。多數 bottleneck 在 application 跟 DB 兩層、但不能跳過其他層 — 偶爾真的在意外位置。&lt;/p>
&lt;p>&lt;strong>1. 應用層（application）&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>thread / coroutine pool 使用率：是否已飽和&lt;/li>
&lt;li>event loop lag（Node.js、async runtime）：&amp;gt; 50ms 是警訊&lt;/li>
&lt;li>GC pause 頻率與時長：影響 p99 / p999&lt;/li>
&lt;li>request queue（accept queue、application internal queue）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>2. DB 層&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>connection pool 使用率（最常見隱性 bottleneck）&lt;/li>
&lt;li>slow query frequency&lt;/li>
&lt;li>replication lag&lt;/li>
&lt;li>lock contention（row lock、table lock）&lt;/li>
&lt;li>transaction queue depth&lt;/li>
&lt;/ul>
&lt;p>定位到 DB 層瓶頸時、優先檢查 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/query-anti-patterns/" data-link-title="1.13 應用層查詢反模式與 Query 預算" data-link-desc="整理 N&amp;#43;1、select *、缺索引、ORM lazy load、long transaction 等查詢反模式與每請求的 query 預算判讀">1.13 應用層查詢反模式&lt;/a> 清單 — 多數 DB 層瓶頸的根因是「應用程式發給 DB 的 query 寫法」、不是 DB 規格不夠。N+1 query 放大 connection 占用、long-running transaction 放大 lock contention、缺索引讓 slow query frequency 升高、&lt;code>SELECT *&lt;/code> 放大 transaction queue。這層判讀走完、再考慮 DB 規格升級或加 replica。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>瓶頸定位的責任是回答「為什麼擴 app 沒用」這類問題。當 <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 之後、下一步是知道 <em>哪個 resource</em> 先 saturate。沒有定位、容量規劃只能 <em>全部翻倍</em>；有定位、可以 <em>精準加在瓶頸層</em>。</p>
<p>跟其他章節的關係：跟 9.4 是姊妹章（9.4 找出 knee、9.5 定位 knee 的成因）、跟 <a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a> 互補（9.8 訊號治理、9.5 用訊號做定位）。</p>
<p>本章不深入工具操作、聚焦在 <em>方法論</em> — 怎麼按層次定位、怎麼避免常見誤判、怎麼區分可分散 vs 不可分散瓶頸。</p>
<h2 id="use-methodresource-oriented-觀察">USE method：resource-oriented 觀察</h2>
<p>Brendan Gregg 的 USE method 提供逐層定位的最小框架：對每個資源、量三個維度。</p>
<p><strong>Utilization</strong>：資源使用率 0-100%。CPU 70%、memory 60%、disk 40% 這類數字。
<strong>Saturation</strong>：資源排隊量（queue depth）。CPU run queue length、memory swap rate、disk I/O wait queue、connection pool wait count。
<strong>Errors</strong>：資源層錯誤。CPU page fault、memory OOM、disk I/O error、network packet drop、connection refused。</p>
<p>對每個資源（CPU / RAM / disk / network / DB connection / cache connection / file descriptor）逐一檢查。<em>第一個出現 saturation 上升的資源是 bottleneck</em>、不是 utilization 最高的那個。</p>
<p>USE 跟 <a href="/blog/backend/knowledge-cards/red-method/" data-link-title="RED Method" data-link-desc="Tom Wilkie 提出的請求層 Rate / Errors / Duration 三維度量測法">RED method</a>（rate / errors / duration）互補：USE 看「哪個資源頂不住」、RED 看「哪個 endpoint 表現變差」。容量規劃通常先用 USE 找瓶頸、再用 RED 看影響面。</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="逐層定位流程">逐層定位流程</h2>
<p>從 application 層往下查、按依賴鏈逐層檢查。多數 bottleneck 在 application 跟 DB 兩層、但不能跳過其他層 — 偶爾真的在意外位置。</p>
<p><strong>1. 應用層（application）</strong>：</p>
<ul>
<li>thread / coroutine pool 使用率：是否已飽和</li>
<li>event loop lag（Node.js、async runtime）：&gt; 50ms 是警訊</li>
<li>GC pause 頻率與時長：影響 p99 / p999</li>
<li>request queue（accept queue、application internal queue）</li>
</ul>
<p><strong>2. DB 層</strong>：</p>
<ul>
<li>connection pool 使用率（最常見隱性 bottleneck）</li>
<li>slow query frequency</li>
<li>replication lag</li>
<li>lock contention（row lock、table lock）</li>
<li>transaction queue depth</li>
</ul>
<p>定位到 DB 層瓶頸時、優先檢查 <a href="/blog/backend/01-database/query-anti-patterns/" data-link-title="1.13 應用層查詢反模式與 Query 預算" data-link-desc="整理 N&#43;1、select *、缺索引、ORM lazy load、long transaction 等查詢反模式與每請求的 query 預算判讀">1.13 應用層查詢反模式</a> 清單 — 多數 DB 層瓶頸的根因是「應用程式發給 DB 的 query 寫法」、不是 DB 規格不夠。N+1 query 放大 connection 占用、long-running transaction 放大 lock contention、缺索引讓 slow query frequency 升高、<code>SELECT *</code> 放大 transaction queue。這層判讀走完、再考慮 DB 規格升級或加 replica。</p>
<p><strong>3. Cache 層</strong>：</p>
<ul>
<li>hit rate（突然下降是訊號）</li>
<li>eviction rate</li>
<li>connection 飽和（cache pool 也會耗盡）</li>
<li>memory utilization</li>
</ul>
<p><strong>4. Broker / queue 層</strong>：</p>
<ul>
<li>consumer lag（最重要的單一指標）</li>
<li>queue depth</li>
<li>dead-letter rate</li>
<li>broker connection count</li>
</ul>
<p><strong>5. 外部 API / 第三方 quota</strong>：</p>
<ul>
<li>rate limit 觸發頻率</li>
<li>retry storm（自家 retry 把對方 quota 打爆）</li>
<li>circuit breaker trip</li>
<li>timeout rate</li>
</ul>
<p><strong>6. 網路層</strong>：</p>
<ul>
<li>bandwidth utilization</li>
<li>packets per second（PPS limit）</li>
<li>socket count（file descriptor limit）</li>
<li>跨 region / 跨 AZ latency</li>
</ul>
<p><strong>7. DNS / load balancer</strong>：</p>
<ul>
<li>DNS resolution latency</li>
<li>LB connection establishment time</li>
<li>TLS handshake duration</li>
<li>backend health check failure</li>
</ul>
<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</a> RDB connection limit 是隱性 bottleneck、CPU / RAM 都還行；<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> — 把高頻搶票流量跟低頻付款流量分離、避免一層拖累另一層。</p>
<h2 id="profile-工具鏈">Profile 工具鏈</h2>
<p>USE 找出哪一層 saturate 之後、profile 工具找出 <em>該層的哪段 code</em> 拖累。</p>
<p><strong>Continuous profiling</strong>：Datadog Continuous Profiler、Pyroscope（開源 + Grafana 整合）、Parca（CNCF）、GCP Cloud Profiler、Azure Application Insights Profiler、AWS CodeGuru Profiler。production 持續取樣 CPU / heap / lock、overhead 通常 &lt; 1%。</p>
<p><strong>Distributed tracing</strong>：OpenTelemetry、Jaeger、Tempo、AWS X-Ray、GCP Cloud Trace、Azure Application Insights。記錄 request 在每個 service / 每個 stage 花了多少時間、找跨服務的 latency 累積。</p>
<p><strong>Flame graph</strong>：profile 結果視覺化的標準。從寬度可以看到「哪段 code 佔 CPU 最多」。學會看 flame graph 是 SRE 的基本功。</p>
<p><strong>Profile diff</strong>：壓測 baseline 跟 release candidate 比 stack 差異。看 <em>相對變化</em> 而非絕對值。詳見 <a href="/blog/backend/knowledge-cards/profile-diff/" data-link-title="Profile Diff" data-link-desc="對比兩次 profile（如 release candidate vs baseline）找出 hottest 變化">Profile Diff 卡片</a>。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">Netflix Aurora storage / compute 分離</a> — DB 統一後 application profile 變單純、退化來源更容易識別。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/continuous-profiling/" data-link-title="Continuous Profiling" data-link-desc="在 production 持續取得低 overhead profile 的觀察方法">Continuous Profiling 卡片</a>。</p>
<h2 id="跨層依賴鏈">跨層依賴鏈</h2>
<p>瓶頸不一定在 <em>本服務</em>、可能在 <em>下游服務</em>。這層判斷常被忽略。</p>
<p><strong>第三方 API quota</strong> 是常見隱性瓶頸。Twilio SMS、Stripe API、Slack webhook、Sendgrid email、Google Maps API 都有 rate limit。自家服務看起來健康、實際是 <em>對方 throttle</em>、自家 retry 再讓對方更慢。</p>
<p><strong>跨 region / 跨 zone 網路延遲</strong> 是累積的。一個 user request 經過 5 個 service、每個 service 跨 AZ 一次、累積 10-20ms cross-AZ latency。看起來每個 service 都很快、但 end-to-end 慢。</p>
<p><strong>Downstream cache</strong> 也是依賴。app 看起來健康、但其實是 cache 在擋；cache 突然 cold start（restart、eviction storm）、application 直接被打爆。</p>
<p>對應案例：<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 處理通知與訊息功能、支撐次秒級反應">PayPay 行動支付</a> — DynamoDB 寫入可以撐 3K msg/sec、但 APNs / FCM 一天的 quota 有限、推送下游才是瓶頸。</p>
<h2 id="可分散-vs-不可分散瓶頸">可分散 vs 不可分散瓶頸</h2>
<p>定位完瓶頸後、要判斷它 <em>可不可以橫向擴</em>。這個判斷決定能不能用「加機器」解決。</p>
<p><strong>可分散瓶頸</strong>：</p>
<ul>
<li>stateless app server → 加機器有用</li>
<li>partitioned KV / OLTP（partition key 均勻時）→ 加 partition 有用</li>
<li>read replica（read-heavy workload）→ 加 replica 有用</li>
<li>worker pool → 加 worker 有用</li>
</ul>
<p><strong>不可分散瓶頸</strong>：</p>
<ul>
<li>consensus DB（RAFT / Paxos）→ 加節點不一定快（<a href="/blog/backend/knowledge-cards/quorum/" data-link-title="Quorum" data-link-desc="分散式系統以多數節點同意作為提交或讀取有效性的門檻">quorum</a> overhead）</li>
<li>single leader DB（master 寫）→ 必須垂直擴</li>
<li>中央 coordinator → 必須拆解或垂直擴</li>
<li>共享 cache（hot key）→ 必須改 partition key 或加 local cache</li>
</ul>
<p>判斷不可分散的關鍵是「協調成本」。一個操作必須 <em>跟所有 / 多數節點協調</em> 才能完成、就不可水平擴。</p>
<p>對應案例：<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 RAFT consensus</a> — consensus 不可水平擴、所以 <em>選擇不擴</em>、改用單機極致；<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">Spanner TrueTime</a> — TrueTime 把協調成本 amortize 到 hardware（GPS + 原子鐘）、讓 OLTP 可水平擴。</p>
<h2 id="常見定位陷阱">常見定位陷阱</h2>
<p><strong>看單一指標就下結論</strong>：CPU 100% 不一定是 bottleneck（可能 saturation queue 空）；CPU 50% 不一定健康（可能 saturation queue 已滿）。always 看 USE 三個維度。</p>
<p><strong>平均看 OK、p99 看不出來</strong>：average latency 50ms 看起來健康、p99 500ms 已經出事。用 percentile、不用 average。</p>
<p><strong>Observer effect</strong>：profile / tracing 本身有 overhead、量測會輕微影響系統。critical path 上的 instrumentation 要 sampled 不要 100%。</p>
<p><strong>跨 release 比較 baseline 沒對齊</strong>：上週的 baseline 對應 v1.2、這週的 candidate 對應 v1.3、但 v1.2 跟 v1.3 之間還有 schema migration / hardware 變化、baseline 已經漂移。重新建 baseline 再 diff。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <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 隱性 bottleneck</td>
      </tr>
      <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>關鍵路徑切分避免 cross contamination</td>
      </tr>
      <tr>
          <td><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 RAFT consensus</a></td>
          <td>不可分散 bottleneck</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>下游 APNs / FCM quota 瓶頸</td>
      </tr>
  </tbody>
</table>
<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/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>（針對 bottleneck 規劃）</li>
<li>下游：<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>（用 profile diff 改進）</li>
<li>下游：<a href="/blog/backend/01-database/query-anti-patterns/" data-link-title="1.13 應用層查詢反模式與 Query 預算" data-link-desc="整理 N&#43;1、select *、缺索引、ORM lazy load、long transaction 等查詢反模式與每請求的 query 預算判讀">1.13 應用層查詢反模式與 Query 預算</a>（DB 層 bottleneck 多半在 query 寫法）</li>
<li>跨模組：<a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> / <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<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/red-method/" data-link-title="RED Method" data-link-desc="Tom Wilkie 提出的請求層 Rate / Errors / Duration 三維度量測法">RED Method</a></li>
<li><a href="/blog/backend/knowledge-cards/continuous-profiling/" data-link-title="Continuous Profiling" data-link-desc="在 production 持續取得低 overhead profile 的觀察方法">Continuous Profiling</a></li>
<li><a href="/blog/backend/knowledge-cards/profile-diff/" data-link-title="Profile Diff" data-link-desc="對比兩次 profile（如 release candidate vs baseline）找出 hottest 變化">Profile Diff</a></li>
<li><a href="/blog/backend/knowledge-cards/universal-scalability-law/" data-link-title="Universal Scalability Law (USL)" data-link-desc="說明系統擴容到一定規模後吞吐反而下降的數學模型">Universal Scalability Law</a></li>
</ul>
]]></content:encoded></item><item><title>9.C5 Amazon Ads：DynamoDB 9000 萬 reads/sec 的廣告事件量測</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/amazon-ads-dynamodb-extreme-kv/</guid><description>&lt;p>這個案例的核心責任是提供「key-value 持續高吞吐」的極限參考點。廣告事件量測屬 &lt;em>write-heavy + read-heavy 同時存在&lt;/em> 的負載 — 每個曝光都要寫進度、每個曝光也都要查 metadata。這類負載沒有明顯峰谷、是長期 sustained growth、跟事件型峰值的容量設計邏輯不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Amazon Ads 在 DynamoDB 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>讀吞吐&lt;/td>
 &lt;td>9000 萬 reads / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>寫吞吐&lt;/td>
 &lt;td>500 萬 writes / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>99.999%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用途&lt;/td>
 &lt;td>廣告事件量測&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>讀寫比約 18:1。這個比例反映「曝光發生 1 次、後續查詢可能發生 18 次」的廣告計費邏輯。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這個案例最重要的不是「DynamoDB 能撐多少」、而是「為什麼可以這樣設計」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>單表分散到上千個 partition&lt;/strong>：DynamoDB 把每個 table 拆成多個 partition、每個 partition 內部還可以再分散。9000 萬 reads / 秒 是上千個 partition 加總的結果、單一節點達不到這個量級。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的 sharding 邊界、跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 partition 設計。&lt;/li>
&lt;li>&lt;strong>partition key 選擇直接決定容量上限&lt;/strong>：DynamoDB 的容量是「每 partition 上限 × partition 數量」。partition key 不均勻會出現 hot partition、實際容量遠低於名義容量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 saturation 不一定是整體 saturation、而是 &lt;em>最熱的 partition&lt;/em> saturation。&lt;/li>
&lt;li>&lt;strong>99.999% availability ≈ 5 分鐘 / 年的容錯&lt;/strong>：廣告計費 1 分鐘斷線可能損失幾百萬美金廣告收入。這個 SLO 不是行銷數字、是真實的營收邊界。對應 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「9000 萬 reads / 秒」這種敘述通常是 &lt;em>年度峰值的最高一秒&lt;/em>、不是平均值。容量規劃要區分「最大瞬時」、「99 百分位平均」、「常態流量」三個不同口徑。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>partition key 設計是 KV 容量的第一決策&lt;/strong>：均勻分散、避免 hot partition、必要時加 random suffix 強制分散。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema design 章節。&lt;/li>
&lt;li>&lt;strong>read-heavy 跟 write-heavy 比例變化是容量警訊&lt;/strong>：當業務邏輯改變（例如新增即時報表）、讀寫比可能跳一個量級、原本的容量規劃會失效。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性&lt;/a> 持續監控比例變化。&lt;/li>
&lt;li>&lt;strong>on-demand vs provisioned 是成本 vs 反應速度的取捨&lt;/strong>：on-demand 自動擴容但成本高、provisioned 便宜但需要預測。Amazon Ads 這種 sustained workload 通常用 provisioned + auto scaling、不用 on-demand。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Cloud Bigtable + 良好 row key 設計、Azure Cosmos DB partition key 設計都是對等概念。差異是 DynamoDB 的 partition 透明度（你看不到 partition 數量）vs Bigtable 的明確 tablet 模型。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「key-value 持續高吞吐」的極限參考點。廣告事件量測屬 <em>write-heavy + read-heavy 同時存在</em> 的負載 — 每個曝光都要寫進度、每個曝光也都要查 metadata。這類負載沒有明顯峰谷、是長期 sustained growth、跟事件型峰值的容量設計邏輯不同。</p>
<h2 id="觀察">觀察</h2>
<p>Amazon Ads 在 DynamoDB 的關鍵數字（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>讀吞吐</td>
          <td>9000 萬 reads / 秒</td>
      </tr>
      <tr>
          <td>寫吞吐</td>
          <td>500 萬 writes / 秒</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>99.999%</td>
      </tr>
      <tr>
          <td>用途</td>
          <td>廣告事件量測</td>
      </tr>
  </tbody>
</table>
<p>讀寫比約 18:1。這個比例反映「曝光發生 1 次、後續查詢可能發生 18 次」的廣告計費邏輯。</p>
<h2 id="判讀">判讀</h2>
<p>這個案例最重要的不是「DynamoDB 能撐多少」、而是「為什麼可以這樣設計」。</p>
<ol>
<li><strong>單表分散到上千個 partition</strong>：DynamoDB 把每個 table 拆成多個 partition、每個 partition 內部還可以再分散。9000 萬 reads / 秒 是上千個 partition 加總的結果、單一節點達不到這個量級。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的 sharding 邊界、跟 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 partition 設計。</li>
<li><strong>partition key 選擇直接決定容量上限</strong>：DynamoDB 的容量是「每 partition 上限 × partition 數量」。partition key 不均勻會出現 hot partition、實際容量遠低於名義容量。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 saturation 不一定是整體 saturation、而是 <em>最熱的 partition</em> saturation。</li>
<li><strong>99.999% availability ≈ 5 分鐘 / 年的容錯</strong>：廣告計費 1 分鐘斷線可能損失幾百萬美金廣告收入。這個 SLO 不是行銷數字、是真實的營收邊界。對應 <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a> 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a>。</li>
</ol>
<p>需要警惕：「9000 萬 reads / 秒」這種敘述通常是 <em>年度峰值的最高一秒</em>、不是平均值。容量規劃要區分「最大瞬時」、「99 百分位平均」、「常態流量」三個不同口徑。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>partition key 設計是 KV 容量的第一決策</strong>：均勻分散、避免 hot partition、必要時加 random suffix 強制分散。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema design 章節。</li>
<li><strong>read-heavy 跟 write-heavy 比例變化是容量警訊</strong>：當業務邏輯改變（例如新增即時報表）、讀寫比可能跳一個量級、原本的容量規劃會失效。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a> 持續監控比例變化。</li>
<li><strong>on-demand vs provisioned 是成本 vs 反應速度的取捨</strong>：on-demand 自動擴容但成本高、provisioned 便宜但需要預測。Amazon Ads 這種 sustained workload 通常用 provisioned + auto scaling、不用 on-demand。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
</ol>
<p>跨平台等效：GCP Cloud Bigtable + 良好 row key 設計、Azure Cosmos DB partition key 設計都是對等概念。差異是 DynamoDB 的 partition 透明度（你看不到 partition 數量）vs Bigtable 的明確 tablet 模型。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 KV 高吞吐架構 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想避免 hot partition → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a></li>
<li>想對照其他 KV 案例 → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth Cosmos DB</a>（Azure 全球分散）</li>
<li>想深入 DynamoDB hot partition 反模式 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想拆 access pattern 對應的 single-table design → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a></li>
<li>想評估 on-demand vs provisioned 切換時機 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/handle-traffic-spikes-with-amazon-dynamodb-provisioned-capacity/">Handle traffic spikes with Amazon DynamoDB provisioned capacity</a></li>
<li><a href="https://aws.amazon.com/blogs/database/demystifying-amazon-dynamodb-on-demand-capacity-mode/">Demystifying Amazon DynamoDB on-demand capacity mode</a></li>
</ul>
]]></content:encoded></item><item><title>4.5 高併發控制與 backpressure</title><link>https://tarrragon.github.io/blog/go/04-concurrency/backpressure/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/04-concurrency/backpressure/</guid><description>&lt;p>這一章處理的是一個比「會不會開 goroutine」更重要的問題：當系統真的進入高併發狀態時，怎麼讓工作量保持可控。Go 很容易啟動大量並發工作，但如果沒有邊界，goroutine、channel、下游連線與記憶體都會一起膨脹。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>理解 bounded concurrency 的用途&lt;/li>
&lt;li>用 semaphore 或 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool&lt;/a> 限制同時工作數&lt;/li>
&lt;li>看懂 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure&lt;/a> 為什麼能保護下游&lt;/li>
&lt;li>在併發流程中保留 cancellation 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a>&lt;/li>
&lt;li>辨認什麼時候該拒絕新工作&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察高併發需要容量邊界">【觀察】高併發需要容量邊界&lt;/h2>
&lt;p>goroutine 很便宜，但每個工作仍會消耗下游連線、記憶體、排隊時間與錯誤處理能力。當所有工作都直接丟進 &lt;code>go func()&lt;/code>，被放大的通常是：&lt;/p>
&lt;ul>
&lt;li>連線數&lt;/li>
&lt;li>記憶體&lt;/li>
&lt;li>排隊延遲&lt;/li>
&lt;li>下游壓力&lt;/li>
&lt;li>故障面積&lt;/li>
&lt;/ul>
&lt;p>高併發設計的第一原則是「可控」。系統需要知道同時有多少工作在跑、多少工作在排隊、滿載時如何回應。&lt;/p>
&lt;h2 id="判讀bounded-concurrency-是基本保護">【判讀】bounded concurrency 是基本保護&lt;/h2>
&lt;p>bounded concurrency 的核心規則是：同一時間只允許有限數量的工作進行。這可以用 worker pool、semaphore 或排隊系統達成。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="nx">sem&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{},&lt;/span> &lt;span class="mi">16&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="k">for&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">job&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="k">range&lt;/span> &lt;span class="nx">jobs&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">sem&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="kd">struct&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">job&lt;/span> &lt;span class="nx">Job&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">sem&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="nf">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">job&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="p">}(&lt;/span>&lt;span class="nx">job&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;/code>&lt;/pre>&lt;/div>&lt;p>這段程式限制同時只有 16 個工作在執行。當工作量暴增時，新的工作會自然排隊，而不是把整台機器一次推爆。&lt;/p>
&lt;h2 id="策略backpressure-保護的是下游">【策略】backpressure 保護的是下游&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure&lt;/a> 的核心規則是：當系統處理不過來時，不要無限累積工作。這可以表現成：&lt;/p>
&lt;ul>
&lt;li>channel 滿了就阻塞&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> 有上限&lt;/li>
&lt;li>goroutine pool 有上限&lt;/li>
&lt;li>佇列滿時直接拒絕請求&lt;/li>
&lt;/ul>
&lt;p>例如 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a>、event &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/consumer/" data-link-title="Consumer" data-link-desc="說明 consumer 如何取得等待處理的工作並產生業務結果">consumer&lt;/a> 或 background worker 如果沒有 backpressure ，輸入端一快，下游就會被放大成連鎖問題。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">select&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="k">case&lt;/span> &lt;span class="nx">jobs&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="nx">job&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="c1">// accepted&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">default&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="k">return&lt;/span> &lt;span class="nx">ErrQueueFull&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;/code>&lt;/pre>&lt;/div>&lt;p>這種寫法的重點是明確表達滿載策略：系統在某些壓力下會拒絕新工作，因為保護整體健康比接住所有請求更重要。&lt;/p>
&lt;h2 id="執行cancellation-與-timeout-不能少">【執行】cancellation 與 timeout 不能少&lt;/h2>
&lt;p>bounded concurrency 只控制數量，不能解決卡死工作。每個工作都應該保留取消訊號與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a>，否則即使數量受限，資源也會被慢工作一直占著。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">parent&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&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="k">defer&lt;/span> &lt;span class="nf">cancel&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>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">doWork&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">job&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&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="k">return&lt;/span> &lt;span class="nx">err&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;/code>&lt;/pre>&lt;/div>&lt;p>這樣可以讓每一筆工作都有自己的時間邊界，避免整體系統因單一慢點而拖垮。&lt;/p>
&lt;h2 id="判讀拒絕工作也是容量策略">【判讀】拒絕工作也是容量策略&lt;/h2>
&lt;p>拒絕新工作是保護容量邊界的一種策略。當以下條件成立時，拒絕通常比勉強接受更合理：&lt;/p>
&lt;ul>
&lt;li>queue 已滿&lt;/li>
&lt;li>下游連線池耗盡&lt;/li>
&lt;li>timeout 已明顯增加&lt;/li>
&lt;li>系統已進入明顯積壓&lt;/li>
&lt;/ul>
&lt;p>這時候回傳 &lt;code>429&lt;/code>、&lt;code>503&lt;/code> 或 domain-level rejection，往往比讓請求默默堆積更健康。&lt;/p></description><content:encoded><![CDATA[<p>這一章處理的是一個比「會不會開 goroutine」更重要的問題：當系統真的進入高併發狀態時，怎麼讓工作量保持可控。Go 很容易啟動大量並發工作，但如果沒有邊界，goroutine、channel、下游連線與記憶體都會一起膨脹。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>理解 bounded concurrency 的用途</li>
<li>用 semaphore 或 <a href="/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool</a> 限制同時工作數</li>
<li>看懂 <a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a> 為什麼能保護下游</li>
<li>在併發流程中保留 cancellation 與 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a></li>
<li>辨認什麼時候該拒絕新工作</li>
</ol>
<hr>
<h2 id="觀察高併發需要容量邊界">【觀察】高併發需要容量邊界</h2>
<p>goroutine 很便宜，但每個工作仍會消耗下游連線、記憶體、排隊時間與錯誤處理能力。當所有工作都直接丟進 <code>go func()</code>，被放大的通常是：</p>
<ul>
<li>連線數</li>
<li>記憶體</li>
<li>排隊延遲</li>
<li>下游壓力</li>
<li>故障面積</li>
</ul>
<p>高併發設計的第一原則是「可控」。系統需要知道同時有多少工作在跑、多少工作在排隊、滿載時如何回應。</p>
<h2 id="判讀bounded-concurrency-是基本保護">【判讀】bounded concurrency 是基本保護</h2>
<p>bounded concurrency 的核心規則是：同一時間只允許有限數量的工作進行。這可以用 worker pool、semaphore 或排隊系統達成。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nx">sem</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="kd">struct</span><span class="p">{},</span> <span class="mi">16</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="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">job</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">jobs</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nx">sem</span> <span class="o">&lt;-</span> <span class="kd">struct</span><span class="p">{}{}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">go</span> <span class="kd">func</span><span class="p">(</span><span class="nx">job</span> <span class="nx">Job</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">defer</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span> <span class="o">&lt;-</span><span class="nx">sem</span> <span class="p">}()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="nf">process</span><span class="p">(</span><span class="nx">job</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">}(</span><span class="nx">job</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這段程式限制同時只有 16 個工作在執行。當工作量暴增時，新的工作會自然排隊，而不是把整台機器一次推爆。</p>
<h2 id="策略backpressure-保護的是下游">【策略】backpressure 保護的是下游</h2>
<p><a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a> 的核心規則是：當系統處理不過來時，不要無限累積工作。這可以表現成：</p>
<ul>
<li>channel 滿了就阻塞</li>
<li><a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> 有上限</li>
<li>goroutine pool 有上限</li>
<li>佇列滿時直接拒絕請求</li>
</ul>
<p>例如 <a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a>、event <a href="/blog/backend/knowledge-cards/consumer/" data-link-title="Consumer" data-link-desc="說明 consumer 如何取得等待處理的工作並產生業務結果">consumer</a> 或 background worker 如果沒有 backpressure ，輸入端一快，下游就會被放大成連鎖問題。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">select</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">case</span> <span class="nx">jobs</span> <span class="o">&lt;-</span> <span class="nx">job</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1">// accepted</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="nx">ErrQueueFull</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這種寫法的重點是明確表達滿載策略：系統在某些壓力下會拒絕新工作，因為保護整體健康比接住所有請求更重要。</p>
<h2 id="執行cancellation-與-timeout-不能少">【執行】cancellation 與 timeout 不能少</h2>
<p>bounded concurrency 只控制數量，不能解決卡死工作。每個工作都應該保留取消訊號與 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a>，否則即使數量受限，資源也會被慢工作一直占著。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">ctx</span><span class="p">,</span> <span class="nx">cancel</span> <span class="o">:=</span> <span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">parent</span><span class="p">,</span> <span class="mi">3</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">defer</span> <span class="nf">cancel</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nf">doWork</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">job</span><span class="p">);</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>這樣可以讓每一筆工作都有自己的時間邊界，避免整體系統因單一慢點而拖垮。</p>
<h2 id="判讀拒絕工作也是容量策略">【判讀】拒絕工作也是容量策略</h2>
<p>拒絕新工作是保護容量邊界的一種策略。當以下條件成立時，拒絕通常比勉強接受更合理：</p>
<ul>
<li>queue 已滿</li>
<li>下游連線池耗盡</li>
<li>timeout 已明顯增加</li>
<li>系統已進入明顯積壓</li>
</ul>
<p>這時候回傳 <code>429</code>、<code>503</code> 或 domain-level rejection，往往比讓請求默默堆積更健康。</p>
]]></content:encoded></item><item><title>9.6 容量規劃模型</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/capacity-planning/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/capacity-planning/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>容量規劃的責任是把「未來 N 個月可能多大」翻成「現在該訂多少 capacity」。這層工作不純靠歷史外推、要結合業務 forecast、事件型成長、頂部風險 buffer。&lt;/p>
&lt;p>跟 &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> 的關係：9.4 提供「當前配置能撐多少」、9.6 用這個數字加上 forecast 推「該規劃多少」。沒有 9.4 的 baseline、9.6 只是猜；沒有 9.6 的 forecast、9.4 的 baseline 只是 snapshot。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸&lt;/a> 的關係：9.13 先決定「沿哪條軸擴」（垂直 / 水平 / Y 軸拆服務 / Z 軸 partition），9.6 才能算出「該擴多少」。同樣是「處理 10 倍流量」、選垂直擴展要算單機規格上限、選水平擴展要算協調成本跟連線池放大、選 Y 軸拆服務要算跨服務 latency budget — 三條軸的容量公式參數完全不同。沒先做 9.13、9.6 的數字會落到錯誤的擴展軸上。&lt;/p>
&lt;p>本章是「規劃決策」的章節、不是執行手冊。讀完後讀者能回答：peak 怎麼預測、headroom 訂多少、autoscaler 怎麼配、不可水平擴的服務怎麼處理。&lt;/p>
&lt;h2 id="容量公式三項">容量公式三項&lt;/h2>
&lt;p>容量規劃的核心公式可以濃縮成三項相乘：&lt;code>容量 = 預期峰值 × (1 + headroom) / 可擴容速度&lt;/code>。每一項都需要獨立分析：&lt;/p>
&lt;p>&lt;strong>預期峰值（peak forecast）&lt;/strong>：歷史 baseline × 預期成長 × 事件因子。三項中最影響整體準度。詳見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast 卡片&lt;/a>。&lt;/p>
&lt;p>&lt;strong>Headroom budget&lt;/strong>：通常 30-50%、為了應付異常 burst + AZ 故障 + forecast 誤差。不同工作負載 headroom 不同。詳見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/headroom-budget/" data-link-title="Headroom Budget" data-link-desc="說明容量規劃中為應付異常 burst &amp;#43; AZ 故障 &amp;#43; forecast 誤差的安全餘量">Headroom Budget 卡片&lt;/a>。&lt;/p>
&lt;p>&lt;strong>可擴容速度（reactive vs predictive）&lt;/strong>：autoscaler 反應時間 vs 流量上升速度。如果流量上升比 autoscaler 快、必須 &lt;em>提前&lt;/em> pre-scale、不能等 reactive 反應。&lt;/p>
&lt;p>這個公式的另一個寫法是「容量 = peak × 安全係數」、安全係數 = (1 + headroom) / 可擴容速度。預測準 + 擴容快 → 安全係數小、容量緊湊；預測差 + 擴容慢 → 安全係數大、成本高。&lt;/p>
&lt;h2 id="peak-forecast-方法">Peak forecast 方法&lt;/h2>
&lt;p>Forecast 方法分三層、按業務型態選用。&lt;/p>
&lt;p>&lt;strong>歷史線性外推&lt;/strong>：拿過去 N 個月的趨勢、按斜率外推到下 N 個月。適合 sustained growth（B2B SaaS 月增 X%）；不適合 event peak（年度活動）跟 surge（產品爆紅）。&lt;/p>
&lt;p>&lt;strong>季節性分解（STL：Seasonal-Trend decomposition using Loess）&lt;/strong>：把長期趨勢、週期成分、殘差分開預測。適合電商（雙 11 / Black Friday）、串流（IPL / Super Bowl）、零售（聖誕節）。需要 &lt;em>至少兩個完整 cycle&lt;/em> 的歷史資料。&lt;/p>
&lt;p>&lt;strong>業務 ML 模型&lt;/strong>：結合 marketing pipeline（廣告投入）、新用戶獲取（acquisition rate）、留存率、產品變化等多 feature。最精準但成本高、需要 ML team。&lt;/p>
&lt;p>&lt;strong>最常見錯誤是「拿去年同期 × (1 + 預期成長 %)」&lt;/strong>：忽略產品改動 + 行銷投入變化 + 外部事件。Prime Day 2025 vs 2024 不只是 +30% — 是 AI shopping assistant 上線、是 ad spend 變化、是新國家上線。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>容量規劃的責任是把「未來 N 個月可能多大」翻成「現在該訂多少 capacity」。這層工作不純靠歷史外推、要結合業務 forecast、事件型成長、頂部風險 buffer。</p>
<p>跟 <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> 的關係：9.4 提供「當前配置能撐多少」、9.6 用這個數字加上 forecast 推「該規劃多少」。沒有 9.4 的 baseline、9.6 只是猜；沒有 9.6 的 forecast、9.4 的 baseline 只是 snapshot。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸</a> 的關係：9.13 先決定「沿哪條軸擴」（垂直 / 水平 / Y 軸拆服務 / Z 軸 partition），9.6 才能算出「該擴多少」。同樣是「處理 10 倍流量」、選垂直擴展要算單機規格上限、選水平擴展要算協調成本跟連線池放大、選 Y 軸拆服務要算跨服務 latency budget — 三條軸的容量公式參數完全不同。沒先做 9.13、9.6 的數字會落到錯誤的擴展軸上。</p>
<p>本章是「規劃決策」的章節、不是執行手冊。讀完後讀者能回答：peak 怎麼預測、headroom 訂多少、autoscaler 怎麼配、不可水平擴的服務怎麼處理。</p>
<h2 id="容量公式三項">容量公式三項</h2>
<p>容量規劃的核心公式可以濃縮成三項相乘：<code>容量 = 預期峰值 × (1 + headroom) / 可擴容速度</code>。每一項都需要獨立分析：</p>
<p><strong>預期峰值（peak forecast）</strong>：歷史 baseline × 預期成長 × 事件因子。三項中最影響整體準度。詳見 <a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast 卡片</a>。</p>
<p><strong>Headroom budget</strong>：通常 30-50%、為了應付異常 burst + AZ 故障 + forecast 誤差。不同工作負載 headroom 不同。詳見 <a href="/blog/backend/knowledge-cards/headroom-budget/" data-link-title="Headroom Budget" data-link-desc="說明容量規劃中為應付異常 burst &#43; AZ 故障 &#43; forecast 誤差的安全餘量">Headroom Budget 卡片</a>。</p>
<p><strong>可擴容速度（reactive vs predictive）</strong>：autoscaler 反應時間 vs 流量上升速度。如果流量上升比 autoscaler 快、必須 <em>提前</em> pre-scale、不能等 reactive 反應。</p>
<p>這個公式的另一個寫法是「容量 = peak × 安全係數」、安全係數 = (1 + headroom) / 可擴容速度。預測準 + 擴容快 → 安全係數小、容量緊湊；預測差 + 擴容慢 → 安全係數大、成本高。</p>
<h2 id="peak-forecast-方法">Peak forecast 方法</h2>
<p>Forecast 方法分三層、按業務型態選用。</p>
<p><strong>歷史線性外推</strong>：拿過去 N 個月的趨勢、按斜率外推到下 N 個月。適合 sustained growth（B2B SaaS 月增 X%）；不適合 event peak（年度活動）跟 surge（產品爆紅）。</p>
<p><strong>季節性分解（STL：Seasonal-Trend decomposition using Loess）</strong>：把長期趨勢、週期成分、殘差分開預測。適合電商（雙 11 / Black Friday）、串流（IPL / Super Bowl）、零售（聖誕節）。需要 <em>至少兩個完整 cycle</em> 的歷史資料。</p>
<p><strong>業務 ML 模型</strong>：結合 marketing pipeline（廣告投入）、新用戶獲取（acquisition rate）、留存率、產品變化等多 feature。最精準但成本高、需要 ML team。</p>
<p><strong>最常見錯誤是「拿去年同期 × (1 + 預期成長 %)」</strong>：忽略產品改動 + 行銷投入變化 + 外部事件。Prime Day 2025 vs 2024 不只是 +30% — 是 AI shopping assistant 上線、是 ad spend 變化、是新國家上線。</p>
<p>對應案例：<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 的峰值數字 — 一年一次可預期峰值的容量設計參考">Prime Day 年增率 +30% ~ +77%</a> — 連 Amazon 自家每年成長都不能線性外推；<a href="/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">Disney+ 新片發布</a> — 事件型 forecast、按過去新片 metric 預估。</p>
<p>Forecast 必須有 <em>誤差範圍</em>、不能單一數字。給上下界（最壞 / 預期 / 最好）、容量規劃才能用 worst-case 訂 baseline。</p>
<h2 id="headroom-budget-設計">Headroom budget 設計</h2>
<p>Headroom 不是 over-provisioning 浪費、是容量規劃的安全邊界。常見比例 30-50%、按 saturation 行為跟工作負載敏感度調整。</p>
<p><strong>為什麼是 30-50% 而不是 10%</strong>：</p>
<ul>
<li>forecast 誤差：預測準度通常 ±20-30%</li>
<li>burst pattern：瞬間 spike 超過 average peak、需要短時間吸收</li>
<li>AZ / region failover：一個 AZ 掛、剩下兩個要承擔全部（多 33% 容量）</li>
<li>系統老化 / drift：軟硬體升級後 saturation 點可能位移</li>
</ul>
<p><strong>不同工作負載不同 headroom</strong>：</p>
<ul>
<li>stateless service：30%（autoscaler 反應快、headroom 可以薄）</li>
<li>DB：50%（不易擴容、要備援足夠空間）</li>
<li>broker / queue：60%（consumer 落後恢復時要瞬間吃下積壓）</li>
<li>consensus DB：80%+（完全不能 reactive 擴）</li>
</ul>
<p><strong>headroom 太低 → 出事</strong>：peak 期間進 cliff、用戶體驗變差。
<strong>headroom 太高 → 浪費錢</strong>：平日成本拉高、CFO 質疑。</p>
<p>對應案例：<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 體育博彩峰值流量">GR8 Tech AI 預測</a> — 預測準了可以降 headroom 比例；預測不準必須拉高 headroom 補回安全邊界。</p>
<h2 id="growth-curve-形狀分類">Growth curve 形狀分類</h2>
<p>不同 growth curve 形狀對應不同 forecast 方法跟 review 節奏。</p>
<p><strong>Linear growth</strong>：用戶月增 X%。B2B SaaS 最常見。forecast 線性外推、每季 review、headroom 可以薄（成長可預測）。</p>
<p><strong>Step growth</strong>：每次行銷 / 活動跳一階、之間 plateau。需要 event tier 規劃、每個事件單獨 forecast、headroom 跟 event 強度連動。</p>
<p><strong>Exponential growth</strong>：早期初創、病毒擴散。forecast 容易低估、傳統線性外推會大幅低估；headroom 必須拉到 100%+、不能省。</p>
<p><strong>S-curve growth</strong>：成熟產品、會 saturate。Forecast 初期像 exponential、中期 plateau、晚期 mature。需要識別 inflection point、過了就調 forecast 方法。</p>
<p><strong>Cyclical</strong>：電商季節性。每年 Black Friday / Cyber Monday / Christmas / Chinese New Year 都重複、forecast 用 STL 季節性分解。</p>
<p>對應案例：<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 撐住會議後端">Zoom 30x COVID</a> — step growth、外部衝擊讓 baseline 永久上移；<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 怎麼即時補容量">Pokemon GO 50x surge</a> — exponential（早期）+ 之後 S-curve；<a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">ASOS Black Friday</a> — cyclical。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/growth-curve/" data-link-title="Growth Curve" data-link-desc="說明用戶 / 流量隨時間成長的五種典型形狀、影響容量規劃方法">Growth Curve 卡片</a>。</p>
<h2 id="autoscaling-sizing">Autoscaling sizing</h2>
<p>訂好 capacity 之後、要設計 autoscaler 把這個容量 <em>動態使用</em>。</p>
<p><strong>min / max / target metric 三個參數</strong>：</p>
<ul>
<li>min 太低 → cold start 風險（流量上來時還在 boot）</li>
<li>min 太高 → 平日浪費</li>
<li>max 太低 → 限流（peak 時 autoscaler 不能再擴）</li>
<li>max 太高 → 月底炸帳單（autoscaler 不受控、過 peak 不會主動降）</li>
<li>target 太高 → autoscale 啟動太晚、進 knee 才反應</li>
<li>target 太低 → autoscale 太敏感、頻繁 scale up / down 浪費</li>
</ul>
<p><strong>Predictive vs reactive</strong>：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/predictive-scaling/" data-link-title="Predictive Scaling" data-link-desc="說明用歷史模式或 ML 模型預測流量、提前擴容的 autoscaler 模式">predictive scaling</a>：根據歷史 pattern 或 ML 模型提前擴</li>
<li>reactive scaling：根據當下指標擴</li>
<li>兩者組合最穩：predictive 處理已知 pattern、reactive 處理 unexpected burst</li>
</ul>
<p><strong>Scheduled vs metric-based</strong>：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">scheduled scaling</a>：時段觸發（年度活動、daily peak）</li>
<li>metric-based：根據 utilization / queue depth 觸發</li>
<li>三層組合（scheduled + predictive + reactive）最穩</li>
</ul>
<p><strong>不同層的 autoscaler 各自設計</strong>：</p>
<ul>
<li>EC2 Auto Scaling Group：infrastructure 層</li>
<li>Kubernetes HPA / VPA：pod 層</li>
<li>Karpenter：node 層</li>
<li>DynamoDB auto-scaling：DB capacity 層</li>
<li>CloudFront：CDN 層</li>
</ul>
<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 30 分鐘擴 130 倍</a> — 6 台 → 800 台靠 ASG + AMI prebuild + ELB warmup；<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 的峰值數字 — 一年一次可預期峰值的容量設計參考">Prime Day predictive</a> — pre-scaling 30-77% 年增率提前算進容量。</p>
<h2 id="不可水平擴容服務的容量規劃">不可水平擴容服務的容量規劃</h2>
<p>部分服務不能用「加機器」解決容量問題。這類服務的容量規劃有獨立邏輯。</p>
<p><strong>典型不可水平擴</strong>：</p>
<ul>
<li>consensus DB（RAFT / Paxos）：節點數量是 consensus 一部分、不能臨時增減</li>
<li>single leader DB（PostgreSQL primary、MySQL master）：寫只有一個 leader</li>
<li>中央 coordinator：必須拆解才可擴</li>
</ul>
<p><strong>容量公式變成</strong>：單機極限 × headroom、沒有 elastic 救援。
<strong>設計重點</strong>：</p>
<ul>
<li>預先 provision 到能撐 peak、不依賴 reactive 擴</li>
<li>垂直擴容（更大 instance）為主、不是橫向</li>
<li>留更高 headroom（80%+）、出事沒有第二招</li>
</ul>
<p>對應案例：<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 pre-provision</a> — RAFT 限制下完全 pre-provision、不 autoscale；<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">Spanner 節點即容量單位</a> — 雖然全球可擴、但每個 region 內節點數要預先規劃。</p>
<h2 id="跨地理--跨-region-容量規劃">跨地理 / 跨 region 容量規劃</h2>
<p>跨 region 服務不能用 <em>全球總量</em> 平攤、每個 region 獨立規劃。</p>
<p><strong>為什麼不能聚合</strong>：</p>
<ul>
<li>用戶在哪、流量就在哪、不會自動 spread</li>
<li>跨 region 切流量有延遲（DNS TTL、用戶習慣）、不能即時 rebalance</li>
<li>資料駐留合規可能強制各 region 獨立</li>
</ul>
<p><strong>規劃方法</strong>：</p>
<ul>
<li>每個 region 抽各自的 workload model</li>
<li>各自跑 saturation discovery</li>
<li>各自訂 headroom（區域峰值 + 區域 AZ failover）</li>
<li>跨 region failover plan：哪個 region 掛了、流量去哪、目標 region 要留多少 headroom 接</li>
</ul>
<p>對應案例：<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 個受監管市場">Standard Chartered 7 個受監管市場</a> — 跨市場獨立容量規劃；<a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">Genesys 15 region</a> — 15 主 region + 5 衛星 region 各自規劃；<a href="/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &#43; 1.5 億商品、用 GCP Vertex AI Search &#43; BigQuery 提供近即時搜尋與分析">Mercado Libre 18 國</a> — 每國獨立 cycle。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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</a></td>
          <td>可預期峰值的 forecast + pre-scaling</td>
      </tr>
      <tr>
          <td><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></td>
          <td>AI 預測式擴容、縮短反應時間</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>30x surge 後 baseline 永久上移</td>
      </tr>
      <tr>
          <td><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></td>
          <td>跨市場獨立容量規劃</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>不可水平擴的 pre-provision</td>
      </tr>
  </tbody>
</table>
<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> / <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/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸與 Stateless 前提</a>（先選軸再算數量、不可水平擴容服務的判讀基底）</li>
<li>下游：<a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a>（容量翻成成本）</li>
<li>下游：<a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="活動、季節性流量、推廣事件的 capacity readiness 流程">9.11 高峰事件準備</a></li>
<li>跨模組：<a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> autoscaler 實作</li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast</a></li>
<li><a href="/blog/backend/knowledge-cards/headroom-budget/" data-link-title="Headroom Budget" data-link-desc="說明容量規劃中為應付異常 burst &#43; AZ 故障 &#43; forecast 誤差的安全餘量">Headroom Budget</a></li>
<li><a href="/blog/backend/knowledge-cards/growth-curve/" data-link-title="Growth Curve" data-link-desc="說明用戶 / 流量隨時間成長的五種典型形狀、影響容量規劃方法">Growth Curve</a></li>
<li><a href="/blog/backend/knowledge-cards/predictive-scaling/" data-link-title="Predictive Scaling" data-link-desc="說明用歷史模式或 ML 模型預測流量、提前擴容的 autoscaler 模式">Predictive Scaling</a></li>
<li><a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">Scheduled Scaling</a></li>
</ul>
]]></content:encoded></item><item><title>9.C6 Tinder：ElastiCache for Valkey 撐 4700 萬月活的配對引擎</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tinder-elasticache-valkey-matching/</guid><description>&lt;p>這個案例的核心責任是說明「cache layer 在持續成長服務」的角色 — 不是峰值問題、是延遲 SLA 與成本曲線同時拉緊的長期工程議題。Tinder 的配對引擎需要在每次滑動都查多個快取（用戶 profile、距離、偏好過濾、推薦池），單次互動的延遲就是 UX 本身。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Tinder 在 ElastiCache for Valkey 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/elasticache/customers/">ElastiCache customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>月活用戶&lt;/td>
 &lt;td>約 4700 萬 MAU (2025)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>配對累計&lt;/td>
 &lt;td>超過 10 億次配對&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>地理覆蓋&lt;/td>
 &lt;td>190 個國家&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務年數&lt;/td>
 &lt;td>自 2012 年起&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲特性&lt;/td>
 &lt;td>sub-millisecond latency&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>ElastiCache for Redis 7.1 在 r7g.4xlarge 上可達單節點 100 萬 RPS、單 cluster 5 億 RPS（引自 &lt;a href="https://aws.amazon.com/blogs/database/achieve-over-500-million-requests-per-second-per-cluster-with-amazon-elasticache-for-redis-7-1/">AWS Database Blog&lt;/a>）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Tinder 案例值得讀的是「快取在 long-running 服務的角色變化」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>快取不是 DB 的補救、是主要服務面&lt;/strong>：配對引擎每次互動讀 cache 不讀 DB、cache miss 是 &lt;em>邊緣案例&lt;/em>。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache-as-source-of-truth 與 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary&lt;/a> 設計。&lt;/li>
&lt;li>&lt;strong>次毫秒延遲是業務 KPI、不只是技術指標&lt;/strong>：手指滑動之後 250ms 內必須給結果、否則「卡頓」。中間整個 chain（網路、cache、序列化）的 latency budget 必須緊。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 反推。&lt;/li>
&lt;li>&lt;strong>長期 sustained growth 的容量曲線是成本曲線&lt;/strong>：47M MAU 沒有明顯峰谷、容量規劃變成「每月線性擴容 X%」的長期決策、不是峰值規劃。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的長期成本工程。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：Tinder 的「configurable matching」業務邏輯複雜、快取資料的 schema 變化頻繁。一個 schema 變更可能讓既有 cache 全部 invalid、引發 cache stampede。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">02.6 cache migration stampede rollback&lt;/a>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>cache layer 容量規劃跟 DB 容量規劃要分開&lt;/strong>：cache 容量受 working set size 影響、DB 容量受 total dataset 影響、兩者擴容邏輯不一樣。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache sizing。&lt;/li>
&lt;li>&lt;strong>cache 命中率變化是業務變化的訊號&lt;/strong>：突然命中率掉、可能是新功能影響 access pattern、不一定是 cache 容量問題。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性&lt;/a> 的訊號治理。&lt;/li>
&lt;li>&lt;strong>Valkey vs Redis OSS vs MemoryDB 是不同 trade-off&lt;/strong>：Valkey（社群分支、AWS 主推）、Redis OSS（受授權變化影響）、MemoryDB（持久化）三者選擇影響長期 vendor lock-in。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Memorystore for Redis / Valkey、Azure Cache for Redis、自建 Redis Cluster + Sentinel 都可以實作對等架構。差異是 vendor 的 patch cadence 與容量擴張流程。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「cache layer 在持續成長服務」的角色 — 不是峰值問題、是延遲 SLA 與成本曲線同時拉緊的長期工程議題。Tinder 的配對引擎需要在每次滑動都查多個快取（用戶 profile、距離、偏好過濾、推薦池），單次互動的延遲就是 UX 本身。</p>
<h2 id="觀察">觀察</h2>
<p>Tinder 在 ElastiCache for Valkey 的關鍵數字（引自 <a href="https://aws.amazon.com/elasticache/customers/">ElastiCache customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月活用戶</td>
          <td>約 4700 萬 MAU (2025)</td>
      </tr>
      <tr>
          <td>配對累計</td>
          <td>超過 10 億次配對</td>
      </tr>
      <tr>
          <td>地理覆蓋</td>
          <td>190 個國家</td>
      </tr>
      <tr>
          <td>服務年數</td>
          <td>自 2012 年起</td>
      </tr>
      <tr>
          <td>延遲特性</td>
          <td>sub-millisecond latency</td>
      </tr>
  </tbody>
</table>
<p>ElastiCache for Redis 7.1 在 r7g.4xlarge 上可達單節點 100 萬 RPS、單 cluster 5 億 RPS（引自 <a href="https://aws.amazon.com/blogs/database/achieve-over-500-million-requests-per-second-per-cluster-with-amazon-elasticache-for-redis-7-1/">AWS Database Blog</a>）。</p>
<h2 id="判讀">判讀</h2>
<p>Tinder 案例值得讀的是「快取在 long-running 服務的角色變化」。</p>
<ol>
<li><strong>快取不是 DB 的補救、是主要服務面</strong>：配對引擎每次互動讀 cache 不讀 DB、cache miss 是 <em>邊緣案例</em>。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache-as-source-of-truth 與 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a> 設計。</li>
<li><strong>次毫秒延遲是業務 KPI、不只是技術指標</strong>：手指滑動之後 250ms 內必須給結果、否則「卡頓」。中間整個 chain（網路、cache、序列化）的 latency budget 必須緊。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget 反推。</li>
<li><strong>長期 sustained growth 的容量曲線是成本曲線</strong>：47M MAU 沒有明顯峰谷、容量規劃變成「每月線性擴容 X%」的長期決策、不是峰值規劃。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的長期成本工程。</li>
</ol>
<p>需要警惕：Tinder 的「configurable matching」業務邏輯複雜、快取資料的 schema 變化頻繁。一個 schema 變更可能讓既有 cache 全部 invalid、引發 cache stampede。對應 <a href="/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">02.6 cache migration stampede rollback</a>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>cache layer 容量規劃跟 DB 容量規劃要分開</strong>：cache 容量受 working set size 影響、DB 容量受 total dataset 影響、兩者擴容邏輯不一樣。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache sizing。</li>
<li><strong>cache 命中率變化是業務變化的訊號</strong>：突然命中率掉、可能是新功能影響 access pattern、不一定是 cache 容量問題。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.8 效能可觀測性</a> 的訊號治理。</li>
<li><strong>Valkey vs Redis OSS vs MemoryDB 是不同 trade-off</strong>：Valkey（社群分支、AWS 主推）、Redis OSS（受授權變化影響）、MemoryDB（持久化）三者選擇影響長期 vendor lock-in。</li>
</ol>
<p>跨平台等效：GCP Memorystore for Redis / Valkey、Azure Cache for Redis、自建 Redis Cluster + Sentinel 都可以實作對等架構。差異是 vendor 的 patch cadence 與容量擴張流程。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 cache layer 容量 → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>想做 latency budget 反推 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a></li>
<li>想理解 cache stampede 風險 → <a href="/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">02.6 cache migration stampede rollback</a></li>
<li>對照其他 cache 案例 → <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>（KV 高吞吐）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/elasticache/customers/">Amazon ElastiCache Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/achieve-over-500-million-requests-per-second-per-cluster-with-amazon-elasticache-for-redis-7-1/">Achieve over 500 million requests per second per cluster with ElastiCache for Redis 7.1</a></li>
<li><a href="https://aws.amazon.com/blogs/database/optimize-redis-client-performance-for-amazon-elasticache/">Optimize Redis Client Performance for ElastiCache and MemoryDB</a></li>
</ul>
]]></content:encoded></item><item><title>9.7 成本邊界與 efficiency</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>成本工程的責任是讓容量決策有經濟邊界。沒有成本意識時、容量規劃會「保險起見全部擴」、最終帳單炸裂；有成本意識之後、能 &lt;em>在每一個容量決策點&lt;/em> 把「多保險」跟「多省錢」一起評估。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/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 容量規劃模型&lt;/a> 的關係：9.6 算「該訂多少容量」、9.7 算「這樣訂值不值得」。兩者必須一起做、不能先決定容量再算成本。&lt;/p>
&lt;p>本章從 cost per request 這個 unit economics 開始、推到 cost curve、TCO、降級成本、人力成本工程化、FinOps 整合。讀完後讀者能回答「容量設計的成本邊界在哪、什麼時候該降級而非擴容」。&lt;/p>
&lt;h2 id="cost-per-request-模型">Cost per request 模型&lt;/h2>
&lt;p>雲端帳單從月度視角看是黑箱、從 cost per request 視角看可拆解。&lt;/p>
&lt;p>&lt;strong>基本公式&lt;/strong>：月帳單總額 / 月總 RPS = cost per request。但這只是平均、不同 endpoint 成本差很大。
&lt;strong>分 stage 拆解&lt;/strong>：app compute + DB read + DB write + cache + network egress + 第三方 API。每個 stage 自己有 unit cost。
&lt;strong>分 endpoint 拆解&lt;/strong>：登入請求可能 $0.0001、結帳請求可能 $0.001（10x 差距）。原因：結帳走更多 stage、可能跨 region、可能呼叫第三方支付。&lt;/p>
&lt;p>&lt;strong>對齊業務 metric&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>cost per active user：總成本 / MAU&lt;/li>
&lt;li>cost per transaction：總成本 / 完成的訂單數&lt;/li>
&lt;li>cost per ML inference：總成本 / inference 次數&lt;/li>
&lt;/ul>
&lt;p>業務 metric 級別的 cost 才能跟收入對比、才能算 unit economics。&lt;/p>
&lt;p>對應案例：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">Zomato 50% 成本下降&lt;/a> — 算出每筆計費事件的 cost per request 後、發現 TiDB over-provision 拖累、遷移 DynamoDB 後減半；&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">Netflix Aurora 28% 成本降&lt;/a> — DB consolidation 把多套 DB 的 cost 統一到 Aurora、Aurora 自己的 cost per request 更便宜。&lt;/p>
&lt;p>詳見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">Cost Per Request 卡片&lt;/a>。&lt;/p>
&lt;h2 id="cost-curve-形狀">Cost curve 形狀&lt;/h2>
&lt;p>不同 pricing 模式的 cost curve 形狀不同、組合起來才能最佳化。&lt;/p>
&lt;p>&lt;strong>On-demand（pay-per-use）&lt;/strong>：流量上升、成本同步上升。線性 cost curve。優點：彈性、不用承諾；缺點：單位成本最貴。
&lt;strong>Reserved instances（RI）/ Savings Plans&lt;/strong>：承諾 1-3 年用量、單位成本降 30-60%。階梯 cost curve。優點：便宜；缺點：承諾期內如果用量低、浪費。
&lt;strong>Spot instances&lt;/strong>：用 cloud 閒置 capacity、單位成本降 70-90%。可被中斷。優點：最便宜；缺點：可能突然被收回。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>成本工程的責任是讓容量決策有經濟邊界。沒有成本意識時、容量規劃會「保險起見全部擴」、最終帳單炸裂；有成本意識之後、能 <em>在每一個容量決策點</em> 把「多保險」跟「多省錢」一起評估。</p>
<p>跟 <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> 的關係：9.6 算「該訂多少容量」、9.7 算「這樣訂值不值得」。兩者必須一起做、不能先決定容量再算成本。</p>
<p>本章從 cost per request 這個 unit economics 開始、推到 cost curve、TCO、降級成本、人力成本工程化、FinOps 整合。讀完後讀者能回答「容量設計的成本邊界在哪、什麼時候該降級而非擴容」。</p>
<h2 id="cost-per-request-模型">Cost per request 模型</h2>
<p>雲端帳單從月度視角看是黑箱、從 cost per request 視角看可拆解。</p>
<p><strong>基本公式</strong>：月帳單總額 / 月總 RPS = cost per request。但這只是平均、不同 endpoint 成本差很大。
<strong>分 stage 拆解</strong>：app compute + DB read + DB write + cache + network egress + 第三方 API。每個 stage 自己有 unit cost。
<strong>分 endpoint 拆解</strong>：登入請求可能 $0.0001、結帳請求可能 $0.001（10x 差距）。原因：結帳走更多 stage、可能跨 region、可能呼叫第三方支付。</p>
<p><strong>對齊業務 metric</strong>：</p>
<ul>
<li>cost per active user：總成本 / MAU</li>
<li>cost per transaction：總成本 / 完成的訂單數</li>
<li>cost per ML inference：總成本 / inference 次數</li>
</ul>
<p>業務 metric 級別的 cost 才能跟收入對比、才能算 unit economics。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">Zomato 50% 成本下降</a> — 算出每筆計費事件的 cost per request 後、發現 TiDB over-provision 拖累、遷移 DynamoDB 後減半；<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">Netflix Aurora 28% 成本降</a> — DB consolidation 把多套 DB 的 cost 統一到 Aurora、Aurora 自己的 cost per request 更便宜。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">Cost Per Request 卡片</a>。</p>
<h2 id="cost-curve-形狀">Cost curve 形狀</h2>
<p>不同 pricing 模式的 cost curve 形狀不同、組合起來才能最佳化。</p>
<p><strong>On-demand（pay-per-use）</strong>：流量上升、成本同步上升。線性 cost curve。優點：彈性、不用承諾；缺點：單位成本最貴。
<strong>Reserved instances（RI）/ Savings Plans</strong>：承諾 1-3 年用量、單位成本降 30-60%。階梯 cost curve。優點：便宜；缺點：承諾期內如果用量低、浪費。
<strong>Spot instances</strong>：用 cloud 閒置 capacity、單位成本降 70-90%。可被中斷。優點：最便宜；缺點：可能突然被收回。</p>
<p><strong>最佳組合通常是「Reserved baseline + On-demand spike + Spot batch」</strong>：</p>
<ul>
<li>Reserved 覆蓋 baseline 容量（永遠用得到）</li>
<li>On-demand 處理 peak 跟 unpredicted burst</li>
<li>Spot 跑 batch 工作（不在 critical path、可被中斷）</li>
</ul>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">Riot Games 年省 1000 萬</a> — 從自管 Mesos 遷到 EKS、降的不只是 instance cost、是 cluster 管理人力 + ops 簡化；<a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">Capcom 30% 成本下降</a> — DynamoDB + EKS 取代自管、釋放 DBA 人力。</p>
<h2 id="over-provisioning-vs-under-provisioning-取捨">Over-provisioning vs under-provisioning 取捨</h2>
<p>容量決策的核心經濟學問題：訂多大容量才是最划算？</p>
<p><strong>Over-provisioning 成本</strong>：每月多付 $X 雲端費。這個數字直接看帳單。
<strong>Under-provisioning 成本</strong>：sigma 機率 × downtime × revenue per minute。這個數字更難算 — 需要 historical incident rate + downtime impact analysis。</p>
<p><strong>兩個成本平衡點 = 經濟最佳 headroom</strong>。但實務上 under-provisioning 成本不容易量化、保守做法是把 sigma 機率拉高（用 worst-case 估）、headroom 訂寬一點。</p>
<p><strong>Critical workload</strong>（金融、醫療、付款）：under-provisioning 成本極高（合約違約 + 客戶流失 + 法規）、寧可 over-provisioning 30-50%。
<strong>Non-critical workload</strong>（內部工具、分析、batch）：under-provisioning 成本低、可以更貼近 minimum capacity。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">Zomato TiDB 必須 over-provision</a> — 為了應付 spike、TiDB 必須長期 over-provision；DynamoDB on-demand 不必、pay-per-use 自然處理。</p>
<h2 id="降級的成本邊界">降級的成本邊界</h2>
<p>「降級 vs 擴容」是常見容量決策、但常被當成「技術問題」而非「成本問題」。</p>
<p><strong>降級不是免費</strong>：</p>
<ul>
<li>流失轉換：UI 顯示「系統忙碌」、用戶可能放棄</li>
<li>客訴成本：客服處理客訴的 OpEx</li>
<li>品牌損失：社群媒體負面評論、口碑下降</li>
<li>合約違約：B2B 客戶可能基於 SLA 求償</li>
</ul>
<p><strong>算「降級 vs 擴容」哪個成本低</strong>：</p>
<ul>
<li>擴容成本：peak 時段多付的 cloud 費用</li>
<li>降級成本：上述四項合計</li>
<li>哪邊低就選哪邊</li>
</ul>
<p><strong>降級觸發條件</strong>通常按負載門檻 / 成本門檻 / SLA 觸發：</p>
<ul>
<li>負載門檻：utilization &gt; 85% → 啟動降級</li>
<li>成本門檻：本月雲端費已超預算 X% → 啟動降級</li>
<li>SLA 觸發：<a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 快用完 → 啟動降級保 SLA</li>
</ul>
<p>對應案例：<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 怎麼即時補容量">Pokemon GO 50x surge</a> — surge 期間無法等比擴容、必須降級保住核心遊戲機制、犧牲附加功能。</p>
<h2 id="人力成本工程化">人力成本工程化</h2>
<p>雲端帳單是顯性成本、但 <em>人力成本</em> 是常被忽略的隱性容量成本。</p>
<p><strong>自建 vs managed 的人力成本對比</strong>：</p>
<ul>
<li>自建 Kafka / PostgreSQL / Redis：需要 DBA / SRE 持續維護 + 升級 + 故障處理</li>
<li>Managed 服務（MSK、Aurora、ElastiCache）：vendor 負責 patch、backup、failover</li>
<li>差距通常 <em>3-10 倍</em> 人力成本</li>
</ul>
<p><strong>DBA / SRE / network engineer 都是隱性容量成本</strong>：</p>
<ul>
<li>一個資深 DBA 在美國年薪 $200K+、台灣 NTD 200-400 萬</li>
<li>工程師時間是有上限的、自管系統佔的時間就是 <em>無法投入產品開發</em> 的機會成本</li>
</ul>
<p><strong>「90% 工程工時下降」是管理 ROI 的關鍵</strong>：重點是把工程資源從 <em>維持</em> 轉移到 <em>建構</em>、不是拿來吹噓技術。這條自建 vs managed 的人力成本對比、是 <a href="/blog/backend/00-service-selection/capability-buy-vs-build/" data-link-title="0.22 能力級買 vs 建：feature-as-a-service 與 BaaS bundle 選型" data-link-desc="在交付形態決定整個系統要不要自建之後、逐能力判斷該外包還是自建：辨識 managed 基礎設施、feature SaaS 與 BaaS bundle 三種外包深度、no-code 到 dev-tool 的服務光譜、買 vs 建判準與權重浮動、整合接縫與遷出代價">0.22 能力級買 vs 建</a> 裡「計費隨規模成長、自建 TCO 出現交叉點」那條 tripwire 的算法側 — 選型方向在 0.22 判、成本量化在這裡做。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">Spotify Kafka → Pub/Sub</a> — 不是因為 Pub/Sub 便宜、是因為 Spotify 規模下自管 Kafka 的人力成本不划算；<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 90% 工程工時降</a> — managed 路線讓電信商級新串流服務只用 5-10 個工程師 launch；<a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">Capcom DBA 釋放</a> — 把 DBA 時間從 patching 轉到遊戲品質。</p>
<h2 id="finops-跟容量規劃的整合">FinOps 跟容量規劃的整合</h2>
<p>FinOps 是 <em>財務跟工程的協作框架</em>、把成本決策從事後對帳變成事前規劃。</p>
<p><strong>Showback / chargeback</strong>：把雲端成本攤到團隊 / 服務 / feature。每個團隊看得到自己的成本、自然開始 optimize。chargeback（實際扣預算）比 showback（純展示）更有效但組織複雜度高。</p>
<p><strong>每月 cost review 變成容量 review 的一部分</strong>：</p>
<ul>
<li>對比預算 vs 實際</li>
<li>找出 top 5 cost driver</li>
<li>對比上月趨勢、看是否有 anomaly</li>
<li>跟 capacity team 一起討論 right-sizing</li>
</ul>
<p><strong>Spot diversification</strong>：spot 中斷風險可以靠 <em>多 instance type 跟多 AZ</em> 分散。例如：spot pool 同時包含 m5.large + m5a.large + m5n.large、各 AZ 都有、單一 type pool 撤回時其他 type 還在。</p>
<p><strong>Right-sizing</strong>：定期 review instance type 是否最適。常見浪費：訂太大 instance（CPU / RAM 用 30%）、過時 instance generation（用 c5 沒升到 c7）、reserved 過剩。</p>
<h2 id="反模式">反模式</h2>
<p>容量成本的常見錯誤模式：</p>
<p><strong>Autoscaling max 設無限大</strong>：流量爆衝時 autoscaler 跟著爆衝、月底帳單炸裂。max 必須訂、是 financial circuit breaker。</p>
<p><strong>全部用 on-demand、沒談 reserved / savings plan</strong>：cloud spending &gt; $10K/月 已經值得跟雲商 talk discount、savings plan 通常 30-60% off。</p>
<p><strong>沒成本 monitoring、直到帳單來才知道</strong>：要建 daily cost dashboard、anomaly 即時 alert、不要等月帳單。</p>
<p><strong>降級用人工觸發、出事時來不及</strong>：降級邏輯要 <em>自動化</em>、按 metric 觸發、不是 oncall 工程師看到 dashboard 才下指令。</p>
<p><strong>忘了人力成本</strong>：算 build vs buy 只算 cloud 費、忘了 SRE / DBA 時間、結果發現「省的 cloud 費 &lt; 多花的人力」。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a></td>
          <td>50% 成本下降（從 over-provision 解放）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a></td>
          <td>年省 1000 萬（EKS 替代 Mesos）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a></td>
          <td>28% 成本下降（DB consolidation）</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>90% 工程工時降（managed 路線）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a></td>
          <td>30% 成本下降（DBA 釋放到遊戲品質）</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<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></li>
<li>下游：<a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a>（cost attribution）</li>
<li>跨模組：<a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04.14 cost attribution</a></li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">Cost Per Request</a></li>
<li><a href="/blog/backend/knowledge-cards/headroom-budget/" data-link-title="Headroom Budget" data-link-desc="說明容量規劃中為應付異常 burst &#43; AZ 故障 &#43; forecast 誤差的安全餘量">Headroom Budget</a></li>
</ul>
]]></content:encoded></item><item><title>9.C7 Lyft：100+ 微服務在 8 倍峰值下的 Auto Scaling</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/</guid><description>&lt;p>這個案例的核心責任是說明「微服務架構在事件型峰值下的容量治理」。共乘服務的負載形狀獨特 — 平日早晚通勤雙峰、週末晚間爆量、特殊事件（演唱會、球賽結束、機場）瞬間爆量、每個城市跟每個時段都不同。100+ 個微服務各自有不同的峰值時段、需要獨立擴容策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Lyft 在 AWS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>峰值倍數&lt;/td>
 &lt;td>8x 平日基線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務數&lt;/td>
 &lt;td>100+ 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>月均搭乘&lt;/td>
 &lt;td>1400 萬 / 月&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務城市&lt;/td>
 &lt;td>200+&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon DynamoDB（搭乘追蹤、GPS 座標）、Amazon Redshift（客戶洞察）、Amazon Kinesis（即時事件串流）、AWS Auto Scaling、Amazon EC2 Container Registry。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Lyft 的工程做法揭露三個微服務容量治理重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>微服務不是「全部 8x」、是「特定服務 8x」&lt;/strong>：8x 是 &lt;em>某些核心服務&lt;/em> 在週末爆量時刻的擴容比、不是 100 個服務全部 8x。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 必須先做「哪個服務是熱點」的層次定位。&lt;/li>
&lt;li>&lt;strong>微服務粒度 = 擴容粒度&lt;/strong>：把 ride matching、payment、driver tracking、notification 切成獨立服務、每個服務的 autoscaling policy 可以獨立設計。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的服務邊界。&lt;/li>
&lt;li>&lt;strong>GPS 座標寫入 DynamoDB 是高頻 sustained workload&lt;/strong>：每個 driver 每秒寫 1-2 次位置、200+ 城市 × 每個城市數萬司機 = 巨量持續寫入、跟峰值無關。對應 &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的 KV 高吞吐設計同類。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「8x 峰值」是 &lt;em>峰值倍數&lt;/em>、不是 &lt;em>尖峰持續時間&lt;/em>。週末晚間的尖峰可能持續 3-4 小時、機場特殊事件可能持續 30 分鐘、演唱會結束可能只有 10 分鐘瞬間。容量策略要按持續時間區分。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>微服務粒度切到「同性質擴容單位」&lt;/strong>：同步 vs async、stateful vs stateless、CPU-bound vs I/O-bound 不該混在同一服務、否則擴容邏輯互相衝突。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 service decomposition。&lt;/li>
&lt;li>&lt;strong>預測式 + 反應式擴容混用&lt;/strong>：可預測（早晚通勤）用 scheduled scaling、不可預測（演唱會散場）用 reactive autoscaling、兩者組合。&lt;/li>
&lt;li>&lt;strong>GPS 類持續寫入適合 KV / time-series store&lt;/strong>：不適合放 OLTP DB、會佔用 transaction 資源。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 storage choice。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP GKE + HPA / VPA / Karpenter、Azure AKS + KEDA、自建 Kubernetes + Cluster Autoscaler 都可以實作對等架構。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想做微服務容量治理 → &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>&lt;/li>
&lt;li>想規劃事件型峰值 → &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &amp;#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2 GR8 Tech&lt;/a>&lt;/li>
&lt;li>想設計高頻 sustained workload → &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> + &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft Case Study&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「微服務架構在事件型峰值下的容量治理」。共乘服務的負載形狀獨特 — 平日早晚通勤雙峰、週末晚間爆量、特殊事件（演唱會、球賽結束、機場）瞬間爆量、每個城市跟每個時段都不同。100+ 個微服務各自有不同的峰值時段、需要獨立擴容策略。</p>
<h2 id="觀察">觀察</h2>
<p>Lyft 在 AWS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>峰值倍數</td>
          <td>8x 平日基線</td>
      </tr>
      <tr>
          <td>微服務數</td>
          <td>100+ 個</td>
      </tr>
      <tr>
          <td>月均搭乘</td>
          <td>1400 萬 / 月</td>
      </tr>
      <tr>
          <td>服務城市</td>
          <td>200+</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon DynamoDB（搭乘追蹤、GPS 座標）、Amazon Redshift（客戶洞察）、Amazon Kinesis（即時事件串流）、AWS Auto Scaling、Amazon EC2 Container Registry。</p>
<h2 id="判讀">判讀</h2>
<p>Lyft 的工程做法揭露三個微服務容量治理重點。</p>
<ol>
<li><strong>微服務不是「全部 8x」、是「特定服務 8x」</strong>：8x 是 <em>某些核心服務</em> 在週末爆量時刻的擴容比、不是 100 個服務全部 8x。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 必須先做「哪個服務是熱點」的層次定位。</li>
<li><strong>微服務粒度 = 擴容粒度</strong>：把 ride matching、payment、driver tracking、notification 切成獨立服務、每個服務的 autoscaling policy 可以獨立設計。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 跟 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的服務邊界。</li>
<li><strong>GPS 座標寫入 DynamoDB 是高頻 sustained workload</strong>：每個 driver 每秒寫 1-2 次位置、200+ 城市 × 每個城市數萬司機 = 巨量持續寫入、跟峰值無關。對應 <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> 的 KV 高吞吐設計同類。</li>
</ol>
<p>需要警惕：「8x 峰值」是 <em>峰值倍數</em>、不是 <em>尖峰持續時間</em>。週末晚間的尖峰可能持續 3-4 小時、機場特殊事件可能持續 30 分鐘、演唱會結束可能只有 10 分鐘瞬間。容量策略要按持續時間區分。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>微服務粒度切到「同性質擴容單位」</strong>：同步 vs async、stateful vs stateless、CPU-bound vs I/O-bound 不該混在同一服務、否則擴容邏輯互相衝突。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 service decomposition。</li>
<li><strong>預測式 + 反應式擴容混用</strong>：可預測（早晚通勤）用 scheduled scaling、不可預測（演唱會散場）用 reactive autoscaling、兩者組合。</li>
<li><strong>GPS 類持續寫入適合 KV / time-series store</strong>：不適合放 OLTP DB、會佔用 transaction 資源。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 storage choice。</li>
</ol>
<p>跨平台等效：GCP GKE + HPA / VPA / Karpenter、Azure AKS + KEDA、自建 Kubernetes + Cluster Autoscaler 都可以實作對等架構。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想做微服務容量治理 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想規劃事件型峰值 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</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</a></li>
<li>想設計高頻 sustained workload → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <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></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/lyft/">Lyft Case Study</a></li>
<li><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a></li>
</ul>
]]></content:encoded></item><item><title>5.7 錯誤處理與測試在高併發服務中的角色</title><link>https://tarrragon.github.io/blog/go/05-error-testing/service-reliability/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/05-error-testing/service-reliability/</guid><description>&lt;p>高併發服務的可靠性來自錯誤處理與測試共同形成的保護機制。錯誤處理讓失敗路徑可見，測試讓失敗路徑可重現；兩者一起決定系統在壓力下是否仍能被理解、修復與持續交付。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>把錯誤處理看成可靠性的一部分&lt;/li>
&lt;li>區分可恢復錯誤與不可恢復錯誤&lt;/li>
&lt;li>用測試保護失敗路徑與並發路徑&lt;/li>
&lt;li>讓 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a>、取消與 race condition 能被提早發現&lt;/li>
&lt;li>理解為什麼高併發服務更需要明確的測試邊界&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察錯誤是服務常態">【觀察】錯誤是服務常態&lt;/h2>
&lt;p>在高併發服務裡，錯誤是日常情況的一部分。網路會失敗、下游會超時、資料會不完整、狀態會競爭；這些情境都應進入系統設計，而不是只留給事故發生後人工排查。&lt;/p>
&lt;p>Go 把錯誤放在回傳值中，就是要讓這些常態能被直接看見。&lt;/p>
&lt;h2 id="判讀測試要先保護脆弱邊界">【判讀】測試要先保護脆弱邊界&lt;/h2>
&lt;p>高併發服務最容易出問題的地方，通常是：&lt;/p>
&lt;ul>
&lt;li>HTTP handler 與外部輸入邊界&lt;/li>
&lt;li>goroutine 之間的共享狀態&lt;/li>
&lt;li>timeout 與 cancellation&lt;/li>
&lt;li>event / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> 的重複或漏處理&lt;/li>
&lt;/ul>
&lt;p>這些邊界都應該有測試。人工驗證可以輔助檢查流程，但它無法穩定重現 timeout、取消、race condition 與高併發失敗路徑。&lt;/p>
&lt;h2 id="策略錯誤路徑也要被測">【策略】錯誤路徑也要被測&lt;/h2>
&lt;p>服務測試需要同時覆蓋成功路徑與失敗路徑。只驗證成功路徑會讓 timeout、下游錯誤、取消與狀態競爭在壓力下才暴露，修復成本會更高。&lt;/p>
&lt;p>你至少應該測：&lt;/p>
&lt;ul>
&lt;li>參數不合法時是否回傳穩定錯誤&lt;/li>
&lt;li>下游失敗時是否有正確的包裝錯誤&lt;/li>
&lt;li>timeout 是否真的會停止工作&lt;/li>
&lt;li>取消 context 後 goroutine 是否退出&lt;/li>
&lt;/ul>
&lt;h2 id="執行並發測試要看資源是否被正確回收">【執行】並發測試要看資源是否被正確回收&lt;/h2>
&lt;p>高併發測試的核心目標是確認資源會被正確回收。跑很多 goroutine 只是製造壓力；真正需要驗證的是：&lt;/p>
&lt;ul>
&lt;li>goroutine 會不會 leak&lt;/li>
&lt;li>channel 會不會卡住&lt;/li>
&lt;li>鎖的範圍是否合理&lt;/li>
&lt;li>資源關閉後流程是否停止&lt;/li>
&lt;/ul>
&lt;p>可靠性測試至少要證明流程能正確結束。只證明「看起來可以跑」會漏掉 goroutine leak、channel 卡住、鎖範圍過大與資源未釋放等問題。&lt;/p></description><content:encoded><![CDATA[<p>高併發服務的可靠性來自錯誤處理與測試共同形成的保護機制。錯誤處理讓失敗路徑可見，測試讓失敗路徑可重現；兩者一起決定系統在壓力下是否仍能被理解、修復與持續交付。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>把錯誤處理看成可靠性的一部分</li>
<li>區分可恢復錯誤與不可恢復錯誤</li>
<li>用測試保護失敗路徑與並發路徑</li>
<li>讓 <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a>、取消與 race condition 能被提早發現</li>
<li>理解為什麼高併發服務更需要明確的測試邊界</li>
</ol>
<hr>
<h2 id="觀察錯誤是服務常態">【觀察】錯誤是服務常態</h2>
<p>在高併發服務裡，錯誤是日常情況的一部分。網路會失敗、下游會超時、資料會不完整、狀態會競爭；這些情境都應進入系統設計，而不是只留給事故發生後人工排查。</p>
<p>Go 把錯誤放在回傳值中，就是要讓這些常態能被直接看見。</p>
<h2 id="判讀測試要先保護脆弱邊界">【判讀】測試要先保護脆弱邊界</h2>
<p>高併發服務最容易出問題的地方，通常是：</p>
<ul>
<li>HTTP handler 與外部輸入邊界</li>
<li>goroutine 之間的共享狀態</li>
<li>timeout 與 cancellation</li>
<li>event / <a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> 的重複或漏處理</li>
</ul>
<p>這些邊界都應該有測試。人工驗證可以輔助檢查流程，但它無法穩定重現 timeout、取消、race condition 與高併發失敗路徑。</p>
<h2 id="策略錯誤路徑也要被測">【策略】錯誤路徑也要被測</h2>
<p>服務測試需要同時覆蓋成功路徑與失敗路徑。只驗證成功路徑會讓 timeout、下游錯誤、取消與狀態競爭在壓力下才暴露，修復成本會更高。</p>
<p>你至少應該測：</p>
<ul>
<li>參數不合法時是否回傳穩定錯誤</li>
<li>下游失敗時是否有正確的包裝錯誤</li>
<li>timeout 是否真的會停止工作</li>
<li>取消 context 後 goroutine 是否退出</li>
</ul>
<h2 id="執行並發測試要看資源是否被正確回收">【執行】並發測試要看資源是否被正確回收</h2>
<p>高併發測試的核心目標是確認資源會被正確回收。跑很多 goroutine 只是製造壓力；真正需要驗證的是：</p>
<ul>
<li>goroutine 會不會 leak</li>
<li>channel 會不會卡住</li>
<li>鎖的範圍是否合理</li>
<li>資源關閉後流程是否停止</li>
</ul>
<p>可靠性測試至少要證明流程能正確結束。只證明「看起來可以跑」會漏掉 goroutine leak、channel 卡住、鎖範圍過大與資源未釋放等問題。</p>
]]></content:encoded></item><item><title>3.7 並行處理 - threading、multiprocessing、concurrent.futures</title><link>https://tarrragon.github.io/blog/python/03-stdlib/concurrency/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/03-stdlib/concurrency/</guid><description>&lt;p>Python 提供了多種並行處理的方式。本章介紹三個核心模組，幫助你根據任務特性選擇合適的方案。&lt;/p>
&lt;h2 id="為什麼需要並行處理">為什麼需要並行處理？&lt;/h2>
&lt;p>在實際開發中，我們常遇到需要同時處理多個任務的情況：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 情境 1：批次下載多個檔案（I/O 密集）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;https://example.com/file1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;https://example.com/file2&amp;#34;&lt;/span>&lt;span class="p">,&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="c1"># 一個一個下載太慢了！&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># 情境 2：處理大量資料（CPU 密集）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n">data_chunks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">chunk1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk3&lt;/span>&lt;span class="p">,&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">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 能不能同時處理多個資料區塊？&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>並行處理可以顯著提升這類任務的效率。&lt;/p>
&lt;h2 id="io-密集-vs-cpu-密集">I/O 密集 vs CPU 密集&lt;/h2>
&lt;p>在選擇並行方案之前，首先要判斷你的任務類型：&lt;/p>
&lt;h3 id="io-密集任務">I/O 密集任務&lt;/h3>
&lt;p>程式大部分時間在「等待」外部資源：&lt;/p>
&lt;ul>
&lt;li>網路請求（HTTP、API 呼叫）&lt;/li>
&lt;li>檔案讀寫&lt;/li>
&lt;li>資料庫查詢&lt;/li>
&lt;/ul>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># I/O 密集的特徵：大部分時間在等待&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_data&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="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 等待網路回應&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="cpu-密集任務">CPU 密集任務&lt;/h3>
&lt;p>程式大部分時間在「計算」：&lt;/p>
&lt;ul>
&lt;li>數學運算&lt;/li>
&lt;li>資料處理與轉換&lt;/li>
&lt;li>圖像處理&lt;/li>
&lt;/ul>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># CPU 密集的特徵：大部分時間在計算&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">compute_heavy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&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="k">return&lt;/span> &lt;span class="nb">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># 純計算&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="gil全域直譯器鎖">GIL（全域直譯器鎖）&lt;/h2>
&lt;p>在深入各模組之前，需要先了解 Python 的一個重要機制。&lt;/p>
&lt;h3 id="什麼是-gil">什麼是 GIL？&lt;/h3>
&lt;p>GIL（Global Interpreter Lock）是 CPython 直譯器的一個機制，它確保同一時間只有一個執行緒能執行 Python bytecode。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">┌─────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">│ Python 直譯器 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">│ ┌─────┐ ┌─────┐ ┌─────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">│ │執行緒1│ │執行緒2│ │執行緒3│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ └──┬──┘ └──┬──┘ └──┬──┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">│ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">│ └────────┼────────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ ▼ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ ┌───────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ │ GIL │ ← 同時只有一個能執行 │
&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="gil-的影響">GIL 的影響&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>任務類型&lt;/th>
 &lt;th>GIL 影響&lt;/th>
 &lt;th>原因&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>I/O 密集&lt;/td>
 &lt;td>影響小&lt;/td>
 &lt;td>等待 I/O 時會釋放 GIL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CPU 密集&lt;/td>
 &lt;td>影響大&lt;/td>
 &lt;td>多執行緒無法真正並行計算&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這就是為什麼：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>I/O 密集&lt;/strong>：使用 &lt;code>threading&lt;/code> 即可&lt;/li>
&lt;li>&lt;strong>CPU 密集&lt;/strong>：需要使用 &lt;code>multiprocessing&lt;/code> 繞過 GIL&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>：Python 3.13+ 推出了 Free-threading（無 GIL）版本，詳見 &lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&amp;#43; 無 GIL 版本的完整指南">3.8 Free-Threading&lt;/a>&lt;/p>&lt;/blockquote>
&lt;h2 id="threading-模組">threading 模組&lt;/h2>
&lt;p>&lt;code>threading&lt;/code> 模組提供執行緒級別的並行，適合 I/O 密集任務。&lt;/p>
&lt;h3 id="基本用法">基本用法&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">threading&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">worker&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">delay&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 開始工作&amp;#34;&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="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delay&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 模擬 I/O 等待&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 完成工作&amp;#34;&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>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">t1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">worker&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Worker-1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&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 class="n">t2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">threading&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Thread&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">worker&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">args&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Worker-2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 啟動執行緒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="n">t1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&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="n">t2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&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>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c1"># 等待執行緒完成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="n">t1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="n">t2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;所有工作完成&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="執行緒安全與-lock">執行緒安全與 Lock&lt;/h3>
&lt;p>當多個執行緒存取共享資源時，需要使用鎖來避免競爭條件：&lt;/p></description><content:encoded><![CDATA[<p>Python 提供了多種並行處理的方式。本章介紹三個核心模組，幫助你根據任務特性選擇合適的方案。</p>
<h2 id="為什麼需要並行處理">為什麼需要並行處理？</h2>
<p>在實際開發中，我們常遇到需要同時處理多個任務的情況：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 情境 1：批次下載多個檔案（I/O 密集）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;https://example.com/file1&#34;</span><span class="p">,</span> <span class="s2">&#34;https://example.com/file2&#34;</span><span class="p">,</span> <span class="o">...</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 一個一個下載太慢了！</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 情境 2：處理大量資料（CPU 密集）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">data_chunks</span> <span class="o">=</span> <span class="p">[</span><span class="n">chunk1</span><span class="p">,</span> <span class="n">chunk2</span><span class="p">,</span> <span class="n">chunk3</span><span class="p">,</span> <span class="o">...</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 能不能同時處理多個資料區塊？</span></span></span></code></pre></div><p>並行處理可以顯著提升這類任務的效率。</p>
<h2 id="io-密集-vs-cpu-密集">I/O 密集 vs CPU 密集</h2>
<p>在選擇並行方案之前，首先要判斷你的任務類型：</p>
<h3 id="io-密集任務">I/O 密集任務</h3>
<p>程式大部分時間在「等待」外部資源：</p>
<ul>
<li>網路請求（HTTP、API 呼叫）</li>
<li>檔案讀寫</li>
<li>資料庫查詢</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># I/O 密集的特徵：大部分時間在等待</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">fetch_data</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>  <span class="c1"># 等待網路回應</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span></span></span></code></pre></div><h3 id="cpu-密集任務">CPU 密集任務</h3>
<p>程式大部分時間在「計算」：</p>
<ul>
<li>數學運算</li>
<li>資料處理與轉換</li>
<li>圖像處理</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># CPU 密集的特徵：大部分時間在計算</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">compute_heavy</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>  <span class="c1"># 純計算</span></span></span></code></pre></div><h2 id="gil全域直譯器鎖">GIL（全域直譯器鎖）</h2>
<p>在深入各模組之前，需要先了解 Python 的一個重要機制。</p>
<h3 id="什麼是-gil">什麼是 GIL？</h3>
<p>GIL（Global Interpreter Lock）是 CPython 直譯器的一個機制，它確保同一時間只有一個執行緒能執行 Python bytecode。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">┌─────────────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">│              Python 直譯器                │
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│  ┌─────┐  ┌─────┐  ┌─────┐              │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│  │執行緒1│  │執行緒2│  │執行緒3│              │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│  └──┬──┘  └──┬──┘  └──┬──┘              │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│     │        │        │                 │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│     └────────┼────────┘                 │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│              ▼                          │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│         ┌───────┐                       │
</span></span><span class="line"><span class="ln">10</span><span class="cl">│         │  GIL  │ ← 同時只有一個能執行      │
</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></span></code></pre></div><h3 id="gil-的影響">GIL 的影響</h3>
<table>
  <thead>
      <tr>
          <th>任務類型</th>
          <th>GIL 影響</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>I/O 密集</td>
          <td>影響小</td>
          <td>等待 I/O 時會釋放 GIL</td>
      </tr>
      <tr>
          <td>CPU 密集</td>
          <td>影響大</td>
          <td>多執行緒無法真正並行計算</td>
      </tr>
  </tbody>
</table>
<p>這就是為什麼：</p>
<ul>
<li><strong>I/O 密集</strong>：使用 <code>threading</code> 即可</li>
<li><strong>CPU 密集</strong>：需要使用 <code>multiprocessing</code> 繞過 GIL</li>
</ul>
<blockquote>
<p><strong>注意</strong>：Python 3.13+ 推出了 Free-threading（無 GIL）版本，詳見 <a href="/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&#43; 無 GIL 版本的完整指南">3.8 Free-Threading</a></p></blockquote>
<h2 id="threading-模組">threading 模組</h2>
<p><code>threading</code> 模組提供執行緒級別的並行，適合 I/O 密集任務。</p>
<h3 id="基本用法">基本用法</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">delay</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> 開始工作&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span><span class="p">)</span>  <span class="c1"># 模擬 I/O 等待</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> 完成工作&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 建立執行緒</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">t1</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;Worker-1&#34;</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">t2</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;Worker-2&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 啟動執行緒</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">t1</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">t2</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 等待執行緒完成</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">t1</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">t2</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;所有工作完成&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="執行緒安全與-lock">執行緒安全與 Lock</h3>
<p>當多個執行緒存取共享資源時，需要使用鎖來避免競爭條件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">threading</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="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">lock</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">increment</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">global</span> <span class="n">counter</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">with</span> <span class="n">lock</span><span class="p">:</span>  <span class="c1"># 使用 context manager 自動獲取和釋放鎖</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</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="c1"># 建立多個執行緒</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">threads</span> <span class="o">=</span> <span class="p">[</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">increment</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">threads</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">t</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Counter: </span><span class="si">{</span><span class="n">counter</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># 應該是 500000</span></span></span></code></pre></div><h3 id="何時使用-threading">何時使用 threading</h3>
<ul>
<li>網路請求（HTTP、API）</li>
<li>檔案讀寫</li>
<li>資料庫操作</li>
<li>任何需要等待外部資源的任務</li>
</ul>
<h2 id="multiprocessing-模組">multiprocessing 模組</h2>
<p><code>multiprocessing</code> 模組使用多個進程來實現真正的並行，繞過 GIL 限制。</p>
<h3 id="基本用法-1">基本用法</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">multiprocessing</span> <span class="kn">import</span> <span class="n">Process</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="k">def</span> <span class="nf">cpu_intensive</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;CPU 密集計算&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;計算完成: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>  <span class="c1"># 在 Windows 上必須使用這個保護</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">processes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">cpu_intensive</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="mi">10_000_000</span><span class="p">,))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">processes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">p</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">processes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">p</span><span class="o">.</span><span class="n">join</span><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="nb">print</span><span class="p">(</span><span class="s2">&#34;所有計算完成&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="進程間通訊">進程間通訊</h3>
<p>進程之間不共享記憶體，需要使用 Queue 或 Pipe 來通訊：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">multiprocessing</span> <span class="kn">import</span> <span class="n">Process</span><span class="p">,</span> <span class="n">Queue</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="k">def</span> <span class="nf">worker</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">queue</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>  <span class="c1"># 將結果放入佇列</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">queue</span> <span class="o">=</span> <span class="n">Queue</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">processes</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="mi">5_000_000</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">processes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">p</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">processes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">p</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 收集結果</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[</span><span class="n">queue</span><span class="o">.</span><span class="n">get</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;結果: </span><span class="si">{</span><span class="n">results</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="何時使用-multiprocessing">何時使用 multiprocessing</h3>
<ul>
<li>CPU 密集計算</li>
<li>資料處理與轉換</li>
<li>需要真正並行執行的任務</li>
</ul>
<h2 id="concurrentfutures推薦入門">concurrent.futures（推薦入門）</h2>
<p><code>concurrent.futures</code> 提供了更高階、更簡潔的 API，統一了執行緒和進程的使用方式。</p>
<h3 id="threadpoolexecutor">ThreadPoolExecutor</h3>
<p>適合 I/O 密集任務：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">urllib.request</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">fetch_url</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;下載網頁並返回大小&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">with</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">return</span> <span class="n">url</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="n">url</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</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="n">urls</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;https://www.python.org&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;https://docs.python.org&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;https://pypi.org&#34;</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></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 使用執行緒池並行下載</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fetch_url</span><span class="p">,</span> <span class="n">urls</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="k">for</span> <span class="n">url</span><span class="p">,</span> <span class="n">size</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">url</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">size</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="processpoolexecutor">ProcessPoolExecutor</h3>
<p>適合 CPU 密集任務：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ProcessPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</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="k">def</span> <span class="nf">compute_heavy</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;CPU 密集計算&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="n">n</span><span class="p">,</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">numbers</span> <span class="o">=</span> <span class="p">[</span><span class="mi">10_000_000</span><span class="p">,</span> <span class="mi">20_000_000</span><span class="p">,</span> <span class="mi">15_000_000</span><span class="p">,</span> <span class="mi">5_000_000</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">with</span> <span class="n">ProcessPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 方法 1：使用 map（保持順序）</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">compute_heavy</span><span class="p">,</span> <span class="n">numbers</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="c1"># 方法 2：使用 submit + as_completed（先完成先處理）</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span><span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">compute_heavy</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span> <span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">numbers</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">n</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;n=</span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="處理異常">處理異常</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</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="k">def</span> <span class="nf">risky_task</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;不喜歡 3！&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="n">n</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span><span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">risky_task</span><span class="p">,</span> <span class="n">i</span><span class="p">):</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">n</span> <span class="o">=</span> <span class="n">futures</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;任務 </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2"> 完成: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;任務 </span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s2"> 失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="選擇指南">選擇指南</h2>
<table>
  <thead>
      <tr>
          <th>任務類型</th>
          <th>推薦方案</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>I/O 密集</td>
          <td><code>ThreadPoolExecutor</code></td>
          <td>輕量、共享記憶體、GIL 影響小</td>
      </tr>
      <tr>
          <td>CPU 密集</td>
          <td><code>ProcessPoolExecutor</code></td>
          <td>繞過 GIL、真正並行</td>
      </tr>
      <tr>
          <td>需要細控制</td>
          <td><code>threading</code>/<code>multiprocessing</code></td>
          <td>底層 API、更多控制</td>
      </tr>
      <tr>
          <td>Python 3.14+ CPU 密集</td>
          <td><code>threading</code> + Free-threading</td>
          <td>真正的多執行緒並行</td>
      </tr>
  </tbody>
</table>
<h3 id="決策流程">決策流程</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">任務類型是什麼？
</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">    ├─→ I/O 密集（網路、檔案、DB）
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    │       │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    │       └─→ 使用 ThreadPoolExecutor
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    └─→ CPU 密集（計算、處理）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            ├─→ Python 3.14+ Free-threaded
</span></span><span class="line"><span class="ln">10</span><span class="cl">            │       │
</span></span><span class="line"><span class="ln">11</span><span class="cl">            │       └─→ 可以使用 threading
</span></span><span class="line"><span class="ln">12</span><span class="cl">            │
</span></span><span class="line"><span class="ln">13</span><span class="cl">            └─→ 傳統 Python
</span></span><span class="line"><span class="ln">14</span><span class="cl">                    │
</span></span><span class="line"><span class="ln">15</span><span class="cl">                    └─→ 使用 ProcessPoolExecutor</span></span></code></pre></div><h2 id="常見陷阱與最佳實踐">常見陷阱與最佳實踐</h2>
<h3 id="1-設定合理的-worker-數量">1. 設定合理的 worker 數量</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</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="c1"># I/O 密集：可以設定較多的 worker</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">io_workers</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># CPU 密集：不要超過 CPU 核心數</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">cpu_workers</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span></span></span></code></pre></div><h3 id="2-避免共享可變狀態">2. 避免共享可變狀態</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好：共享可變狀態</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">bad_worker</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">n</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>  <span class="c1"># 危險！多執行緒存取</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 好：返回結果，由主執行緒收集</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">good_worker</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">n</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">good_worker</span><span class="p">,</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)))</span></span></span></code></pre></div><h3 id="3-使用-context-manager">3. 使用 context manager</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 好：使用 with 語句自動管理資源</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">task</span><span class="p">,</span> <span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 不好：手動管理</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">results</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">task</span><span class="p">,</span> <span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="n">executor</span><span class="o">.</span><span class="n">shutdown</span><span class="p">(</span><span class="n">wait</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>  <span class="c1"># 容易忘記</span></span></span></code></pre></div><h3 id="4-multiprocessing-的-if-__name__--__main__-保護">4. multiprocessing 的 <code>if __name__ == &quot;__main__&quot;</code> 保護</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">multiprocessing</span> <span class="kn">import</span> <span class="n">Process</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="k">def</span> <span class="nf">worker</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Working...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># Windows 上必須使用這個保護，否則會無限遞迴</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">p</span> <span class="o">=</span> <span class="n">Process</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">worker</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">p</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">p</span><span class="o">.</span><span class="n">join</span><span class="p">()</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 I/O 密集任務使用 <code>threading</code> 就夠了，而 CPU 密集任務需要 <code>multiprocessing</code>？</li>
<li><code>ThreadPoolExecutor</code> 和手動建立 <code>Thread</code> 有什麼優缺點？</li>
<li>在什麼情況下，並行處理反而會比序列處理更慢？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>寫一個函式，使用 <code>ThreadPoolExecutor</code> 同時檢查多個網址是否可以連線</li>
<li>使用 <code>ProcessPoolExecutor</code> 計算一組大數字的質因數分解</li>
<li>實作一個進度顯示器，顯示多個任務的完成進度</li>
</ol>
<h2 id="延伸閱讀進階系列">延伸閱讀（進階系列）</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">實戰效能優化：並行處理</a> - 真實案例的並行化改造</li>
<li><a href="/blog/python-advanced/01-asyncio/" data-link-title="模組一：非同步程式設計（asyncio）" data-link-desc="Python 的異步程式設計模型，掌握現代 Web/網路開發的必備技能">asyncio 非同步程式設計</a> - 學習協程與事件迴圈</li>
<li><a href="/blog/python-advanced/04-cpython-internals/gil-threading/" data-link-title="3.4 GIL 與執行緒模型" data-link-desc="深入理解 GIL 的設計與實現">GIL 與執行緒模型</a> - 深入理解 GIL 的設計與實現</li>
<li><a href="/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&#43; 無 GIL 版本的完整指南">Free-Threading</a> - Python 3.13+ 無 GIL 多執行緒</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python/03-stdlib/argparse/" data-link-title="3.6 argparse - CLI 介面" data-link-desc="命令列參數解析">argparse - CLI 介面</a></em>
<em>下一章：<a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">效能迷思與優化策略</a></em></p>
]]></content:encoded></item><item><title>9.8 效能可觀測性</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-observability/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-observability/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>效能可觀測性的責任是讓容量決策有訊號基礎。沒有適當訊號時、就算有壓測結果跟容量計畫、也看不到「現在實際距離 saturation 多遠」、無法做即時調整。&lt;/p>
&lt;p>跟 &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> 的關係：9.4 找到 saturation 點、9.8 定義持續監控這個點的訊號跟 dashboard。跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 是 sibling — 04 處理通用觀測、9.8 處理 &lt;em>容量規劃用&lt;/em> 的觀測。&lt;/p>
&lt;p>本章不重複 04 的訊號治理基礎、聚焦在 &lt;em>容量 / 效能 / 成本三條觀測線怎麼整合&lt;/em>。讀完後讀者能設計一個「容量 dashboard」、回答「現在距離 saturation 還有多遠、什麼時候該擴」。&lt;/p>
&lt;h2 id="use-method-在-production-持續監控">USE method 在 production 持續監控&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/use-method/" data-link-title="USE Method" data-link-desc="Brendan Gregg 提出的資源層 Utilization / Saturation / Errors 三維度量測法">USE method&lt;/a> 不只是壓測時用、production 也要持續監控。&lt;/p>
&lt;p>對每個資源（CPU / RAM / disk / network / DB connection / cache pool / file descriptor）量三個維度：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Utilization&lt;/strong>（使用率 0-100%）：直觀但會誤判&lt;/li>
&lt;li>&lt;strong>Saturation&lt;/strong>（queue depth）：早期警訊&lt;/li>
&lt;li>&lt;strong>Errors&lt;/strong>（資源層錯誤）：已經出事的訊號&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>為什麼不能只看 utilization&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>CPU 100% 但 run queue 空 → 還能撐（單純 CPU bound）&lt;/li>
&lt;li>CPU 80% 但 run queue 不斷增長 → 已 saturate（saturation 比 utilization 領先）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Saturation metric 是 capacity warning 的最早訊號&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>queue depth（每個 queue / pool）&lt;/li>
&lt;li>connection pool 使用率（最常見隱性 bottleneck）&lt;/li>
&lt;li>thread pool / coroutine count&lt;/li>
&lt;li>event loop lag（Node.js、async runtime）&lt;/li>
&lt;li>GC pause time / frequency&lt;/li>
&lt;li>cache hit rate / eviction rate&lt;/li>
&lt;li>replication lag&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Dashboard 設計&lt;/strong>：每個關鍵資源獨立 panel、同時顯示 utilization 跟 saturation。alert 在 &lt;em>saturation 起飛&lt;/em> 時觸發、不是 utilization 滿。&lt;/p>
&lt;p>對應案例：&lt;a href="https://tarrragon.github.io/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 &amp;#43; AWS Media Services 撐 30 channels live &amp;#43; 5M MAU、工程工時下降 90%">Lemino connection limit&lt;/a> — connection saturation 是 RDB 的真正 bottleneck、不是 CPU；&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">Zomato latency 降 90%&lt;/a> — 從 TiDB 換到 DynamoDB、saturation 行為完全不同、observability 也要跟著改。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>效能可觀測性的責任是讓容量決策有訊號基礎。沒有適當訊號時、就算有壓測結果跟容量計畫、也看不到「現在實際距離 saturation 多遠」、無法做即時調整。</p>
<p>跟 <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> 的關係：9.4 找到 saturation 點、9.8 定義持續監控這個點的訊號跟 dashboard。跟 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 是 sibling — 04 處理通用觀測、9.8 處理 <em>容量規劃用</em> 的觀測。</p>
<p>本章不重複 04 的訊號治理基礎、聚焦在 <em>容量 / 效能 / 成本三條觀測線怎麼整合</em>。讀完後讀者能設計一個「容量 dashboard」、回答「現在距離 saturation 還有多遠、什麼時候該擴」。</p>
<h2 id="use-method-在-production-持續監控">USE method 在 production 持續監控</h2>
<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> 不只是壓測時用、production 也要持續監控。</p>
<p>對每個資源（CPU / RAM / disk / network / DB connection / cache pool / file descriptor）量三個維度：</p>
<ul>
<li><strong>Utilization</strong>（使用率 0-100%）：直觀但會誤判</li>
<li><strong>Saturation</strong>（queue depth）：早期警訊</li>
<li><strong>Errors</strong>（資源層錯誤）：已經出事的訊號</li>
</ul>
<p><strong>為什麼不能只看 utilization</strong>：</p>
<ul>
<li>CPU 100% 但 run queue 空 → 還能撐（單純 CPU bound）</li>
<li>CPU 80% 但 run queue 不斷增長 → 已 saturate（saturation 比 utilization 領先）</li>
</ul>
<p><strong>Saturation metric 是 capacity warning 的最早訊號</strong>：</p>
<ul>
<li>queue depth（每個 queue / pool）</li>
<li>connection pool 使用率（最常見隱性 bottleneck）</li>
<li>thread pool / coroutine count</li>
<li>event loop lag（Node.js、async runtime）</li>
<li>GC pause time / frequency</li>
<li>cache hit rate / eviction rate</li>
<li>replication lag</li>
</ul>
<p><strong>Dashboard 設計</strong>：每個關鍵資源獨立 panel、同時顯示 utilization 跟 saturation。alert 在 <em>saturation 起飛</em> 時觸發、不是 utilization 滿。</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 connection limit</a> — connection saturation 是 RDB 的真正 bottleneck、不是 CPU；<a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">Zomato latency 降 90%</a> — 從 TiDB 換到 DynamoDB、saturation 行為完全不同、observability 也要跟著改。</p>
<h2 id="red-method請求層的容量訊號">RED method：請求層的容量訊號</h2>
<p><a href="/blog/backend/knowledge-cards/red-method/" data-link-title="RED Method" data-link-desc="Tom Wilkie 提出的請求層 Rate / Errors / Duration 三維度量測法">RED method</a> 跟 USE 互補、從請求層看容量。</p>
<ul>
<li><strong>Rate</strong>：requests per second（每個 service / endpoint）</li>
<li><strong>Errors</strong>：error rate</li>
<li><strong>Duration</strong>：latency distribution（histogram、不是單一 percentile）</li>
</ul>
<p><strong>Duration 比 Errors 早</strong>：duration p99 飆通常先於 error rate 上升、是 saturation 的早期警訊。</p>
<p><strong>每個 endpoint 都要有 RED</strong>：不能只看全站 average、要分 endpoint。登入 endpoint 跟結帳 endpoint 的 saturation 行為不同、混在一起看不到 issue。</p>
<p><strong>Histogram 是必須、不是 nice-to-have</strong>：</p>
<ul>
<li>只記 p99 → 看不到 p999、看不到 distribution shape</li>
<li>記 histogram → 可以隨時算任何 percentile、可以做 long-tail 分析</li>
<li>Prometheus histogram、OpenMetrics histogram 是現代標準</li>
</ul>
<p>對應案例：<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 體育博彩峰值流量">GR8 Tech 25ms p95</a> — p95 是業務 KPI、不是技術指標、每個 endpoint 都有獨立 SLO。</p>
<h2 id="p50--p95--p99--p999-的取捨">p50 / p95 / p99 / p999 的取捨</h2>
<p>不同 percentile 反映不同問題、選錯 percentile 會錯失 issue。</p>
<ul>
<li><strong>p50（中位數）</strong>：整體狀況、感覺正常的指標、對長尾不敏感</li>
<li><strong>p95</strong>：日常 user-perceived experience、大多數用戶感受到的延遲</li>
<li><strong>p99</strong>：minority but critical 用戶體驗、SLO 常訂在這</li>
<li><strong>p999</strong>：極端長尾、受 GC pause / leader election / retry storm 影響、internal critical 系統訂在這</li>
</ul>
<p><strong>業務 SLO 通常訂 p99</strong>：「99% 用戶 request &lt; 500ms」是常見承諾、合約 SLA 也通常基於 p99。
<strong>Internal critical 系統訂 p99.9</strong>：金融交易、即時配對、客服 SaaS（5 個 9 可用性對應 5 個 9 latency 期待）。</p>
<p><strong>紀錄分布、不只紀錄 percentile</strong>：</p>
<ul>
<li>gauge p99 → 看不到 distribution shape、看不到 multimodal 分布</li>
<li>histogram → 可以重新計算任何 percentile、可以對比 distribution、可以找 anomaly</li>
</ul>
<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 inference 在 p99 才能控制用戶體驗、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> — 必須關注 p999、RAFT 系統長尾顯著。</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="cost-dashboard">Cost dashboard</h2>
<p>成本訊號跟容量訊號要 <em>並列顯示</em>、不要分開看。</p>
<p><strong>Per-service / per-endpoint cost attribution</strong>：</p>
<ul>
<li>每個 service 自己的雲端成本</li>
<li>拆到每個 endpoint</li>
<li>跟 RPS / latency 並列、看「成本上升是因為流量還是低效」</li>
</ul>
<p><strong>Cost per request 的時序變化</strong>：</p>
<ul>
<li>突然上升通常是 <em>退化</em> 訊號（新版本沒效率）</li>
<li>緩慢上升通常是 <em>規模</em> 訊號（用戶增加但 efficiency 沒變）</li>
</ul>
<p><strong>成本異常告警（vs 容量異常告警）</strong>：</p>
<ul>
<li>容量告警：utilization &gt; X% → 擴容</li>
<li>成本告警：cost spike &gt; X% → review</li>
<li>兩者可能同時觸發（autoscaler 擴容也擴 cost）、要區分</li>
</ul>
<p><strong>跟業務 metric 對齊</strong>：cost per active user、cost per transaction、cost per ML inference。業務 metric 級別的 cost 才能 review unit economics。</p>
<p>對應案例：<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; 城市">Lyft 100+ 微服務各自 cost</a> — 微服務粒度的 cost attribution、找出哪個 service 過貴；對應 <a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04.14 cost attribution</a>。</p>
<h2 id="continuous-profiling">Continuous profiling</h2>
<p><a href="/blog/backend/knowledge-cards/continuous-profiling/" data-link-title="Continuous Profiling" data-link-desc="在 production 持續取得低 overhead profile 的觀察方法">Continuous profiling</a> 是現代效能 observability 的關鍵環節 — production 持續取 profile（CPU / heap / lock）、隨時可以做 diff 跟 root cause。</p>
<p><strong>工具生態</strong>：</p>
<ul>
<li>Datadog Continuous Profiler、Pyroscope（開源 + Grafana 整合）、Parca（CNCF）</li>
<li>GCP Cloud Profiler、Azure Application Insights Profiler、AWS CodeGuru Profiler</li>
<li>Overhead 通常 &lt; 1% CPU、放心開在 production</li>
</ul>
<p><strong>跟 distributed tracing 整合</strong>：trace → span → profile。一個 slow request 點下去、能看到對應 span、再下去看 profile。</p>
<p><strong>Profile diff 是 release gate 的核心訊號</strong>：每次 deploy 後自動對比 baseline、退化幅度過門檻 trigger alert。詳見 <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> 跟 <a href="/blog/backend/knowledge-cards/profile-diff/" data-link-title="Profile Diff" data-link-desc="對比兩次 profile（如 release candidate vs baseline）找出 hottest 變化">Profile Diff 卡片</a>。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">Netflix 多 DB 統一後 profile 變單純</a> — DB 統一 → application 層 profile 噪音降低 → 退化定位更快。</p>
<h2 id="cardinality-cost-governance">Cardinality cost governance</h2>
<p>效能 observability 的成本經常爆炸、源頭通常是 high cardinality metric。</p>
<p><strong>高 cardinality 來源</strong>：</p>
<ul>
<li>per-user metric（user_id label）</li>
<li>per-request metric（request_id label）</li>
<li>per-trace metric（trace_id label）</li>
</ul>
<p><strong>為什麼會爆</strong>：Prometheus 等 metric system 為每個 label 組合存獨立 time series、cardinality = 所有 label value 的笛卡爾積。100 萬 user × 100 endpoint × 10 region = 10 億 time series、儲存爆炸。</p>
<p><strong>對策</strong>：</p>
<ul>
<li>high cardinality 資訊放 log / trace、不放 metric</li>
<li>metric label 限制在 low-cardinality 維度（service、endpoint、region、status）</li>
<li>真的需要 high-cardinality 分析、用 sampled trace + log query</li>
</ul>
<p>對應 <a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">04.10 cardinality cost governance</a>、跟 <a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">Metric Cardinality 卡片</a>。</p>
<h2 id="訊號跟-slo-對接">訊號跟 SLO 對接</h2>
<p>最後一層整合：每個 saturation metric 都要對應一個 SLO threshold、訊號驅動行動。</p>
<p><strong>訊號 → 行動鏈</strong>：</p>
<ul>
<li>saturation metric 超 threshold → trigger alert</li>
<li>alert 觸發 → trigger autoscaler / runbook / oncall</li>
<li>持續超 threshold → trigger <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> burn alert</li>
<li>error budget 用完 → trigger release freeze</li>
</ul>
<p><strong>Alert 不要太敏感</strong>：</p>
<ul>
<li>false positive 浪費 oncall、長期會 alert fatigue（<a href="/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">Alert Fatigue 卡片</a>）</li>
<li>用 multi-window multi-burn-rate alert（Google SRE 推薦）</li>
<li>用 symptom-based alert（業務影響）而非 cause-based alert（單一資源）</li>
</ul>
<p>跟 <a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</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/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 99.999%</a></td>
          <td>SLO 5 個 9 的訊號治理</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys 12 個月 99.999%</a></td>
          <td>滾動 SLO 觀測</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 p99 分解</a></td>
          <td>ML inference 多 stage latency budget</td>
      </tr>
      <tr>
          <td><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 p95 是業務 KPI</a></td>
          <td>latency 不只是技術指標</td>
      </tr>
  </tbody>
</table>
<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> / <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/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a></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/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/red-method/" data-link-title="RED Method" data-link-desc="Tom Wilkie 提出的請求層 Rate / Errors / Duration 三維度量測法">RED 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/continuous-profiling/" data-link-title="Continuous Profiling" data-link-desc="在 production 持續取得低 overhead profile 的觀察方法">Continuous Profiling</a></li>
<li><a href="/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">Cost Per Request</a></li>
</ul>
]]></content:encoded></item><item><title>9.C8 Niantic Pokémon GO：在 GCP 上承載 50 倍突發流量</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/niantic-pokemon-go-fifty-x-surge-gcp/</guid><description>&lt;p>這個案例的核心責任是說明「surge load」（突發遠超預期）跟 event-peak（事件型可預測峰值）的差異。Pokémon GO 在 2016-07 上線時、實際流量達到原始容量規劃目標的 50 倍 — 根因是 &lt;em>根本沒人能預測這個產品會這麼紅&lt;/em>、峰值規劃方法論本身沒有失敗。這類負載對容量設計的要求跟其他案例本質不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Niantic Pokémon GO 在 GCP 上的關鍵敘述（引自 &lt;a href="https://cloud.google.com/blog/products/gcp/bringing-pokemon-go-to-life-on-google-cloud">Bringing Pokémon GO to life on Google Cloud&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>實際流量&lt;/td>
 &lt;td>達到原始 target 的 50 倍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>應用層&lt;/td>
 &lt;td>Google Container Engine (GKE)&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容器編排&lt;/td>
 &lt;td>Kubernetes（planetary-scale 設計）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量支援&lt;/td>
 &lt;td>Google CRE 即時擴容&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「Niantic chose GKE for its ability to orchestrate container clusters at planetary-scale」「Google CRE seamlessly provisioned extra capacity on behalf of Niantic to stay ahead of their record-setting growth」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這個案例最重要的判讀是「surge load 跟可預測峰值是不同問題」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>50x surge 沒辦法事前規劃&lt;/strong>：任何合理的 capacity planning 都不會預留 50x headroom — 那會讓平日成本爆炸。surge 的工程做法不是「事前撐住」、是「事中快速補上」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 事故處理模組&lt;/a> 的事件管理。&lt;/li>
&lt;li>&lt;strong>CRE 不是技術、是 vendor 關係&lt;/strong>：Google Customer Reliability Engineering 是 GCP 提供給戰略客戶的 24/7 工程支援團隊。能即時為 Niantic 補容量靠的是 &lt;em>人 + 流程 + 工具&lt;/em> 的組合、不是純技術。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/operations-control-service-selection/" data-link-title="0.12 觀測、可靠性與事故服務選型" data-link-desc="從訊號、驗證與響應三層能力判斷操作控制服務的選型順序">00.6 操作控制服務選型&lt;/a> 的廠商支援能力評估。&lt;/li>
&lt;li>&lt;strong>Kubernetes 是 surge 的前置條件&lt;/strong>：如果 Niantic 用 VM-based 架構、即使 CRE 想補容量也來不及 boot up。Container orchestrator 把 provisioning 時間從分鐘級降到秒級、才讓 surge 反應變得可能。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 platform 選型。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「Google CRE 即時補容量」這種敘述對中小客戶不適用。一般客戶在 surge 下能依賴的是 &lt;em>自己的 autoscaler&lt;/em>、不是 vendor 工程師。設計 surge 對應策略時要假設「沒有 vendor 救援」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>接受 surge 不可避免、設計快速 onboard 流程&lt;/strong>：核心問題不是「會不會 surge」、是「surge 之後 24 小時內能不能撐住」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">08.8 incident communication&lt;/a>。&lt;/li>
&lt;li>&lt;strong>降級機制作為 surge 救命稻草&lt;/strong>：當容量不足時、優先保住核心功能、暫時關閉非核心。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02.3 cache stampede&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 high concurrency access&lt;/a> 的降級設計。&lt;/li>
&lt;li>&lt;strong>預先談好 vendor 緊急支援條款&lt;/strong>：戰略服務在簽約時就要談好 surge 期間的容量配額、限流豁免、CRE / TAM 支援、不要等出事才談。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 vendor relationship 設計。&lt;/li>
&lt;li>&lt;strong>container-first 是 surge 反應的前置&lt;/strong>：VM-based 架構在 surge 下擴容速度比 container 慢一個量級、會直接成為 bottleneck。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS Enterprise Support + TAM、Azure Premier Support + CSAM 都有對等服務、但能即時動用工程師補容量的程度跟客戶等級綁定。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「surge load」（突發遠超預期）跟 event-peak（事件型可預測峰值）的差異。Pokémon GO 在 2016-07 上線時、實際流量達到原始容量規劃目標的 50 倍 — 根因是 <em>根本沒人能預測這個產品會這麼紅</em>、峰值規劃方法論本身沒有失敗。這類負載對容量設計的要求跟其他案例本質不同。</p>
<h2 id="觀察">觀察</h2>
<p>Niantic Pokémon GO 在 GCP 上的關鍵敘述（引自 <a href="https://cloud.google.com/blog/products/gcp/bringing-pokemon-go-to-life-on-google-cloud">Bringing Pokémon GO to life on Google Cloud</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>實際流量</td>
          <td>達到原始 target 的 50 倍</td>
      </tr>
      <tr>
          <td>應用層</td>
          <td>Google Container Engine (GKE)</td>
      </tr>
      <tr>
          <td>容器編排</td>
          <td>Kubernetes（planetary-scale 設計）</td>
      </tr>
      <tr>
          <td>容量支援</td>
          <td>Google CRE 即時擴容</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「Niantic chose GKE for its ability to orchestrate container clusters at planetary-scale」「Google CRE seamlessly provisioned extra capacity on behalf of Niantic to stay ahead of their record-setting growth」。</p>
<h2 id="判讀">判讀</h2>
<p>這個案例最重要的判讀是「surge load 跟可預測峰值是不同問題」。</p>
<ol>
<li><strong>50x surge 沒辦法事前規劃</strong>：任何合理的 capacity planning 都不會預留 50x headroom — 那會讓平日成本爆炸。surge 的工程做法不是「事前撐住」、是「事中快速補上」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 跟 <a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 事故處理模組</a> 的事件管理。</li>
<li><strong>CRE 不是技術、是 vendor 關係</strong>：Google Customer Reliability Engineering 是 GCP 提供給戰略客戶的 24/7 工程支援團隊。能即時為 Niantic 補容量靠的是 <em>人 + 流程 + 工具</em> 的組合、不是純技術。對應 <a href="/blog/backend/00-service-selection/operations-control-service-selection/" data-link-title="0.12 觀測、可靠性與事故服務選型" data-link-desc="從訊號、驗證與響應三層能力判斷操作控制服務的選型順序">00.6 操作控制服務選型</a> 的廠商支援能力評估。</li>
<li><strong>Kubernetes 是 surge 的前置條件</strong>：如果 Niantic 用 VM-based 架構、即使 CRE 想補容量也來不及 boot up。Container orchestrator 把 provisioning 時間從分鐘級降到秒級、才讓 surge 反應變得可能。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 platform 選型。</li>
</ol>
<p>需要警惕：「Google CRE 即時補容量」這種敘述對中小客戶不適用。一般客戶在 surge 下能依賴的是 <em>自己的 autoscaler</em>、不是 vendor 工程師。設計 surge 對應策略時要假設「沒有 vendor 救援」。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>接受 surge 不可避免、設計快速 onboard 流程</strong>：核心問題不是「會不會 surge」、是「surge 之後 24 小時內能不能撐住」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 跟 <a href="/blog/backend/08-incident-response/incident-communication/" data-link-title="8.4 事故通訊與狀態更新" data-link-desc="建立內外部通報節奏與狀態更新格式">08.8 incident communication</a>。</li>
<li><strong>降級機制作為 surge 救命稻草</strong>：當容量不足時、優先保住核心功能、暫時關閉非核心。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02.3 cache stampede</a> 跟 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 high concurrency access</a> 的降級設計。</li>
<li><strong>預先談好 vendor 緊急支援條款</strong>：戰略服務在簽約時就要談好 surge 期間的容量配額、限流豁免、CRE / TAM 支援、不要等出事才談。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 vendor relationship 設計。</li>
<li><strong>container-first 是 surge 反應的前置</strong>：VM-based 架構在 surge 下擴容速度比 container 慢一個量級、會直接成為 bottleneck。</li>
</ol>
<p>跨平台等效：AWS Enterprise Support + TAM、Azure Premier Support + CSAM 都有對等服務、但能即時動用工程師補容量的程度跟客戶等級綁定。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想對應 surge load → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">08.6 incident severity trigger</a></li>
<li>想設計降級策略 → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 high concurrency access</a> + <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a></li>
<li>想評估 vendor 支援 → <a href="/blog/backend/00-service-selection/operations-control-service-selection/" data-link-title="0.12 觀測、可靠性與事故服務選型" data-link-desc="從訊號、驗證與響應三層能力判斷操作控制服務的選型順序">00.6 operations control service selection</a></li>
<li>對照可預測峰值案例 → <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 AWS Prime Day</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/gcp/bringing-pokemon-go-to-life-on-google-cloud">Bringing Pokémon GO to life on Google Cloud</a></li>
<li><a href="https://cloud.google.com/customer-reliability-engineering">Google Customer Reliability Engineering</a></li>
</ul>
]]></content:encoded></item><item><title>6.8 高併發下的 Redis 與 SQL 使用原則</title><link>https://tarrragon.github.io/blog/go/06-practical/data-access-boundaries/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/06-practical/data-access-boundaries/</guid><description>&lt;p>這一章從 Go 服務的角度整理資料存取原則。重點在於：當併發增加時，Go 端要用明確邊界使用 Redis 或 SQL，讓下游維持可承受的請求節奏。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>理解高併發下最常見的資料存取風險&lt;/li>
&lt;li>區分 Redis 與 SQL 各自適合的角色&lt;/li>
&lt;li>用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a> 與批次策略控制壓力&lt;/li>
&lt;li>避免 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/cache-stampede/" data-link-title="Cache Stampede" data-link-desc="說明快取同時失效時大量 request 如何壓垮正式來源">cache stampede&lt;/a> 與慢查詢連鎖&lt;/li>
&lt;li>在 Go 服務內設計可控的下游存取邊界&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="觀察go-端要先控住請求節奏">【觀察】Go 端要先控住請求節奏&lt;/h2>
&lt;p>高併發時，資料存取風險通常來自請求節奏超過下游承受能力。你可以有很多 goroutine，但 Redis 與 SQL 不會因為 goroutine 多就自動變快。&lt;/p>
&lt;p>Go 端通常要先做的是：&lt;/p>
&lt;ul>
&lt;li>限制同時對下游發出的請求數&lt;/li>
&lt;li>設定明確 timeout&lt;/li>
&lt;li>避免無限 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/fan-out/" data-link-title="Fan-out" data-link-desc="說明單一事件同時分發給多個下游的訊息拓撲">fan-out&lt;/a>&lt;/li>
&lt;li>在壓力過高時拒絕新工作&lt;/li>
&lt;/ul>
&lt;h2 id="判讀redis-適合快取狀態與短生命週期資料">【判讀】Redis 適合快取、狀態與短生命週期資料&lt;/h2>
&lt;p>Redis 在 Go 服務裡常見用途包括：&lt;/p>
&lt;ul>
&lt;li>cache&lt;/li>
&lt;li>session&lt;/li>
&lt;li>counter&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rate-limit/" data-link-title="Rate Limit" data-link-desc="說明限流如何保護服務入口、下游依賴與租戶公平性">rate limit&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency&lt;/a> key&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> / stream&lt;/li>
&lt;/ul>
&lt;p>Go 端使用 Redis 時要注意：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>問題&lt;/th>
 &lt;th>風險&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>熱 key&lt;/td>
 &lt;td>單點壓力過大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>cache &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/cache-hit-miss/" data-link-title="Cache Hit / Miss" data-link-desc="說明快取命中與未命中如何影響讀取成本與下游壓力">miss&lt;/a> 擁塞&lt;/td>
 &lt;td>大量 goroutine 同時打到後端&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>pipeline 太大&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a> 與記憶體壓力增加&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>缺少 timeout&lt;/td>
 &lt;td>慢 request 會堆積成連鎖問題&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀sql-適合正式狀態與一致性資料">【判讀】SQL 適合正式狀態與一致性資料&lt;/h2>
&lt;p>SQL 在 Go 服務裡通常承接的是：&lt;/p>
&lt;ul>
&lt;li>最終狀態&lt;/li>
&lt;li>查詢&lt;/li>
&lt;li>交易&lt;/li>
&lt;li>可追蹤資料&lt;/li>
&lt;/ul>
&lt;p>Go 端最重要的原則是共用 &lt;code>*sql.DB&lt;/code>，讓 connection pool 真正發揮作用，並讓每個 query 都有 context 與 timeout。&lt;/p>
&lt;p>需要特別注意的是：&lt;/p>
&lt;ul>
&lt;li>太高的同時連線數會壓垮資料庫&lt;/li>
&lt;li>太長的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction&lt;/a> 會卡住連線池&lt;/li>
&lt;li>慢查詢會把 goroutine 一起拖住&lt;/li>
&lt;/ul>
&lt;h2 id="策略go-端要用邊界保護下游">【策略】Go 端要用邊界保護下游&lt;/h2>
&lt;p>高併發下的資料存取，通常要搭配以下做法：&lt;/p>
&lt;ul>
&lt;li>&lt;code>sql.DB&lt;/code> 與 Redis client 長期共用&lt;/li>
&lt;li>所有操作都帶 &lt;code>context&lt;/code>&lt;/li>
&lt;li>用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool&lt;/a> 或 semaphore 控制同時請求數&lt;/li>
&lt;li>對 cache miss 做去重或保護&lt;/li>
&lt;li>對寫入高峰做批次或排隊&lt;/li>
&lt;/ul>
&lt;p>這些做法是讓高併發系統能長時間穩定運行的基本條件。&lt;/p></description><content:encoded><![CDATA[<p>這一章從 Go 服務的角度整理資料存取原則。重點在於：當併發增加時，Go 端要用明確邊界使用 Redis 或 SQL，讓下游維持可承受的請求節奏。</p>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>理解高併發下最常見的資料存取風險</li>
<li>區分 Redis 與 SQL 各自適合的角色</li>
<li>用 <a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a>、<a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 與批次策略控制壓力</li>
<li>避免 <a href="/blog/backend/knowledge-cards/cache-stampede/" data-link-title="Cache Stampede" data-link-desc="說明快取同時失效時大量 request 如何壓垮正式來源">cache stampede</a> 與慢查詢連鎖</li>
<li>在 Go 服務內設計可控的下游存取邊界</li>
</ol>
<hr>
<h2 id="觀察go-端要先控住請求節奏">【觀察】Go 端要先控住請求節奏</h2>
<p>高併發時，資料存取風險通常來自請求節奏超過下游承受能力。你可以有很多 goroutine，但 Redis 與 SQL 不會因為 goroutine 多就自動變快。</p>
<p>Go 端通常要先做的是：</p>
<ul>
<li>限制同時對下游發出的請求數</li>
<li>設定明確 timeout</li>
<li>避免無限 <a href="/blog/backend/knowledge-cards/fan-out/" data-link-title="Fan-out" data-link-desc="說明單一事件同時分發給多個下游的訊息拓撲">fan-out</a></li>
<li>在壓力過高時拒絕新工作</li>
</ul>
<h2 id="判讀redis-適合快取狀態與短生命週期資料">【判讀】Redis 適合快取、狀態與短生命週期資料</h2>
<p>Redis 在 Go 服務裡常見用途包括：</p>
<ul>
<li>cache</li>
<li>session</li>
<li>counter</li>
<li><a href="/blog/backend/knowledge-cards/rate-limit/" data-link-title="Rate Limit" data-link-desc="說明限流如何保護服務入口、下游依賴與租戶公平性">rate limit</a></li>
<li><a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> key</li>
<li><a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> / stream</li>
</ul>
<p>Go 端使用 Redis 時要注意：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>熱 key</td>
          <td>單點壓力過大</td>
      </tr>
      <tr>
          <td>cache <a href="/blog/backend/knowledge-cards/cache-hit-miss/" data-link-title="Cache Hit / Miss" data-link-desc="說明快取命中與未命中如何影響讀取成本與下游壓力">miss</a> 擁塞</td>
          <td>大量 goroutine 同時打到後端</td>
      </tr>
      <tr>
          <td>pipeline 太大</td>
          <td><a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a> 與記憶體壓力增加</td>
      </tr>
      <tr>
          <td>缺少 timeout</td>
          <td>慢 request 會堆積成連鎖問題</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀sql-適合正式狀態與一致性資料">【判讀】SQL 適合正式狀態與一致性資料</h2>
<p>SQL 在 Go 服務裡通常承接的是：</p>
<ul>
<li>最終狀態</li>
<li>查詢</li>
<li>交易</li>
<li>可追蹤資料</li>
</ul>
<p>Go 端最重要的原則是共用 <code>*sql.DB</code>，讓 connection pool 真正發揮作用，並讓每個 query 都有 context 與 timeout。</p>
<p>需要特別注意的是：</p>
<ul>
<li>太高的同時連線數會壓垮資料庫</li>
<li>太長的 <a href="/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction</a> 會卡住連線池</li>
<li>慢查詢會把 goroutine 一起拖住</li>
</ul>
<h2 id="策略go-端要用邊界保護下游">【策略】Go 端要用邊界保護下游</h2>
<p>高併發下的資料存取，通常要搭配以下做法：</p>
<ul>
<li><code>sql.DB</code> 與 Redis client 長期共用</li>
<li>所有操作都帶 <code>context</code></li>
<li>用 <a href="/blog/backend/knowledge-cards/worker-pool/" data-link-title="Worker Pool" data-link-desc="說明一組 worker 如何限制同時處理量並保護下游資源">worker pool</a> 或 semaphore 控制同時請求數</li>
<li>對 cache miss 做去重或保護</li>
<li>對寫入高峰做批次或排隊</li>
</ul>
<p>這些做法是讓高併發系統能長時間穩定運行的基本條件。</p>
]]></content:encoded></item><item><title>3.8 效能迷思與優化策略</title><link>https://tarrragon.github.io/blog/python/03-stdlib/performance/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/03-stdlib/performance/</guid><description>&lt;p>「Python 很慢」是程式設計社群中最常見的說法之一。本章將探討這個說法的真相、何時效能真的重要，以及如何有效地優化 Python 程式。&lt;/p>
&lt;h2 id="python慢的真相">Python「慢」的真相&lt;/h2>
&lt;h3 id="直譯語言-vs-編譯語言">直譯語言 vs 編譯語言&lt;/h3>
&lt;p>Python 是直譯語言，程式碼在執行時才被轉換成機器碼：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">編譯語言（C/C++/Rust）：
&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>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> 一次編譯，多次執行
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">直譯語言（Python）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">原始碼 → 直譯器 → 逐行執行
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> 每次執行都要解釋&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這意味著 Python 在純計算任務上確實比編譯語言慢，通常是 10-100 倍的差距。&lt;/p>
&lt;h3 id="但這重要嗎">但這重要嗎？&lt;/h3>
&lt;p>讓我們看一個來自 Reddit 社群的經典回答：&lt;/p>
&lt;blockquote>
&lt;p>「如果你要問 Python 是不是太慢，那就不關你的事。」
— Reddit 用戶 scandii&lt;/p>&lt;/blockquote>
&lt;p>這聽起來很直接，但背後有深刻的道理：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 情境 1：網頁後端&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1"># Python 處理請求：50ms&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 網路延遲：200ms&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 資料庫查詢：100ms&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"># 總計：350ms&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 就算 Python 快 10 倍（5ms），總時間也只變成 305ms&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"># 用戶感受差異：幾乎沒有&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 情境 2：命令列工具&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行時間：0.5 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 用戶可接受？當然可以&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="設計哲學的取捨">設計哲學的取捨&lt;/h3>
&lt;p>Python 的設計哲學是「開發速度 &amp;gt; 執行速度」：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>Python&lt;/th>
 &lt;th>C++&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開發時間&lt;/td>
 &lt;td>短&lt;/td>
 &lt;td>長&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>執行速度&lt;/td>
 &lt;td>慢&lt;/td>
 &lt;td>快&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式碼可讀性&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>除錯難度&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>學習曲線&lt;/td>
 &lt;td>緩&lt;/td>
 &lt;td>陡&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>對於大多數應用來說，開發效率和維護成本遠比執行速度重要。&lt;/p>
&lt;h2 id="真正的瓶頸在哪裡">真正的瓶頸在哪裡？&lt;/h2>
&lt;p>在優化之前，你需要先找出真正的瓶頸。以下是常見的效能瓶頸排名：&lt;/p>
&lt;h3 id="1-io-操作">1. I/O 操作&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 網路請求：通常是最大的瓶頸&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&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="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://api.example.com/data&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 50-500ms&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;網路請求: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.3f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&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>&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&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 class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;large_file.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 取決於檔案大小和硬碟速度&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;檔案讀取: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.3f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-資料庫查詢">2. 資料庫查詢&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 一個沒有索引的查詢可能需要幾秒鐘&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1"># SELECT * FROM users WHERE email = &amp;#39;...&amp;#39; # 無索引：慢&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># SELECT * FROM users WHERE id = 123 # 有索引：快&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="c1"># N+1 查詢問題&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">users&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="n">orders&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_orders&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 每個用戶一次查詢 → 很慢&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">orders&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_orders_for_users&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">u&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">users&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1"># 一次查詢&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-演算法複雜度">3. 演算法複雜度&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># O(n²) vs O(n) 的差異遠大於語言差異&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="c1"># O(n²) - 10000 個元素需要 100,000,000 次操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">find_duplicates_slow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&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="n">duplicates&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&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="k">for&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">other&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&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="k">if&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">other&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">duplicates&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&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="k">return&lt;/span> &lt;span class="n">duplicates&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="c1"># O(n) - 10000 個元素只需要 10000 次操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">find_duplicates_fast&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">items&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="n">seen&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">set&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="n">duplicates&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">16&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">items&lt;/span>&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="k">if&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">seen&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">duplicates&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">seen&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">duplicates&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="瓶頸排名">瓶頸排名&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">通常的效能瓶頸（由大到小）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">1. 網路延遲 100-1000ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">2. 資料庫查詢 10-1000ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">3. 檔案 I/O 1-100ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">4. 演算法複雜度 視情況
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">5. Python 本身 0.001-1ms&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="優化方案總覽">優化方案總覽&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>方案&lt;/th>
 &lt;th>適用場景&lt;/th>
 &lt;th>學習成本&lt;/th>
 &lt;th>效果&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>演算法優化&lt;/td>
 &lt;td>通用&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>最高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>NumPy/Pandas&lt;/td>
 &lt;td>數值計算&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>concurrent.futures&lt;/td>
 &lt;td>並行任務&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>中-高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Free-threading&lt;/td>
 &lt;td>CPU 並行&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cython&lt;/td>
 &lt;td>熱點程式碼&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>PyPy&lt;/td>
 &lt;td>通用加速&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>asyncio&lt;/td>
 &lt;td>I/O 並發&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>中-高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="1-演算法優化">1. 演算法優化&lt;/h3>
&lt;p>永遠是第一優先：&lt;/p></description><content:encoded><![CDATA[<p>「Python 很慢」是程式設計社群中最常見的說法之一。本章將探討這個說法的真相、何時效能真的重要，以及如何有效地優化 Python 程式。</p>
<h2 id="python慢的真相">Python「慢」的真相</h2>
<h3 id="直譯語言-vs-編譯語言">直譯語言 vs 編譯語言</h3>
<p>Python 是直譯語言，程式碼在執行時才被轉換成機器碼：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">編譯語言（C/C++/Rust）：
</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></span><span class="line"><span class="ln">4</span><span class="cl">              一次編譯，多次執行
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">直譯語言（Python）：
</span></span><span class="line"><span class="ln">7</span><span class="cl">原始碼 → 直譯器 → 逐行執行
</span></span><span class="line"><span class="ln">8</span><span class="cl">              ↑
</span></span><span class="line"><span class="ln">9</span><span class="cl">         每次執行都要解釋</span></span></code></pre></div><p>這意味著 Python 在純計算任務上確實比編譯語言慢，通常是 10-100 倍的差距。</p>
<h3 id="但這重要嗎">但這重要嗎？</h3>
<p>讓我們看一個來自 Reddit 社群的經典回答：</p>
<blockquote>
<p>「如果你要問 Python 是不是太慢，那就不關你的事。」
— Reddit 用戶 scandii</p></blockquote>
<p>這聽起來很直接，但背後有深刻的道理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 情境 1：網頁後端</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># Python 處理請求：50ms</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 網路延遲：200ms</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 資料庫查詢：100ms</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 總計：350ms</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 就算 Python 快 10 倍（5ms），總時間也只變成 305ms</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 用戶感受差異：幾乎沒有</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 情境 2：命令列工具</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 執行時間：0.5 秒</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 用戶可接受？當然可以</span></span></span></code></pre></div><h3 id="設計哲學的取捨">設計哲學的取捨</h3>
<p>Python 的設計哲學是「開發速度 &gt; 執行速度」：</p>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Python</th>
          <th>C++</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開發時間</td>
          <td>短</td>
          <td>長</td>
      </tr>
      <tr>
          <td>執行速度</td>
          <td>慢</td>
          <td>快</td>
      </tr>
      <tr>
          <td>程式碼可讀性</td>
          <td>高</td>
          <td>中</td>
      </tr>
      <tr>
          <td>除錯難度</td>
          <td>低</td>
          <td>高</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>緩</td>
          <td>陡</td>
      </tr>
  </tbody>
</table>
<p>對於大多數應用來說，開發效率和維護成本遠比執行速度重要。</p>
<h2 id="真正的瓶頸在哪裡">真正的瓶頸在哪裡？</h2>
<p>在優化之前，你需要先找出真正的瓶頸。以下是常見的效能瓶頸排名：</p>
<h3 id="1-io-操作">1. I/O 操作</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 網路請求：通常是最大的瓶頸</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;https://api.example.com/data&#34;</span><span class="p">)</span>  <span class="c1"># 50-500ms</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;網路請求: </span><span class="si">{</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 檔案讀寫</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;large_file.txt&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>  <span class="c1"># 取決於檔案大小和硬碟速度</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;檔案讀取: </span><span class="si">{</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="2-資料庫查詢">2. 資料庫查詢</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 一個沒有索引的查詢可能需要幾秒鐘</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># SELECT * FROM users WHERE email = &#39;...&#39;  # 無索引：慢</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># SELECT * FROM users WHERE id = 123       # 有索引：快</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># N+1 查詢問題</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">for</span> <span class="n">user</span> <span class="ow">in</span> <span class="n">users</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">orders</span> <span class="o">=</span> <span class="n">get_orders</span><span class="p">(</span><span class="n">user</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>  <span class="c1"># 每個用戶一次查詢 → 很慢</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 應該改成</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">orders</span> <span class="o">=</span> <span class="n">get_orders_for_users</span><span class="p">([</span><span class="n">u</span><span class="o">.</span><span class="n">id</span> <span class="k">for</span> <span class="n">u</span> <span class="ow">in</span> <span class="n">users</span><span class="p">])</span>  <span class="c1"># 一次查詢</span></span></span></code></pre></div><h3 id="3-演算法複雜度">3. 演算法複雜度</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># O(n²) vs O(n) 的差異遠大於語言差異</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="c1"># O(n²) - 10000 個元素需要 100,000,000 次操作</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">find_duplicates_slow</span><span class="p">(</span><span class="n">items</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">duplicates</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">items</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">for</span> <span class="n">j</span><span class="p">,</span> <span class="n">other</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">items</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">if</span> <span class="n">i</span> <span class="o">!=</span> <span class="n">j</span> <span class="ow">and</span> <span class="n">item</span> <span class="o">==</span> <span class="n">other</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                <span class="n">duplicates</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">duplicates</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="c1"># O(n) - 10000 個元素只需要 10000 次操作</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">find_duplicates_fast</span><span class="p">(</span><span class="n">items</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">seen</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">duplicates</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">if</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">seen</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">duplicates</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">seen</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">duplicates</span></span></span></code></pre></div><h3 id="瓶頸排名">瓶頸排名</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">通常的效能瓶頸（由大到小）：
</span></span><span class="line"><span class="ln">2</span><span class="cl">1. 網路延遲         100-1000ms
</span></span><span class="line"><span class="ln">3</span><span class="cl">2. 資料庫查詢        10-1000ms
</span></span><span class="line"><span class="ln">4</span><span class="cl">3. 檔案 I/O          1-100ms
</span></span><span class="line"><span class="ln">5</span><span class="cl">4. 演算法複雜度      視情況
</span></span><span class="line"><span class="ln">6</span><span class="cl">5. Python 本身        0.001-1ms</span></span></code></pre></div><h2 id="優化方案總覽">優化方案總覽</h2>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>適用場景</th>
          <th>學習成本</th>
          <th>效果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>演算法優化</td>
          <td>通用</td>
          <td>中</td>
          <td>最高</td>
      </tr>
      <tr>
          <td>NumPy/Pandas</td>
          <td>數值計算</td>
          <td>低</td>
          <td>高</td>
      </tr>
      <tr>
          <td>concurrent.futures</td>
          <td>並行任務</td>
          <td>低</td>
          <td>中-高</td>
      </tr>
      <tr>
          <td>Free-threading</td>
          <td>CPU 並行</td>
          <td>中</td>
          <td>高</td>
      </tr>
      <tr>
          <td>Cython</td>
          <td>熱點程式碼</td>
          <td>高</td>
          <td>高</td>
      </tr>
      <tr>
          <td>PyPy</td>
          <td>通用加速</td>
          <td>低</td>
          <td>中</td>
      </tr>
      <tr>
          <td>asyncio</td>
          <td>I/O 並發</td>
          <td>中</td>
          <td>中-高</td>
      </tr>
  </tbody>
</table>
<h3 id="1-演算法優化">1. 演算法優化</h3>
<p>永遠是第一優先：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 用合適的資料結構</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">items_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">...</span><span class="p">]</span>    <span class="c1"># 查找 O(n)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">items_set</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">...</span><span class="p">}</span>     <span class="c1"># 查找 O(1)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 用合適的演算法</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nb">sorted</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>                   <span class="c1"># O(n log n)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">items</span><span class="o">.</span><span class="n">sort</span><span class="p">()</span>                    <span class="c1"># O(n log n)，但原地排序更省記憶體</span></span></span></code></pre></div><h3 id="2-使用-numpypandas">2. 使用 NumPy/Pandas</h3>
<p>把計算交給 C 實現的函式庫：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</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="c1"># 純 Python：慢</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">sum_squares_python</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># NumPy：快 10-100 倍</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">sum_squares_numpy</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">arr</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">arange</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">np</span><span class="o">.</span><span class="n">sum</span><span class="p">(</span><span class="n">arr</span> <span class="o">*</span> <span class="n">arr</span><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="c1"># 向量化操作是關鍵</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 不好：Python 迴圈</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span><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="c1"># 好：NumPy 向量化</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">data</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span></span></span></code></pre></div><h3 id="3-並行處理">3. 並行處理</h3>
<p>見 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a> 和 <a href="/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&#43; 無 GIL 版本的完整指南">3.8 Free-Threading</a></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">ProcessPoolExecutor</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="c1"># I/O 密集：使用執行緒</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fetch_url</span><span class="p">,</span> <span class="n">urls</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># CPU 密集：使用進程（或 Free-threading）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">with</span> <span class="n">ProcessPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">compute_heavy</span><span class="p">,</span> <span class="n">data_chunks</span><span class="p">)</span></span></span></code></pre></div><h3 id="4-pypy">4. PyPy</h3>
<p>PyPy 是 Python 的另一個實現，使用 JIT 編譯：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 安裝 PyPy</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># macOS: brew install pypy3</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># Ubuntu: apt install pypy3</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 執行</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">pypy3 your_script.py</span></span></code></pre></div><p>PyPy 對於迴圈密集的程式碼特別有效：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這種程式碼在 PyPy 上可能快 10-50 倍</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">compute</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10_000_000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="n">total</span> <span class="o">+=</span> <span class="n">i</span> <span class="o">*</span> <span class="n">i</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">return</span> <span class="n">total</span></span></span></code></pre></div><h2 id="python-313-314-效能改進">Python 3.13-3.14 效能改進</h2>
<h3 id="新的直譯器">新的直譯器</h3>
<p>Python 3.14 引入了使用尾調用的新直譯器，在支援的編譯器上快 3-5%：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 需要使用 Clang 19+ 編譯，並啟用配置選項</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">./configure --with-tail-call-interp</span></span></code></pre></div><h3 id="增量垃圾回收">增量垃圾回收</h3>
<p>循環垃圾回收現在是增量式的，減少了長時間停頓：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</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="c1"># 舊版：可能造成明顯停頓</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 3.14：增量回收，影響更平滑</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></span></span></code></pre></div><h3 id="free-threading">Free-Threading</h3>
<p>詳見 <a href="/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&#43; 無 GIL 版本的完整指南">3.8 Free-Threading</a>。</p>
<h2 id="什麼時候該優化">什麼時候該優化？</h2>
<h3 id="過早優化是萬惡之源">「過早優化是萬惡之源」</h3>
<p>Donald Knuth 的這句名言經常被誤解。完整的引言是：</p>
<blockquote>
<p>「程式設計師花費了大量時間思考或擔心程式非關鍵部分的速度，而當考慮到除錯和維護時，這些效率的嘗試實際上會產生強烈的負面影響。我們應該忘記小的效率問題，比如說 97% 的時間：<strong>過早優化是萬惡之源</strong>。然而，我們不應該放棄那關鍵的 3% 的機會。」</p></blockquote>
<h3 id="優化的正確流程">優化的正確流程</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">1. 讓程式正確運作
</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">2. 讓程式碼可讀、可維護
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">      ↓
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">3. 測量效能（profiling）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      ↓
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">4. 找出瓶頸（通常是 20% 的程式碼佔 80% 的時間）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      ↓
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">5. 只優化瓶頸
</span></span><span class="line"><span class="ln">10</span><span class="cl">      ↓
</span></span><span class="line"><span class="ln">11</span><span class="cl">6. 再次測量，確認改善</span></span></code></pre></div><h3 id="8020-法則">80/20 法則</h3>
<p>在大多數程式中：</p>
<ul>
<li>20% 的程式碼佔用 80% 的執行時間</li>
<li>優化錯誤的地方不會有任何效果</li>
</ul>
<h2 id="效能測量工具">效能測量工具</h2>
<h3 id="簡單計時">簡單計時</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</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="k">def</span> <span class="nf">measure_time</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量函式執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">func</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.6f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">measure_time</span><span class="p">(</span><span class="n">my_function</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用-timeit">使用 timeit</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</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="c1"># 測量小段程式碼</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">time_taken</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s1">&#39;sum(range(1000))&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">number</span><span class="o">=</span><span class="mi">10000</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;平均執行時間: </span><span class="si">{</span><span class="n">time_taken</span> <span class="o">/</span> <span class="mi">10000</span><span class="si">:</span><span class="s2">.6f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 比較兩種實現</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">setup</span> <span class="o">=</span> <span class="s2">&#34;data = list(range(1000))&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">time1</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s1">&#39;sum(data)&#39;</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="n">setup</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">10000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">time2</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s1">&#39;sum(x for x in data)&#39;</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="n">setup</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">10000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;直接 sum: </span><span class="si">{</span><span class="n">time1</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;生成器 sum: </span><span class="si">{</span><span class="n">time2</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用-cprofile">使用 cProfile</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 基本用法</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">cProfile</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s1">&#39;my_function()&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 詳細分析</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 執行你的程式碼</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">my_function</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="s1">&#39;cumulative&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">stats</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>  <span class="c1"># 顯示前 20 個</span></span></span></code></pre></div><h3 id="使用-line_profiler逐行分析">使用 line_profiler（逐行分析）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pip install line_profiler</span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在函式上加上 @profile 裝飾器</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@profile</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">slow_function</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="n">total</span> <span class="o">+=</span> <span class="n">i</span> <span class="o">*</span> <span class="n">i</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">return</span> <span class="n">total</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">kernprof -l -v your_script.py</span></span></code></pre></div><h3 id="使用-memory_profiler記憶體分析">使用 memory_profiler（記憶體分析）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pip install memory_profiler</span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">memory_profiler</span> <span class="kn">import</span> <span class="n">profile</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="nd">@profile</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">def</span> <span class="nf">memory_hungry_function</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">big_list</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000000</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">big_list</span><span class="p">)</span></span></span></code></pre></div><h2 id="實際案例">實際案例</h2>
<h3 id="案例-1優化資料處理">案例 1：優化資料處理</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 原始版本：慢</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">process_data_slow</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">if</span> <span class="n">item</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 優化版本 1：列表推導式（快 20-30%）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">process_data_v1</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">item</span> <span class="o">*</span> <span class="mi">2</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span> <span class="k">if</span> <span class="n">item</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 優化版本 2：NumPy（大數據時快 10-100 倍）</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">def</span> <span class="nf">process_data_v2</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">arr</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="n">arr</span><span class="p">[</span><span class="n">arr</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="mi">2</span></span></span></code></pre></div><h3 id="案例-2快取昂貴的計算">案例 2：快取昂貴的計算</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</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="c1"># 沒有快取：每次都重新計算</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">fibonacci_slow</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="n">n</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">fibonacci_slow</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fibonacci_slow</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 有快取：已計算的結果會被記住</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">fibonacci_fast</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">n</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="n">fibonacci_fast</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fibonacci_fast</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># fibonacci_slow(35) 需要幾秒鐘</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># fibonacci_fast(35) 幾乎瞬間完成</span></span></span></code></pre></div><h3 id="案例-3選擇正確的資料結構">案例 3：選擇正確的資料結構</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</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="c1"># 用 list 查找（O(n)）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">find_in_list</span><span class="p">(</span><span class="n">items</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="n">target</span> <span class="ow">in</span> <span class="n">items</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 用 set 查找（O(1)）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">find_in_set</span><span class="p">(</span><span class="n">items</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">target</span> <span class="ow">in</span> <span class="n">items</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 測試</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">data_list</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">1_000_000</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">data_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">1_000_000</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">target</span> <span class="o">=</span> <span class="mi">999_999</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">find_in_list</span><span class="p">(</span><span class="n">data_list</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List 查找: </span><span class="si">{</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="s2">.6f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">find_in_set</span><span class="p">(</span><span class="n">data_set</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Set 查找: </span><span class="si">{</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="si">:</span><span class="s2">.6f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"># List 查找: 0.015000s（取決於位置）</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"># Set 查找:  0.000001s（幾乎瞬間）</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>為什麼「過早優化是萬惡之源」？什麼時候優化才是適當的？</li>
<li>在什麼情況下，Python 的「慢」確實是個問題？</li>
<li>NumPy 為什麼比純 Python 迴圈快這麼多？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>使用 <code>cProfile</code> 分析一個現有的 Python 程式，找出效能瓶頸</li>
<li>將一個使用 Python 迴圈的數值計算程式改寫成 NumPy 版本，比較效能差異</li>
<li>實作一個帶有快取的 API 客戶端，避免重複請求相同的資料</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://wiki.python.org/moin/PythonSpeed/PerformanceTips">Python 官方效能提示</a></li>
<li><a href="https://www.oreilly.com/library/view/high-performance-python/9781492055013/">High Performance Python, 2nd Edition</a></li>
<li><a href="https://numpy.org/doc/stable/user/basics.html">NumPy 官方文件 - 效能</a></li>
</ul>
<h2 id="延伸閱讀進階系列">延伸閱讀（進階系列）</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">實戰效能優化</a> - 真實案例的效能調優實戰</li>
<li><a href="/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">CPython 內部機制</a> - 理解 Python 的運作原理以優化效能</li>
<li><a href="/blog/python-advanced/04-cpython-internals/free-threading/" data-link-title="4.5 Free-Threading - Python 的真正多執行緒時代" data-link-desc="Python 3.13&#43; 無 GIL 版本的完整指南">Free-Threading</a> - Python 3.13+ 無 GIL 多執行緒</li>
<li><a href="/blog/python-advanced/05-c-extensions/" data-link-title="模組五：用 C 擴展 Python" data-link-desc="學習使用 ctypes、cffi、Cython、pybind11 擴展 Python">用 C 擴展 Python</a> - 使用 ctypes、Cython、pybind11 提升效能</li>
<li><a href="/blog/python-advanced/06-rust-extensions/" data-link-title="模組六：用 Rust 擴展 Python" data-link-desc="學習使用 PyO3 和 Maturin 用 Rust 擴展 Python">用 Rust 擴展 Python</a> - 使用 PyO3 建立高效能安全的擴展</li>
</ul>
<hr>
<p>上一章：<a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">並行處理</a></p>
]]></content:encoded></item><item><title>9.9 Performance Improvement Loop</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/improvement-loop/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/improvement-loop/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Improvement loop 的責任是把效能優化從「事件型 hotfix」變成「持續改進的工程流程」。沒有 loop 時、效能問題靠 oncall 觸發、改了又改、改完又退化；有 loop 之後、每次 release 都通過 perf gate、退化在發布前就攔住。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">06.13 perf regression gate&lt;/a> 的關係：06.13 是 release gate 的一個環節、9.9 是這個 gate 背後的完整工程閉環。06.13 處理「進 gate 後怎麼判斷」、9.9 處理「進 gate 前怎麼產生比較資料」。&lt;/p>
&lt;p>本章聚焦在 &lt;em>閉環設計&lt;/em> — 怎麼建 baseline、怎麼跑 re-test、怎麼用 profile diff、怎麼整合 CI。讀完後讀者能設計一個 perf improvement workflow、不是只有 ad-hoc 壓測。&lt;/p>
&lt;h2 id="loop-五個階段">Loop 五個階段&lt;/h2>
&lt;p>完整的 improvement loop 包含五個階段、缺一不可：&lt;/p>
&lt;p>&lt;strong>1. Baseline 建立&lt;/strong>：壓測 + profile 取得「當前正常」snapshot。
&lt;strong>2. 變更 + re-test&lt;/strong>：每次 release candidate 跑壓測、跟 baseline diff。
&lt;strong>3. Profile diff&lt;/strong>：用 flame graph diff 定位退化原因。
&lt;strong>4. Fix&lt;/strong>：rollback 或修正 code path。
&lt;strong>5. Update baseline&lt;/strong>：通過後更新 baseline、進下個 cycle。&lt;/p>
&lt;p>少了 baseline → re-test 沒有比較對象、看絕對數字會錯判。
少了 profile diff → 退化定位靠猜、修錯方向。
少了 update baseline → 永遠跟 old baseline 比、退化累積看不出來。
少了 fix → 退化通過 gate、production 出事。&lt;/p>
&lt;h2 id="baseline-設計">Baseline 設計&lt;/h2>
&lt;p>Baseline 不是「歷史最佳」、是「最低可接受效能」。&lt;/p>
&lt;p>&lt;strong>設計原則&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>不只一個 baseline、按 workload model 訂多個（不同 endpoint、不同 user tier 各自 baseline）&lt;/li>
&lt;li>baseline 必須可重複：固定 seed、固定資料集、固定環境、固定壓測參數&lt;/li>
&lt;li>定期 review：硬體 / 軟體升級會讓 baseline 該往好的方向走、不更新就是裝盲&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>儲存策略&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>baseline as artifact：存進 release artifact、隨 release 帶走&lt;/li>
&lt;li>baseline as code：用 Pulumi / Terraform / dedicated config 管理、可 version control&lt;/li>
&lt;li>baseline as service：dedicated service 管 baseline、提供 query API&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Drift 監控&lt;/strong>：baseline 每月對比上月、看趨勢是否往好方向。drift 超門檻 → re-baseline 並 review 原因。&lt;/p>
&lt;h2 id="profile-diff">Profile diff&lt;/h2>
&lt;p>退化定位的關鍵工具是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/profile-diff/" data-link-title="Profile Diff" data-link-desc="對比兩次 profile（如 release candidate vs baseline）找出 hottest 變化">profile diff&lt;/a> — 對比兩次 profile 找 hottest 變化。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Improvement loop 的責任是把效能優化從「事件型 hotfix」變成「持續改進的工程流程」。沒有 loop 時、效能問題靠 oncall 觸發、改了又改、改完又退化；有 loop 之後、每次 release 都通過 perf gate、退化在發布前就攔住。</p>
<p>跟 <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 控制與退化定位">06.13 perf regression gate</a> 的關係：06.13 是 release gate 的一個環節、9.9 是這個 gate 背後的完整工程閉環。06.13 處理「進 gate 後怎麼判斷」、9.9 處理「進 gate 前怎麼產生比較資料」。</p>
<p>本章聚焦在 <em>閉環設計</em> — 怎麼建 baseline、怎麼跑 re-test、怎麼用 profile diff、怎麼整合 CI。讀完後讀者能設計一個 perf improvement workflow、不是只有 ad-hoc 壓測。</p>
<h2 id="loop-五個階段">Loop 五個階段</h2>
<p>完整的 improvement loop 包含五個階段、缺一不可：</p>
<p><strong>1. Baseline 建立</strong>：壓測 + profile 取得「當前正常」snapshot。
<strong>2. 變更 + re-test</strong>：每次 release candidate 跑壓測、跟 baseline diff。
<strong>3. Profile diff</strong>：用 flame graph diff 定位退化原因。
<strong>4. Fix</strong>：rollback 或修正 code path。
<strong>5. Update baseline</strong>：通過後更新 baseline、進下個 cycle。</p>
<p>少了 baseline → re-test 沒有比較對象、看絕對數字會錯判。
少了 profile diff → 退化定位靠猜、修錯方向。
少了 update baseline → 永遠跟 old baseline 比、退化累積看不出來。
少了 fix → 退化通過 gate、production 出事。</p>
<h2 id="baseline-設計">Baseline 設計</h2>
<p>Baseline 不是「歷史最佳」、是「最低可接受效能」。</p>
<p><strong>設計原則</strong>：</p>
<ul>
<li>不只一個 baseline、按 workload model 訂多個（不同 endpoint、不同 user tier 各自 baseline）</li>
<li>baseline 必須可重複：固定 seed、固定資料集、固定環境、固定壓測參數</li>
<li>定期 review：硬體 / 軟體升級會讓 baseline 該往好的方向走、不更新就是裝盲</li>
</ul>
<p><strong>儲存策略</strong>：</p>
<ul>
<li>baseline as artifact：存進 release artifact、隨 release 帶走</li>
<li>baseline as code：用 Pulumi / Terraform / dedicated config 管理、可 version control</li>
<li>baseline as service：dedicated service 管 baseline、提供 query API</li>
</ul>
<p><strong>Drift 監控</strong>：baseline 每月對比上月、看趨勢是否往好方向。drift 超門檻 → re-baseline 並 review 原因。</p>
<h2 id="profile-diff">Profile diff</h2>
<p>退化定位的關鍵工具是 <a href="/blog/backend/knowledge-cards/profile-diff/" data-link-title="Profile Diff" data-link-desc="對比兩次 profile（如 release candidate vs baseline）找出 hottest 變化">profile diff</a> — 對比兩次 profile 找 hottest 變化。</p>
<p><strong>工具實作</strong>：</p>
<ul>
<li>Brendan Gregg 的 differential flame graph：開源、需要手動 generate</li>
<li>Pyroscope diff：UI 直接對比兩個時間段</li>
<li>Datadog Continuous Profiler diff：跟 deployment marker 整合</li>
<li>Parca compare：CNCF 標準</li>
<li>AWS CodeGuru Profiler：自動偵測 CPU / memory anti-pattern</li>
</ul>
<p><strong>正確使用方法</strong>：</p>
<ul>
<li>在 <em>相同負載 + 相同硬體 + 相同 sampling rate</em> 下取兩次 profile</li>
<li>比較 <em>相對變化</em>、不是絕對 CPU%</li>
<li>看 wider stack（不只看 leaf function）找 systemic regression</li>
</ul>
<p><strong>Profile diff 結果通常需要工程師判讀</strong>：「多花 20% CPU 但 throughput 多 50%」可能是好變化、不能純自動化判斷退化是否可接受。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">Netflix Aurora 統一</a> — DB 層統一後 profile diff 噪音降低、退化來源更容易識別。</p>
<h2 id="regression-gate-整合-ci">Regression gate 整合 CI</h2>
<p>效能改進閉環必須整合到 CI、不能只在 release 前一次性跑。</p>
<p><strong>Multi-tier 壓測策略</strong>：</p>
<ul>
<li>每個 PR：跑 lightweight perf test（單 endpoint、5 分鐘）、合併前比 baseline</li>
<li>主分支 nightly：跑 medium perf test（多 endpoint、30 分鐘）</li>
<li>Release candidate：跑 complete perf test（完整 workload model、數小時）</li>
</ul>
<p><strong>Gate 觸發條件</strong>：</p>
<ul>
<li>p99 退化 &gt; X%（例如 10%）</li>
<li>吞吐降 &gt; Y%（例如 5%）</li>
<li>error rate 升 &gt; Z%</li>
<li>cost per request 升 &gt; W%</li>
</ul>
<p><strong>Gate 通過 / 不通過的後果</strong>：</p>
<ul>
<li>通過：自動 promote 到下個 stage（staging / canary / production）</li>
<li>不通過：block release、自動 notify owner、附 profile diff link</li>
</ul>
<p><strong>Gate 太敏感的反模式</strong>：</p>
<ul>
<li>每天 false positive、最後沒人看（alert fatigue）</li>
<li>false positive 來源：壓測環境噪音、baseline drift 未更新、業務變化</li>
<li>對策：multi-window detection（變化必須持續 N 個 sample）、配合 manual override（資深工程師判斷異常正常）</li>
</ul>
<p>對應案例：<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 控制與退化定位">06.13 perf regression gate</a> 的實作建議。</p>
<h2 id="canary-perf-check">Canary perf check</h2>
<p><a href="/blog/backend/knowledge-cards/canary-perf-check/" data-link-title="Canary Perf Check" data-link-desc="canary release 中針對 latency / throughput 而非 error rate 的退化檢查">Canary perf check</a> 是 release 階段的另一道 perf gate。跟 regression gate（pre-release）對應、是 <em>production</em> 階段的監控。</p>
<p><strong>Canary 階段除了看 error rate、也看</strong>：</p>
<ul>
<li>latency p99 / p999（最先看到的 regression 訊號）</li>
<li>throughput（是否處理變慢）</li>
<li>resource utilization（CPU / RAM / connection 變化）</li>
<li>cost per request（是否更貴）</li>
</ul>
<p><strong>Canary 流量 vs control 流量比較</strong>：</p>
<ul>
<li>同樣流量同樣時段、不同版本的差才有意義</li>
<li>不能拿 canary 跟 historical baseline 比（外部變數太多）</li>
<li>abort condition：canary p99 比 control 退化 &gt; X%</li>
</ul>
<p><strong>漸進放大策略</strong>：1% → 5% → 25% → 50% → 100%、每階段觀察足夠時間（至少 15 分鐘看 long-tail）。</p>
<p>對應案例：<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 的峰值數字 — 一年一次可預期峰值的容量設計參考">Prime Day FIS 8x chaos</a> — canary 模式跟 chaos test 並行、確保新版本在故障場景也撐得住。</p>
<h2 id="pre-release-改進迴圈頻率">Pre-release 改進迴圈頻率</h2>
<p>不同層級的 review 在不同節奏：</p>
<ul>
<li><strong>每日 PR 級 perf check</strong>：lightweight、單 endpoint、5 分鐘</li>
<li><strong>每週 release candidate 完整壓測</strong>：完整 workload model、數小時</li>
<li><strong>每月 baseline review + drift 評估</strong>：對比歷史趨勢、決定是否 re-baseline</li>
<li><strong>每季容量地圖 review</strong>：跟 <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> 連動</li>
</ul>
<p>頻率不夠 → 退化累積看不到；頻率太高 → 工程資源吃緊。按團隊規模跟 release 節奏調整。</p>
<h2 id="退化的常見來源">退化的常見來源</h2>
<p>知道退化怎麼來、才能設計對應的 detection：</p>
<ul>
<li><strong>新功能引入 N+1 query</strong>：ORM lazy loading、loop 內 query。看 DB call count 變化</li>
<li><strong>ORM 沒下 index、cache miss 飆升</strong>：看 slow query 跟 cache hit rate</li>
<li><strong>第三方 library upgrade 帶來 overhead</strong>：新版本可能多了 telemetry / validation。看 profile diff</li>
<li><strong>GC tuning 變動</strong>：JVM / Go GC config 調整造成 pause time 變化。看 p999</li>
<li><strong>container resource limit 變動</strong>：Kubernetes limit 改、限制更嚴造成 throttling。看 CPU throttling event</li>
</ul>
<h2 id="反模式">反模式</h2>
<ul>
<li><strong>只在 release 前一次性壓測</strong>：退化已累積數月、找不出原因</li>
<li><strong>baseline 不更新</strong>：永遠跟舊版本比、低估目前狀態</li>
<li><strong>改了又改、改完忘記更新 baseline</strong>：下次 release 又跟過時 baseline 比、迴圈失效</li>
<li><strong>缺 profile diff、退化原因靠猜</strong>：修錯方向、退化還在</li>
<li><strong>gate 訊號跟業務無關</strong>：技術指標退化但業務 metric 沒事、被當 false positive</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a></td>
          <td>統一 DB 後 profile 變單純</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a></td>
          <td>遷移後重新做 baseline</td>
      </tr>
      <tr>
          <td><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 FIS 8x</a></td>
          <td>持續改進的混沌 + 壓測迴圈</td>
      </tr>
  </tbody>
</table>
<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> / <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/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/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">06.13 perf regression gate</a> / <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate</a></li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/profile-diff/" data-link-title="Profile Diff" data-link-desc="對比兩次 profile（如 release candidate vs baseline）找出 hottest 變化">Profile Diff</a></li>
<li><a href="/blog/backend/knowledge-cards/continuous-profiling/" data-link-title="Continuous Profiling" data-link-desc="在 production 持續取得低 overhead profile 的觀察方法">Continuous Profiling</a></li>
<li><a href="/blog/backend/knowledge-cards/canary-perf-check/" data-link-title="Canary Perf Check" data-link-desc="canary release 中針對 latency / throughput 而非 error rate 的退化檢查">Canary Perf Check</a></li>
</ul>
]]></content:encoded></item><item><title>9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/</guid><description>&lt;p>這個案例的核心責任是說明「事件交付系統的容量規劃，靠 managed service 卸載 vs 自管 broker」的長期成本對照。Spotify 從 Kafka 遷到 Pub/Sub 的驅動力是 &lt;em>容量規劃的工程成本&lt;/em> 在 sustained growth 下變得不划算、Kafka 能力本身不是瓶頸。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Spotify 在 Google Cloud 的遷移敘述（引自 &lt;a href="https://cloud.google.com/blog/products/gcp/spotifys-journey-to-cloud-why-spotify-migrated-its-event-delivery-system-from-kafka-to-google-cloud-pubsub">Spotify&amp;rsquo;s journey to cloud&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>內容&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>用戶規模&lt;/td>
 &lt;td>7500 萬 + 用戶（遷移時期）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移系統&lt;/td>
 &lt;td>Event Delivery System（事件交付）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷出技術&lt;/td>
 &lt;td>自管 Apache Kafka&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷入技術&lt;/td>
 &lt;td>Google Cloud Pub/Sub&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>大數據生態&lt;/td>
 &lt;td>BigQuery / Dataflow / Dataproc / Pub/Sub&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵動機：「moving event delivery to a managed service」— 卸下 Kafka broker 的容量規劃與運維負擔。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Spotify 遷移揭露三個 broker 容量規劃的長期工程問題。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>自管 broker 的容量規劃是長期 tax&lt;/strong>：Kafka cluster 需要 partition planning、broker 數量、副本因子、disk capacity、network bandwidth、ZooKeeper / KRaft 治理 — 每個維度都要持續規劃、每次擴容都是工程專案。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的 broker basics 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的人力成本評估。&lt;/li>
&lt;li>&lt;strong>managed service 的容量是 trade-off、不是免費午餐&lt;/strong>：Pub/Sub 自動 scaling、但 vendor lock-in、cost-per-message 累積、message ordering / latency 特性跟 Kafka 不同。遷移本身要驗證 &lt;em>業務語意&lt;/em> 跟 Pub/Sub 兼容。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">03.4 broker basics&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移本身是容量規劃題目&lt;/strong>：把 7500 萬用戶的事件交付從 A 平台搬到 B 平台、不能停機、不能丟 message。這個遷移過程本身就是高併發容量工程。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence&lt;/a> 的同類流程。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：Spotify 這個決定不是「Kafka 不好」、是「Spotify 規模下、自管 Kafka 的工程投入不划算」。對中小團隊、自管 Kafka 可能是更便宜的選項。讀案例時要看 &lt;em>規模門檻&lt;/em> 跟 &lt;em>團隊能力&lt;/em>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>broker 自管 vs managed 是長期 TCO 評估&lt;/strong>：算「平日運維 + 容量擴容 + 故障處理 + 升級遷移」的人力成本、不只算「broker 雲端費用」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移分階段：dual write → shadow → cutover&lt;/strong>：先寫兩邊、驗證一致性、再切流量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence&lt;/a> 的同類流程。&lt;/li>
&lt;li>&lt;strong>業務語意對映是遷移關鍵&lt;/strong>：Kafka 的 partition / offset / consumer group 在 Pub/Sub 對映成不同概念（subscription / ordering key / message attribute）、不是 1:1。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS SNS / SQS / Kinesis、Amazon MSK（managed Kafka）、Azure Service Bus / Event Hubs / Event Grid 都是對等候選。差異是 message ordering 保證、delivery guarantee、cost model。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「事件交付系統的容量規劃，靠 managed service 卸載 vs 自管 broker」的長期成本對照。Spotify 從 Kafka 遷到 Pub/Sub 的驅動力是 <em>容量規劃的工程成本</em> 在 sustained growth 下變得不划算、Kafka 能力本身不是瓶頸。</p>
<h2 id="觀察">觀察</h2>
<p>Spotify 在 Google Cloud 的遷移敘述（引自 <a href="https://cloud.google.com/blog/products/gcp/spotifys-journey-to-cloud-why-spotify-migrated-its-event-delivery-system-from-kafka-to-google-cloud-pubsub">Spotify&rsquo;s journey to cloud</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用戶規模</td>
          <td>7500 萬 + 用戶（遷移時期）</td>
      </tr>
      <tr>
          <td>遷移系統</td>
          <td>Event Delivery System（事件交付）</td>
      </tr>
      <tr>
          <td>遷出技術</td>
          <td>自管 Apache Kafka</td>
      </tr>
      <tr>
          <td>遷入技術</td>
          <td>Google Cloud Pub/Sub</td>
      </tr>
      <tr>
          <td>大數據生態</td>
          <td>BigQuery / Dataflow / Dataproc / Pub/Sub</td>
      </tr>
  </tbody>
</table>
<p>關鍵動機：「moving event delivery to a managed service」— 卸下 Kafka broker 的容量規劃與運維負擔。</p>
<h2 id="判讀">判讀</h2>
<p>Spotify 遷移揭露三個 broker 容量規劃的長期工程問題。</p>
<ol>
<li><strong>自管 broker 的容量規劃是長期 tax</strong>：Kafka cluster 需要 partition planning、broker 數量、副本因子、disk capacity、network bandwidth、ZooKeeper / KRaft 治理 — 每個維度都要持續規劃、每次擴容都是工程專案。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的 broker basics 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本評估。</li>
<li><strong>managed service 的容量是 trade-off、不是免費午餐</strong>：Pub/Sub 自動 scaling、但 vendor lock-in、cost-per-message 累積、message ordering / latency 特性跟 Kafka 不同。遷移本身要驗證 <em>業務語意</em> 跟 Pub/Sub 兼容。對應 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">03.4 broker basics</a>。</li>
<li><strong>遷移本身是容量規劃題目</strong>：把 7500 萬用戶的事件交付從 A 平台搬到 B 平台、不能停機、不能丟 message。這個遷移過程本身就是高併發容量工程。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的同類流程。</li>
</ol>
<p>需要警惕：Spotify 這個決定不是「Kafka 不好」、是「Spotify 規模下、自管 Kafka 的工程投入不划算」。對中小團隊、自管 Kafka 可能是更便宜的選項。讀案例時要看 <em>規模門檻</em> 跟 <em>團隊能力</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>broker 自管 vs managed 是長期 TCO 評估</strong>：算「平日運維 + 容量擴容 + 故障處理 + 升級遷移」的人力成本、不只算「broker 雲端費用」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>遷移分階段：dual write → shadow → cutover</strong>：先寫兩邊、驗證一致性、再切流量。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的同類流程。</li>
<li><strong>業務語意對映是遷移關鍵</strong>：Kafka 的 partition / offset / consumer group 在 Pub/Sub 對映成不同概念（subscription / ordering key / message attribute）、不是 1:1。</li>
</ol>
<p>跨平台等效：AWS SNS / SQS / Kinesis、Amazon MSK（managed Kafka）、Azure Service Bus / Event Hubs / Event Grid 都是對等候選。差異是 message ordering 保證、delivery guarantee、cost model。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想評估 broker 自管 vs managed → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>想做大規模 message 系統遷移 → <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的對等流程</li>
<li>想理解 broker 容量規劃 → <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">03.4 broker basics</a></li>
<li>對照其他事件型負載 → <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></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/gcp/spotifys-journey-to-cloud-why-spotify-migrated-its-event-delivery-system-from-kafka-to-google-cloud-pubsub">Spotify&rsquo;s journey to cloud: why Spotify migrated its event delivery system from Kafka to Google Cloud Pub/Sub</a></li>
<li><a href="https://cloud.google.com/blog/products/gcp/spotify-chooses-google-cloud-platform-to-power-data-infrastructure/">Spotify chooses Google Cloud Platform</a></li>
<li><a href="https://cloud.google.com/blog/products/gcp/spotifys-experiments-with-stream-processing-on-google-cloud-dataflow">Spotify&rsquo;s experiments with stream processing on Google Cloud Dataflow</a></li>
</ul>
]]></content:encoded></item><item><title>模組九：效能工程與容量規劃</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/</guid><description>&lt;p>效能工程與容量規劃模組的核心目標是回答兩個工程問題：目前的服務配置能承載多少負載，以及面對預期或意外的流量增長時要加多少資源。語言教材會處理 algorithm、hot path 與 memory profile 等程式層效能；本模組負責 workload modeling、壓測工具選型、saturation discovery、瓶頸定位、容量規劃、成本邊界、效能可觀測性與改進閉環。&lt;/p>
&lt;p>本模組跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">模組六：可靠性驗證流程&lt;/a> 是 sibling 工程紀律。06 看「失敗模式如何被驗證」，走 SLO、Error Budget、Failure Mode、Chaos Hypothesis 的詞彙；09 看「正常負載如何被量化與規劃」，走 Workload、Saturation、Capacity、Cost、Throughput、Latency 的詞彙。兩個模組共用案例庫但讀法不同：06 從案例讀「失敗模式驗證」、09 從案例讀「容量量化實踐」。&lt;/p>
&lt;h2 id="教材定位">教材定位&lt;/h2>
&lt;p>效能工程的角色是把「我不知道目前配置能撐多少」這個常見焦慮，變成可量測、可重播、可改進的工程流程。&lt;/p>
&lt;p>多數後端服務不會每天遇到高併發，真正的工程問題是平常運作時的容量地圖。平常運作正常時，目前的配置距離 saturation 還有多遠；當意外流量出現時，現有配置能撐到 autoscaling 介入嗎；要加機器時，怎麼算出該加多少、加在哪一層；加了機器之後，怎麼確認瓶頸真的被移除了。&lt;/p>
&lt;p>這四個問題不需要假設高併發場景，而是要求系統在任何配置下都能回答「現在的容量地圖長什麼樣」。沒有這張地圖，加機器是猜測、不加機器是賭運氣、改架構是恐慌。&lt;/p>
&lt;h2 id="教材邊界">教材邊界&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>放在語言教材&lt;/th>
 &lt;th>放在本模組&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>程式層效能&lt;/td>
 &lt;td>algorithm、data structure、hot path、memory profile、micro benchmark&lt;/td>
 &lt;td>workload model、production traffic replay、end-to-end load test&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並發模型&lt;/td>
 &lt;td>goroutine、event loop、thread pool、connection pool 的程式邊界&lt;/td>
 &lt;td>並發設計如何決定 saturation 與 connection pressure 邊界&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Profiling&lt;/td>
 &lt;td>runtime profiler、flame graph、heap dump 解讀&lt;/td>
 &lt;td>continuous profiling 接入、profile diff 作為 regression 定位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量量測&lt;/td>
 &lt;td>resource metric API、process memory、GC pause 訊號&lt;/td>
 &lt;td>saturation metric、USE method、RED method、cost dashboard&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量規劃&lt;/td>
 &lt;td>（不負責）&lt;/td>
 &lt;td>peak forecast、headroom model、growth curve、autoscaling sizing、cost ceiling&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>壓測工具&lt;/td>
 &lt;td>（不負責）&lt;/td>
 &lt;td>k6、JMeter、Gatling、Locust、Vegeta、production traffic replay 工具的選型與整合&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="問題節點">問題節點&lt;/h2>
&lt;p>問題節點先描述「不知道答案會發生什麼」，再描述「怎麼建立答案」。讀者能先理解這個問題為什麼重要，再看到怎麼處理。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>節點&lt;/th>
 &lt;th>工程問題&lt;/th>
 &lt;th>觀察訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Workload Modeling&lt;/td>
 &lt;td>壓測模型是否貼近 production traffic shape&lt;/td>
 &lt;td>percentile distribution、cohort mix、burst pattern&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Load Test Tooling&lt;/td>
 &lt;td>該用哪種工具、怎麼整合 CI 跟 staging&lt;/td>
 &lt;td>tool capability vs workload shape、CI 整合成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Saturation Discovery&lt;/td>
 &lt;td>配置距離飽和還有多少 headroom&lt;/td>
 &lt;td>throughput plateau、latency knee、resource saturation&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bottleneck Localization&lt;/td>
 &lt;td>瓶頸在哪一層、是 app / DB / cache / broker&lt;/td>
 &lt;td>resource utilization、queue depth、connection exhaustion&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Capacity Planning&lt;/td>
 &lt;td>要加多少機器、加在哪一層&lt;/td>
 &lt;td>peak forecast、headroom budget、growth curve&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost Engineering&lt;/td>
 &lt;td>容量擴張的成本曲線、降級的成本邊界&lt;/td>
 &lt;td>cost per request、autoscaling cost ceiling、over-provision waste&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Performance Observability&lt;/td>
 &lt;td>容量訊號怎麼看、跟 SLO 怎麼接&lt;/td>
 &lt;td>saturation metric、cost attribution、SLO budget&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Improvement Loop&lt;/td>
 &lt;td>從壓測到 release 怎麼閉環&lt;/td>
 &lt;td>profile diff、regression gate、canary perf signal&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production Validation&lt;/td>
 &lt;td>怎麼在 production 安全驗證新配置&lt;/td>
 &lt;td>shadow traffic、dark launch、canary perf check&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Peak Event Readiness&lt;/td>
 &lt;td>預知的流量事件怎麼準備&lt;/td>
 &lt;td>event capacity forecast、pre-warm checklist、rollback path&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這張表的責任是路由。當讀者卡住時，先問三個問題：是模型還是訊號的問題、是量測還是規劃的問題、是技術瓶頸還是成本邊界的問題。這三個問題會把讀者導向不同主章。&lt;/p></description><content:encoded><![CDATA[<p>效能工程與容量規劃模組的核心目標是回答兩個工程問題：目前的服務配置能承載多少負載，以及面對預期或意外的流量增長時要加多少資源。語言教材會處理 algorithm、hot path 與 memory profile 等程式層效能；本模組負責 workload modeling、壓測工具選型、saturation discovery、瓶頸定位、容量規劃、成本邊界、效能可觀測性與改進閉環。</p>
<p>本模組跟 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">模組六：可靠性驗證流程</a> 是 sibling 工程紀律。06 看「失敗模式如何被驗證」，走 SLO、Error Budget、Failure Mode、Chaos Hypothesis 的詞彙；09 看「正常負載如何被量化與規劃」，走 Workload、Saturation、Capacity、Cost、Throughput、Latency 的詞彙。兩個模組共用案例庫但讀法不同：06 從案例讀「失敗模式驗證」、09 從案例讀「容量量化實踐」。</p>
<h2 id="教材定位">教材定位</h2>
<p>效能工程的角色是把「我不知道目前配置能撐多少」這個常見焦慮，變成可量測、可重播、可改進的工程流程。</p>
<p>多數後端服務不會每天遇到高併發，真正的工程問題是平常運作時的容量地圖。平常運作正常時，目前的配置距離 saturation 還有多遠；當意外流量出現時，現有配置能撐到 autoscaling 介入嗎；要加機器時，怎麼算出該加多少、加在哪一層；加了機器之後，怎麼確認瓶頸真的被移除了。</p>
<p>這四個問題不需要假設高併發場景，而是要求系統在任何配置下都能回答「現在的容量地圖長什麼樣」。沒有這張地圖，加機器是猜測、不加機器是賭運氣、改架構是恐慌。</p>
<h2 id="教材邊界">教材邊界</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>放在語言教材</th>
          <th>放在本模組</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式層效能</td>
          <td>algorithm、data structure、hot path、memory profile、micro benchmark</td>
          <td>workload model、production traffic replay、end-to-end load test</td>
      </tr>
      <tr>
          <td>並發模型</td>
          <td>goroutine、event loop、thread pool、connection pool 的程式邊界</td>
          <td>並發設計如何決定 saturation 與 connection pressure 邊界</td>
      </tr>
      <tr>
          <td>Profiling</td>
          <td>runtime profiler、flame graph、heap dump 解讀</td>
          <td>continuous profiling 接入、profile diff 作為 regression 定位</td>
      </tr>
      <tr>
          <td>容量量測</td>
          <td>resource metric API、process memory、GC pause 訊號</td>
          <td>saturation metric、USE method、RED method、cost dashboard</td>
      </tr>
      <tr>
          <td>容量規劃</td>
          <td>（不負責）</td>
          <td>peak forecast、headroom model、growth curve、autoscaling sizing、cost ceiling</td>
      </tr>
      <tr>
          <td>壓測工具</td>
          <td>（不負責）</td>
          <td>k6、JMeter、Gatling、Locust、Vegeta、production traffic replay 工具的選型與整合</td>
      </tr>
  </tbody>
</table>
<h2 id="問題節點">問題節點</h2>
<p>問題節點先描述「不知道答案會發生什麼」，再描述「怎麼建立答案」。讀者能先理解這個問題為什麼重要，再看到怎麼處理。</p>
<table>
  <thead>
      <tr>
          <th>節點</th>
          <th>工程問題</th>
          <th>觀察訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Workload Modeling</td>
          <td>壓測模型是否貼近 production traffic shape</td>
          <td>percentile distribution、cohort mix、burst pattern</td>
      </tr>
      <tr>
          <td>Load Test Tooling</td>
          <td>該用哪種工具、怎麼整合 CI 跟 staging</td>
          <td>tool capability vs workload shape、CI 整合成本</td>
      </tr>
      <tr>
          <td>Saturation Discovery</td>
          <td>配置距離飽和還有多少 headroom</td>
          <td>throughput plateau、latency knee、resource saturation</td>
      </tr>
      <tr>
          <td>Bottleneck Localization</td>
          <td>瓶頸在哪一層、是 app / DB / cache / broker</td>
          <td>resource utilization、queue depth、connection exhaustion</td>
      </tr>
      <tr>
          <td>Capacity Planning</td>
          <td>要加多少機器、加在哪一層</td>
          <td>peak forecast、headroom budget、growth curve</td>
      </tr>
      <tr>
          <td>Cost Engineering</td>
          <td>容量擴張的成本曲線、降級的成本邊界</td>
          <td>cost per request、autoscaling cost ceiling、over-provision waste</td>
      </tr>
      <tr>
          <td>Performance Observability</td>
          <td>容量訊號怎麼看、跟 SLO 怎麼接</td>
          <td>saturation metric、cost attribution、SLO budget</td>
      </tr>
      <tr>
          <td>Improvement Loop</td>
          <td>從壓測到 release 怎麼閉環</td>
          <td>profile diff、regression gate、canary perf signal</td>
      </tr>
      <tr>
          <td>Production Validation</td>
          <td>怎麼在 production 安全驗證新配置</td>
          <td>shadow traffic、dark launch、canary perf check</td>
      </tr>
      <tr>
          <td>Peak Event Readiness</td>
          <td>預知的流量事件怎麼準備</td>
          <td>event capacity forecast、pre-warm checklist、rollback path</td>
      </tr>
  </tbody>
</table>
<p>這張表的責任是路由。當讀者卡住時，先問三個問題：是模型還是訊號的問題、是量測還是規劃的問題、是技術瓶頸還是成本邊界的問題。這三個問題會把讀者導向不同主章。</p>
<h2 id="跟既有模組的分工">跟既有模組的分工</h2>
<table>
  <thead>
      <tr>
          <th>既有模組</th>
          <th>09 與其分工</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型</a></td>
          <td>00 提供需求量化輸入（traffic / data / failure cost），09 把這些輸入翻成壓測模型與容量計畫</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性</a></td>
          <td>04 提供 metric / dashboard / SLO baseline，09 定義 saturation metric、USE / RED 訊號、cost attribution 需求</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台</a></td>
          <td>05 處理 autoscaling、HPA、load balancer 的平台實作，09 提供 capacity 規劃輸入（要 scale 到多少、什麼條件觸發）</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06 可靠性驗證</a></td>
          <td>06 看失敗模式（chaos / error budget / SLO），09 看正常負載（workload / saturation / capacity），共享 6.2 / 6.9 / 6.13 入口</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 事故處理</a></td>
          <td>08 處理 capacity-related incident 的事中事後，09 提供事前演練與容量門檻</td>
      </tr>
  </tbody>
</table>
<p>跟 06 的邊界要特別清楚。06.2 load-testing、6.9 capacity-cost、6.13 perf regression gate 留下「在驗證流程中的角色」入口；09 負責「壓測理論、模型、工具、瓶頸定位、容量規劃、成本邊界」的深化。當讀者問「load test 在 release gate 的判讀條件」屬 06；問「load test 的 workload model 怎麼設計、工具怎麼選、瓶頸怎麼定位」屬 09。</p>
<h2 id="從章節到實作的-chain">從章節到實作的 chain</h2>
<p>各章節交付三樣：問題節點、判讀訊號、控制面 link。判讀完成後沿兩條 chain 進入 implementation。</p>
<ol>
<li><strong>Mechanism chain</strong>：點問題節點表的 <code>[control-name]</code> link 進 <a href="/blog/backend/knowledge-cards/" data-link-title="Knowledge Cards" data-link-desc="用原子化卡片整理後端服務選型前需要理解的 domain knowhow">knowledge-cards</a>，那層展開機制、邊界、context-dependence。例：<code>[saturation point]</code> 的 knowledge-card 是該 control 的 mechanism SSoT。</li>
<li><strong>Delivery chain</strong>：章節「交接路由」欄位指向下游模組，包括可觀測性（saturation metric / cost dashboard）、部署平台（autoscaling policy / HPA sizing）、可靠性（perf regression gate / SLO budget）與事故處理（capacity incident playbook）。</li>
</ol>
<p>兩條 chain 走完，控制面交付完整。Implementation 強度取決於兩條 chain 的完成度，章節閱讀本身完成 routing 階段。</p>
<h2 id="主章規劃">主章規劃</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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></td>
          <td>Performance Theory</td>
          <td>Little&rsquo;s Law、queueing theory、USL、saturation curve 的工程意義</td>
      </tr>
      <tr>
          <td><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></td>
          <td>Workload Modeling</td>
          <td>把 production traffic shape 翻成可重播的壓測模型</td>
      </tr>
      <tr>
          <td><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>
          <td>Load Test Tooling</td>
          <td>k6 / JMeter / Gatling / Locust / Vegeta / Production Replay 的選型判讀</td>
      </tr>
      <tr>
          <td><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></td>
          <td>Saturation Discovery</td>
          <td>找出 throughput plateau 與 latency knee 的方法</td>
      </tr>
      <tr>
          <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></td>
          <td>Bottleneck Localization</td>
          <td>從 app 到 DB、cache、broker、第三方 quota 的逐層定位</td>
      </tr>
      <tr>
          <td><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></td>
          <td>Capacity Planning</td>
          <td>peak forecast、headroom、growth curve、autoscaling sizing</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a></td>
          <td>Cost Engineering</td>
          <td>cost per request、cost curve、降級成本、over-provisioning trade-off</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a></td>
          <td>Performance Observability</td>
          <td>saturation metric、USE / RED method、cost dashboard</td>
      </tr>
      <tr>
          <td><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 Performance Improvement Loop</a></td>
          <td>Improvement Loop</td>
          <td>壓測 → profile → fix → re-test → release gate 的閉環</td>
      </tr>
      <tr>
          <td><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></td>
          <td>Production Validation</td>
          <td>shadow traffic、dark launch、canary、production-like load test</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="活動、季節性流量、推廣事件的 capacity readiness 流程">9.11 高峰事件準備</a></td>
          <td>Peak Event Readiness</td>
          <td>活動、季節性流量、推廣事件的 capacity readiness 流程</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a></td>
          <td>SLO Coupling</td>
          <td>performance budget 跟 SLO / error budget 的對接</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸與 Stateless 前提</a></td>
          <td>Scaling Axes</td>
          <td>垂直 / 水平擴展取捨、stateless 前提、auto scaling 操作模型</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/connection-pool-amplification/" data-link-title="9.14 連線池放大解法（PgBouncer / RDS Proxy / ProxySQL）" data-link-desc="水平擴展應用層時 DB 連線池放大問題的具體解法、connection pooler 三大選項對比、解 9.13 提出但未深入的隱性成本">9.14 連線池放大解法</a></td>
          <td>Connection Pool Amplification</td>
          <td>PgBouncer / RDS Proxy / ProxySQL 對比、解 9.13 提出的連線池放大隱性成本</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>14 個主章已完成首輪正文。後續工作是補 <code>vendors/</code> 工具入口、提升案例回寫密度，並校正各章與 06 reliability 的分工。</p></blockquote>
<p>主章撰寫順序：9.1 → 9.2 → 9.4 → 9.5 → 9.6 → 9.3 → 9.8 → 9.9 → 9.7 → 9.10 → 9.11 → 9.12。理論與模型先行，工具落地放在 saturation 與 bottleneck 概念成熟之後，最後處理成本與 production 驗證的進階主題。</p>
<h2 id="案例庫規劃">案例庫規劃</h2>
<p>案例庫主軸採「AWS Customer Success Stories」公開案例。這層案例提供具體流量、實例、延遲、成本數字，比一般 engineering blog 更接近實戰判讀。完整索引、讀法與規劃中案例見 <a href="/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C 案例正文</a>。</p>
<h3 id="已發佈案例">已發佈案例</h3>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>負載形狀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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</a></td>
          <td>AWS Prime Day 2025 dogfood</td>
          <td>可預期極端峰值（SQS 1.66 億 msg/sec）</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>GR8 Tech 體育博彩 AI 預測式擴容</td>
          <td>事件型不可預期峰值（54K TPS @ 25ms p95）</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Coinbase 超低延遲交易</td>
          <td>無峰值低延遲（100K msg/sec、sub-ms）</td>
      </tr>
  </tbody>
</table>
<p>三篇對應三種負載形狀，讀完可以開始把自己的服務歸類，再回到對應主章規劃容量地圖。</p>
<h3 id="規劃中案例補不同視角與規模">規劃中案例（補不同視角與規模）</h3>
<table>
  <thead>
      <tr>
          <th>候選來源</th>
          <th>預期教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Lyft / Slack</td>
          <td>微服務 + Auto Scaling、事件型流量的擴容粒度治理</td>
      </tr>
      <tr>
          <td>Riot Games</td>
          <td>EKS 多集群（246 cluster）治理、跨地區延遲與成本平衡</td>
      </tr>
      <tr>
          <td>FanDuel</td>
          <td>直播流量 + 投注峰值的雙重峰值對齊</td>
      </tr>
      <tr>
          <td>Hotstar</td>
          <td>即時 live streaming 全球峰值（1860 萬同時觀看）</td>
      </tr>
      <tr>
          <td>Zoom</td>
          <td>COVID 期間 30 倍成長（1000 萬 → 3 億 DAU）</td>
      </tr>
  </tbody>
</table>
<h3 id="engineering-blog-補充候選">Engineering Blog 補充候選</h3>
<p>當 AWS 案例缺乏某些工程紀律的深度（例如 chaos hypothesis、cell-based architecture 細節），補引 engineering blog 作為交叉驗證。候選來源：Shopify BFCM、Netflix Tech Blog、Amazon Builders&rsquo; Library、Google SRE Book、LinkedIn Engineering、Stripe Engineering、Cloudflare Blog、Discord Engineering、Uber Engineering、Pinterest Engineering 等。這層不另開資料夾，補在主章「案例對照」段。</p>
<h2 id="跨語言適配評估">跨語言適配評估</h2>
<p>效能工程使用方式會受語言的並發模型、runtime overhead、profiler 工具鏈與 client library 成熟度影響。</p>
<ol>
<li>同步 thread-based runtime（Java、C#、傳統 Python / Ruby）：<a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a> 是首要瓶頸、blocking I/O 會把 thread 鎖住、壓測時要量 thread saturation 跟 pool exhaustion。</li>
<li>async / event-loop runtime（Node.js、Python asyncio、Tokio）：要量 event loop lag、避免 CPU-bound work 阻塞 loop、<a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a> 失控時 throughput 跟 latency 會同時崩。</li>
<li>Goroutine 或 lightweight task runtime（Go、Erlang）：goroutine 廉價但下游連線、檔案 handle、broker channel 仍是昂貴資源、要量「廉價並發 → 昂貴資源」的轉換點。</li>
<li>JIT 語言（JVM、.NET）：warmup 期 latency 高、壓測要區分 cold 與 warm 階段、profile diff 要排除 GC noise。</li>
<li>AOT 語言（Go、Rust、C++）：<a href="/blog/backend/knowledge-cards/cold-start/" data-link-title="Cold Start" data-link-desc="說明服務或快取剛啟動時尚未累積狀態造成的延遲與壓力">cold start</a> 較快、但 GC（Go）或 allocator 行為仍影響長時間 latency。</li>
<li>動態語言（Python、Ruby、PHP）：interpreter overhead 是基線、要先排除 framework 預設配置的隱性成本（worker model、GIL、autoload）。</li>
</ol>
<h2 id="服務分類規範">服務分類規範</h2>
<p>每個討論具體壓測工具或容量服務的章節（k6、JMeter、Gatling、Locust、Vegeta、Grafana k6 Cloud、AWS Distributed Load Testing、Datadog Synthetics、Akamas），都必須包含「成本權衡與機會成本」段落，至少回答：</p>
<ol>
<li>這個工具降低哪一種風險（容量未知、缺少持續驗證、缺少瓶頸定位）。</li>
<li>工具本身的維運成本：runner、artifact、結果儲存、CI 整合成本。</li>
<li>在大規模壓測下會增加哪些雲端成本（流量費、跨區、目標服務的容量壓力）。</li>
<li>團隊需要承擔哪些前置成本：workload model 設計、結果判讀、baseline 維護。</li>
<li>若選擇更簡單方案（人工 ad-hoc 壓測），會承擔哪些風險。</li>
<li>什麼條件出現時，原本的工具選擇應該被重新評估。</li>
</ol>
<h2 id="vendor-清單">Vendor 清單</h2>
<p>實作工具見 <a href="/blog/backend/09-performance-capacity/vendors/" data-link-title="效能與容量工具清單" data-link-desc="整理效能工程、容量規劃、壓測、production replay 與 profiling 工具的服務責任與選型路由">vendors</a> — 已建立 k6 / JMeter / Gatling / Locust / Vegeta 五個壓測工具頁、GoReplay / Service Mesh Mirroring / AWS VPC Traffic Mirroring 三個 production traffic replay 頁，Datadog Continuous Profiler / Pyroscope / Parca 三個 continuous profiling 頁，以及 Akamas / Vantage / CloudHealth / AWS Cost Explorer 四個 capacity / cost analysis 頁。跟 <a href="/blog/backend/06-reliability/vendors/" data-link-title="可靠性 Vendor 清單" data-link-desc="規劃 CI、壓測、chaos engineering 與 SLO 工具的服務頁撰寫順序與判準">06 vendors</a> 的差異：06 收錄壓測工具是為了「驗證流程的工具鏈」、09 收錄是為了「效能工程的工具鏈」、選型角度不同。</p>
<p>Deep article（工具自身的配置、故障、容量）跟 migration playbook（跨工具遷移流程）的撰寫進度見 <a href="/blog/backend/09-performance-capacity/vendors/" data-link-title="效能與容量工具清單" data-link-desc="整理效能工程、容量規劃、壓測、production replay 與 profiling 工具的服務責任與選型路由">vendors/</a> 的「內容覆蓋進度」段。</p>
<h2 id="09-模組專屬知識卡片">09 模組專屬知識卡片</h2>
<p>09 模組已建立 22 張效能工程與容量規劃專屬卡片、覆蓋理論基礎、量測方法、規劃決策、production 驗證與 SLO 治理四個面向。</p>
<p><strong>理論基礎（5 張）</strong>：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/little-law/" data-link-title="Little&#39;s Law" data-link-desc="說明系統內並發數、到達率與逗留時間三者的數學關係">Little&rsquo;s Law</a> — 並發、到達率、逗留時間的數學關係</li>
<li><a href="/blog/backend/knowledge-cards/universal-scalability-law/" data-link-title="Universal Scalability Law (USL)" data-link-desc="說明系統擴容到一定規模後吞吐反而下降的數學模型">Universal Scalability Law</a> — 擴容到某點後 throughput 反向下降的數學模型</li>
<li><a href="/blog/backend/knowledge-cards/saturation-point/" data-link-title="Saturation Point" data-link-desc="說明系統從線性穩態進入 latency 指數成長區的關鍵流量點">Saturation Point</a> — linear / knee / cliff 三段曲線的臨界點</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> — 資源層 Utilization / Saturation / Errors</li>
<li><a href="/blog/backend/knowledge-cards/red-method/" data-link-title="RED Method" data-link-desc="Tom Wilkie 提出的請求層 Rate / Errors / Duration 三維度量測法">RED Method</a> — 請求層 Rate / Errors / Duration</li>
</ul>
<p><strong>Workload 與容量規劃（8 張）</strong>：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/workload-model/" data-link-title="Workload Model" data-link-desc="描述 production traffic 形狀的可重播模型 — 容量規劃跟壓測的共同輸入">Workload Model</a> — production traffic shape 量化模型</li>
<li><a href="/blog/backend/knowledge-cards/tail-latency/" data-link-title="Tail Latency" data-link-desc="說明 p99 / p999 等長尾延遲為何比平均延遲更能反映 saturation">Tail Latency</a> — p99 / p999 長尾為何比平均更能反映 saturation</li>
<li><a href="/blog/backend/knowledge-cards/hot-partition/" data-link-title="Hot Partition" data-link-desc="說明分散式 KV / OLTP 中、單一 partition 流量遠超其他的容量問題">Hot Partition</a> — 分散式 KV 的隱性 saturation</li>
<li><a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast</a> — 預期峰值的預測方法</li>
<li><a href="/blog/backend/knowledge-cards/headroom-budget/" data-link-title="Headroom Budget" data-link-desc="說明容量規劃中為應付異常 burst &#43; AZ 故障 &#43; forecast 誤差的安全餘量">Headroom Budget</a> — 容量規劃的安全餘量</li>
<li><a href="/blog/backend/knowledge-cards/growth-curve/" data-link-title="Growth Curve" data-link-desc="說明用戶 / 流量隨時間成長的五種典型形狀、影響容量規劃方法">Growth Curve</a> — 五種典型成長形狀</li>
<li><a href="/blog/backend/knowledge-cards/predictive-scaling/" data-link-title="Predictive Scaling" data-link-desc="說明用歷史模式或 ML 模型預測流量、提前擴容的 autoscaler 模式">Predictive Scaling</a> — 預測式擴容</li>
<li><a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">Scheduled Scaling</a> — 已知時間表預先擴容</li>
</ul>
<p><strong>Production 驗證（5 張）</strong>：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic</a> — production traffic 複製驗證</li>
<li><a href="/blog/backend/knowledge-cards/dark-launch/" data-link-title="Dark Launch" data-link-desc="新功能上線但暫不開放 UI 入口、走 production traffic 但對用戶不可見的發布模式">Dark Launch</a> — UI 入口暫不開放的發布模式</li>
<li><a href="/blog/backend/knowledge-cards/canary-perf-check/" data-link-title="Canary Perf Check" data-link-desc="canary release 中針對 latency / throughput 而非 error rate 的退化檢查">Canary Perf Check</a> — canary 階段的 latency 退化檢查</li>
<li><a href="/blog/backend/knowledge-cards/profile-diff/" data-link-title="Profile Diff" data-link-desc="對比兩次 profile（如 release candidate vs baseline）找出 hottest 變化">Profile Diff</a> — 兩次 profile 對比找退化原因</li>
<li><a href="/blog/backend/knowledge-cards/continuous-profiling/" data-link-title="Continuous Profiling" data-link-desc="在 production 持續取得低 overhead profile 的觀察方法">Continuous Profiling</a> — production 持續低 overhead profile</li>
</ul>
<p><strong>成本與 SLO（4 張）</strong>：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/cost-per-request/" data-link-title="Cost Per Request" data-link-desc="把雲端成本拆到單一 API 請求的 unit economics 模型">Cost Per Request</a> — 雲端成本 unit economics</li>
<li><a href="/blog/backend/knowledge-cards/performance-budget/" data-link-title="Performance Budget" data-link-desc="跟 error budget 同類概念、但用於 latency / throughput 退化的可控額度">Performance Budget</a> — 跟 error budget 並列的效能退化額度</li>
<li><a href="/blog/backend/knowledge-cards/latency-budget/" data-link-title="Latency Budget" data-link-desc="把 user-perceived latency 拆到每個 stage 的配額、反推架構選擇">Latency Budget</a> — end-to-end latency 拆到每 stage 配額</li>
<li><a href="/blog/backend/knowledge-cards/slo-baseline-drift/" data-link-title="SLO Baseline Drift" data-link-desc="SLO baseline 因業務變化 / surge / 架構改動而需要重新校準的現象">SLO Baseline Drift</a> — SLO 需要重新校準的現象</li>
</ul>
<h2 id="既有可引用卡片">既有可引用卡片</h2>
<p>從其他模組沿用的卡片：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/load-test/" data-link-title="Load Test" data-link-desc="說明在預期流量下驗證容量、延遲與降級策略的測試">Load Test</a></li>
<li><a href="/blog/backend/knowledge-cards/throughput/" data-link-title="Throughput" data-link-desc="整理系統單位時間內可處理的工作量">Throughput</a></li>
<li><a href="/blog/backend/knowledge-cards/consumer-capacity/" data-link-title="Consumer Capacity" data-link-desc="說明 consumer 群組每秒能穩定處理多少工作">Consumer Capacity</a></li>
<li><a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">Connection Pool</a></li>
<li><a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">Backpressure</a></li>
<li><a href="/blog/backend/knowledge-cards/load-shedding/" data-link-title="Load Shedding" data-link-desc="說明服務過載時如何主動拒絕低優先工作以保護核心能力">Load Shedding</a></li>
<li><a href="/blog/backend/knowledge-cards/rate-limit/" data-link-title="Rate Limit" data-link-desc="說明限流如何保護服務入口、下游依賴與租戶公平性">Rate Limit</a></li>
<li><a href="/blog/backend/knowledge-cards/cold-start/" data-link-title="Cold Start" data-link-desc="說明服務或快取剛啟動時尚未累積狀態造成的延遲與壓力">Cold Start</a></li>
<li><a href="/blog/backend/knowledge-cards/thundering-herd/" data-link-title="Thundering Herd" data-link-desc="說明大量工作同時被喚醒或同時競爭資源時的尖峰風險">Thundering Herd</a></li>
<li><a href="/blog/backend/knowledge-cards/bulkhead/" data-link-title="Bulkhead" data-link-desc="說明 bulkhead 如何用資源分艙限制故障擴散">Bulkhead</a></li>
<li><a href="/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI / SLO</a></li>
<li><a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">Error Budget</a></li>
<li><a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">Metric Cardinality</a></li>
<li><a href="/blog/backend/knowledge-cards/game-day/" data-link-title="Game Day" data-link-desc="說明事故演練如何驗證流程、工具與團隊協作">Game Day</a></li>
<li><a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">Blast Radius</a></li>
<li><a href="/blog/backend/knowledge-cards/autoscaling/" data-link-title="Autoscaling" data-link-desc="說明系統如何依負載自動調整服務實例數量">Autoscaling</a></li>
</ul>
<h2 id="模組方法">模組方法</h2>
<p>問題驅動方法的核心是讓案例退到證據角色，讓知識網以「容量量化問題」為主體。</p>
<ol>
<li>先定義效能或容量問題的責任邊界。</li>
<li>再定義判讀訊號（saturation curve、cost curve、percentile distribution）與門檻條件。</li>
<li>接著定義交接路由與前置控制面。</li>
<li>最後在問題觸發時引用對應服務案例。</li>
</ol>
<h2 id="規劃方向">規劃方向</h2>
<p>本模組的核心是把模組架構為「容量量化問題 + 服務級實踐案例」兩層結構。</p>
<ol>
<li><strong>問題節點先行</strong>：9.1-9.12 主章已建立理論、模型、工具、saturation、瓶頸、容量、成本、可觀測性、改進閉環、production 驗證、高峰準備與 SLO 對接的基礎。</li>
<li><strong>服務級案例庫</strong>：以公開效能與容量實踐（Shopify BFCM / Netflix scale / Amazon cost / Google performance budget / LinkedIn capacity planning）作 cases，每個服務累積容量規劃脈絡。</li>
<li><strong>跟 06 共用案例但不同讀法</strong>：服務 case 同一批、但 06 讀「失敗模式驗證」、09 讀「容量量化實踐」、避免重複案例蒐集成本。</li>
</ol>
<p>不經實作即可推進的理由：效能工程的價值在「容量地圖建立與成本邊界判讀」，這層跟具體框架解耦，performance engineering 公開素材成熟，符合先建概念層的條件。</p>
<h2 id="tripwire">Tripwire</h2>
<ul>
<li>寫到第 6 章發現持續繞回 06 已有章節 → 軸線過於相似、合併回 06 或重切。</li>
<li>案例庫跟 06 cases/ 重疊度 &gt; 70% → 改共用 06 案例、不另起一份。</li>
<li>工具章節寫起來像 vendor 比較表、缺判讀邏輯 → 改寫成「workload model → 工具選型」的決策章節。</li>
<li>9.6 capacity planning 跟 9.7 cost engineering 變成兩篇都在講同一個 trade-off → 合併。</li>
<li>9.10 production validation 跟 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">06.20 experiment safety boundary</a> 內容開始重疊 → 明確分工：9.10 走「正常負載驗證」、6.20 走「故障注入安全邊界」。</li>
<li>寫 T1 服務第 3 個時、若 case 之間無共通分類軸 → 改用單服務獨立檔，不開資料夾。</li>
</ul>
<h2 id="模組完成狀態">模組完成狀態</h2>
<p>模組主章與案例庫已完成首輪正文，<code>vendors/</code> 已建立壓測工具、production traffic replay 與 continuous profiling 第一批工具頁。後續工作排序：先補 capacity / cost analysis 工具頁，再提高 9.7-9.12 對案例的回寫密度，最後整理跟 06 reliability 共用案例的分工。</p>
<hr>
<p><em>文件版本：v0.1.0</em>
<em>最後更新：2026-05-12</em>
<em>系列狀態：主章首輪完成，進入工具入口與案例回寫補強</em></p>
]]></content:encoded></item><item><title>9.10 Production-Side 驗證</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/production-validation/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/production-validation/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Production-side 驗證的責任是回答「staging 過了 production 一定過嗎」。多數 staging 環境的硬體 / 流量 / 資料 / 第三方依賴都跟 production 不一樣、staging 通過不代表 production 安全。本章處理「在 production 安全驗證新配置」的工程做法。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">06.20 experiment safety boundary&lt;/a> 的關係：06.20 走「故障注入」的安全邊界（chaos）、9.10 走「正常負載」的 production 驗證（perf）。兩者方法論類似、目標完全不同。chaos test 是「主動破壞看會不會出事」、production perf validation 是「真實流量看新版本能不能跑」。&lt;/p>
&lt;p>本章四個工具（shadow traffic、dark launch、canary、production-like load test）按 &lt;em>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a>&lt;/em> 從小到大排列、每個適合不同驗證場景。&lt;/p>
&lt;h2 id="shadow-traffic">Shadow traffic&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow traffic&lt;/a> 是 blast radius 最小的工具：複製 production traffic 到新版本、但 &lt;em>不把結果返回用戶&lt;/em>。&lt;/p>
&lt;p>&lt;strong>運作機制&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>用戶看到的還是舊版本回應、體驗不變&lt;/li>
&lt;li>新版本只是「並行跑、看會不會崩」&lt;/li>
&lt;li>新版本的結果可以跟舊版本對比、找出邏輯差異&lt;/li>
&lt;li>對下游的寫入要 &lt;em>特別處理&lt;/em>：要麼寫入 sandbox、要麼 dry-run（純驗證 query plan、不真寫）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>工具實作&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>GoReplay：tcpdump-based 開源、適合 HTTP&lt;/li>
&lt;li>Service mesh shadow（Istio、Linkerd mirror）：mesh 層 mirror、零 application invasion&lt;/li>
&lt;li>AWS VPC Traffic Mirroring：底層網路層、加密 traffic 要另處理&lt;/li>
&lt;li>Diffy（已 deprecated 但概念有效）：dual-write 對比結果&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>適合場景&lt;/strong>：架構大改、想驗證 &lt;em>是否能撐 production traffic&lt;/em> 但不能影響用戶。例如「DB 從 PostgreSQL 換 Aurora、想看新 DB 在真實 query pattern 下穩不穩」。&lt;/p>
&lt;p>&lt;strong>注意事項&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>shadow traffic 也消耗 production 下游資源（DB read、API call）— 必須算進容量&lt;/li>
&lt;li>加密 / PII 資料需要處理&lt;/li>
&lt;li>shadow 通常跑 1-7 天看 long-tail、不是 30 分鐘就下結論&lt;/li>
&lt;/ul>
&lt;p>對應案例：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">Tixcraft 10K t2.micro 壓測&lt;/a> — pre-event 壓測但走 staging；real shadow 則是 &lt;em>production-traffic-driven&lt;/em> 而非合成。&lt;/p>
&lt;h2 id="dark-launch">Dark launch&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dark-launch/" data-link-title="Dark Launch" data-link-desc="新功能上線但暫不開放 UI 入口、走 production traffic 但對用戶不可見的發布模式">Dark launch&lt;/a> 介於 shadow 跟 canary 之間：程式碼上線、走 production traffic、但 &lt;em>UI 入口暫不開放&lt;/em>。&lt;/p>
&lt;p>&lt;strong>跟 shadow 的差別&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Shadow：traffic 複製、新版本 &lt;em>不寫入真實狀態&lt;/em>&lt;/li>
&lt;li>Dark launch：&lt;em>真實寫入 production&lt;/em>、但用戶看不到 UI&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>運作機制&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Production-side 驗證的責任是回答「staging 過了 production 一定過嗎」。多數 staging 環境的硬體 / 流量 / 資料 / 第三方依賴都跟 production 不一樣、staging 通過不代表 production 安全。本章處理「在 production 安全驗證新配置」的工程做法。</p>
<p>跟 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">06.20 experiment safety boundary</a> 的關係：06.20 走「故障注入」的安全邊界（chaos）、9.10 走「正常負載」的 production 驗證（perf）。兩者方法論類似、目標完全不同。chaos test 是「主動破壞看會不會出事」、production perf validation 是「真實流量看新版本能不能跑」。</p>
<p>本章四個工具（shadow traffic、dark launch、canary、production-like load test）按 <em><a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a></em> 從小到大排列、每個適合不同驗證場景。</p>
<h2 id="shadow-traffic">Shadow traffic</h2>
<p><a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow traffic</a> 是 blast radius 最小的工具：複製 production traffic 到新版本、但 <em>不把結果返回用戶</em>。</p>
<p><strong>運作機制</strong>：</p>
<ul>
<li>用戶看到的還是舊版本回應、體驗不變</li>
<li>新版本只是「並行跑、看會不會崩」</li>
<li>新版本的結果可以跟舊版本對比、找出邏輯差異</li>
<li>對下游的寫入要 <em>特別處理</em>：要麼寫入 sandbox、要麼 dry-run（純驗證 query plan、不真寫）</li>
</ul>
<p><strong>工具實作</strong>：</p>
<ul>
<li>GoReplay：tcpdump-based 開源、適合 HTTP</li>
<li>Service mesh shadow（Istio、Linkerd mirror）：mesh 層 mirror、零 application invasion</li>
<li>AWS VPC Traffic Mirroring：底層網路層、加密 traffic 要另處理</li>
<li>Diffy（已 deprecated 但概念有效）：dual-write 對比結果</li>
</ul>
<p><strong>適合場景</strong>：架構大改、想驗證 <em>是否能撐 production traffic</em> 但不能影響用戶。例如「DB 從 PostgreSQL 換 Aurora、想看新 DB 在真實 query pattern 下穩不穩」。</p>
<p><strong>注意事項</strong>：</p>
<ul>
<li>shadow traffic 也消耗 production 下游資源（DB read、API call）— 必須算進容量</li>
<li>加密 / PII 資料需要處理</li>
<li>shadow 通常跑 1-7 天看 long-tail、不是 30 分鐘就下結論</li>
</ul>
<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> — pre-event 壓測但走 staging；real shadow 則是 <em>production-traffic-driven</em> 而非合成。</p>
<h2 id="dark-launch">Dark launch</h2>
<p><a href="/blog/backend/knowledge-cards/dark-launch/" data-link-title="Dark Launch" data-link-desc="新功能上線但暫不開放 UI 入口、走 production traffic 但對用戶不可見的發布模式">Dark launch</a> 介於 shadow 跟 canary 之間：程式碼上線、走 production traffic、但 <em>UI 入口暫不開放</em>。</p>
<p><strong>跟 shadow 的差別</strong>：</p>
<ul>
<li>Shadow：traffic 複製、新版本 <em>不寫入真實狀態</em></li>
<li>Dark launch：<em>真實寫入 production</em>、但用戶看不到 UI</li>
</ul>
<p><strong>運作機制</strong>：</p>
<ul>
<li>後端 code 部署到 production</li>
<li>用 feature flag 控制 UI 暴露</li>
<li>從內部 API、cron job、employee-only access 觸發新功能</li>
<li>真正寫入 production DB / cache / queue</li>
<li>用戶看不到 UI 入口、無感</li>
</ul>
<p><strong>Exit criteria</strong>：</p>
<ul>
<li>跑足夠時間（通常 1-2 週）</li>
<li>內部使用沒有 critical issue</li>
<li>metric 在預期範圍</li>
</ul>
<p><strong>適合場景</strong>：新功能後端風險高、想 production-validate 再開放給用戶。
<strong>不適合</strong>：純 UI 改動（沒有後端風險、直接 canary）。</p>
<p>對應案例：<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 服務">SeatGeek Virtual Waiting Room</a> 從第三方換到自建、必然有 dark launch 階段驗證 token 配發機制、再正式 cutover。</p>
<h2 id="canary">Canary</h2>
<p>Canary 是 production-side 驗證最常用工具：小比例流量導到新版本、跟舊版本對比。</p>
<p><strong>運作機制</strong>：</p>
<ul>
<li>小比例（1% / 5% / 10%）流量導到新版本</li>
<li>大部分流量（99% / 95% / 90%）走舊版本</li>
<li>比較 perf / error / business metric</li>
<li>通過 → 漸進放大；不通過 → 自動 rollback</li>
</ul>
<p><strong>漸進放大策略</strong>：1% → 5% → 25% → 50% → 100%、每階段觀察足夠時間（至少 15 分鐘看 long-tail）。</p>
<p><strong>自動 rollback 條件</strong>：</p>
<ul>
<li>error rate canary 比 control 高 X%（例如 50%）</li>
<li>p99 latency canary 比 control 退化 X%（例如 10%）</li>
<li>business metric（conversion rate）canary 比 control 低 X%</li>
</ul>
<p><strong>Canary perf check 跟一般 canary 的差異</strong>：</p>
<ul>
<li>一般 canary：看 error rate 為主</li>
<li><a href="/blog/backend/knowledge-cards/canary-perf-check/" data-link-title="Canary Perf Check" data-link-desc="canary release 中針對 latency / throughput 而非 error rate 的退化檢查">Canary perf check</a>：看 latency / throughput / cost、退化通常早於 error rate</li>
</ul>
<p><strong>比較的對象是 control（同時跑的舊版本）、不是 baseline</strong>：同樣流量同樣時段才能對比、不能拿 canary 跟昨天 baseline 比（外部變數太多）。</p>
<p>對應案例：<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 的峰值數字 — 一年一次可預期峰值的容量設計參考">Prime Day pre-event 驗證</a> / <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; 州的雙重峰值">FanDuel canary across 20 州</a> — 按 region 漸進放大、控制 blast radius。</p>
<h2 id="production-like-load-test">Production-like load test</h2>
<p>當需要驗證 <em>peak 場景</em> 但 production 平日流量達不到時、在 production 跑額外的 synthetic load。</p>
<p><strong>為什麼要在 production 跑</strong>：</p>
<ul>
<li>staging 環境的硬體 / 網路 / 第三方依賴跟 production 不同</li>
<li>staging 沒有 production 級資料量、cache hit pattern 不一樣</li>
<li>只有 production 才能驗證真實 peak</li>
</ul>
<p><strong>風險高、必須有安全邊界</strong>：</p>
<ul>
<li>blast radius 限制（用 dedicated test endpoint、限制影響範圍）</li>
<li>abort condition（什麼訊號觸發停止）</li>
<li>rollback path（rollback 流程跟時間）</li>
<li>通訊（相關 oncall 通知、避免誤判 incident）</li>
</ul>
<p><strong>通常用在</strong>：</p>
<ul>
<li>Pre-event 壓測（Black Friday、Super Bowl、IPL 決賽 前一週）</li>
<li>重大架構變更後驗證</li>
<li>容量規劃 review（每年 / 每季）</li>
</ul>
<p><strong>跟 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">06.20 experiment safety boundary</a> 同等嚴格的安全要求</strong>：production 壓測本質是 controlled experiment、必須有 game day-level 的計畫跟人員。</p>
<p>對應案例：<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 的峰值數字 — 一年一次可預期峰值的容量設計參考">Prime Day FIS 8x chaos</a> — 把 chaos test 跟 load test 結合、production-like 驗證；<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> — pre-event 大規模壓測模擬實際售票場景。</p>
<h2 id="ab-test-與-perf-對齊">A/B test 與 perf 對齊</h2>
<p>Product A/B test（測試新功能對 conversion 的影響）同時也是 perf A/B test。</p>
<p><strong>為什麼要對齊</strong>：</p>
<ul>
<li>新 feature 可能帶來 perf 退化（多 query、多 component、額外 logic）</li>
<li>純看 conversion lift 會誤判：「conversion 上升、所以 OK」可能掩蓋「但 p99 上升 30%」</li>
<li>A/B 同時看 conversion 跟 perf 兩個 metric</li>
</ul>
<p><strong>Guardrails</strong>：</p>
<ul>
<li>業務 metric 改善 + perf 退化 → 工程判斷是否值得（trade-off review）</li>
<li>業務 metric 沒改善 + perf 退化 → 直接 reject</li>
<li>業務 metric 改善 + perf 改善 → 直接 ship</li>
<li>業務 metric 退化 → 不論 perf 怎樣、reject</li>
</ul>
<p>對應 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">06.20 experiment safety boundary</a> 的 experiment guardrails。</p>
<h2 id="pre-event-readiness-checkgame-day">Pre-event readiness check（game day）</h2>
<p>大事件前跑「全系統 production-like 壓測」、是 production-side 驗證的整合演練。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="活動、季節性流量、推廣事件的 capacity readiness 流程">9.11 高峰事件準備</a> 直接對接 — game day 是 readiness 流程的一個 stage。</p>
<p>Shopify game day、Stripe game day 是業界範本（<a href="/blog/backend/06-reliability/cases/" data-link-title="可靠性服務案例庫" data-link-desc="按服務組織的 SRE 實踐案例庫，累積架構脈絡與工程文化">06 cases</a> 有完整案例）。</p>
<h2 id="安全邊界設計">安全邊界設計</h2>
<p>任何 production-side 驗證都要有清楚的安全邊界、不能臨機應變。</p>
<p><strong>Blast radius</strong>：</p>
<ul>
<li>影響哪些用戶（X% 流量、特定 cohort、特定 region）</li>
<li>影響哪些 service（受 perf 影響的下游）</li>
<li>影響哪些 metric（哪些 business metric 可能變化）</li>
</ul>
<p><strong>Abort condition</strong>：</p>
<ul>
<li>什麼訊號觸發停止（error rate &gt; X%、latency &gt; Y ms、特定 alert 觸發）</li>
<li>由誰觸發（自動 vs oncall 手動）</li>
<li>觸發後多久內必須完成 abort（&lt; 60 秒）</li>
</ul>
<p><strong>Rollback path</strong>：</p>
<ul>
<li>rollback 流程是什麼（feature flag、deployment rollback、traffic shift）</li>
<li>rollback 需要多久（target &lt; 5 分鐘）</li>
<li>rollback 是否需要 data 處理（已寫入的資料怎麼處理）</li>
</ul>
<p><strong>通訊</strong>：</p>
<ul>
<li>啟動驗證前 notify 哪些 channel</li>
<li>期間 oncall 待命</li>
<li>結束後 retro</li>
</ul>
<h2 id="反模式">反模式</h2>
<ul>
<li><strong>Canary 比例太大</strong>（50% 起跳）：出事影響大、blast radius 失控</li>
<li><strong>沒 control group</strong>：不知道 baseline、看絕對數字會誤判</li>
<li><strong>Canary 跑太短時間</strong>（&lt; 15 分鐘）：看不到 long-tail、看不到 user pattern shift</li>
<li><strong>沒 abort condition</strong>：人工監控失誤就出事、不可預測</li>
<li><strong>shadow traffic 寫入真實狀態</strong>：可能造成 double charge、duplicate notification</li>
<li><strong>production load test 沒 notify 相關團隊</strong>：被當成 incident、誤觸 escalation</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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 FIS 8x</a></td>
          <td>pre-event chaos + perf 驗證</td>
      </tr>
      <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 10K t2.micro 壓測</a></td>
          <td>pre-event 大規模壓測</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>跨 20 州 canary 控制 blast radius</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>從第三方換到自建的 dark launch</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<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></li>
<li>下游：<a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="活動、季節性流量、推廣事件的 capacity readiness 流程">9.11 高峰事件準備</a></li>
<li>跨模組：<a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">06.20 experiment safety boundary</a> / <a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">06.4 chaos testing</a></li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic</a></li>
<li><a href="/blog/backend/knowledge-cards/dark-launch/" data-link-title="Dark Launch" data-link-desc="新功能上線但暫不開放 UI 入口、走 production traffic 但對用戶不可見的發布模式">Dark Launch</a></li>
<li><a href="/blog/backend/knowledge-cards/canary-perf-check/" data-link-title="Canary Perf Check" data-link-desc="canary release 中針對 latency / throughput 而非 error rate 的退化檢查">Canary Perf Check</a></li>
</ul>
]]></content:encoded></item><item><title>9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/</guid><description>&lt;p>這個案例的核心責任是提供「全球一致性 OLTP」的容量參考點。Spanner 是 Google 內部支撐 Ads、Play、Cloud Search 等服務的核心 DB、後來開放為 GCP 服務、是少數公開能撐每秒 10 億請求且維持強一致性的 OLTP 資料庫。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Spanner 公開數字（引自 &lt;a href="https://cloud.google.com/spanner">Spanner overview&lt;/a> / &lt;a href="https://cloud.google.com/spanner/docs/performance">Spanner performance docs&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>內部峰值&lt;/td>
 &lt;td>&amp;gt; 10 億 requests / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Spanner Omni 區域峰值&lt;/td>
 &lt;td>數百萬 QPS、PB 級資料量&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>線性擴展性&lt;/td>
 &lt;td>2 nodes → 45000 reads/sec、4 nodes → 90000 reads/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一致性模型&lt;/td>
 &lt;td>external consistency（強一致 + 線性化）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>代表性客戶：Google 內部所有支付、廣告計費、Play 商店、Search 索引；公開客戶包括 Blockchain.com、Niantic（部分服務）、Sharechat、ZEE5、Wayfair。&lt;/p>
&lt;p>關鍵設計：TrueTime API（GPS + 原子鐘）讓跨地區交易能維持 external consistency、不是 eventual。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Spanner 案例最值得讀的不是「能撐多大」、是「為什麼要這樣設計才能撐」。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>線性擴展是 OLTP 的最高設計目標&lt;/strong>：「2 nodes → 45K reads/sec、4 nodes → 90K reads/sec」這個 linear scaling 在傳統 OLTP（PostgreSQL、MySQL）做不到 — 因為 &lt;em>跨節點交易&lt;/em> 需要 coordinator、coordinator 是 bottleneck。Spanner 用 Paxos + TrueTime 把 coordinator 變成「拓樸感知的多 leader」、才達成線性。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的設計取捨。&lt;/li>
&lt;li>&lt;strong>強一致 vs 全球部署不是必須二選&lt;/strong>：CAP 定理常被解讀為「全球部署只能 eventual consistency」、Spanner 顯示「投入專屬硬體（GPS、原子鐘）+ 演算法（TrueTime）可以同時拿到 strong consistency + global distribution」。但這套硬體投資對其他 vendor 不容易複製。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的全球 OLTP 選項。&lt;/li>
&lt;li>&lt;strong>計費粒度 = 容量規劃顆粒&lt;/strong>：Spanner 早期最小單位是 100 processing units（pu）≈ 1 node、太大讓中小負載難以用。後來推出 100 pu 起跳的 granular sizing、讓容量規劃可以從小開始。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的容量單位選擇。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「10 億 req/sec」是 Google 內部的某個峰值瞬間、是 Spanner 服務 &lt;em>全部使用者加總&lt;/em>、不是單一 instance 數字。讀案例時要區分「全球聚合峰值」跟「單一客戶能拿到的最大配額」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>跨地區一致性需求要在設計初期決定&lt;/strong>：如果業務必需 strong consistency（金融、ticketing）、選 Spanner 等對等服務；如果 eventual 可接受（社群、推薦）、選 Cassandra / DynamoDB Global Tables 等更便宜的選項。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的全球一致性需求識別。&lt;/li>
&lt;li>&lt;strong>節點數即容量單位、預先規劃 sizing&lt;/strong>：Spanner 容量 = 節點數 × 單節點 QPS。每年 capacity review 主要在調節點數、不在調 schema。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>。&lt;/li>
&lt;li>&lt;strong>跨地區 latency 是強一致的代價&lt;/strong>：external consistency 必須等多區 quorum、跨洲交易延遲可達 100-200ms。延遲敏感型業務不能用跨地區 strong consistency。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 反推。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS Aurora DSQL（2024 推出、跨地區 strong consistency）、CockroachDB（自管）、TiDB（自管或 cloud）都是對等候選。差異是 TrueTime / 同等同步機制的成熟度。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「全球一致性 OLTP」的容量參考點。Spanner 是 Google 內部支撐 Ads、Play、Cloud Search 等服務的核心 DB、後來開放為 GCP 服務、是少數公開能撐每秒 10 億請求且維持強一致性的 OLTP 資料庫。</p>
<h2 id="觀察">觀察</h2>
<p>Spanner 公開數字（引自 <a href="https://cloud.google.com/spanner">Spanner overview</a> / <a href="https://cloud.google.com/spanner/docs/performance">Spanner performance docs</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>內部峰值</td>
          <td>&gt; 10 億 requests / 秒</td>
      </tr>
      <tr>
          <td>Spanner Omni 區域峰值</td>
          <td>數百萬 QPS、PB 級資料量</td>
      </tr>
      <tr>
          <td>線性擴展性</td>
          <td>2 nodes → 45000 reads/sec、4 nodes → 90000 reads/sec</td>
      </tr>
      <tr>
          <td>一致性模型</td>
          <td>external consistency（強一致 + 線性化）</td>
      </tr>
  </tbody>
</table>
<p>代表性客戶：Google 內部所有支付、廣告計費、Play 商店、Search 索引；公開客戶包括 Blockchain.com、Niantic（部分服務）、Sharechat、ZEE5、Wayfair。</p>
<p>關鍵設計：TrueTime API（GPS + 原子鐘）讓跨地區交易能維持 external consistency、不是 eventual。</p>
<h2 id="判讀">判讀</h2>
<p>Spanner 案例最值得讀的不是「能撐多大」、是「為什麼要這樣設計才能撐」。</p>
<ol>
<li><strong>線性擴展是 OLTP 的最高設計目標</strong>：「2 nodes → 45K reads/sec、4 nodes → 90K reads/sec」這個 linear scaling 在傳統 OLTP（PostgreSQL、MySQL）做不到 — 因為 <em>跨節點交易</em> 需要 coordinator、coordinator 是 bottleneck。Spanner 用 Paxos + TrueTime 把 coordinator 變成「拓樸感知的多 leader」、才達成線性。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的設計取捨。</li>
<li><strong>強一致 vs 全球部署不是必須二選</strong>：CAP 定理常被解讀為「全球部署只能 eventual consistency」、Spanner 顯示「投入專屬硬體（GPS、原子鐘）+ 演算法（TrueTime）可以同時拿到 strong consistency + global distribution」。但這套硬體投資對其他 vendor 不容易複製。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的全球 OLTP 選項。</li>
<li><strong>計費粒度 = 容量規劃顆粒</strong>：Spanner 早期最小單位是 100 processing units（pu）≈ 1 node、太大讓中小負載難以用。後來推出 100 pu 起跳的 granular sizing、讓容量規劃可以從小開始。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的容量單位選擇。</li>
</ol>
<p>需要警惕：「10 億 req/sec」是 Google 內部的某個峰值瞬間、是 Spanner 服務 <em>全部使用者加總</em>、不是單一 instance 數字。讀案例時要區分「全球聚合峰值」跟「單一客戶能拿到的最大配額」。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>跨地區一致性需求要在設計初期決定</strong>：如果業務必需 strong consistency（金融、ticketing）、選 Spanner 等對等服務；如果 eventual 可接受（社群、推薦）、選 Cassandra / DynamoDB Global Tables 等更便宜的選項。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的全球一致性需求識別。</li>
<li><strong>節點數即容量單位、預先規劃 sizing</strong>：Spanner 容量 = 節點數 × 單節點 QPS。每年 capacity review 主要在調節點數、不在調 schema。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
<li><strong>跨地區 latency 是強一致的代價</strong>：external consistency 必須等多區 quorum、跨洲交易延遲可達 100-200ms。延遲敏感型業務不能用跨地區 strong consistency。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget 反推。</li>
</ol>
<p>跨平台等效：AWS Aurora DSQL（2024 推出、跨地區 strong consistency）、CockroachDB（自管）、TiDB（自管或 cloud）都是對等候選。差異是 TrueTime / 同等同步機制的成熟度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想評估全球一致性需求 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想規劃 OLTP 容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想對照其他 OLTP 案例 → <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></li>
<li>想看不需要強一致的全球 KV → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth Cosmos DB</a></li>
<li>想理解 TrueTime ε 與外部一致性實作 → <a href="/blog/backend/01-database/vendors/spanner/truetime-api-depth/" data-link-title="Spanner TrueTime API 深度：GPS &#43; 原子鐘、commit wait、為什麼 line-rate scaling 才是設計目的" data-link-desc="TrueTime 是手段、line-rate scaling 才是 Spanner 的設計目的。本文先扣商業邏輯：傳統 OLTP coordinator 為什麼是 bottleneck、Spanner 怎麼用 TrueTime &#43; Paxos 換成拓樸感知多 leader；再展開 TrueTime ε / commit wait 數學、ε 暴衝失敗模式、cross-region voting 對 latency 的影響、跟 9.C10 Google internal dogfood 揭露的線性擴展模式對照">Spanner TrueTime API 深入</a></li>
<li>想對照 Spanner / Aurora DSQL / CockroachDB 不同一致性層 → <a href="/blog/backend/01-database/vendors/spanner/consistency-models-comparison/" data-link-title="Spanner Consistency Models 對照：external consistency vs serializability vs linearizability" data-link-desc="external consistency、serializability、linearizability 是三個常被混用的概念。本文先精確定義三者差異、再用 line-rate scaling 對照表（PG SSI / CockroachDB / Spanner / Aurora DSQL）回答為什麼 Spanner 不只是『更強的 serializable』、最後用 9.C10 揭露的 cross-region quorum 100-200ms 物理硬限解釋『強一致 &#43; 全球部署』的真實 cost">Spanner 一致性模型對照</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/spanner">Spanner: Always-on, virtually unlimited scale database</a></li>
<li><a href="https://cloud.google.com/spanner/docs/performance">Spanner Performance overview</a></li>
<li><a href="https://cloud.google.com/blog/products/databases/using-cloud-spanner-to-handle-high-throughput-writes/">Using Cloud Spanner to handle high throughput writes</a></li>
<li><a href="https://cloud.google.com/blog/products/databases/get-more-out-of-spanner-with-granular-instance-sizing">Get more out of Spanner with granular instance sizing</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-aurora-dsql-for-global-scale-financial-transactions/">Amazon Aurora DSQL for global-scale financial transactions</a></li>
</ul>
]]></content:encoded></item><item><title>8.10 Go 的高併發服務案例</title><link>https://tarrragon.github.io/blog/go/08-case-studies/high-concurrency-services/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/08-case-studies/high-concurrency-services/</guid><description>&lt;p>高併發服務案例的核心判斷是「大量工作是否同時存在，且每個工作都需要清楚的生命週期」。Go 適合這類服務，因為 goroutine、channel、context、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a> 與標準網路庫可以共同描述工作如何開始、等待、取消與清理。&lt;/p>
&lt;h2 id="高併發型態">高併發型態&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>型態&lt;/th>
 &lt;th>主要壓力&lt;/th>
 &lt;th>相關案例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>長連線與即時推送&lt;/td>
 &lt;td>大量 client、慢連線、斷線清理&lt;/td>
 &lt;td>Twitch、Stream、Cloudflare&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>網路代理與邊緣服務&lt;/td>
 &lt;td>timeout、連線管理、資源限制&lt;/td>
 &lt;td>Cloudflare、Kubernetes 生態工具&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>背景處理與 pipeline&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/fan-out/" data-link-title="Fan-out" data-link-desc="說明單一事件同時分發給多個下游的訊息拓撲">fan-out&lt;/a>、排隊、取消、錯誤回報&lt;/td>
 &lt;td>PayPal、Dropbox&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分散式資料服務&lt;/td>
 &lt;td>複製、一致性、節點協調&lt;/td>
 &lt;td>Cockroach Labs&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="長連線與即時推送先看-client-是否持續留在線上">長連線與即時推送：先看 client 是否持續留在線上&lt;/h3>
&lt;p>長連線服務的核心訊號是「request 結束後，server 仍然需要替 client 保留狀態」。聊天室、直播狀態、feed 更新與即時通知，都需要管理 client 註冊、訂閱、心跳、send &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a> 與清理流程。Go 的價值在於讓每條連線的讀取、寫入與取消責任能被拆成可讀的 goroutine 流程。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go-advanced/02-networking-websocket/" data-link-title="模組二：WebSocket 服務架構" data-link-desc="WebSocket client lifecycle、heartbeat、訂閱路由與慢客戶端管理">WebSocket 服務架構&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/02-networking-websocket/slow-client/" data-link-title="2.4 慢客戶端與 send buffer 管理" data-link-desc="控制推送佇列與記憶體風險">慢客戶端與 send buffer 管理&lt;/a>。&lt;/p>
&lt;h3 id="網路代理與邊緣服務先看邊界是否充滿-timeout">網路代理與邊緣服務：先看邊界是否充滿 timeout&lt;/h3>
&lt;p>網路代理與邊緣服務的核心訊號是「大量 I/O 邊界同時存在」。每個 request 都可能等待 DNS、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tls-mtls/" data-link-title="TLS / mTLS" data-link-desc="說明傳輸加密與雙向憑證驗證如何保護跨邊界資料流">TLS&lt;/a>、上游服務、client body 或 downstream response。Go 的 &lt;code>net/http&lt;/code>、&lt;code>context&lt;/code> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline&lt;/a> 設計讓 timeout 和 cancellation 可以沿著 request 傳遞。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go/03-stdlib/http-handler/" data-link-title="3.5 net/http 與 handler 設計" data-link-desc="用 net/http 建立健康檢查、API endpoint 與清楚的 handler 邊界">net/http 與 handler 設計&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/03-stdlib/context/" data-link-title="3.7 context：取消、逾時與生命週期" data-link-desc="用 context 傳遞取消、逾時與請求生命週期">context：取消、逾時與生命週期&lt;/a>。&lt;/p>
&lt;h3 id="背景處理與-pipeline先看工作是否可以從-request-中拆出">背景處理與 pipeline：先看工作是否可以從 request 中拆出&lt;/h3>
&lt;p>背景處理的核心訊號是「使用者請求只負責提交工作，真正處理需要在後面持續執行」。例如檔案轉換、通知寄送、資料同步、報表產生與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">webhook&lt;/a> retry。Go 的 goroutine 和 channel 可以先建立單一 process 內的 worker 模型；當工作需要跨 process 保證時，再接到 Backend 的 message &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> 與 outbox 章節。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go-advanced/01-concurrency-patterns/worker-pool/" data-link-title="1.5 bounded worker pool" data-link-desc="限制同時執行的 goroutine 數量，讓背景工作有明確容量邊界">bounded worker pool&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">Backend：訊息佇列與事件傳遞&lt;/a>。&lt;/p>
&lt;h3 id="分散式資料服務先看狀態是否跨節點協調">分散式資料服務：先看狀態是否跨節點協調&lt;/h3>
&lt;p>分散式資料服務的核心訊號是「資料狀態需要跨節點維持一致」。這類服務會同時處理網路延遲、節點失效、複製、leader election、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction&lt;/a> 與觀測訊號。Go 提供的是可讀的並發與錯誤處理基礎；資料庫演算法、共識協定與持久化設計則需要專門章節或外部資料補足。&lt;/p>
&lt;p>對應章節：&lt;a href="https://tarrragon.github.io/blog/go-advanced/04-architecture-boundaries/source-of-truth/" data-link-title="4.3 Source of Truth：狀態邊界" data-link-desc="集中狀態更新、保護可變資料、設計查詢 projection">Source of Truth：狀態邊界&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/07-distributed-operations/database-transactions/" data-link-title="7.1 資料庫 transaction 與 schema migration" data-link-desc="把 repository 邊界延伸到資料庫交易、migration 與一致性語意">資料庫 transaction 與 schema migration&lt;/a>。&lt;/p>
&lt;h2 id="案例閱讀檢查">案例閱讀檢查&lt;/h2>
&lt;p>閱讀高併發案例時，先找出三個問題：工作如何被限制數量、失敗如何回到 owner、資源如何被清理。若案例只談速度而沒有談生命週期，就很難轉成可維護的 Go 設計。&lt;/p></description><content:encoded><![CDATA[<p>高併發服務案例的核心判斷是「大量工作是否同時存在，且每個工作都需要清楚的生命週期」。Go 適合這類服務，因為 goroutine、channel、context、<a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 與標準網路庫可以共同描述工作如何開始、等待、取消與清理。</p>
<h2 id="高併發型態">高併發型態</h2>
<table>
  <thead>
      <tr>
          <th>型態</th>
          <th>主要壓力</th>
          <th>相關案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>長連線與即時推送</td>
          <td>大量 client、慢連線、斷線清理</td>
          <td>Twitch、Stream、Cloudflare</td>
      </tr>
      <tr>
          <td>網路代理與邊緣服務</td>
          <td>timeout、連線管理、資源限制</td>
          <td>Cloudflare、Kubernetes 生態工具</td>
      </tr>
      <tr>
          <td>背景處理與 pipeline</td>
          <td><a href="/blog/backend/knowledge-cards/fan-out/" data-link-title="Fan-out" data-link-desc="說明單一事件同時分發給多個下游的訊息拓撲">fan-out</a>、排隊、取消、錯誤回報</td>
          <td>PayPal、Dropbox</td>
      </tr>
      <tr>
          <td>分散式資料服務</td>
          <td>複製、一致性、節點協調</td>
          <td>Cockroach Labs</td>
      </tr>
  </tbody>
</table>
<h3 id="長連線與即時推送先看-client-是否持續留在線上">長連線與即時推送：先看 client 是否持續留在線上</h3>
<p>長連線服務的核心訊號是「request 結束後，server 仍然需要替 client 保留狀態」。聊天室、直播狀態、feed 更新與即時通知，都需要管理 client 註冊、訂閱、心跳、send <a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a> 與清理流程。Go 的價值在於讓每條連線的讀取、寫入與取消責任能被拆成可讀的 goroutine 流程。</p>
<p>對應章節：<a href="/blog/go-advanced/02-networking-websocket/" data-link-title="模組二：WebSocket 服務架構" data-link-desc="WebSocket client lifecycle、heartbeat、訂閱路由與慢客戶端管理">WebSocket 服務架構</a>、<a href="/blog/go-advanced/02-networking-websocket/slow-client/" data-link-title="2.4 慢客戶端與 send buffer 管理" data-link-desc="控制推送佇列與記憶體風險">慢客戶端與 send buffer 管理</a>。</p>
<h3 id="網路代理與邊緣服務先看邊界是否充滿-timeout">網路代理與邊緣服務：先看邊界是否充滿 timeout</h3>
<p>網路代理與邊緣服務的核心訊號是「大量 I/O 邊界同時存在」。每個 request 都可能等待 DNS、<a href="/blog/backend/knowledge-cards/tls-mtls/" data-link-title="TLS / mTLS" data-link-desc="說明傳輸加密與雙向憑證驗證如何保護跨邊界資料流">TLS</a>、上游服務、client body 或 downstream response。Go 的 <code>net/http</code>、<code>context</code> 與 <a href="/blog/backend/knowledge-cards/deadline/" data-link-title="Deadline" data-link-desc="說明整體操作的截止時間如何沿著服務邊界傳遞">deadline</a> 設計讓 timeout 和 cancellation 可以沿著 request 傳遞。</p>
<p>對應章節：<a href="/blog/go/03-stdlib/http-handler/" data-link-title="3.5 net/http 與 handler 設計" data-link-desc="用 net/http 建立健康檢查、API endpoint 與清楚的 handler 邊界">net/http 與 handler 設計</a>、<a href="/blog/go/03-stdlib/context/" data-link-title="3.7 context：取消、逾時與生命週期" data-link-desc="用 context 傳遞取消、逾時與請求生命週期">context：取消、逾時與生命週期</a>。</p>
<h3 id="背景處理與-pipeline先看工作是否可以從-request-中拆出">背景處理與 pipeline：先看工作是否可以從 request 中拆出</h3>
<p>背景處理的核心訊號是「使用者請求只負責提交工作，真正處理需要在後面持續執行」。例如檔案轉換、通知寄送、資料同步、報表產生與 <a href="/blog/backend/knowledge-cards/webhook/" data-link-title="Webhook" data-link-desc="說明外部系統回呼事件的接收、驗證與處理邊界">webhook</a> retry。Go 的 goroutine 和 channel 可以先建立單一 process 內的 worker 模型；當工作需要跨 process 保證時，再接到 Backend 的 message <a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> 與 outbox 章節。</p>
<p>對應章節：<a href="/blog/go-advanced/01-concurrency-patterns/worker-pool/" data-link-title="1.5 bounded worker pool" data-link-desc="限制同時執行的 goroutine 數量，讓背景工作有明確容量邊界">bounded worker pool</a>、<a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">Backend：訊息佇列與事件傳遞</a>。</p>
<h3 id="分散式資料服務先看狀態是否跨節點協調">分散式資料服務：先看狀態是否跨節點協調</h3>
<p>分散式資料服務的核心訊號是「資料狀態需要跨節點維持一致」。這類服務會同時處理網路延遲、節點失效、複製、leader election、<a href="/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction</a> 與觀測訊號。Go 提供的是可讀的並發與錯誤處理基礎；資料庫演算法、共識協定與持久化設計則需要專門章節或外部資料補足。</p>
<p>對應章節：<a href="/blog/go-advanced/04-architecture-boundaries/source-of-truth/" data-link-title="4.3 Source of Truth：狀態邊界" data-link-desc="集中狀態更新、保護可變資料、設計查詢 projection">Source of Truth：狀態邊界</a>、<a href="/blog/go-advanced/07-distributed-operations/database-transactions/" data-link-title="7.1 資料庫 transaction 與 schema migration" data-link-desc="把 repository 邊界延伸到資料庫交易、migration 與一致性語意">資料庫 transaction 與 schema migration</a>。</p>
<h2 id="案例閱讀檢查">案例閱讀檢查</h2>
<p>閱讀高併發案例時，先找出三個問題：工作如何被限制數量、失敗如何回到 owner、資源如何被清理。若案例只談速度而沒有談生命週期，就很難轉成可維護的 Go 設計。</p>
]]></content:encoded></item><item><title>Rate Limit 實作</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/rate-limit-implementation/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/rate-limit-implementation/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rate-limit/" data-link-title="Rate Limit" data-link-desc="說明限流如何保護服務入口、下游依賴與租戶公平性">Rate limit&lt;/a> 的實作分成三個層次：單機 middleware（一個 server instance 內的限速）、分散式限速（多個 instance 共用的限速狀態）、配額設計（不同 client 和 endpoint 的差異化配額）。Rate limit 的概念基礎（token bucket / sliding window / 和背壓的區別）見 &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/rate-limiting/" data-link-title="Rate Limiting" data-link-desc="主動限制每個來源的請求速率 — per-client vs global、token bucket vs sliding window、優先級豁免">DevOps 流量管控&lt;/a>，本章聚焦後端的程式碼實作。&lt;/p>
&lt;h2 id="單機-middleware-實作">單機 Middleware 實作&lt;/h2>
&lt;p>Rate limit middleware 在 HTTP handler 之前攔截請求。每個 request 過一次 limiter，通過就進入 handler，超限就回 429。&lt;/p>
&lt;h3 id="go-實作">Go 實作&lt;/h3>
&lt;p>Go 標準生態的 &lt;code>golang.org/x/time/rate&lt;/code> 提供 token bucket 的 &lt;code>rate.Limiter&lt;/code>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="s">&amp;#34;golang.org/x/time/rate&amp;#34;&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="c1">// 全域 limiter：每秒 100 個 request、burst 上限 200&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">globalLimiter&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">rate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewLimiter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">200&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">rateLimitMiddleware&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">next&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Handler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Handler&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="k">return&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandlerFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&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="k">if&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">globalLimiter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Allow&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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="nx">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Header&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Retry-After&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;1&amp;#34;&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="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Too Many Requests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StatusTooManyRequests&lt;/span>&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 class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &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">next&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ServeHTTP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&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="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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="per-client-限速">Per-client 限速&lt;/h3>
&lt;p>全域 limiter 對所有 client 共用一個配額。Per-client 限速讓每個 client（by API key、IP、或 tenant ID）有各自的配額。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">clients&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Map&lt;/span> &lt;span class="c1">// map[string]*rate.Limiter&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="kd">func&lt;/span> &lt;span class="nf">getClientLimiter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">clientID&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">rate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Limiter&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="k">if&lt;/span> &lt;span class="nx">limiter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">clients&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">clientID&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">ok&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="k">return&lt;/span> &lt;span class="nx">limiter&lt;/span>&lt;span class="p">.(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">rate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Limiter&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="nx">limiter&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">rate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewLimiter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 每 client 每秒 10 個&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="nx">clients&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Store&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">clientID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">limiter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">limiter&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;/code>&lt;/pre>&lt;/div>&lt;p>Per-client limiter 用 &lt;code>sync.Map&lt;/code> 存、首次出現的 client 自動建立 limiter。長期運行的服務需要定期清理不再活躍的 client limiter（用 goroutine + ticker 掃描最後使用時間）。&lt;/p>
&lt;h3 id="回應格式">回應格式&lt;/h3>
&lt;p>超限時的 HTTP response 需要帶足夠資訊讓 client 做正確的重試決策。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">HTTP/1.1 429 Too Many Requests
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">Retry-After: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">X-RateLimit-Limit: 100
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">X-RateLimit-Remaining: 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">X-RateLimit-Reset: 1719014400&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>Retry-After&lt;/code> 告訴 client 等多久再試（秒數或 HTTP date）。&lt;code>X-RateLimit-*&lt;/code> headers 不是 RFC 標準但被廣泛使用（GitHub API、Stripe API 都用），讓 client 在被限速前就知道剩餘配額。&lt;/p>
&lt;h2 id="分散式限速redis-backed">分散式限速（Redis-backed）&lt;/h2>
&lt;p>單機 limiter 的計數存在 process 記憶體中。多個 server instance 各自有獨立的 limiter，client 的請求被 load balancer 分配到不同 instance 時，每個 instance 只看到部分請求 — 全域限速失效。&lt;/p>
&lt;p>Redis 做共用的計數儲存，所有 instance 查同一個 counter。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/backend/knowledge-cards/rate-limit/" data-link-title="Rate Limit" data-link-desc="說明限流如何保護服務入口、下游依賴與租戶公平性">Rate limit</a> 的實作分成三個層次：單機 middleware（一個 server instance 內的限速）、分散式限速（多個 instance 共用的限速狀態）、配額設計（不同 client 和 endpoint 的差異化配額）。Rate limit 的概念基礎（token bucket / sliding window / 和背壓的區別）見 <a href="/blog/devops/03-traffic-management/rate-limiting/" data-link-title="Rate Limiting" data-link-desc="主動限制每個來源的請求速率 — per-client vs global、token bucket vs sliding window、優先級豁免">DevOps 流量管控</a>，本章聚焦後端的程式碼實作。</p>
<h2 id="單機-middleware-實作">單機 Middleware 實作</h2>
<p>Rate limit middleware 在 HTTP handler 之前攔截請求。每個 request 過一次 limiter，通過就進入 handler，超限就回 429。</p>
<h3 id="go-實作">Go 實作</h3>
<p>Go 標準生態的 <code>golang.org/x/time/rate</code> 提供 token bucket 的 <code>rate.Limiter</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="s">&#34;golang.org/x/time/rate&#34;</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="c1">// 全域 limiter：每秒 100 個 request、burst 上限 200</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">var</span> <span class="nx">globalLimiter</span> <span class="p">=</span> <span class="nx">rate</span><span class="p">.</span><span class="nf">NewLimiter</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">200</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">func</span> <span class="nf">rateLimitMiddleware</span><span class="p">(</span><span class="nx">next</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span><span class="p">)</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Handler</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="nx">http</span><span class="p">.</span><span class="nf">HandlerFunc</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">w</span> <span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span> <span class="nx">r</span> <span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">if</span> <span class="p">!</span><span class="nx">globalLimiter</span><span class="p">.</span><span class="nf">Allow</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Retry-After&#34;</span><span class="p">,</span> <span class="s">&#34;1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="s">&#34;Too Many Requests&#34;</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusTooManyRequests</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="k">return</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="nx">next</span><span class="p">.</span><span class="nf">ServeHTTP</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">r</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></code></pre></div><h3 id="per-client-限速">Per-client 限速</h3>
<p>全域 limiter 對所有 client 共用一個配額。Per-client 限速讓每個 client（by API key、IP、或 tenant ID）有各自的配額。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">var</span> <span class="nx">clients</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">Map</span> <span class="c1">// map[string]*rate.Limiter</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="kd">func</span> <span class="nf">getClientLimiter</span><span class="p">(</span><span class="nx">clientID</span> <span class="kt">string</span><span class="p">)</span> <span class="o">*</span><span class="nx">rate</span><span class="p">.</span><span class="nx">Limiter</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="nx">limiter</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">clients</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="nx">clientID</span><span class="p">);</span> <span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">return</span> <span class="nx">limiter</span><span class="p">.(</span><span class="o">*</span><span class="nx">rate</span><span class="p">.</span><span class="nx">Limiter</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="nx">limiter</span> <span class="o">:=</span> <span class="nx">rate</span><span class="p">.</span><span class="nf">NewLimiter</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="c1">// 每 client 每秒 10 個</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nx">clients</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">clientID</span><span class="p">,</span> <span class="nx">limiter</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="nx">limiter</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Per-client limiter 用 <code>sync.Map</code> 存、首次出現的 client 自動建立 limiter。長期運行的服務需要定期清理不再活躍的 client limiter（用 goroutine + ticker 掃描最後使用時間）。</p>
<h3 id="回應格式">回應格式</h3>
<p>超限時的 HTTP response 需要帶足夠資訊讓 client 做正確的重試決策。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">HTTP/1.1 429 Too Many Requests
</span></span><span class="line"><span class="ln">2</span><span class="cl">Retry-After: 1
</span></span><span class="line"><span class="ln">3</span><span class="cl">X-RateLimit-Limit: 100
</span></span><span class="line"><span class="ln">4</span><span class="cl">X-RateLimit-Remaining: 0
</span></span><span class="line"><span class="ln">5</span><span class="cl">X-RateLimit-Reset: 1719014400</span></span></code></pre></div><p><code>Retry-After</code> 告訴 client 等多久再試（秒數或 HTTP date）。<code>X-RateLimit-*</code> headers 不是 RFC 標準但被廣泛使用（GitHub API、Stripe API 都用），讓 client 在被限速前就知道剩餘配額。</p>
<h2 id="分散式限速redis-backed">分散式限速（Redis-backed）</h2>
<p>單機 limiter 的計數存在 process 記憶體中。多個 server instance 各自有獨立的 limiter，client 的請求被 load balancer 分配到不同 instance 時，每個 instance 只看到部分請求 — 全域限速失效。</p>
<p>Redis 做共用的計數儲存，所有 instance 查同一個 counter。</p>
<h3 id="sliding-window-counter">Sliding Window Counter</h3>
<p>用 Redis 的 INCR + EXPIRE 實作 sliding window counter。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- Redis Lua script（原子操作）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kd">local</span> <span class="n">key</span> <span class="o">=</span> <span class="n">KEYS</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kd">local</span> <span class="n">limit</span> <span class="o">=</span> <span class="n">tonumber</span><span class="p">(</span><span class="n">ARGV</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">local</span> <span class="n">window</span> <span class="o">=</span> <span class="n">tonumber</span><span class="p">(</span><span class="n">ARGV</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kd">local</span> <span class="n">current</span> <span class="o">=</span> <span class="n">redis.call</span><span class="p">(</span><span class="s1">&#39;INCR&#39;</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kr">if</span> <span class="n">current</span> <span class="o">==</span> <span class="mi">1</span> <span class="kr">then</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">redis.call</span><span class="p">(</span><span class="s1">&#39;EXPIRE&#39;</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">window</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kr">if</span> <span class="n">current</span> <span class="o">&gt;</span> <span class="n">limit</span> <span class="kr">then</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="kr">return</span> <span class="mi">0</span>  <span class="c1">-- 超限</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="kr">return</span> <span class="mi">1</span>      <span class="c1">-- 通過</span></span></span></code></pre></div><p>Key 的設計：<code>ratelimit:{client_id}:{endpoint}:{window_start}</code>。Window start 用當前時間截斷到秒或分鐘（如 <code>1719014400</code>），每個窗口一個 key，EXPIRE 自動清理過期窗口。</p>
<h3 id="現成套件">現成套件</h3>
<p>自己寫 Lua script 適合學習，production 用現成套件更可靠：</p>
<table>
  <thead>
      <tr>
          <th>語言</th>
          <th>套件</th>
          <th>特點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Go</td>
          <td><code>go-redis/redis_rate</code></td>
          <td>Token bucket 演算法、原子操作、直接整合 go-redis</td>
      </tr>
      <tr>
          <td>Node</td>
          <td><code>rate-limit-redis</code> + <code>express-rate-limit</code></td>
          <td>Express middleware、Redis store 外掛</td>
      </tr>
      <tr>
          <td>Python</td>
          <td><code>limits</code> + Redis backend</td>
          <td>多演算法支援（fixed window / sliding window / token bucket）</td>
      </tr>
  </tbody>
</table>
<h2 id="配額設計">配額設計</h2>
<h3 id="差異化配額">差異化配額</h3>
<p>不同的 endpoint 和 client 有不同的配額需求。搜尋 API 比列表 API 消耗更多計算資源，應該有更低的速率上限。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>配額範例</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Per-API key</td>
          <td>1000 req/min</td>
          <td>每個 client 的公平上限</td>
      </tr>
      <tr>
          <td>Per-endpoint</td>
          <td>搜尋 100 req/min、列表 500 req/min</td>
          <td>搜尋比列表貴</td>
      </tr>
      <tr>
          <td>Per-tenant</td>
          <td>免費 100 req/min、付費 10000 req/min</td>
          <td>商業差異化</td>
      </tr>
  </tbody>
</table>
<h3 id="配額溢出的處理">配額溢出的處理</h3>
<p>超限時的處理策略依業務需求決定：</p>
<p><strong>Reject（429）</strong>：直接拒絕。最簡單，適合 API 服務。Client 收到 429 後按 Retry-After 重試。</p>
<p><strong>Queue（排隊等）</strong>：超限的請求進入等待隊列，按順序處理。適合不能丟棄的操作（付款確認、訂單建立）。代價是 client 端等待時間增加。</p>
<p><strong>Degrade（降級回應）</strong>：超限時回傳簡化版的回應（cached 結果、摘要而非完整資料）。適合讀取操作。</p>
<h2 id="和-monitoring-的整合">和 Monitoring 的整合</h2>
<p>Rate limit 的命中事件應該記入監控系統，讓團隊知道哪些 client 在撞限速、哪些 endpoint 的配額是否合理。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// Rate limit hit 時送 metric 事件</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">monitor</span><span class="p">.</span><span class="nf">Metric</span><span class="p">(</span><span class="s">&#34;ratelimit.hit&#34;</span><span class="p">,</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s">&#34;client_id&#34;</span><span class="p">:</span> <span class="nx">clientID</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="s">&#34;endpoint&#34;</span><span class="p">:</span>  <span class="nx">r</span><span class="p">.</span><span class="nx">URL</span><span class="p">.</span><span class="nx">Path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="s">&#34;limit&#34;</span><span class="p">:</span>     <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="s">&#34;window&#34;</span><span class="p">:</span>    <span class="s">&#34;1m&#34;</span><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>Dashboard 視圖：rate limit hit 的時間趨勢 + 按 client 和 endpoint 分群。Hit 數持續上升代表配額設太低（正常使用被限速）或某個 client 在濫用。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Rate limit 的概念基礎 → <a href="/blog/devops/03-traffic-management/rate-limiting/" data-link-title="Rate Limiting" data-link-desc="主動限制每個來源的請求速率 — per-client vs global、token bucket vs sliding window、優先級豁免">DevOps 流量管控 — Rate Limiting</a></li>
<li>背壓機制（被動的流量控制）→ <a href="/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &#43; 回壓訊號的設計、和 rate limit 的區別">DevOps 背壓機制</a></li>
<li>Rate limit 知識卡 → <a href="/blog/backend/knowledge-cards/rate-limit/" data-link-title="Rate Limit" data-link-desc="說明限流如何保護服務入口、下游依賴與租戶公平性">Rate Limit</a></li>
<li>監控系統中的 ingestion 限速 → <a href="/blog/monitoring/04-collector/ingestion-scaling/" data-link-title="Ingestion Scaling" data-link-desc="四層防線應對 ingestion 端的流量擴展 — SDK 取樣、Collector 背壓、水平擴展、Queue 解耦">Monitoring Ingestion Scaling</a></li>
</ul>
]]></content:encoded></item><item><title>9.11 高峰事件準備</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/peak-event-readiness/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/peak-event-readiness/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>高峰事件準備的責任是把「事件臨頭才動手」變成「事前數週流程化準備」。沒有 readiness 流程時、年度活動靠 oncall 撐、出事率高；有流程之後、活動成「routine event」、工程資源穩定釋放。&lt;/p>
&lt;p>本章 &lt;em>是&lt;/em> &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> 跟 &lt;a href="https://tarrragon.github.io/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 容量規劃模型&lt;/a> 在「事件型場景」的應用組合、不重新建立方法論。要看具體方法回到那兩章、本章聚焦在 &lt;em>流程整合&lt;/em>。&lt;/p>
&lt;p>讀完後讀者能設計一個 T-90 → T-0 的事件準備時程、回答「Black Friday 該怎麼準備、Super Bowl 該怎麼準備、新片發布該怎麼準備」。&lt;/p>
&lt;h2 id="事件分類五種負載形狀">事件分類：五種負載形狀&lt;/h2>
&lt;p>不同事件對應不同準備強度、第一步要分類。&lt;/p>
&lt;p>&lt;strong>可預期極端峰值&lt;/strong>：年度活動、預售、賽事決賽。提前數月已知時間、業務影響大。例：Prime Day、Black Friday、Super Bowl、IPL 決賽。
&lt;strong>事件型不可預期峰值&lt;/strong>：賽事高潮、突發新聞、KOL 推廣。時間或大小不完全可預測。例：賽事進球瞬間、KOL 帶貨、突發新聞引發的流量。
&lt;strong>Flash-sale 瞬間爆量&lt;/strong>：售票開賣、報名活動、限量搶購。t=0 瞬間爆量、5-30 分鐘結束。例：演唱會售票、限量商品搶購、報名截止前最後一小時。
&lt;strong>產品爆紅 surge&lt;/strong>：新 app 紅、病毒擴散。完全不可預期、流量會隨熱度消退。例：Pokemon GO、ChatGPT 爆紅初期、TikTok challenge。
&lt;strong>結構性 surge&lt;/strong>：COVID 類外部衝擊、永久 baseline 上移。不會回到舊水準。例：COVID 期間遠距工作工具、烏俄戰爭期間能源類 app。&lt;/p>
&lt;p>對應案例：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C1 / 9.C13 / 9.C21 / 9.C27 / 9.C29&lt;/a>（predictable）/ &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C2 / 9.C4 / 9.C7 / 9.C28&lt;/a>（event）/ &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C15 / 9.C16 / 9.C17&lt;/a>（flash-sale）/ &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C8 / 9.C18&lt;/a>（surge）。&lt;/p>
&lt;h2 id="t-90--t-0-準備時程">T-90 → T-0 準備時程&lt;/h2>
&lt;p>可預期極端峰值的完整準備時程：&lt;/p>
&lt;p>&lt;strong>T-90 天&lt;/strong>：流量 forecast + 容量計畫敲定。確認預期峰值倍數、確認 headroom 比例、確認跨 region / AZ 分布。產出 &lt;em>容量計畫文件&lt;/em>。&lt;/p>
&lt;p>&lt;strong>T-30 天&lt;/strong>：基礎設施 quota 申請。雲端 instance limit、connection pool、API rate limit、DynamoDB throughput、Lambda concurrency 都要 &lt;em>提前申請&lt;/em>、不能事件當天才發現 quota 不夠。AWS Infrastructure Event Management（IEM）等服務在這階段啟動。&lt;/p>
&lt;p>&lt;strong>T-14 天&lt;/strong>：第一輪 production-like 壓測。驗證容量計畫是否真的能撐預期峰值、找出第一輪 bottleneck。&lt;/p>
&lt;p>&lt;strong>T-7 天&lt;/strong>：完整 game day 演練。注入故障場景（DB failure、AZ outage、第三方 quota 耗盡）、驗證降級、failover、rollback 流程。修正最後問題、更新 runbook。&lt;/p>
&lt;p>&lt;strong>T-2 天&lt;/strong>：pre-scaling 開始。CDN cache pre-warm、Lambda provisioned concurrency 啟動、autoscaler scheduled 開始、DB capacity 預先 scale up。避免事件當天還在 boot。&lt;/p>
&lt;p>&lt;strong>T-0 day&lt;/strong>：watch room 待命、runbook 開機可執行。所有相關 oncall 跨團隊聯合 channel、dashboard 集中、escalation path 清楚。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>高峰事件準備的責任是把「事件臨頭才動手」變成「事前數週流程化準備」。沒有 readiness 流程時、年度活動靠 oncall 撐、出事率高；有流程之後、活動成「routine event」、工程資源穩定釋放。</p>
<p>本章 <em>是</em> <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> 跟 <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> 在「事件型場景」的應用組合、不重新建立方法論。要看具體方法回到那兩章、本章聚焦在 <em>流程整合</em>。</p>
<p>讀完後讀者能設計一個 T-90 → T-0 的事件準備時程、回答「Black Friday 該怎麼準備、Super Bowl 該怎麼準備、新片發布該怎麼準備」。</p>
<h2 id="事件分類五種負載形狀">事件分類：五種負載形狀</h2>
<p>不同事件對應不同準備強度、第一步要分類。</p>
<p><strong>可預期極端峰值</strong>：年度活動、預售、賽事決賽。提前數月已知時間、業務影響大。例：Prime Day、Black Friday、Super Bowl、IPL 決賽。
<strong>事件型不可預期峰值</strong>：賽事高潮、突發新聞、KOL 推廣。時間或大小不完全可預測。例：賽事進球瞬間、KOL 帶貨、突發新聞引發的流量。
<strong>Flash-sale 瞬間爆量</strong>：售票開賣、報名活動、限量搶購。t=0 瞬間爆量、5-30 分鐘結束。例：演唱會售票、限量商品搶購、報名截止前最後一小時。
<strong>產品爆紅 surge</strong>：新 app 紅、病毒擴散。完全不可預期、流量會隨熱度消退。例：Pokemon GO、ChatGPT 爆紅初期、TikTok challenge。
<strong>結構性 surge</strong>：COVID 類外部衝擊、永久 baseline 上移。不會回到舊水準。例：COVID 期間遠距工作工具、烏俄戰爭期間能源類 app。</p>
<p>對應案例：<a href="/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C1 / 9.C13 / 9.C21 / 9.C27 / 9.C29</a>（predictable）/ <a href="/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C2 / 9.C4 / 9.C7 / 9.C28</a>（event）/ <a href="/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C15 / 9.C16 / 9.C17</a>（flash-sale）/ <a href="/blog/backend/09-performance-capacity/cases/" data-link-title="模組九案例正文" data-link-desc="雲端服務商實戰案例庫 — 從 AWS / GCP / Azure 公開案例整理高併發、峰值流量與容量規劃實踐">9.C8 / 9.C18</a>（surge）。</p>
<h2 id="t-90--t-0-準備時程">T-90 → T-0 準備時程</h2>
<p>可預期極端峰值的完整準備時程：</p>
<p><strong>T-90 天</strong>：流量 forecast + 容量計畫敲定。確認預期峰值倍數、確認 headroom 比例、確認跨 region / AZ 分布。產出 <em>容量計畫文件</em>。</p>
<p><strong>T-30 天</strong>：基礎設施 quota 申請。雲端 instance limit、connection pool、API rate limit、DynamoDB throughput、Lambda concurrency 都要 <em>提前申請</em>、不能事件當天才發現 quota 不夠。AWS Infrastructure Event Management（IEM）等服務在這階段啟動。</p>
<p><strong>T-14 天</strong>：第一輪 production-like 壓測。驗證容量計畫是否真的能撐預期峰值、找出第一輪 bottleneck。</p>
<p><strong>T-7 天</strong>：完整 game day 演練。注入故障場景（DB failure、AZ outage、第三方 quota 耗盡）、驗證降級、failover、rollback 流程。修正最後問題、更新 runbook。</p>
<p><strong>T-2 天</strong>：pre-scaling 開始。CDN cache pre-warm、Lambda provisioned concurrency 啟動、autoscaler scheduled 開始、DB capacity 預先 scale up。避免事件當天還在 boot。</p>
<p><strong>T-0 day</strong>：watch room 待命、runbook 開機可執行。所有相關 oncall 跨團隊聯合 channel、dashboard 集中、escalation path 清楚。</p>
<p><strong>T+7 天</strong>：retro。對比預測 vs 實際、紀錄 incident 跟 near-miss、列下個事件要改的事。寫進 <a href="/blog/backend/06-reliability/cases/" data-link-title="可靠性服務案例庫" data-link-desc="按服務組織的 SRE 實踐案例庫，累積架構脈絡與工程文化">06 cases</a> 或本模組 cases。</p>
<h2 id="pre-scaling-策略">Pre-scaling 策略</h2>
<p>T-2 階段的 pre-scaling 是「不依賴 autoscaler 反應」的容量保險。</p>
<p><strong>Pre-scaling 涵蓋層次</strong>：</p>
<ul>
<li><strong>ELB warm-up</strong>：請 AWS 預先 warm up ELB，避免流量上來時 ELB 自身需要時間擴容</li>
<li><strong>Lambda provisioned concurrency</strong>：預先 boot 一定數量 instance、避免 cold start</li>
<li><strong>DynamoDB / Cosmos DB capacity</strong>：scheduled 提前 scale up</li>
<li><strong>EC2 ASG</strong>：min instances 提前拉高</li>
<li><strong>CDN cache pre-warm</strong>：重要 URL 提前 invalidate / pre-populate</li>
<li><strong>DB connection pool</strong>：應用層提前 warm up connection</li>
<li><strong>Cache warmup</strong>：把 hot key 提前 populate 進 cache</li>
</ul>
<p><strong>Pre-warm window 通常 30 分鐘到 2 小時</strong>、取決於：</p>
<ul>
<li>Instance boot time（VM-based 慢、container 快）</li>
<li>Cache warmup 時間（cold cache 命中率低、要時間 populate）</li>
<li>Connection pool 預熱（DB connection establish 有 latency）</li>
</ul>
<h3 id="cdn-pre-warm-操作細節">CDN Pre-warm 操作細節</h3>
<p>CDN pre-warm 在 T-2 階段是 high-impact 操作、但跟其他 pre-scaling 的特性不同。具體做法：</p>
<ul>
<li><strong>找出活動會大量被讀取的 URL 清單</strong>：商品頁、活動 landing page、新 release 內容</li>
<li><strong>在每個 CDN edge POP 觸發 cache populate</strong>：可以用 vendor warmup API（Cloudflare Argo、Fastly Image Optimizer pre-fetch、Akamai NetStorage push），或從多個 region 發 synthetic request 強制 edge 拉取</li>
<li><strong>驗證 hit ratio 已升高</strong>：用 vendor dashboard 觀察 cache_status=HIT 比例、確認 pre-warm 生效</li>
<li><strong>預估 origin 流量曲線</strong>：pre-warm 完成後、活動開始時 edge miss 流量應該大幅降低、origin 容量規劃可以對應放鬆</li>
</ul>
<p>跟其他 pre-scaling 不同的是 <strong>CDN pre-warm 沒有「容量上限」這個概念</strong> — edge cache 是被動填的、warm 完就是 warm、不像 EC2 / Lambda 那樣需要 reserve 容量。風險不在「填不夠」、在「填錯」（key 不對、TTL 設錯讓 pre-warm 立刻過期）。詳見 <a href="/blog/backend/05-deployment-platform/edge-cdn-static-distribution/" data-link-title="5.9 邊緣分發與靜態資源（CDN / Origin Protection）" data-link-desc="整理 CDN 與 edge cache 在部署平台中的責任邊界、origin protection、purge 與 invalidation 策略">5.9 邊緣分發</a> 的 purge 與 cacheable 判讀。</p>
<p><strong>事件結束後也要 <em>scheduled scale down</em></strong>：autoscaler 通常 scale up 快、scale down 慢、長期 over-provision 浪費錢。</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 30 分鐘擴 130 倍</a> — pre-scaling + Auto Scaling Group + AMI prebuild + ELB warmup 組合；<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 的峰值數字 — 一年一次可預期峰值的容量設計參考">Prime Day pre-scaling</a> — predictive scaling + scheduled scaling 兩種組合。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/predictive-scaling/" data-link-title="Predictive Scaling" data-link-desc="說明用歷史模式或 ML 模型預測流量、提前擴容的 autoscaler 模式">Predictive Scaling 卡片</a> 跟 <a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">Scheduled Scaling 卡片</a>。</p>
<h2 id="watch-room-設計">Watch room 設計</h2>
<p>T-0 當天的指揮中心、跨團隊聯合 channel。</p>
<p><strong>人員配置</strong>：</p>
<ul>
<li>跨團隊聯合 channel：app / infra / network / SRE / business / customer support</li>
<li>24/7 輪班（國際事件可能跨 24 小時）</li>
<li>明確 incident commander（<a href="/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">08.7 incident command roles</a>）</li>
</ul>
<p><strong>Dashboard 集中</strong>：</p>
<ul>
<li>流量 dashboard：總 RPS、按 region 拆分、按 endpoint 拆分</li>
<li>延遲 dashboard：p50 / p95 / p99 即時、按 service 拆分</li>
<li>錯誤 dashboard：error rate、按 endpoint、按 status code</li>
<li>成本 dashboard：當前 hourly cost、預估全天 cost</li>
<li>業務 dashboard：訂單數、轉換率、收入</li>
</ul>
<p><strong>Runbook 隨手可用</strong>：常見問題 → 對應動作的明確指引。不要事件當下還在 wiki 找資料。</p>
<p><strong>Escalation path</strong>：什麼狀況找誰、多久升級。寫成決策樹、不要靠人記。對應 <a href="/blog/backend/08-incident-response/incident-command-roles/" data-link-title="8.2 事故指揮與角色分工" data-link-desc="定義 incident commander 與跨角色協作責任">08.7 incident command roles</a>。</p>
<p>對應 <a href="/blog/backend/knowledge-cards/game-day/" data-link-title="Game Day" data-link-desc="說明事故演練如何驗證流程、工具與團隊協作">Game Day 卡片</a>。</p>
<h2 id="vendor-緊急支援">Vendor 緊急支援</h2>
<p>戰略事件可以申請 vendor 工程師待命、是「人力 backup」。</p>
<p><strong>AWS Infrastructure Event Management（IEM）</strong>：年度重大事件可以申請、提供 pre-scaling 與專屬監控通道。
<strong>GCP Customer Reliability Engineering（CRE）</strong>：戰略客戶的 24/7 工程支援、能即時為客戶補容量。
<strong>Azure Premier Support + CSAM</strong>：對等服務。</p>
<p><strong>注意</strong>：這類服務通常綁定 enterprise 等級合約、不是所有客戶都能用。設計事件準備時要假設「沒有 vendor 救援」、vendor 是 bonus 而非 primary plan。</p>
<p>對應案例：<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 體育博彩峰值流量">GR8 Tech World Cup IEM</a> — AWS Infrastructure Event Management 在 2022 FIFA World Cup 期間支援；<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 怎麼即時補容量">Pokemon GO CRE</a> — GCP CRE 即時補容量、撐過 50x surge。</p>
<h2 id="game-day-演練">Game day 演練</h2>
<p>T-7 階段的核心活動、把 readiness 從計畫變實戰。</p>
<p><strong>演練場景</strong>：</p>
<ul>
<li>模擬「事件當天 worst case」</li>
<li>注入故障：DB primary failure、AZ outage、第三方 quota 達標、network partition</li>
<li>演練降級：哪些功能關閉、用戶看到什麼</li>
<li>演練 failover：流量切到備援</li>
<li>演練 rollback：發現新版本問題、能不能快速回退</li>
</ul>
<p><strong>Game day 學習目標</strong>：</p>
<ul>
<li>runbook 不夠詳細 → 補</li>
<li>訊號不夠 → 加 metric / alert</li>
<li>人員不夠 → 排班補</li>
<li>工具不夠 → 工程補</li>
</ul>
<p>對應 <a href="/blog/backend/06-reliability/cases/shopify/" data-link-title="Shopify" data-link-desc="Shopify BFCM Scaling / Pod-based Isolation / Capacity Planning">06 cases Shopify game day</a> — Shopify game day 是業界範本、值得直接參考。</p>
<h2 id="event-tier-分級">Event tier 分級</h2>
<p>不同事件規模對應不同準備強度、不能一律照 T-90 流程跑。</p>
<p><strong>Regular event</strong>（每週 promo、small feature launch）：</p>
<ul>
<li>scheduled scaling 即可</li>
<li>無 dedicated watch room</li>
<li>對應 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate</a> 的常規 release</li>
</ul>
<p><strong>Major event</strong>（季度行銷、新功能發布）：</p>
<ul>
<li>pre-scaling + watch room</li>
<li>簡化版 T-14 → T-0 流程</li>
<li>跨 team coordination</li>
</ul>
<p><strong>Critical event</strong>（年度大促、Super Bowl、IPL）：</p>
<ul>
<li>完整 T-90 流程</li>
<li>vendor IEM + game day</li>
<li>24/7 watch room</li>
<li>C-level visibility</li>
</ul>
<p>對應案例：<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; 州的雙重峰值">FanDuel</a> regular game → playoff → Super Bowl 三 tier — NFL 賽季 baseline → playoffs 升 2-3x → championship 升 4-5x → Super Bowl 升 5-10x、每 tier 對應不同準備強度。</p>
<h2 id="事後-retro">事後 retro</h2>
<p>T+7 retro 是讓 readiness 持續改進的關鍵。</p>
<p><strong>Retro 必答的問題</strong>：</p>
<ul>
<li>流量 forecast 跟實際差多少？（forecast 改進方向）</li>
<li>容量 utilization 峰值多少？（headroom 是否合適）</li>
<li>有沒有 incident 跟 near-miss？（runbook 更新方向）</li>
<li>下個事件要改的事是什麼？</li>
</ul>
<p><strong>Retro 產出</strong>：</p>
<ul>
<li>forecast 改進建議（給 <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>）</li>
<li>新 runbook 或 runbook 更新</li>
<li>新 monitoring / alert</li>
<li>新工程任務（補容量、補工具）</li>
</ul>
<p>對應 <a href="/blog/backend/08-incident-response/post-incident-review/" data-link-title="8.5 復盤與改進追蹤" data-link-desc="把 RCA 與 action items 轉成可驗證閉環">08.13 post-incident review</a> — retro 不只用在 incident、event readiness 也需要。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>教學重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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</a></td>
          <td>可預期極端峰值教科書範本</td>
      </tr>
      <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>flash-sale T-2 pre-scaling</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>全球直播 watch room</td>
      </tr>
      <tr>
          <td><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></td>
          <td>AWS IEM + 自家 AI 預測組合</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>event tier 分級（playoff → SB）</td>
      </tr>
      <tr>
          <td><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 Pokemon GO</a></td>
          <td>surge 場景的 vendor 救援（CRE）</td>
      </tr>
  </tbody>
</table>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<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> / <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/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸</a>（pre-scaling 前要分辨可不可水平擴展）</li>
<li>跨模組：<a href="/blog/backend/05-deployment-platform/edge-cdn-static-distribution/" data-link-title="5.9 邊緣分發與靜態資源（CDN / Origin Protection）" data-link-desc="整理 CDN 與 edge cache 在部署平台中的責任邊界、origin protection、purge 與 invalidation 策略">5.9 邊緣分發與靜態資源</a>（CDN pre-warm / origin protection 是 T-2 核心）</li>
<li>跨模組：<a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">06.20 experiment safety boundary</a> / <a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 事故處理模組</a></li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/predictive-scaling/" data-link-title="Predictive Scaling" data-link-desc="說明用歷史模式或 ML 模型預測流量、提前擴容的 autoscaler 模式">Predictive Scaling</a></li>
<li><a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">Scheduled Scaling</a></li>
<li><a href="/blog/backend/knowledge-cards/game-day/" data-link-title="Game Day" data-link-desc="說明事故演練如何驗證流程、工具與團隊協作">Game Day</a></li>
<li><a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast</a></li>
<li><a href="/blog/backend/knowledge-cards/headroom-budget/" data-link-title="Headroom Budget" data-link-desc="說明容量規劃中為應付異常 burst &#43; AZ 故障 &#43; forecast 誤差的安全餘量">Headroom Budget</a></li>
</ul>
]]></content:encoded></item><item><title>9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/</guid><description>&lt;p>這個案例的核心責任是說明「全球分散式 multi-model DB」的容量設計取捨。Minecraft Earth 是 AR 手機遊戲（已停運、但案例本身保留）、跟 Pokémon GO 同類負載 — 玩家位置即時更新、跨地區即時互動、預期會在熱門地區 surge。Cosmos DB 的設計回應這類「跨地區 + 多 model」需求。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Minecraft Earth 在 Azure Cosmos DB 的關鍵敘述（引自 &lt;a href="https://azure.microsoft.com/en-us/blog/minecraft-earth-and-azure-cosmos-db-part-2-delivering-turnkey-geographic-distribution/">Minecraft Earth and Azure Cosmos DB&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字 / 內容&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>容量測試&lt;/td>
 &lt;td>100 萬 RU/s（Request Units / 秒）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲承諾&lt;/td>
 &lt;td>99 百分位 &amp;lt; 10ms（地區內讀）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>一致性選項&lt;/td>
 &lt;td>5 個一致性層級（strong → eventual）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>地理分散&lt;/td>
 &lt;td>turnkey global distribution&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性 SLA&lt;/td>
 &lt;td>99.99%（multi-region 99.999%）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Cosmos DB 平台特性（引自 &lt;a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">Cosmos DB technical overview&lt;/a>）：&lt;/p>
&lt;ul>
&lt;li>配置擴容延遲：99 百分位 5 秒內生效&lt;/li>
&lt;li>多 model 支援：SQL API、MongoDB API、Cassandra API、Gremlin、Table&lt;/li>
&lt;li>partition 動態分裂：透明&lt;/li>
&lt;li>5 個 well-defined consistency levels（strong / bounded staleness / session / consistent prefix / eventual）&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Cosmos DB 設計揭露三個全球 KV / document DB 的容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>一致性是 spectrum、不是 binary&lt;/strong>：Cosmos DB 提供 5 個層級、每個延遲與吞吐特性不同。AR 遊戲的玩家位置不需要 strong consistency（位置稍微 stale 沒問題）、但庫存交易需要 strong。同一 application 內不同操作選不同 consistency、是進階的容量設計策略。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的一致性取捨。&lt;/li>
&lt;li>&lt;strong>Request Unit (RU) 是抽象容量單位&lt;/strong>：1 RU = 1 KB document 的 strong read 成本、寫成本約 5 RU、複雜 query 可達數百 RU。容量規劃變成「估每個操作多少 RU × 操作頻率」、跟「估 CPU / IOPS」是不同的思維。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的容量單位設計。&lt;/li>
&lt;li>&lt;strong>turnkey global distribution = 容量單位的全球複製&lt;/strong>：開啟跨地區後、容量在每個地區都 mirror 一份、成本乘以地區數。對中等規模團隊、turnkey 省下大量 ops、但要算「全球複製的成本是否值得業務需求」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「100 萬 RU/s 通過測試」是 &lt;em>壓測通過&lt;/em>、不是 &lt;em>生產持續跑&lt;/em>。實際營運要看 partition key 設計是否均勻、是否有 hot partition、跨地區複製延遲是否符合業務需求。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>一致性需求分流到不同 collection / table&lt;/strong>：同一 application 不同操作有不同一致性需求、用不同 collection 配不同 consistency level、不要一刀切。&lt;/li>
&lt;li>&lt;strong>partition key 設計影響容量上限&lt;/strong>：跟 DynamoDB 一樣、hot partition 會讓名義容量達不到。Cosmos DB 的特殊性是「synthetic partition key」可以混合多個 field 強制分散。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 hot partition 識別。&lt;/li>
&lt;li>&lt;strong>RU-based pricing 鼓勵 query 最佳化&lt;/strong>：每個 expensive query 都吃 RU、優化 query 直接降成本。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop&lt;/a> 的持續改進迴圈。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS DynamoDB Global Tables（global KV）、GCP Spanner（global SQL with strong consistency）、ScyllaDB Cloud（自管 Cassandra）都是對等候選。差異是 multi-model 廣度（Cosmos 最廣）vs 一致性深度（Spanner 最強）。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「全球分散式 multi-model DB」的容量設計取捨。Minecraft Earth 是 AR 手機遊戲（已停運、但案例本身保留）、跟 Pokémon GO 同類負載 — 玩家位置即時更新、跨地區即時互動、預期會在熱門地區 surge。Cosmos DB 的設計回應這類「跨地區 + 多 model」需求。</p>
<h2 id="觀察">觀察</h2>
<p>Minecraft Earth 在 Azure Cosmos DB 的關鍵敘述（引自 <a href="https://azure.microsoft.com/en-us/blog/minecraft-earth-and-azure-cosmos-db-part-2-delivering-turnkey-geographic-distribution/">Minecraft Earth and Azure Cosmos DB</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字 / 內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>容量測試</td>
          <td>100 萬 RU/s（Request Units / 秒）</td>
      </tr>
      <tr>
          <td>延遲承諾</td>
          <td>99 百分位 &lt; 10ms（地區內讀）</td>
      </tr>
      <tr>
          <td>一致性選項</td>
          <td>5 個一致性層級（strong → eventual）</td>
      </tr>
      <tr>
          <td>地理分散</td>
          <td>turnkey global distribution</td>
      </tr>
      <tr>
          <td>可用性 SLA</td>
          <td>99.99%（multi-region 99.999%）</td>
      </tr>
  </tbody>
</table>
<p>Cosmos DB 平台特性（引自 <a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">Cosmos DB technical overview</a>）：</p>
<ul>
<li>配置擴容延遲：99 百分位 5 秒內生效</li>
<li>多 model 支援：SQL API、MongoDB API、Cassandra API、Gremlin、Table</li>
<li>partition 動態分裂：透明</li>
<li>5 個 well-defined consistency levels（strong / bounded staleness / session / consistent prefix / eventual）</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>Cosmos DB 設計揭露三個全球 KV / document DB 的容量設計重點。</p>
<ol>
<li><strong>一致性是 spectrum、不是 binary</strong>：Cosmos DB 提供 5 個層級、每個延遲與吞吐特性不同。AR 遊戲的玩家位置不需要 strong consistency（位置稍微 stale 沒問題）、但庫存交易需要 strong。同一 application 內不同操作選不同 consistency、是進階的容量設計策略。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的一致性取捨。</li>
<li><strong>Request Unit (RU) 是抽象容量單位</strong>：1 RU = 1 KB document 的 strong read 成本、寫成本約 5 RU、複雜 query 可達數百 RU。容量規劃變成「估每個操作多少 RU × 操作頻率」、跟「估 CPU / IOPS」是不同的思維。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的容量單位設計。</li>
<li><strong>turnkey global distribution = 容量單位的全球複製</strong>：開啟跨地區後、容量在每個地區都 mirror 一份、成本乘以地區數。對中等規模團隊、turnkey 省下大量 ops、但要算「全球複製的成本是否值得業務需求」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
</ol>
<p>需要警惕：「100 萬 RU/s 通過測試」是 <em>壓測通過</em>、不是 <em>生產持續跑</em>。實際營運要看 partition key 設計是否均勻、是否有 hot partition、跨地區複製延遲是否符合業務需求。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>一致性需求分流到不同 collection / table</strong>：同一 application 不同操作有不同一致性需求、用不同 collection 配不同 consistency level、不要一刀切。</li>
<li><strong>partition key 設計影響容量上限</strong>：跟 DynamoDB 一樣、hot partition 會讓名義容量達不到。Cosmos DB 的特殊性是「synthetic partition key」可以混合多個 field 強制分散。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 hot partition 識別。</li>
<li><strong>RU-based pricing 鼓勵 query 最佳化</strong>：每個 expensive query 都吃 RU、優化 query 直接降成本。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop</a> 的持續改進迴圈。</li>
</ol>
<p>跨平台等效：AWS DynamoDB Global Tables（global KV）、GCP Spanner（global SQL with strong consistency）、ScyllaDB Cloud（自管 Cassandra）都是對等候選。差異是 multi-model 廣度（Cosmos 最廣）vs 一致性深度（Spanner 最強）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計全球分散 KV → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想對照強一致全球 OLTP → <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想對照單區 KV 高吞吐 → <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></li>
<li>想理解 consistency level 的取捨 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想理解 Cosmos DB 五層一致性的工程選擇 → <a href="/blog/backend/01-database/vendors/cosmosdb/consistency-levels-engineering/" data-link-title="Cosmos DB 5 Consistency Levels：Session 預設、Bounded staleness、Strong 邊界跟跨 collection 分流策略" data-link-desc="Cosmos DB 5 個 consistency level 的工程選擇邏輯、Session 為何是 production 預設、per-request override 跟跨 collection 分流的進階策略、Strong &#43; multi-region 互斥的 cross-link — 從 Minecraft Earth &#43; ASOS 切入">Cosmos DB 一致性層次工程</a></li>
<li>想做全球 multi-region write 衝突收斂 → <a href="/blog/backend/01-database/vendors/cosmosdb/multi-region-write-conflict/" data-link-title="Cosmos DB Multi-Region Write：active-active、LWW、custom merge、Strong &#43; multi-region 互斥的 AP 取捨" data-link-desc="Multi-region active-active write 的 conflict resolution（LWW / custom merge / conflict feed）、Strong 跟 multi-region write 為什麼互斥、廣告 SLA vs 實測可用性鏈路拆解 — 從 Minecraft Earth &#43; Toyota Connected 切入">Cosmos DB 多 region write 衝突</a></li>
<li>想拆 partition key 設計與全球分散搭配 → <a href="/blog/backend/01-database/vendors/cosmosdb/partition-key-design/" data-link-title="Cosmos DB Partition Key Design：synthetic / composite / hierarchical &#43; 不可逆性硬約束" data-link-desc="Cosmos DB logical partition 10000 RU/s 上限、partition key 不可改、三種設計模式（synthetic / composite / hierarchical）、跟 DynamoDB / MongoDB 可逆性對比、latency budget 拆解 — 從 Minecraft Earth &#43; ASOS 切入">Cosmos DB partition key 設計</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://azure.microsoft.com/en-us/blog/minecraft-earth-and-azure-cosmos-db-part-2-delivering-turnkey-geographic-distribution/">Minecraft Earth and Azure Cosmos DB part 2: Delivering turnkey geographic distribution</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">A technical overview of Azure Cosmos DB</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/azure-cosmos-db-pushing-the-frontier-of-globally-distributed-databases/">Azure Cosmos DB: Pushing the frontier of globally distributed databases</a></li>
</ul>
]]></content:encoded></item><item><title>SQLite Backend 效能基準</title><link>https://tarrragon.github.io/blog/monitoring/04-collector/sqlite-performance-baseline/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/04-collector/sqlite-performance-baseline/</guid><description>&lt;p>SQLite Backend 的效能受三個因素影響：儲存裝置（SSD vs HDD vs SD card）、Go driver 選擇（modernc.org/sqlite pure Go vs mattn/go-sqlite3 CGO）、並發模型（WAL mode + single-writer）。本章根據 SQLite 的技術特性和業界基準推導預期效能範圍，並提供實測方法讓使用者在自己的環境驗證。所有數字是預期範圍而非實測值 — 實際效能依硬體和 workload 而定。&lt;/p>
&lt;h2 id="寫入吞吐">寫入吞吐&lt;/h2>
&lt;p>寫入吞吐決定 collector 每秒能消化多少事件。SQLite 的寫入效能主要受 fsync 頻率和 WAL checkpoint 影響。&lt;/p>
&lt;h3 id="單筆-insert">單筆 INSERT&lt;/h3>
&lt;p>每筆 INSERT 獨立一個 transaction 時，每次 commit 都會 fsync。WAL mode 的 fsync 成本比 journal mode 低（append-only），但仍是寫入的主要瓶頸。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>儲存裝置&lt;/th>
 &lt;th>單筆 INSERT 延遲&lt;/th>
 &lt;th>理論上限&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>NVMe SSD&lt;/td>
 &lt;td>10-30 μs&lt;/td>
 &lt;td>30,000-100,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SATA SSD&lt;/td>
 &lt;td>30-50 μs&lt;/td>
 &lt;td>20,000-30,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>HDD&lt;/td>
 &lt;td>50-200 μs&lt;/td>
 &lt;td>5,000-20,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SD card&lt;/td>
 &lt;td>500-2000 μs&lt;/td>
 &lt;td>500-2,000 inserts/sec&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>modernc.org/sqlite（pure Go）的效能約為 CGO driver（mattn/go-sqlite3）的 60-80%。上表數字基於 CGO driver，pure Go 需打八折。Go HTTP handler 的開銷（JSON 解碼、schema 驗證、goroutine 調度）再扣 10-20%。&lt;/p>
&lt;h3 id="批次-insert">批次 INSERT&lt;/h3>
&lt;p>一個 transaction 包裹多筆 INSERT，只做一次 fsync。Collector 接收 SDK 的 flush batch（一個 HTTP request 帶一批事件）天然適合批次寫入。&lt;/p>
&lt;p>吞吐提升幅度和批次大小的關係：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>批次大小&lt;/th>
 &lt;th>相對單筆的吞吐提升&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>10 筆/tx&lt;/td>
 &lt;td>3-5x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>100 筆/tx&lt;/td>
 &lt;td>5-10x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1000 筆/tx&lt;/td>
 &lt;td>8-15x&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>提升來自 fsync 次數從「每筆一次」降到「每批一次」。超過 100 筆/tx 後邊際收益遞減。&lt;/p>
&lt;h3 id="實際預期">實際預期&lt;/h3>
&lt;p>結合 pure Go driver、HTTP handler 開銷和批次寫入，不同環境下的預期吞吐：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>環境&lt;/th>
 &lt;th>單筆&lt;/th>
 &lt;th>批次（100/tx）&lt;/th>
 &lt;th>適合場景&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Mac M1/M2 NVMe + pure Go&lt;/td>
 &lt;td>~5,000/sec&lt;/td>
 &lt;td>~30,000/sec&lt;/td>
 &lt;td>開發機&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Linux VPS SATA SSD&lt;/td>
 &lt;td>~3,000/sec&lt;/td>
 &lt;td>~20,000/sec&lt;/td>
 &lt;td>小型部署&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Raspberry Pi 4 SD card&lt;/td>
 &lt;td>~200/sec&lt;/td>
 &lt;td>~1,000/sec&lt;/td>
 &lt;td>邊緣設備&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="和事件產生速率的對照">和事件產生速率的對照&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>預估 events/sec&lt;/th>
 &lt;th>SQLite 批次能撐嗎&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>自用 1 個 app&lt;/td>
 &lt;td>&amp;lt; 10&lt;/td>
 &lt;td>遠超需求&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>小團隊 5 人各跑 1 個 app&lt;/td>
 &lt;td>&amp;lt; 50&lt;/td>
 &lt;td>綽綽有餘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>10 SDK 同時 flush&lt;/td>
 &lt;td>100-1000 burst&lt;/td>
 &lt;td>批次 INSERT 撐得住&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>100+ 使用者持續活躍&lt;/td>
 &lt;td>500+ 持續&lt;/td>
 &lt;td>邊界 — 觀察 database is locked&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>burst 和持續的差異在於：burst 是短暫的高峰（flush batch 到達後數秒內消化完），持續是長時間的穩定高流量。SQLite 的 WAL mode 對 burst 容忍度高（write lock 等待時間短），對持續高流量容忍度有限（write lock 等待累積）。&lt;/p></description><content:encoded><![CDATA[<p>SQLite Backend 的效能受三個因素影響：儲存裝置（SSD vs HDD vs SD card）、Go driver 選擇（modernc.org/sqlite pure Go vs mattn/go-sqlite3 CGO）、並發模型（WAL mode + single-writer）。本章根據 SQLite 的技術特性和業界基準推導預期效能範圍，並提供實測方法讓使用者在自己的環境驗證。所有數字是預期範圍而非實測值 — 實際效能依硬體和 workload 而定。</p>
<h2 id="寫入吞吐">寫入吞吐</h2>
<p>寫入吞吐決定 collector 每秒能消化多少事件。SQLite 的寫入效能主要受 fsync 頻率和 WAL checkpoint 影響。</p>
<h3 id="單筆-insert">單筆 INSERT</h3>
<p>每筆 INSERT 獨立一個 transaction 時，每次 commit 都會 fsync。WAL mode 的 fsync 成本比 journal mode 低（append-only），但仍是寫入的主要瓶頸。</p>
<table>
  <thead>
      <tr>
          <th>儲存裝置</th>
          <th>單筆 INSERT 延遲</th>
          <th>理論上限</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>NVMe SSD</td>
          <td>10-30 μs</td>
          <td>30,000-100,000 inserts/sec</td>
      </tr>
      <tr>
          <td>SATA SSD</td>
          <td>30-50 μs</td>
          <td>20,000-30,000 inserts/sec</td>
      </tr>
      <tr>
          <td>HDD</td>
          <td>50-200 μs</td>
          <td>5,000-20,000 inserts/sec</td>
      </tr>
      <tr>
          <td>SD card</td>
          <td>500-2000 μs</td>
          <td>500-2,000 inserts/sec</td>
      </tr>
  </tbody>
</table>
<p>modernc.org/sqlite（pure Go）的效能約為 CGO driver（mattn/go-sqlite3）的 60-80%。上表數字基於 CGO driver，pure Go 需打八折。Go HTTP handler 的開銷（JSON 解碼、schema 驗證、goroutine 調度）再扣 10-20%。</p>
<h3 id="批次-insert">批次 INSERT</h3>
<p>一個 transaction 包裹多筆 INSERT，只做一次 fsync。Collector 接收 SDK 的 flush batch（一個 HTTP request 帶一批事件）天然適合批次寫入。</p>
<p>吞吐提升幅度和批次大小的關係：</p>
<table>
  <thead>
      <tr>
          <th>批次大小</th>
          <th>相對單筆的吞吐提升</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10 筆/tx</td>
          <td>3-5x</td>
      </tr>
      <tr>
          <td>100 筆/tx</td>
          <td>5-10x</td>
      </tr>
      <tr>
          <td>1000 筆/tx</td>
          <td>8-15x</td>
      </tr>
  </tbody>
</table>
<p>提升來自 fsync 次數從「每筆一次」降到「每批一次」。超過 100 筆/tx 後邊際收益遞減。</p>
<h3 id="實際預期">實際預期</h3>
<p>結合 pure Go driver、HTTP handler 開銷和批次寫入，不同環境下的預期吞吐：</p>
<table>
  <thead>
      <tr>
          <th>環境</th>
          <th>單筆</th>
          <th>批次（100/tx）</th>
          <th>適合場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Mac M1/M2 NVMe + pure Go</td>
          <td>~5,000/sec</td>
          <td>~30,000/sec</td>
          <td>開發機</td>
      </tr>
      <tr>
          <td>Linux VPS SATA SSD</td>
          <td>~3,000/sec</td>
          <td>~20,000/sec</td>
          <td>小型部署</td>
      </tr>
      <tr>
          <td>Raspberry Pi 4 SD card</td>
          <td>~200/sec</td>
          <td>~1,000/sec</td>
          <td>邊緣設備</td>
      </tr>
  </tbody>
</table>
<h3 id="和事件產生速率的對照">和事件產生速率的對照</h3>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>預估 events/sec</th>
          <th>SQLite 批次能撐嗎</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自用 1 個 app</td>
          <td>&lt; 10</td>
          <td>遠超需求</td>
      </tr>
      <tr>
          <td>小團隊 5 人各跑 1 個 app</td>
          <td>&lt; 50</td>
          <td>綽綽有餘</td>
      </tr>
      <tr>
          <td>10 SDK 同時 flush</td>
          <td>100-1000 burst</td>
          <td>批次 INSERT 撐得住</td>
      </tr>
      <tr>
          <td>100+ 使用者持續活躍</td>
          <td>500+ 持續</td>
          <td>邊界 — 觀察 database is locked</td>
      </tr>
  </tbody>
</table>
<p>burst 和持續的差異在於：burst 是短暫的高峰（flush batch 到達後數秒內消化完），持續是長時間的穩定高流量。SQLite 的 WAL mode 對 burst 容忍度高（write lock 等待時間短），對持續高流量容忍度有限（write lock 等待累積）。</p>
<h2 id="查詢延遲">查詢延遲</h2>
<p>查詢延遲決定 dashboard 的刷新體驗。SQLite 的查詢效能取決於索引覆蓋和掃描行數。</p>
<h3 id="有索引的查詢">有索引的查詢</h3>
<p>建議的索引（見 <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</a> 的建議索引段）覆蓋 dashboard 的核心查詢模式。有索引時的預期延遲：</p>
<table>
  <thead>
      <tr>
          <th>查詢模式</th>
          <th>10 萬筆</th>
          <th>50 萬筆</th>
          <th>100 萬筆</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>等值查詢（WHERE session_id = ?）</td>
          <td>&lt; 1ms</td>
          <td>&lt; 1ms</td>
          <td>&lt; 1ms</td>
      </tr>
      <tr>
          <td>範圍查詢（WHERE ts BETWEEN ? AND ?）</td>
          <td>&lt; 10ms</td>
          <td>10-50ms</td>
          <td>50-100ms</td>
      </tr>
      <tr>
          <td>GROUP BY name</td>
          <td>10-50ms</td>
          <td>50-200ms</td>
          <td>200-500ms</td>
      </tr>
      <tr>
          <td>COUNT DISTINCT session_id</td>
          <td>50-100ms</td>
          <td>200-500ms</td>
          <td>500ms-1s</td>
      </tr>
      <tr>
          <td>JOIN + window function</td>
          <td>100ms-1s</td>
          <td>1-3s</td>
          <td>3-10s</td>
      </tr>
  </tbody>
</table>
<h3 id="無索引的查詢">無索引的查詢</h3>
<p>無索引時 SQLite 做全表掃描。掃描速度約 50-100 MB/sec（SSD）、10-30 MB/sec（HDD）。</p>
<table>
  <thead>
      <tr>
          <th>資料量</th>
          <th>預估大小</th>
          <th>SSD 全掃延遲</th>
          <th>HDD 全掃延遲</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10 萬筆</td>
          <td>~40 MB</td>
          <td>200-500ms</td>
          <td>1-3s</td>
      </tr>
      <tr>
          <td>100 萬筆</td>
          <td>~400 MB</td>
          <td>2-5s</td>
          <td>10-30s</td>
      </tr>
      <tr>
          <td>300 萬筆</td>
          <td>~1.2 GB</td>
          <td>5-15s</td>
          <td>30-90s</td>
      </tr>
  </tbody>
</table>
<p>超過 100 萬筆無索引查詢會超出 dashboard 可接受的刷新延遲 — 這是 day-one 就建索引的理由。</p>
<h3 id="dashboard-刷新頻率-vs-查詢延遲">Dashboard 刷新頻率 vs 查詢延遲</h3>
<p>Dashboard 的每個視圖有不同的刷新間隔和可接受延遲。查詢延遲超過可接受值時，dashboard 體驗變差（等待轉圈、資料過時）。</p>
<table>
  <thead>
      <tr>
          <th>Dashboard 視圖</th>
          <th>刷新間隔</th>
          <th>可接受延遲</th>
          <th>10 萬筆有索引</th>
          <th>100 萬筆有索引</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>即時狀態卡</td>
          <td>1-5 秒</td>
          <td>&lt; 100ms</td>
          <td>滿足</td>
          <td>滿足</td>
      </tr>
      <tr>
          <td>Error 列表</td>
          <td>5-10 秒</td>
          <td>&lt; 500ms</td>
          <td>滿足</td>
          <td>滿足</td>
      </tr>
      <tr>
          <td>趨勢圖（最近 24h）</td>
          <td>30 秒</td>
          <td>&lt; 1s</td>
          <td>滿足</td>
          <td>邊界</td>
      </tr>
      <tr>
          <td>長期聚合（最近 30 天）</td>
          <td>5 分鐘</td>
          <td>&lt; 3s</td>
          <td>滿足</td>
          <td>需要預聚合</td>
      </tr>
  </tbody>
</table>
<p>「需要預聚合」代表原始事件的聚合查詢超過可接受延遲，應該依賴分層保留策略中的 hourly_summary / daily_summary 表（見 <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</a> 的分層保留段）。</p>
<h2 id="資源消耗">資源消耗</h2>
<h3 id="記憶體">記憶體</h3>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>佔用</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Go HTTP server</td>
          <td>20-50 MB</td>
          <td>基礎開銷</td>
      </tr>
      <tr>
          <td>SQLite page cache</td>
          <td>2 MB（預設）</td>
          <td><code>PRAGMA cache_size</code> 可調</td>
      </tr>
      <tr>
          <td>寫入 buffer（channel）</td>
          <td>1-10 MB</td>
          <td>取決於 channel 容量和事件大小</td>
      </tr>
      <tr>
          <td>查詢結果暫存</td>
          <td>和結果集成正比</td>
          <td>GROUP BY 10 萬筆 ~10 MB</td>
      </tr>
      <tr>
          <td><strong>Collector 整體</strong></td>
          <td><strong>50-100 MB</strong></td>
          <td>自用場景</td>
      </tr>
  </tbody>
</table>
<p>Raspberry Pi（1 GB RAM）上建議把 page cache 調小（<code>PRAGMA cache_size = -512</code> = 512 KB），避免大結果集查詢（加 LIMIT），dashboard 刷新頻率降低。</p>
<h3 id="cpu">CPU</h3>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>CPU 使用</th>
          <th>備註</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>INSERT（寫入）</td>
          <td>可忽略</td>
          <td>I/O bound，CPU 不是瓶頸</td>
      </tr>
      <tr>
          <td>SELECT（查詢）</td>
          <td>和掃描行數正比</td>
          <td>有索引時可忽略</td>
      </tr>
      <tr>
          <td>Downsample（每小時）</td>
          <td>短暫 spike &lt; 1s</td>
          <td>處理最近一小時的事件</td>
      </tr>
      <tr>
          <td>Purge（每天）</td>
          <td>短暫 spike 1-3s</td>
          <td>分批 DELETE</td>
      </tr>
      <tr>
          <td><strong>整體</strong></td>
          <td><strong>&lt; 5%</strong></td>
          <td>自用場景</td>
      </tr>
  </tbody>
</table>
<h3 id="磁碟">磁碟</h3>
<table>
  <thead>
      <tr>
          <th>日事件量</th>
          <th>原始資料/天</th>
          <th>原始資料/月</th>
          <th>含索引/月</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1,000（極低）</td>
          <td>0.3-0.5 MB</td>
          <td>9-15 MB</td>
          <td>11-18 MB</td>
      </tr>
      <tr>
          <td>10,000（自用）</td>
          <td>3-5 MB</td>
          <td>90-150 MB</td>
          <td>110-180 MB</td>
      </tr>
      <tr>
          <td>100,000（小團隊）</td>
          <td>30-50 MB</td>
          <td>0.9-1.5 GB</td>
          <td>1.1-1.8 GB</td>
      </tr>
  </tbody>
</table>
<p>WAL 檔案通常 &lt; 10 MB（auto-checkpoint 在 WAL 達到 1000 pages 時觸發）。分層保留策略下，原始事件只保留 7 天，長期佔用由聚合摘要表決定（遠小於原始事件）。</p>
<h2 id="邊緣設備場景">邊緣設備場景</h2>
<p>Raspberry Pi、低配 VPS（1 核 / 1 GB RAM）、甚至 NAS 上跑 collector 時的特殊考量：</p>
<p><strong>SD card 的隨機寫入</strong>：SD card 的隨機寫入 IOPS 極低（100-500 IOPS），WAL mode 的 checkpoint（把 WAL 內容合併回主資料庫檔案）可能卡住 1-5 秒。期間新的寫入等待 checkpoint 完成。建議調高 <code>wal_autocheckpoint</code> 的閾值（如 5000 pages），讓 checkpoint 頻率降低但每次時間更長 — 在非活躍時段（凌晨）手動觸發 <code>PRAGMA wal_checkpoint(TRUNCATE)</code>。</p>
<p><strong>1 GB RAM</strong>：cache_size 調小（512 KB）、避免 <code>SELECT *</code> 不帶 LIMIT、GROUP BY 的結果集用 HAVING 條件過濾減少暫存。Dashboard 的長期聚合直接查 hourly_summary 表而非原始事件。</p>
<p><strong>ARM CPU</strong>：pure Go SQLite driver（modernc.org/sqlite）在 ARM 上的效能差距可能比 x86 更大（pure Go 的 C-to-Go 翻譯在 ARM 的指令最佳化較少）。實測確認。</p>
<p><strong>建議配置</strong>：邊緣設備上 collector 的 dashboard 刷新頻率從預設值降低（即時狀態卡 5 秒 → 30 秒，趨勢圖 30 秒 → 5 分鐘），降採樣 job 頻率從每小時改為每 6 小時。</p>
<h2 id="實測方法指引">實測方法指引</h2>
<p>教學的預期數字是推導值，實際效能取決於使用者的硬體和 workload。Collector 提供內建的 benchmark 命令讓使用者在自己的環境實測。</p>
<h3 id="寫入-benchmark">寫入 benchmark</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 單筆寫入：10000 筆，每筆獨立 transaction</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">./collector benchmark write --events<span class="o">=</span><span class="m">10000</span> --batch<span class="o">=</span><span class="m">1</span> --storage<span class="o">=</span>sqlite
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 批次寫入：10000 筆，每 100 筆一個 transaction</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">./collector benchmark write --events<span class="o">=</span><span class="m">10000</span> --batch<span class="o">=</span><span class="m">100</span> --storage<span class="o">=</span>sqlite</span></span></code></pre></div><p>輸出：total duration、events/sec、p50/p95/p99 latency per event。</p>
<h3 id="查詢-benchmark">查詢 benchmark</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 先灌入測試資料</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">./collector benchmark seed --events<span class="o">=</span><span class="m">100000</span> --storage<span class="o">=</span>sqlite
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 跑查詢 benchmark</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">./collector benchmark query --type<span class="o">=</span>error --group-by<span class="o">=</span>name --storage<span class="o">=</span>sqlite
</span></span><span class="line"><span class="ln">6</span><span class="cl">./collector benchmark query --session-id<span class="o">=</span>random --storage<span class="o">=</span>sqlite</span></span></code></pre></div><p>輸出：query duration、rows scanned、rows returned。</p>
<h3 id="production-觀察指標">Production 觀察指標</h3>
<p>部署後用 DevOps dashboard（見 <a href="/blog/monitoring/04-collector/dashboard-devops/" data-link-title="DevOps Dashboard 設計" data-link-desc="Collector 和 SDK 是否健康 — 日常監控的服務狀態卡、吞吐量曲線、儲存用量，以及告警觸發後的排障視圖">DevOps Dashboard 設計</a>）觀察 collector 自身的效能 metric：</p>
<ul>
<li><code>collector.storage.write_duration_ms</code>：每次寫入的延遲。P95 超過 100ms 是瓶頸訊號。</li>
<li><code>collector.storage.query_duration_ms</code>：每次查詢的延遲。P95 超過 dashboard 刷新間隔是瓶頸訊號。</li>
<li><code>collector.storage.db_size_bytes</code>：資料庫大小。接近磁碟可用空間的 80% 時觸發 purge 或擴容。</li>
<li><code>collector.storage.wal_size_bytes</code>：WAL 檔案大小。持續 &gt; 50 MB 代表 checkpoint 跟不上寫入速度。</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>切換到 PostgreSQL 的觸發條件 → <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</a></li>
<li>SQLite 和 PostgreSQL 的功能分層 → <a href="/blog/monitoring/04-collector/feature-tier-boundary/" data-link-title="功能分層與 Backend 選擇" data-link-desc="SQLite 層和 PostgreSQL 層各自承載哪些功能 — 分界線是查詢模式而非資料量、觸發升級的是功能需求而非規模成長">功能分層與 Backend 選擇</a></li>
<li>Ingestion 端的擴展設計 → <a href="/blog/monitoring/04-collector/ingestion-scaling/" data-link-title="Ingestion Scaling" data-link-desc="四層防線應對 ingestion 端的流量擴展 — SDK 取樣、Collector 背壓、水平擴展、Queue 解耦">Ingestion Scaling</a></li>
</ul>
]]></content:encoded></item><item><title>9.12 SLO 與 Performance Budget</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/slo-performance-budget/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/slo-performance-budget/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>SLO 與 performance budget 的責任是讓容量決策有「可衡量的目標 + 可審查的代價」。沒有 SLO 時、容量規劃容易變「越大越好」、沒邊界；有 SLO + budget 之後、所有決策都能回答「是否在 budget 內」、「超出 budget 該怎麼辦」。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO 與 Error Budget&lt;/a> 的關係：06.6 處理「可靠性 SLO」（用 error budget 凍結 release）、9.12 處理「效能 SLO」（用 performance budget 約束容量）。兩者用同一套方法論、目標不同。讀者可以把本章當作 06.6 的 &lt;em>效能對應&lt;/em> 章節。&lt;/p>
&lt;p>本章覆蓋 SLI/SLO/SLA 分層、latency budget 分解、performance budget vs error budget、SLO 等級的成本含義、多 SLO 對齊、SLO drift 維護。讀完後讀者能設計一套完整的 SLO + budget 系統、把容量決策跟 SLO 對接。&lt;/p>
&lt;h2 id="sli--slo--sla-三層分清">SLI / SLO / SLA 三層分清&lt;/h2>
&lt;p>三個名詞常被混用、實際是三個不同層的概念。&lt;/p>
&lt;p>&lt;strong>SLI（Service Level Indicator）&lt;/strong>：客觀量測值。p99 latency、availability、throughput、error rate 都是 SLI。
&lt;strong>SLO（Service Level Objective）&lt;/strong>：團隊內部目標。「99.95% 用戶請求 &amp;lt; 500ms」這類具體承諾。
&lt;strong>SLA（Service Level Agreement）&lt;/strong>：對外合約承諾。達不到要退款、違約金、信用補償。&lt;/p>
&lt;p>&lt;strong>SLO 比 SLA 嚴 — 給內部 buffer&lt;/strong>。SLA 訂 99.9%、SLO 訂 99.95% — 萬一 SLO 沒達到、SLA 還沒違約、有反應時間。&lt;/p>
&lt;p>&lt;strong>容量規劃針對 SLO、不是 SLA&lt;/strong>：SLA 是「最低不能跌破」、SLO 才是「日常目標」。用 SLA 做容量規劃會經常 violate SLA、給用戶 / 客戶不好體驗。&lt;/p>
&lt;p>詳見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI / SLO 卡片&lt;/a>。&lt;/p>
&lt;h2 id="latency-budget-分解">Latency budget 分解&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/latency-budget/" data-link-title="Latency Budget" data-link-desc="把 user-perceived latency 拆到每個 stage 的配額、反推架構選擇">Latency budget&lt;/a> 是把 SLO 翻成可分解工程目標的關鍵工具。&lt;/p>
&lt;p>&lt;strong>從 end-to-end latency 開始&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>用戶感受到的 latency：DNS resolution + TLS handshake + CDN + load balancer + application + cache + DB + serialization + network back&lt;/li>
&lt;li>SLO 訂在 user-perceived：例如「p99 end-to-end &amp;lt; 500ms」&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>拆到每個 stage 的 budget&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>DNS：5ms（assume cached）&lt;/li>
&lt;li>TLS handshake：50ms（first request）&lt;/li>
&lt;li>CDN：20ms&lt;/li>
&lt;li>Load balancer：5ms&lt;/li>
&lt;li>Application：100ms&lt;/li>
&lt;li>Cache lookup：5ms（hit）/ 100ms（miss）&lt;/li>
&lt;li>DB query：30ms&lt;/li>
&lt;li>Serialization：10ms&lt;/li>
&lt;li>Network return：15ms&lt;/li>
&lt;li>&lt;strong>總和&lt;/strong>：240ms（cache hit）/ 335ms（miss）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>每個 stage 的 budget 必須 &lt;em>跟 SLO 對齊&lt;/em>&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>SLO 與 performance budget 的責任是讓容量決策有「可衡量的目標 + 可審查的代價」。沒有 SLO 時、容量規劃容易變「越大越好」、沒邊界；有 SLO + budget 之後、所有決策都能回答「是否在 budget 內」、「超出 budget 該怎麼辦」。</p>
<p>跟 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO 與 Error Budget</a> 的關係：06.6 處理「可靠性 SLO」（用 error budget 凍結 release）、9.12 處理「效能 SLO」（用 performance budget 約束容量）。兩者用同一套方法論、目標不同。讀者可以把本章當作 06.6 的 <em>效能對應</em> 章節。</p>
<p>本章覆蓋 SLI/SLO/SLA 分層、latency budget 分解、performance budget vs error budget、SLO 等級的成本含義、多 SLO 對齊、SLO drift 維護。讀完後讀者能設計一套完整的 SLO + budget 系統、把容量決策跟 SLO 對接。</p>
<h2 id="sli--slo--sla-三層分清">SLI / SLO / SLA 三層分清</h2>
<p>三個名詞常被混用、實際是三個不同層的概念。</p>
<p><strong>SLI（Service Level Indicator）</strong>：客觀量測值。p99 latency、availability、throughput、error rate 都是 SLI。
<strong>SLO（Service Level Objective）</strong>：團隊內部目標。「99.95% 用戶請求 &lt; 500ms」這類具體承諾。
<strong>SLA（Service Level Agreement）</strong>：對外合約承諾。達不到要退款、違約金、信用補償。</p>
<p><strong>SLO 比 SLA 嚴 — 給內部 buffer</strong>。SLA 訂 99.9%、SLO 訂 99.95% — 萬一 SLO 沒達到、SLA 還沒違約、有反應時間。</p>
<p><strong>容量規劃針對 SLO、不是 SLA</strong>：SLA 是「最低不能跌破」、SLO 才是「日常目標」。用 SLA 做容量規劃會經常 violate SLA、給用戶 / 客戶不好體驗。</p>
<p>詳見 <a href="/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI / SLO 卡片</a>。</p>
<h2 id="latency-budget-分解">Latency budget 分解</h2>
<p><a href="/blog/backend/knowledge-cards/latency-budget/" data-link-title="Latency Budget" data-link-desc="把 user-perceived latency 拆到每個 stage 的配額、反推架構選擇">Latency budget</a> 是把 SLO 翻成可分解工程目標的關鍵工具。</p>
<p><strong>從 end-to-end latency 開始</strong>：</p>
<ul>
<li>用戶感受到的 latency：DNS resolution + TLS handshake + CDN + load balancer + application + cache + DB + serialization + network back</li>
<li>SLO 訂在 user-perceived：例如「p99 end-to-end &lt; 500ms」</li>
</ul>
<p><strong>拆到每個 stage 的 budget</strong>：</p>
<ul>
<li>DNS：5ms（assume cached）</li>
<li>TLS handshake：50ms（first request）</li>
<li>CDN：20ms</li>
<li>Load balancer：5ms</li>
<li>Application：100ms</li>
<li>Cache lookup：5ms（hit）/ 100ms（miss）</li>
<li>DB query：30ms</li>
<li>Serialization：10ms</li>
<li>Network return：15ms</li>
<li><strong>總和</strong>：240ms（cache hit）/ 335ms（miss）</li>
</ul>
<p><strong>每個 stage 的 budget 必須 <em>跟 SLO 對齊</em></strong>：</p>
<ul>
<li>每個 stage 加總 = SLO 上限</li>
<li>任何 stage 超 budget → 該 stage 必須改善（不是其他 stage 來補）</li>
<li>每個 stage 必須有 <em>current measurement</em> — 不能訂了沒量</li>
</ul>
<p><strong>Cross-region call 自帶不可壓縮 latency</strong>：</p>
<ul>
<li>同 AZ：&lt; 1ms</li>
<li>跨 AZ：1-2ms</li>
<li>跨 region 同 continent：20-30ms</li>
<li>跨 continent：100-200ms</li>
<li>SLO 訂 50ms 但服務要跨 region 設計 → 不可能達成</li>
</ul>
<p><strong>任何新增 stage 都會吃 budget</strong>：middleware、sidecar、interceptor、API gateway 都會增加 latency。設計時要明確認知這層代價。</p>
<p>對應案例：<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> — sub-millisecond 反推所有架構選擇（Cluster Placement Group 壓網路、z1d 壓 CPU、RAFT 壓共識）；<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 inference 多 stage 各自分配 budget。</p>
<h2 id="performance-budget">Performance budget</h2>
<p><a href="/blog/backend/knowledge-cards/performance-budget/" data-link-title="Performance Budget" data-link-desc="跟 error budget 同類概念、但用於 latency / throughput 退化的可控額度">Performance budget</a> 跟 error budget 是 <em>姊妹概念</em> — 用同一套方法論處理可靠性 vs 效能。</p>
<p><strong>Error budget</strong>（<a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6</a>）：</p>
<ul>
<li>每月有允許的 unavailability 額度</li>
<li>例如 SLO 99.95% → error budget = 0.05% × 30 days = 21.6 分鐘 / 月</li>
<li>額度用完 → freeze new release、focus on reliability</li>
</ul>
<p><strong>Performance budget</strong>（本章）：</p>
<ul>
<li>每月有允許的 latency 退化額度</li>
<li>例如「p99 允許比 baseline 高 10ms 連續 X 分鐘」、用 <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> alert</li>
<li>額度用完 → freeze new feature release、focus on perf</li>
</ul>
<p><strong>兩個 budget 並列、不衝突</strong>：</p>
<ul>
<li>一個燒一個健康 → 部分 freeze（freeze 對應的那條）</li>
<li>兩個都健康 → 全速 release</li>
<li>兩個都燒 → 全面 freeze、deep review</li>
</ul>
<p><strong>Burn rate alert 比 threshold alert 好</strong>：</p>
<ul>
<li>threshold：p99 &gt; 500ms 就 alert → false positive 多</li>
<li>burn rate：過去 1 小時 budget burn rate &gt; 14.4x 就 alert（Google SRE 推薦）→ 對應「再這樣下去 budget 5 分鐘內燒光」</li>
</ul>
<p>對應案例：<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 延遲就是收入</a> — 沒 performance budget 等於沒 release control；<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; 州的雙重峰值">FanDuel 多 SLO</a> — 直播 vs 投注不同 budget。</p>
<h2 id="slo-等級的成本含義">SLO 等級的成本含義</h2>
<p>不同 SLO 等級對應不同容量成本、選 SLO 就是選成本。</p>
<table>
  <thead>
      <tr>
          <th>SLO</th>
          <th>年 downtime 上限</th>
          <th>工程含義</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>99%</td>
          <td>年 87.6 小時</td>
          <td>單 AZ 部署可接受</td>
          <td>B2C 內部工具、非 critical SaaS</td>
      </tr>
      <tr>
          <td>99.9%</td>
          <td>年 8.76 小時</td>
          <td>多 AZ、reactive failover</td>
          <td>B2C consumer-facing</td>
      </tr>
      <tr>
          <td>99.95%</td>
          <td>年 4.38 小時</td>
          <td>多 AZ active-active、autoscale 必要</td>
          <td>B2B SaaS minimum</td>
      </tr>
      <tr>
          <td>99.99%</td>
          <td>年 52.6 分鐘</td>
          <td>多 region active-active、無人工介入</td>
          <td>mission-critical SaaS</td>
      </tr>
      <tr>
          <td>99.999%</td>
          <td>年 5.26 分鐘</td>
          <td>全球多 region、即時 failover、人工極少</td>
          <td>金融 / 醫療 / 電信</td>
      </tr>
  </tbody>
</table>
<p><strong>每多一個 9、容量成本指數成長</strong>：</p>
<ul>
<li>99 → 99.9：成本 +30-50%</li>
<li>99.9 → 99.99：成本 +50-100%</li>
<li>99.99 → 99.999：成本 +200-500%</li>
</ul>
<p><strong>選 SLO 不是 marketing 決策、是工程經濟決策</strong>：選太高、燒錢；選太低、用戶不滿。要算 <em>每個 9 對應的業務價值</em>、是否值得對應的容量投資。</p>
<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 99.999%</a> — 廣告計費 1 分鐘斷線損失幾百萬美金、5 個 9 是真實營收邊界；<a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">Genesys 99.999%</a> — B2B 客服 SaaS、客戶停線 = 客戶失去用戶信任、5 個 9 是合約義務。</p>
<h2 id="多-slo-對齊">多 SLO 對齊</h2>
<p>同一系統不同工作負載可以有不同 SLO、按業務重要性分級。</p>
<p><strong>設計原則</strong>：</p>
<ul>
<li>按「業務重要性 × 用戶感知」分級</li>
<li>同一個 endpoint 不同情境可能有不同 SLO（例如登入 vs 結帳）</li>
<li>多 SLO 必須有 <em>優先順序</em>、衝突時知道犧牲哪個</li>
</ul>
<p><strong>範例</strong>：</p>
<table>
  <thead>
      <tr>
          <th>Endpoint</th>
          <th>SLO</th>
          <th>業務影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>登入</td>
          <td>p99 200ms</td>
          <td>用戶 onboarding</td>
      </tr>
      <tr>
          <td>瀏覽商品</td>
          <td>p99 500ms</td>
          <td>用戶 retention</td>
      </tr>
      <tr>
          <td>結帳</td>
          <td>p99 300ms</td>
          <td>直接影響收入</td>
      </tr>
      <tr>
          <td>推薦</td>
          <td>p99 1000ms</td>
          <td>影響 conversion 但非阻斷</td>
      </tr>
  </tbody>
</table>
<p><strong>衝突處理</strong>：當 capacity 不夠時、優先保 <em>結帳</em> 而非 <em>推薦</em>、即使技術上推薦比較好擴容。</p>
<p>對應案例：<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; 州的雙重峰值">FanDuel</a> 直播秒級 SLO vs 投注毫秒級 SLO、同一個 user 同一場 NFL Super Bowl、兩個服務必須分開部署、各自 SLO。</p>
<h2 id="slo-演進baseline-drift">SLO 演進：baseline drift</h2>
<p><a href="/blog/backend/knowledge-cards/slo-baseline-drift/" data-link-title="SLO Baseline Drift" data-link-desc="SLO baseline 因業務變化 / surge / 架構改動而需要重新校準的現象">SLO 不是訂了就不動</a> — 業務變化要重新校準。</p>
<p><strong>SLO drift 來源</strong>：</p>
<ul>
<li>Structural surge：COVID 類外部衝擊讓 baseline 永久上移</li>
<li>Product change：新 feature 改變用戶 journey</li>
<li>Architectural improvement：DB 換型、cache 加強、CDN 擴點</li>
<li>User behavior：mobile share 上升、跨 region 比例變化</li>
</ul>
<p><strong>Drift 不是 anomaly、是 <em>新常態</em></strong>。</p>
<p><strong>Review 節奏</strong>：</p>
<ul>
<li>每季 review SLO：拉過去 90 天 SLI 分布、看是否需要調整</li>
<li>重大產品改動立即 review</li>
<li>Drift 確認後要更新：alert threshold、autoscaler trigger、performance budget 額度、容量規劃 baseline</li>
</ul>
<p>對應案例：<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 撐住會議後端">Zoom 30x COVID</a> — 30 倍成長後 baseline 永久上移、SLO threshold 跟著重新校準、不能套用 COVID 前的標準。</p>
<h2 id="slo-跟容量規劃對接">SLO 跟容量規劃對接</h2>
<p>回到本章開頭的論點 — SLO 是容量決策的目標。</p>
<p><strong>容量公式</strong>：能撐多少 RPS @ SLO 條件。
<strong>規劃時用「SLO-constrained capacity」、不是「max capacity」</strong>：</p>
<ul>
<li>max capacity：絕對極限、進 cliff</li>
<li>SLO-constrained capacity：知道在 SLO 條件下能撐多少</li>
<li>兩者差 30-50%（headroom）</li>
</ul>
<p><strong>9.4 saturation 找 knee 是技術指標、9.6 容量規劃用 SLO-constrained knee</strong>：</p>
<ul>
<li>saturation 在 utilization 80% 時開始</li>
<li>但 SLO 可能要求 utilization 60% 以下</li>
<li>容量規劃用 60% 而非 80%</li>
</ul>
<p><strong>跟 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本工程</a> 對接</strong>：</p>
<ul>
<li>每多一個 9 多花多少錢</li>
<li>業務需要這個 9 嗎</li>
<li>不需要的話降 SLO 省成本</li>
</ul>
<h2 id="slo-跟-performance-budget-一起用">SLO 跟 performance budget 一起用</h2>
<p>最後的整合 — error budget + performance budget 一起治理 release 節奏。</p>
<p><strong>Error budget 控制 <em>變更節奏</em></strong>：</p>
<ul>
<li>error budget 健康 → release 可以快</li>
<li>error budget 燒光 → freeze release</li>
</ul>
<p><strong>Performance budget 控制 <em>容量決策</em></strong>：</p>
<ul>
<li>performance budget 健康 → 新 feature 可以引入 perf cost</li>
<li>performance budget 燒光 → freeze new feature</li>
</ul>
<p><strong>兩個 budget 並列</strong>：</p>
<ul>
<li>都健康 → 全速 release + 新 feature</li>
<li>error 健康 + perf 燒 → release 但只接 perf-neutral 變更</li>
<li>error 燒 + perf 健康 → 暫停 release、修可靠性</li>
<li>都燒 → 全面 freeze、deep review</li>
</ul>
<p>對應 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO</a> 跟 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate</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/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</a></td>
          <td>latency budget 反推架構</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 / C24 99.999%</a></td>
          <td>5 個 9 的容量代價</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 ML stage budget</a></td>
          <td>p99 多 stage 分配</td>
      </tr>
      <tr>
          <td><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 多 SLO</a></td>
          <td>直播 vs 投注不同 SLO 並存</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>SLO baseline 重新校準</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>（latency budget 反推）</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>（SLO-constrained capacity）</li>
<li>跨模組：<a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO 與 Error Budget 政策</a>（可靠性 SLO）</li>
<li>跨模組：<a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a>（量測層）</li>
</ul>
<h2 id="既建知識卡片">既建知識卡片</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI / SLO</a></li>
<li><a href="/blog/backend/knowledge-cards/latency-budget/" data-link-title="Latency Budget" data-link-desc="把 user-perceived latency 拆到每個 stage 的配額、反推架構選擇">Latency Budget</a></li>
<li><a href="/blog/backend/knowledge-cards/performance-budget/" data-link-title="Performance Budget" data-link-desc="跟 error budget 同類概念、但用於 latency / throughput 退化的可控額度">Performance Budget</a></li>
<li><a href="/blog/backend/knowledge-cards/slo-baseline-drift/" data-link-title="SLO Baseline Drift" data-link-desc="SLO baseline 因業務變化 / surge / 架構改動而需要重新校準的現象">SLO Baseline Drift</a></li>
<li><a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">Error Budget</a></li>
</ul>
]]></content:encoded></item><item><title>9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/</guid><description>&lt;p>這個案例的核心責任是說明「K8s 多 cluster 治理」對容量規劃的影響。Riot Games 經營 League of Legends、VALORANT、TFT 等多款全球遊戲、單一遊戲跨多地區、需要 &amp;lt; 35ms 延遲、需要做到「快速部署新遊戲 / 新區域」— 這套需求把容量規劃的單位從「instance」改成「cluster」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Riot Games 遷移到 EKS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/riot-games-case-study/">Riot Games case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>月活用戶&lt;/td>
 &lt;td>1.8 億 +&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cluster 數量&lt;/td>
 &lt;td>246 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施年省&lt;/td>
 &lt;td>1000 萬美金&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部署速度提升&lt;/td>
 &lt;td>12x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施設定速度&lt;/td>
 &lt;td>+90%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲門檻&lt;/td>
 &lt;td>35ms（VALORANT 等競技遊戲）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>標準化覆蓋率&lt;/td>
 &lt;td>80% 基礎設施移到中央管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開發者基礎設施工作下降&lt;/td>
 &lt;td>-40%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件回應時間下降&lt;/td>
 &lt;td>-50%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon EKS（主要）、AWS Local Zones（低延遲就近部署）、AWS Outposts（on-prem edge）、Karpenter（node lifecycle）、Terraform（IaC）。&lt;/p>
&lt;p>關鍵架構決策：從 multi-tenant cluster 模型改成 &lt;em>single-tenant per game&lt;/em> — 每個遊戲一個獨立 cluster、避免跨遊戲互相影響。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Riot Games 案例揭露三個多 cluster K8s 容量治理重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Cluster 隔離是容量規劃的單位&lt;/strong>：246 個 cluster 看似很多、但 &lt;em>每個 cluster 是獨立容量單位&lt;/em>、不互相影響。一個遊戲的擴容不會吃掉另一個遊戲的容量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 multi-tenant vs single-tenant 取捨。&lt;/li>
&lt;li>&lt;strong>延遲門檻反推 region 部署&lt;/strong>：35ms 是競技遊戲（VALORANT、League）的可接受上限、超過會「卡」。從這個門檻反推：玩家所在 region 不能跨洲、需要區域 cluster。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget。Local Zones / Outposts 是這個門檻的工程回應。&lt;/li>
&lt;li>&lt;strong>Karpenter + Terraform = cluster 容量自動化&lt;/strong>：246 個 cluster 手動管理會崩。Karpenter（node 動態 lifecycle）+ Terraform（IaC）讓 cluster 級操作可重複、可審查。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop&lt;/a> 的自動化迴圈。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「年省 1000 萬」是 &lt;em>vs 自管 Mesos&lt;/em>、不是 &lt;em>vs 沒上雲&lt;/em>。EKS 仍有 vendor cost、只是比自管便宜。讀案例時要看 baseline 是什麼。另外、單一 cluster 的容量上限（pod 數、node 數）仍是工程現實、超過時要做 cluster sharding（這正是 Riot 走 246 個 cluster 的部分原因）。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>single-tenant cluster per workload&lt;/strong>：每個高敏感度工作負載（每個遊戲、每個關鍵服務）一個獨立 cluster、避免 noisy neighbor。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a>。&lt;/li>
&lt;li>&lt;strong>延遲門檻反推 region 部署數量&lt;/strong>：先訂 latency budget、再算 &lt;em>玩家分布 × region cluster 數量&lt;/em>。region 增加會線性增加 ops 成本、要在 latency 跟 cost 之間找平衡。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;li>&lt;strong>cluster 級 IaC + 自動化是 multi-cluster 治理前置&lt;/strong>：Terraform / Pulumi / Crossplane + Karpenter / Cluster Autoscaler 是基本工具。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP GKE Fleet management（multi-cluster）、Azure Fleet Manager、自建 Cluster API + ArgoCD 都可以做 multi-cluster 治理。差異是 vendor 整合度跟政策。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「K8s 多 cluster 治理」對容量規劃的影響。Riot Games 經營 League of Legends、VALORANT、TFT 等多款全球遊戲、單一遊戲跨多地區、需要 &lt; 35ms 延遲、需要做到「快速部署新遊戲 / 新區域」— 這套需求把容量規劃的單位從「instance」改成「cluster」。</p>
<h2 id="觀察">觀察</h2>
<p>Riot Games 遷移到 EKS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/riot-games-case-study/">Riot Games case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月活用戶</td>
          <td>1.8 億 +</td>
      </tr>
      <tr>
          <td>Cluster 數量</td>
          <td>246 個</td>
      </tr>
      <tr>
          <td>基礎設施年省</td>
          <td>1000 萬美金</td>
      </tr>
      <tr>
          <td>部署速度提升</td>
          <td>12x</td>
      </tr>
      <tr>
          <td>基礎設施設定速度</td>
          <td>+90%</td>
      </tr>
      <tr>
          <td>延遲門檻</td>
          <td>35ms（VALORANT 等競技遊戲）</td>
      </tr>
      <tr>
          <td>標準化覆蓋率</td>
          <td>80% 基礎設施移到中央管理</td>
      </tr>
      <tr>
          <td>開發者基礎設施工作下降</td>
          <td>-40%</td>
      </tr>
      <tr>
          <td>事件回應時間下降</td>
          <td>-50%</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon EKS（主要）、AWS Local Zones（低延遲就近部署）、AWS Outposts（on-prem edge）、Karpenter（node lifecycle）、Terraform（IaC）。</p>
<p>關鍵架構決策：從 multi-tenant cluster 模型改成 <em>single-tenant per game</em> — 每個遊戲一個獨立 cluster、避免跨遊戲互相影響。</p>
<h2 id="判讀">判讀</h2>
<p>Riot Games 案例揭露三個多 cluster K8s 容量治理重點。</p>
<ol>
<li><strong>Cluster 隔離是容量規劃的單位</strong>：246 個 cluster 看似很多、但 <em>每個 cluster 是獨立容量單位</em>、不互相影響。一個遊戲的擴容不會吃掉另一個遊戲的容量。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 multi-tenant vs single-tenant 取捨。</li>
<li><strong>延遲門檻反推 region 部署</strong>：35ms 是競技遊戲（VALORANT、League）的可接受上限、超過會「卡」。從這個門檻反推：玩家所在 region 不能跨洲、需要區域 cluster。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget。Local Zones / Outposts 是這個門檻的工程回應。</li>
<li><strong>Karpenter + Terraform = cluster 容量自動化</strong>：246 個 cluster 手動管理會崩。Karpenter（node 動態 lifecycle）+ Terraform（IaC）讓 cluster 級操作可重複、可審查。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.9 Performance Improvement Loop</a> 的自動化迴圈。</li>
</ol>
<p>需要警惕：「年省 1000 萬」是 <em>vs 自管 Mesos</em>、不是 <em>vs 沒上雲</em>。EKS 仍有 vendor cost、只是比自管便宜。讀案例時要看 baseline 是什麼。另外、單一 cluster 的容量上限（pod 數、node 數）仍是工程現實、超過時要做 cluster sharding（這正是 Riot 走 246 個 cluster 的部分原因）。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>single-tenant cluster per workload</strong>：每個高敏感度工作負載（每個遊戲、每個關鍵服務）一個獨立 cluster、避免 noisy neighbor。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a>。</li>
<li><strong>延遲門檻反推 region 部署數量</strong>：先訂 latency budget、再算 <em>玩家分布 × region cluster 數量</em>。region 增加會線性增加 ops 成本、要在 latency 跟 cost 之間找平衡。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>cluster 級 IaC + 自動化是 multi-cluster 治理前置</strong>：Terraform / Pulumi / Crossplane + Karpenter / Cluster Autoscaler 是基本工具。</li>
</ol>
<p>跨平台等效：GCP GKE Fleet management（multi-cluster）、Azure Fleet Manager、自建 Cluster API + ArgoCD 都可以做 multi-cluster 治理。差異是 vendor 整合度跟政策。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 multi-cluster K8s → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想做延遲門檻反推部署 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <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</a></li>
<li>想對照微服務 vs multi-cluster → <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</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/riot-games-case-study/">Riot Games Cuts $10M Annual Infrastructure Costs by Migrating to Amazon EKS</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/riot-games-reinvent/">Riot Games on Using AWS to Improve Gaming</a></li>
</ul>
]]></content:encoded></item><item><title>9.13 擴展軸與 Stateless 前提</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/scaling-axes/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/scaling-axes/</guid><description>&lt;p>「要換更大的機器、還是要加更多臺機器？」這個問題在規模成長過程中會反覆出現。垂直擴展（scale-up）與水平擴展（scale-out）對應不同壓力來源、各自承擔不同代價：垂直擴展用「換更大的機器」換取簡單、水平擴展用「加更多機器」換取彈性。規劃容量時先判讀自己的壓力屬於哪一種、再選對應的擴展軸 — 選錯軸的代價會在事故時放大。&lt;/p>
&lt;h2 id="兩個軸的責任差異">兩個軸的責任差異&lt;/h2>
&lt;p>垂直擴展指把單一機器換成更高規格（更多 CPU / 記憶體 / IOPS），水平擴展指增加機器數量。同樣是「加資源」，兩者面對的工程問題完全不同。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>垂直擴展（scale-up）&lt;/th>
 &lt;th>水平擴展（scale-out）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>操作單位&lt;/td>
 &lt;td>換一臺機器&lt;/td>
 &lt;td>加 N 臺機器&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>程式假設&lt;/td>
 &lt;td>不需要改&lt;/td>
 &lt;td>必須是 stateless 或有狀態同步機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量上限&lt;/td>
 &lt;td>單機物理規格上限&lt;/td>
 &lt;td>理論上線性擴展，實際受協調成本限制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本曲線&lt;/td>
 &lt;td>規格升級非線性（高階機器溢價）&lt;/td>
 &lt;td>線性，但每臺要付 baseline 成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>故障代價&lt;/td>
 &lt;td>單點失敗影響整個服務&lt;/td>
 &lt;td>一臺壞了還有其他臺、可分流&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>變更節奏&lt;/td>
 &lt;td>變更要停機或 failover、頻率低&lt;/td>
 &lt;td>隨時可加減、頻率高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適合場景&lt;/td>
 &lt;td>資料庫主節點、stateful 服務、單點計算&lt;/td>
 &lt;td>API、worker、無狀態服務&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>讀者要從「程式假設」這欄反推自己的選項。如果服務本身是 stateful（資料庫、cache、session store），水平擴展需要設計 partitioning 或 replication；如果是 stateless API server，水平擴展幾乎可以無腦複製。把這個前提搞錯，就會用水平擴展的策略去動 stateful 服務、然後撞牆。&lt;/p>
&lt;h3 id="第三軸拆功能--拆-partitionakf-scale-cube-y--z-軸">第三軸：拆功能 / 拆 partition（AKF Scale Cube Y / Z 軸）&lt;/h3>
&lt;p>兩個軸的對比把擴展簡化成 capacity scaling 的雙軸、但 AKF Scale Cube 模型提了第三軸：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>X 軸（複製 / 水平擴展）&lt;/strong>：本表 scale-out 即此軸、適合 stateless 服務&lt;/li>
&lt;li>&lt;strong>Y 軸（functional decomposition）&lt;/strong>：沿業務邊界拆服務、跟 &lt;a href="https://tarrragon.github.io/blog/backend/10-system-evolution/service-decomposition-boundaries/" data-link-title="10.1 服務拆分與邊界判讀" data-link-desc="整理 monolith vs microservice 取捨、服務邊界判讀訊號、拆分時機與回退路徑">10.1 服務拆分&lt;/a> 對應、適合處理「不同功能的擴展需求差距大」&lt;/li>
&lt;li>&lt;strong>Z 軸（data partition / sharding）&lt;/strong>：沿資料拆 partition、適合處理「stateful 服務超出單機容量」&lt;/li>
&lt;/ul>
&lt;p>實務系統常同時動兩到三軸：API 走 X 軸水平、按業務拆 Y 軸（user service / order service / payment service）、user service 內部再用 user ID hash 做 Z 軸 sharding。本章焦點在 X 軸、但讀者規劃容量時要記住 Y / Z 軸是同時可用的工具。&lt;/p>
&lt;h2 id="stateless-是水平擴展的前提">Stateless 是水平擴展的前提&lt;/h2>
&lt;p>Stateless 的核心定義是「處理一個請求不依賴前一個請求留下的本機狀態」。Session、本機快取、檔案系統暫存都會破壞 stateless 假設。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>狀態類型&lt;/th>
 &lt;th>是否破壞 stateless&lt;/th>
 &lt;th>緩解方向&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Session 存本機&lt;/td>
 &lt;td>破壞&lt;/td>
 &lt;td>把 session 搬到外部 store（Redis、DB），改用 token 認證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>上傳檔案存本機&lt;/td>
 &lt;td>破壞&lt;/td>
 &lt;td>改用物件儲存（S3、GCS）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>本機快取&lt;/td>
 &lt;td>視情境&lt;/td>
 &lt;td>共用快取可接受（每臺 cache 各自 build）；強一致快取要外接&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>WebSocket 長連線&lt;/td>
 &lt;td>破壞&lt;/td>
 &lt;td>用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sticky-session/" data-link-title="Sticky Session" data-link-desc="說明同一 client 如何在一段時間內持續命中同一個後端實例">sticky session&lt;/a> 或外部 broker（Pub/Sub、Redis）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>本機 cron / 排程&lt;/td>
 &lt;td>破壞&lt;/td>
 &lt;td>改用分散式排程（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/leader-election/" data-link-title="Leader Election" data-link-desc="從一群對等節點中選出單一主節點負責獨佔工作、leader 失效時自動選新 leader">leader election&lt;/a> 或外部排程服務）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨請求的記憶體狀態&lt;/td>
 &lt;td>破壞&lt;/td>
 &lt;td>移到外部 state store&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>很多人以為自己的服務是 stateless、但一上水平擴展就出事，原因常常在這張表的某一行。判讀方式：把單一機器停掉、重新分配流量到其他機器，使用者體驗是否完全無感？如果有任何「重新登入」「上傳消失」「資料看不到」的情境，就有 stateful 殘留。&lt;/p>
&lt;p>這張表覆蓋顯式狀態。&lt;strong>隱式狀態&lt;/strong>（implicit state）是另一類常被忽略的破壞 stateless 因素：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>In-flight request state&lt;/strong>：HTTP/2 stream、gRPC bidirectional stream — 跨多個請求保持的連線級狀態&lt;/li>
&lt;li>&lt;strong>TLS session resumption&lt;/strong>：session ticket 跟 session ID cache 跨連線、若不集中存會降低重連性能&lt;/li>
&lt;li>&lt;strong>Rate limiter state&lt;/strong>：per-user token bucket、滑動視窗 — 看似無狀態的 middleware 其實在記每個 user 的計數&lt;/li>
&lt;li>&lt;strong>連線預熱（connection warm-up）&lt;/strong>：HTTP/2 / gRPC 連線建立成本高、機器接到流量後需要時間熱起來&lt;/li>
&lt;/ul>
&lt;p>這類「看似 stateless 但有 implicit state」是水平擴展撞牆的常見主因。處理方式是把隱式狀態抽到外部 store（rate limit 用 Redis、TLS session 用共用 cache）或設計連線級 sticky。&lt;/p></description><content:encoded><![CDATA[<p>「要換更大的機器、還是要加更多臺機器？」這個問題在規模成長過程中會反覆出現。垂直擴展（scale-up）與水平擴展（scale-out）對應不同壓力來源、各自承擔不同代價：垂直擴展用「換更大的機器」換取簡單、水平擴展用「加更多機器」換取彈性。規劃容量時先判讀自己的壓力屬於哪一種、再選對應的擴展軸 — 選錯軸的代價會在事故時放大。</p>
<h2 id="兩個軸的責任差異">兩個軸的責任差異</h2>
<p>垂直擴展指把單一機器換成更高規格（更多 CPU / 記憶體 / IOPS），水平擴展指增加機器數量。同樣是「加資源」，兩者面對的工程問題完全不同。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>垂直擴展（scale-up）</th>
          <th>水平擴展（scale-out）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>操作單位</td>
          <td>換一臺機器</td>
          <td>加 N 臺機器</td>
      </tr>
      <tr>
          <td>程式假設</td>
          <td>不需要改</td>
          <td>必須是 stateless 或有狀態同步機制</td>
      </tr>
      <tr>
          <td>容量上限</td>
          <td>單機物理規格上限</td>
          <td>理論上線性擴展，實際受協調成本限制</td>
      </tr>
      <tr>
          <td>成本曲線</td>
          <td>規格升級非線性（高階機器溢價）</td>
          <td>線性，但每臺要付 baseline 成本</td>
      </tr>
      <tr>
          <td>故障代價</td>
          <td>單點失敗影響整個服務</td>
          <td>一臺壞了還有其他臺、可分流</td>
      </tr>
      <tr>
          <td>變更節奏</td>
          <td>變更要停機或 failover、頻率低</td>
          <td>隨時可加減、頻率高</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>資料庫主節點、stateful 服務、單點計算</td>
          <td>API、worker、無狀態服務</td>
      </tr>
  </tbody>
</table>
<p>讀者要從「程式假設」這欄反推自己的選項。如果服務本身是 stateful（資料庫、cache、session store），水平擴展需要設計 partitioning 或 replication；如果是 stateless API server，水平擴展幾乎可以無腦複製。把這個前提搞錯，就會用水平擴展的策略去動 stateful 服務、然後撞牆。</p>
<h3 id="第三軸拆功能--拆-partitionakf-scale-cube-y--z-軸">第三軸：拆功能 / 拆 partition（AKF Scale Cube Y / Z 軸）</h3>
<p>兩個軸的對比把擴展簡化成 capacity scaling 的雙軸、但 AKF Scale Cube 模型提了第三軸：</p>
<ul>
<li><strong>X 軸（複製 / 水平擴展）</strong>：本表 scale-out 即此軸、適合 stateless 服務</li>
<li><strong>Y 軸（functional decomposition）</strong>：沿業務邊界拆服務、跟 <a href="/blog/backend/10-system-evolution/service-decomposition-boundaries/" data-link-title="10.1 服務拆分與邊界判讀" data-link-desc="整理 monolith vs microservice 取捨、服務邊界判讀訊號、拆分時機與回退路徑">10.1 服務拆分</a> 對應、適合處理「不同功能的擴展需求差距大」</li>
<li><strong>Z 軸（data partition / sharding）</strong>：沿資料拆 partition、適合處理「stateful 服務超出單機容量」</li>
</ul>
<p>實務系統常同時動兩到三軸：API 走 X 軸水平、按業務拆 Y 軸（user service / order service / payment service）、user service 內部再用 user ID hash 做 Z 軸 sharding。本章焦點在 X 軸、但讀者規劃容量時要記住 Y / Z 軸是同時可用的工具。</p>
<h2 id="stateless-是水平擴展的前提">Stateless 是水平擴展的前提</h2>
<p>Stateless 的核心定義是「處理一個請求不依賴前一個請求留下的本機狀態」。Session、本機快取、檔案系統暫存都會破壞 stateless 假設。</p>
<table>
  <thead>
      <tr>
          <th>狀態類型</th>
          <th>是否破壞 stateless</th>
          <th>緩解方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Session 存本機</td>
          <td>破壞</td>
          <td>把 session 搬到外部 store（Redis、DB），改用 token 認證</td>
      </tr>
      <tr>
          <td>上傳檔案存本機</td>
          <td>破壞</td>
          <td>改用物件儲存（S3、GCS）</td>
      </tr>
      <tr>
          <td>本機快取</td>
          <td>視情境</td>
          <td>共用快取可接受（每臺 cache 各自 build）；強一致快取要外接</td>
      </tr>
      <tr>
          <td>WebSocket 長連線</td>
          <td>破壞</td>
          <td>用 <a href="/blog/backend/knowledge-cards/sticky-session/" data-link-title="Sticky Session" data-link-desc="說明同一 client 如何在一段時間內持續命中同一個後端實例">sticky session</a> 或外部 broker（Pub/Sub、Redis）</td>
      </tr>
      <tr>
          <td>本機 cron / 排程</td>
          <td>破壞</td>
          <td>改用分散式排程（<a href="/blog/backend/knowledge-cards/leader-election/" data-link-title="Leader Election" data-link-desc="從一群對等節點中選出單一主節點負責獨佔工作、leader 失效時自動選新 leader">leader election</a> 或外部排程服務）</td>
      </tr>
      <tr>
          <td>跨請求的記憶體狀態</td>
          <td>破壞</td>
          <td>移到外部 state store</td>
      </tr>
  </tbody>
</table>
<p>很多人以為自己的服務是 stateless、但一上水平擴展就出事，原因常常在這張表的某一行。判讀方式：把單一機器停掉、重新分配流量到其他機器，使用者體驗是否完全無感？如果有任何「重新登入」「上傳消失」「資料看不到」的情境，就有 stateful 殘留。</p>
<p>這張表覆蓋顯式狀態。<strong>隱式狀態</strong>（implicit state）是另一類常被忽略的破壞 stateless 因素：</p>
<ul>
<li><strong>In-flight request state</strong>：HTTP/2 stream、gRPC bidirectional stream — 跨多個請求保持的連線級狀態</li>
<li><strong>TLS session resumption</strong>：session ticket 跟 session ID cache 跨連線、若不集中存會降低重連性能</li>
<li><strong>Rate limiter state</strong>：per-user token bucket、滑動視窗 — 看似無狀態的 middleware 其實在記每個 user 的計數</li>
<li><strong>連線預熱（connection warm-up）</strong>：HTTP/2 / gRPC 連線建立成本高、機器接到流量後需要時間熱起來</li>
</ul>
<p>這類「看似 stateless 但有 implicit state」是水平擴展撞牆的常見主因。處理方式是把隱式狀態抽到外部 store（rate limit 用 Redis、TLS session 用共用 cache）或設計連線級 sticky。</p>
<h2 id="auto-scaling-的操作模型">Auto Scaling 的操作模型</h2>
<p>水平擴展通常搭配 <a href="/blog/backend/knowledge-cards/autoscaling/" data-link-title="Autoscaling" data-link-desc="說明系統如何依負載自動調整服務實例數量">auto scaling</a> — 根據訊號自動加減機器數量。常見的擴展訊號跟對應的判讀重點：</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>反應速度</th>
          <th>判讀重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU 使用率</td>
          <td>中</td>
          <td>通用、但對 I/O bound 服務失準</td>
      </tr>
      <tr>
          <td>記憶體使用率</td>
          <td>慢</td>
          <td>適合判 leak、不適合判尖峰流量</td>
      </tr>
      <tr>
          <td>Request rate (RPS)</td>
          <td>快</td>
          <td>適合 API 服務、需要設定 cool-down 避免抖動</td>
      </tr>
      <tr>
          <td>Queue depth</td>
          <td>快</td>
          <td>適合 worker 服務、queue 是天然 buffer</td>
      </tr>
      <tr>
          <td>Latency P95</td>
          <td>中</td>
          <td>用戶體驗訊號、但已經出現延遲才擴展可能來不及</td>
      </tr>
      <tr>
          <td>自訂業務訊號</td>
          <td>視訊號</td>
          <td>訂單數、活動人數，貼近業務但要自己維護 metric pipeline</td>
      </tr>
  </tbody>
</table>
<p>設定 auto scaling 的判讀順序：先選訊號（CPU vs RPS vs queue depth），再設閾值（避免過早觸發或過晚觸發），最後加 cool-down（避免反覆擴縮造成抖動）。三步驟有一步沒做好就會撞牆。</p>
<p>Auto scaling 不是萬靈丹。三類問題它無法解決：擴展速度跟不上（冷啟動時間視 stack 範圍 5-300 秒、流量尖峰若集中在秒級就來不及）、預測式流量（黑五、新片上線、活動）、stateful 服務（資料庫不能用 auto scaling 加 primary）。這三類要分別用 <a href="/blog/backend/knowledge-cards/predictive-scaling/" data-link-title="Predictive Scaling" data-link-desc="說明用歷史模式或 ML 模型預測流量、提前擴容的 autoscaler 模式">predictive scaling</a>、<a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">scheduled scaling</a> 跟 partitioning 處理。</p>
<h2 id="垂直擴展的天花板">垂直擴展的天花板</h2>
<p>垂直擴展看起來簡單但有兩道牆。</p>
<p>第一道是物理上限。雲端機型的最大規格是有限的：以 2025 年公開資料為例、AWS 的 u 系列 instance（如 <code>u7i-12tb</code>、<code>u-24tb1.metal</code>）可達 24 TiB 記憶體級別、vCPU 數量視 SKU 而異；GCP / Azure 也有對應的 memory-optimized 系列、但具體上限隨年份更新。要查最新規格走 vendor 官方文件、不要拿這裡數字當決策依據。對 stateful workload（例如 OLTP 主節點）真實天花板通常出現在 32-64 vCPU 級別、是 lock contention / context switch / memory bandwidth 等架構因素而非規格上限。</p>
<p>第二道是成本曲線。雲端機型的價格不是線性的、越高階的機型每單位資源越貴。以 AWS general-purpose 機型（m 系列）為例、4 vCPU → 8 vCPU 約 ×1.8、8 → 16 約 ×1.9（接近線性）、但到 48 vCPU 以上會明顯偏離線性外推、特別是 memory-optimized（r 系列）跟 high-memory（x 系列）的高階規格溢價更陡。具體曲線依機型 family 跟雲廠商而異 — 走 vendor calculator 算實際 workload 的成本曲線比抓單一倍數可靠。垂直擴展到一定規模、就算物理上撐得住、財務上也會比水平擴展貴。</p>
<p>對 stateful 服務（特別是主資料庫），垂直擴展常常是第一選擇，因為水平擴展需要重新設計 partitioning。但要清楚兩道牆會在什麼時候撞上：基於目前流量增長率，預估垂直擴展能撐多久？多久之後必須改成水平擴展？這個答案要在「還沒撞牆時」就準備好，不是等到下一次撞牆才開始討論。</p>
<h2 id="水平擴展的隱性成本">水平擴展的隱性成本</h2>
<p>水平擴展看起來彈性、但有它自己的代價。</p>
<p><strong>協調成本</strong>：多臺機器要處理「誰是 leader、誰來執行排程、誰來處理同一筆訂單」這類問題。<a href="/blog/backend/knowledge-cards/consensus-protocol/" data-link-title="Consensus Protocol" data-link-desc="讓多個獨立節點在訊息可能延遲、丟失、亂序的網路下對單一決策達成一致的演算法">consensus protocol</a> 跟 <a href="/blog/backend/knowledge-cards/distributed-lock/" data-link-title="Distributed Lock" data-link-desc="跨機器跨 process 的互斥鎖、用 lease 機制處理 holder 失效">distributed lock</a>（含 leader election、Raft / Paxos 演算法）都會引入新的故障模式跟 latency 代價。</p>
<p><strong>連線池放大</strong>：100 臺機器、每臺對資料庫開 10 個連線，等於對 DB 開 1000 個連線。DB 連線是有限資源，水平擴展應用層的同時要評估資料層連線壓力。常見緩解：connection pooler（PgBouncer）、serverless DB（DynamoDB）、讀寫分離。</p>
<p><strong>狀態同步成本</strong>：cache、session、配置這些「跨機器需要一致」的狀態，要靠外部 store 或 broadcast 機制同步。同步延遲跟頻率會反過來影響服務行為。</p>
<p><strong>Cold start</strong>：新機器啟動到接流量需要時間（image pull、init container、warm-up）。auto scaling 觸發跟流量到達之間的延遲就是這段。冷啟動長的服務（JVM、需要載入大量資料的服務）要預留更多 buffer。</p>
<p><strong>Debug 變難</strong>：請求散落在多臺機器，排查問題需要 log 聚合、trace context。沒有這些基礎設施，水平擴展只會把「一臺機器壞」的問題變成「不知道哪一臺機器壞」的問題。</p>
<h2 id="混合策略">混合策略</h2>
<p>純垂直或純水平在實際系統中都罕見。常見的混合模式：</p>
<ul>
<li><strong>小規模垂直、大規模水平</strong>：早期單機就能撐，先用較大規格降低運維複雜度；流量上來後再轉水平，把每臺機器規格降回中等。</li>
<li><strong>stateless 水平、stateful 垂直</strong>：API server 水平擴展、資料庫主節點垂直擴展、加 read replica 做讀路徑水平擴展。</li>
<li><strong>熱資料水平 sharding、冷資料保持單庫</strong>：把熱表用 partition key 拆到多個 shard，冷表保留在主庫不動。</li>
<li><strong>核心服務垂直保底、邊緣服務水平彈性</strong>：核心交易服務用更大規格降低事故風險，前端、推薦等服務走 auto scaling。</li>
</ul>
<p>選混合策略時，要明確標記每個服務在哪個軸上、極限在哪、下一步轉換點在什麼條件下觸發。沒有這張對照表，混合策略容易變成「每個服務都是特例」、最後沒人記得當初為什麼這樣設計。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>加機器後 QPS 沒提升</td>
          <td>stateful 殘留（本機快取 / session / 鎖）</td>
          <td>找出 stateful 點、移到外部 store，或改回垂直擴展</td>
      </tr>
      <tr>
          <td>加機器後 DB 連線爆掉</td>
          <td>連線池放大、DB 是瓶頸</td>
          <td>加 connection pooler、評估讀寫分離、考慮資料層擴展</td>
      </tr>
      <tr>
          <td>Auto scaling 反覆擴縮</td>
          <td>cool-down 太短或訊號抖動</td>
          <td>加 cool-down、改用更穩定訊號（移動平均、business metric）</td>
      </tr>
      <tr>
          <td>流量尖峰時新機器來不及啟動</td>
          <td>cold start 太長 / 預測訊號不夠早</td>
          <td>改 <a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">scheduled scaling</a> 或 <a href="/blog/backend/knowledge-cards/predictive-scaling/" data-link-title="Predictive Scaling" data-link-desc="說明用歷史模式或 ML 模型預測流量、提前擴容的 autoscaler 模式">predictive scaling</a>、warm pool</td>
      </tr>
      <tr>
          <td>垂直擴展後成本曲線陡升</td>
          <td>撞到高階機型溢價</td>
          <td>評估水平擴展轉型 / 重構 stateful 部分</td>
      </tr>
      <tr>
          <td>水平擴展後事故 MTTR 拉長</td>
          <td>觀測能力跟不上</td>
          <td>補 trace context、結構化 log、service topology</td>
      </tr>
  </tbody>
</table>
<h2 id="常見誤區">常見誤區</h2>
<p>把「加機器」當作所有效能問題的萬靈丹。如果瓶頸在演算法、SQL query、序列化、locks，加機器只會讓問題變得更貴。先用 <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> 確定瓶頸位置，再決定擴展軸。</p>
<p>把 auto scaling 當成「設定完就不用管」。auto scaling 是 reactive 策略，它無法處理可預期的尖峰（活動、新片上線、節日）。預期型流量要用 scheduled / predictive scaling 提前準備。</p>
<p>把 stateless 當成「沒有狀態就好」。WebSocket、long-polling、上傳、檔案處理這類服務天然 stateful、強行水平擴展會出事。要分辨「業務本質 stateful」跟「實作偷懶 stateful」，前者用 partitioning 處理、後者用重構移除。</p>
<h2 id="定位邊界">定位邊界</h2>
<p>本章專注「擴展軸的選擇與前提」。當問題進入具體量化（要加多少臺機器？headroom 多少？），交給 <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>；進入瓶頸定位（瓶頸在哪一層？），交給 <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>；進入服務拆分（要不要先把 stateful 部分拆出來再水平擴展？），交給 <a href="/blog/backend/10-system-evolution/service-decomposition-boundaries/" data-link-title="10.1 服務拆分與邊界判讀" data-link-desc="整理 monolith vs microservice 取捨、服務邊界判讀訊號、拆分時機與回退路徑">10.1 服務拆分與邊界判讀</a>。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>擴展軸選擇可用以下案例回寫。每個案例對應的軸不同，引用時要先辨識案例的主要壓力來源，再對照本章相應段落。</p>
<ul>
<li><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 30 倍突發</a> — 案例主軸是「stateless API 層水平擴展、stateful 資料層改用 DynamoDB 移除單點」，直接對應本章「stateless 是水平擴展的前提」段。是本批最貼近 scaling axis 主題的案例。</li>
<li><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理</a> — 案例展示水平擴展到極端規模後，協調成本（cluster 治理、版本一致性）變成新的瓶頸；對照本章「水平擴展的隱性成本 / 協調成本」段。</li>
<li><a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom：DynamoDB + EKS 上的遊戲後端</a> — 案例主軸是 KV 業務語意、不是 scaling axis 取捨；但可反向追問「stateful 玩家狀態為何適合 KV vs RDB」、對照本章「stateless 是水平擴展的前提」段中的「狀態類型 vs 緩解方向」表。</li>
<li><a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix：把關聯式 DB 統一到 Aurora</a> — 案例主軸是「DB 種類整併」、不直接對應 scale-up vs scale-out；但 Aurora 在 single-primary 規格選擇上隱含了「先垂直、再考慮分散」的策略，可作為「垂直擴展天花板」段的對照組。</li>
</ul>
<p>Zomato 跟 Netflix 不在這份案例清單裡的原因要先講清楚：擴展軸的真實示範案例在後端教材中相對稀缺、09 模組多數案例的主軸落在 vendor 或容量規劃。Zoom 是這四個案例中最貼近教科書 — stateless API 水平 + stateful 改用 DynamoDB 的組合直接示範本章核心。Riot Games 揭示水平到極端規模後協調成本翻轉成新瓶頸。Capcom 跟 Netflix Aurora 不直接示範擴展軸取捨、但用反向追問「為什麼選 KV / 為什麼 single-primary 仍是 default」能把它們的決策放回擴展軸框架。</p>
<h2 id="跨模組路由">跨模組路由</h2>
<ol>
<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> 的交接：USL 跟 Little&rsquo;s Law 在理論上推導水平擴展的曲線、本章解釋這道牆在運維現場長什麼樣。</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> 的交接：擴展軸選定後，容量規劃決定具體數字。</li>
<li>與 <a href="/blog/backend/10-system-evolution/service-decomposition-boundaries/" data-link-title="10.1 服務拆分與邊界判讀" data-link-desc="整理 monolith vs microservice 取捨、服務邊界判讀訊號、拆分時機與回退路徑">10.1 服務拆分</a> 的交接：水平擴展常常是服務拆分的觸發點，反之亦然。</li>
<li>與 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01 database high-concurrency-access</a> 的交接：資料層水平擴展（sharding、replica）的具體機制。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p><strong>規模成長路線下一站 → <a href="/blog/backend/01-database/query-anti-patterns/" data-link-title="1.13 應用層查詢反模式與 Query 預算" data-link-desc="整理 N&#43;1、select *、缺索引、ORM lazy load、long transaction 等查詢反模式與每請求的 query 預算判讀">1.13 應用層查詢反模式與 Query 預算</a></strong>：選定擴展軸後、在加機器前先用反模式清單收回單機可撐住的容量。</p>
<p>其他延伸方向：</p>
<ul>
<li>容量計算與 headroom 模型 → <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></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/10-system-evolution/service-decomposition-boundaries/" data-link-title="10.1 服務拆分與邊界判讀" data-link-desc="整理 monolith vs microservice 取捨、服務邊界判讀訊號、拆分時機與回退路徑">10.1 服務拆分與邊界判讀</a></li>
</ul>
]]></content:encoded></item><item><title>9.C13 Disney+ Hotstar：IPL 板球決賽 1860 萬人同時直播</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/</guid><description>&lt;p>這個案例的核心責任是說明「全球大型直播」的容量設計 — 跟 Prime Day 同屬「可預期極端峰值」、但形狀完全不同：Prime Day 是分散全球的購物峰值、Hotstar IPL 是 &lt;em>單一時間點 + 高度集中地理區&lt;/em> 的直播峰值。容量規劃的挑戰在於 CDN、串流伺服器、live encoder、message queue 同時 saturate。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Hotstar IPL 直播的關鍵數字（引自 &lt;a href="https://aws.amazon.com/blogs/media/in-the-news-hotstar-sets-new-global-record-for-live-viewership/">Hotstar global record&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>同時觀看峰值&lt;/td>
 &lt;td>1860 萬 人（2021-03 IPL 決賽）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>全球記錄&lt;/td>
 &lt;td>該時點全球同時觀看直播的最高記錄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>AWS Media Services + AWS CloudFront&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客戶基礎&lt;/td>
 &lt;td>印度為主、跨亞洲&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>AWS Media Services 在大型事件的歷史記錄：Olympics、Super Bowl、IPL Cricket（引自 &lt;a href="https://aws.amazon.com/developer/application-security-performance/articles/large-scale-video-streaming-events/">AWS large-scale streaming events&lt;/a>）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Hotstar 案例揭露三個全球直播容量重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>集中地理區 = CDN 壓力集中&lt;/strong>：Prime Day 的流量分散全球、單一地區 CDN 不會 saturate；IPL 主要觀眾在印度、所有印度 PoP 同一時間 saturate。CDN 容量規劃必須按地區獨立做、不能用「全球總容量」當保證。對應 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的 cardinality 與地區訊號治理、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的「地理分片容量」。&lt;/li>
&lt;li>&lt;strong>直播跟 VoD 是不同容量問題&lt;/strong>：VoD 觀眾分散時間、CDN 可預先 cache；直播觀眾集中時間、每一個 manifest / segment 都是 live 拉取、cache hit 反而是危險（拉到舊的 segment）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache freshness boundary、跟 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列&lt;/a> 的 fan-out 設計。&lt;/li>
&lt;li>&lt;strong>多 bitrate 動態切換 = 真實容量是 bitrate 加權&lt;/strong>：1860 萬觀眾不是都看 1080p — 印度行動網路下大多看 720p 或 480p、bitrate 加權後的 total bandwidth 可能比想像低。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 的真實 workload shape。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「1860 萬同時觀看」是 &lt;em>峰值瞬間&lt;/em>、不是全程平均。決賽 4 小時、觀眾數呈鐘形曲線、峰值維持時間可能只有 10-30 分鐘（比賽關鍵時刻）。容量規劃要看峰值持續時間、不只看峰值高度。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>CDN 容量規劃按地理區分割&lt;/strong>：不要假設「全球 CDN 總量」夠用、要按主要觀眾分布的地區做容量保證。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>。&lt;/li>
&lt;li>&lt;strong>直播必須 pre-scaling、不能依賴 reactive&lt;/strong>：直播開始之後 CDN reactive 擴容已經太晚、觀眾體驗已壞。事件型 scheduled scaling + over-provisioning 是必須。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a>。&lt;/li>
&lt;li>&lt;strong>multi-bitrate / ABR streaming 是容量緩衝&lt;/strong>：當網路擁塞、player 自動降 bitrate、總頻寬壓力下降。這層降級是隱性容量緩衝、要在壓測時驗證。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 saturation 行為。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP CDN + Media CDN、Azure Front Door + Media Services、Akamai / Cloudflare / Fastly 等 multi-CDN 都是對等候選。差異是 PoP 地理分布跟 manifest 處理能力。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「全球大型直播」的容量設計 — 跟 Prime Day 同屬「可預期極端峰值」、但形狀完全不同：Prime Day 是分散全球的購物峰值、Hotstar IPL 是 <em>單一時間點 + 高度集中地理區</em> 的直播峰值。容量規劃的挑戰在於 CDN、串流伺服器、live encoder、message queue 同時 saturate。</p>
<h2 id="觀察">觀察</h2>
<p>Hotstar IPL 直播的關鍵數字（引自 <a href="https://aws.amazon.com/blogs/media/in-the-news-hotstar-sets-new-global-record-for-live-viewership/">Hotstar global record</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同時觀看峰值</td>
          <td>1860 萬 人（2021-03 IPL 決賽）</td>
      </tr>
      <tr>
          <td>全球記錄</td>
          <td>該時點全球同時觀看直播的最高記錄</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>AWS Media Services + AWS CloudFront</td>
      </tr>
      <tr>
          <td>客戶基礎</td>
          <td>印度為主、跨亞洲</td>
      </tr>
  </tbody>
</table>
<p>AWS Media Services 在大型事件的歷史記錄：Olympics、Super Bowl、IPL Cricket（引自 <a href="https://aws.amazon.com/developer/application-security-performance/articles/large-scale-video-streaming-events/">AWS large-scale streaming events</a>）。</p>
<h2 id="判讀">判讀</h2>
<p>Hotstar 案例揭露三個全球直播容量重點。</p>
<ol>
<li><strong>集中地理區 = CDN 壓力集中</strong>：Prime Day 的流量分散全球、單一地區 CDN 不會 saturate；IPL 主要觀眾在印度、所有印度 PoP 同一時間 saturate。CDN 容量規劃必須按地區獨立做、不能用「全球總容量」當保證。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的 cardinality 與地區訊號治理、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的「地理分片容量」。</li>
<li><strong>直播跟 VoD 是不同容量問題</strong>：VoD 觀眾分散時間、CDN 可預先 cache；直播觀眾集中時間、每一個 manifest / segment 都是 live 拉取、cache hit 反而是危險（拉到舊的 segment）。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache freshness boundary、跟 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列</a> 的 fan-out 設計。</li>
<li><strong>多 bitrate 動態切換 = 真實容量是 bitrate 加權</strong>：1860 萬觀眾不是都看 1080p — 印度行動網路下大多看 720p 或 480p、bitrate 加權後的 total bandwidth 可能比想像低。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 的真實 workload shape。</li>
</ol>
<p>需要警惕：「1860 萬同時觀看」是 <em>峰值瞬間</em>、不是全程平均。決賽 4 小時、觀眾數呈鐘形曲線、峰值維持時間可能只有 10-30 分鐘（比賽關鍵時刻）。容量規劃要看峰值持續時間、不只看峰值高度。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>CDN 容量規劃按地理區分割</strong>：不要假設「全球 CDN 總量」夠用、要按主要觀眾分布的地區做容量保證。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
<li><strong>直播必須 pre-scaling、不能依賴 reactive</strong>：直播開始之後 CDN reactive 擴容已經太晚、觀眾體驗已壞。事件型 scheduled scaling + over-provisioning 是必須。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a>。</li>
<li><strong>multi-bitrate / ABR streaming 是容量緩衝</strong>：當網路擁塞、player 自動降 bitrate、總頻寬壓力下降。這層降級是隱性容量緩衝、要在壓測時驗證。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 saturation 行為。</li>
</ol>
<p>跨平台等效：GCP CDN + Media CDN、Azure Front Door + Media Services、Akamai / Cloudflare / Fastly 等 multi-CDN 都是對等候選。差異是 PoP 地理分布跟 manifest 處理能力。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃全球直播 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想做 CDN 容量設計 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a></li>
<li>想理解 cache freshness 在直播的影響 → <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a></li>
<li>對照其他可預期峰值 → <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 AWS Prime Day</a>（分散全球的峰值）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/media/in-the-news-hotstar-sets-new-global-record-for-live-viewership/">In the news: Hotstar sets new global record for live viewership</a></li>
<li><a href="https://aws.amazon.com/developer/application-security-performance/articles/large-scale-video-streaming-events/">Large scale streaming events on AWS</a></li>
<li><a href="https://aws.amazon.com/media/direct-to-consumer-d2c-streaming/">Direct to Consumer &amp; Streaming on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>9.14 連線池放大解法（PgBouncer / RDS Proxy / ProxySQL）</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/connection-pool-amplification/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/connection-pool-amplification/</guid><description>&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸與 Stateless 前提&lt;/a> 指出了水平擴展應用層時的隱性成本之一：連線池放大 — 100 臺機器 × 每臺 10 個連線 = 對 DB 開 1000 個連線、超過 PostgreSQL &lt;code>max_connections&lt;/code> default（100）十倍。本章把這條撞牆訊號的具體解法說清楚 — connection pooler 是什麼、PgBouncer / RDS Proxy / ProxySQL 怎麼選、不同場景的取捨。&lt;/p>
&lt;h2 id="連線池放大的物理本質">連線池放大的物理本質&lt;/h2>
&lt;p>PostgreSQL / MySQL 每個連線都會在 DB server 端配一個 backend process / thread。Backend 佔 5-15 MB 記憶體、context switch 也有成本。當應用層連線數超過 DB 機器能負擔的數量，會出現三類問題：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>記憶體吃光&lt;/strong>：500 個 backend × 10 MB = 5 GB、再加 shared buffer、可能直接 OOM&lt;/li>
&lt;li>&lt;strong>Context switch 抖動&lt;/strong>：上百個 backend 競爭 CPU、上下文切換 overhead 變成主要消耗&lt;/li>
&lt;li>&lt;strong>連線建立失敗&lt;/strong>：超過 &lt;code>max_connections&lt;/code> 後、新請求拿不到連線、即使現有連線多數 idle&lt;/li>
&lt;/ul>
&lt;p>問題的根因不是「連線多」、是「連線&lt;strong>生命週期跟使用率不對齊&lt;/strong>」。應用層 connection pool 通常維持「每臺機器 N 個常駐連線、避免每個 request 重新建連」、但 100 臺機器各自 keep 10 個常駐就是 1000 個 idle 連線。&lt;/p>
&lt;p>解法的方向不是「砍應用層連線數」（會讓 connection acquisition 變慢、影響 latency）、是「在 DB 跟應用層之間放一層 multiplexer」— 把多個應用層連線複用到少數 DB 連線上。這層中介就是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/connection-pooler/" data-link-title="Connection Pooler" data-link-desc="應用層跟資料庫之間的連線複用中介層、解水平擴展時的連線數放大問題">connection pooler&lt;/a>。&lt;/p>
&lt;h2 id="connection-pooler-三大選項">Connection Pooler 三大選項&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>部署模式&lt;/th>
 &lt;th>主要適用 DB&lt;/th>
 &lt;th>主要特點&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>PgBouncer&lt;/td>
 &lt;td>Self-managed / sidecar&lt;/td>
 &lt;td>PostgreSQL only&lt;/td>
 &lt;td>輕量（C 寫的 single process）、三種 pooling 模式可選&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS RDS Proxy&lt;/td>
 &lt;td>Managed&lt;/td>
 &lt;td>RDS / Aurora (PG / MySQL)&lt;/td>
 &lt;td>整合 IAM auth、自動 failover、計價 per vCPU&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ProxySQL&lt;/td>
 &lt;td>Self-managed&lt;/td>
 &lt;td>MySQL&lt;/td>
 &lt;td>規則型 routing、可做 query rewriting、自動 failover&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="pgbouncer--三種-pooling-模式決定一切">PgBouncer — 三種 pooling 模式決定一切&lt;/h3>
&lt;p>PgBouncer 的核心參數是 &lt;code>pool_mode&lt;/code>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Session mode&lt;/strong>：應用層 client 拿到的連線、跟 DB backend 1:1 綁定、整個 session 結束才釋放。其實沒做 multiplexing、只是 connection caching。&lt;/li>
&lt;li>&lt;strong>Transaction mode&lt;/strong>：每個 transaction 結束、應用層 client 的連線釋放回 pool、下個 transaction 再分配 DB backend。multiplexing 比較強、但&lt;strong>不支援 transaction-scoped state&lt;/strong>（如 &lt;code>SET LOCAL&lt;/code>、prepared statement、temporary table）。&lt;/li>
&lt;li>&lt;strong>Statement mode&lt;/strong>：每個 statement 結束就釋放、最強 multiplexing 但&lt;strong>不支援 transaction&lt;/strong>。極少用、只在純 stateless query workload 適用。&lt;/li>
&lt;/ul>
&lt;p>Transaction mode 是多數場景的 default。但要注意：應用層的 ORM / driver 可能默認用 prepared statement、跟 transaction mode 衝突。PostgreSQL 14+ 的 protocol-level prepared statement 才相容、JDBC / asyncpg 等需要特別配置。&lt;/p></description><content:encoded><![CDATA[<p><a href="/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸與 Stateless 前提</a> 指出了水平擴展應用層時的隱性成本之一：連線池放大 — 100 臺機器 × 每臺 10 個連線 = 對 DB 開 1000 個連線、超過 PostgreSQL <code>max_connections</code> default（100）十倍。本章把這條撞牆訊號的具體解法說清楚 — connection pooler 是什麼、PgBouncer / RDS Proxy / ProxySQL 怎麼選、不同場景的取捨。</p>
<h2 id="連線池放大的物理本質">連線池放大的物理本質</h2>
<p>PostgreSQL / MySQL 每個連線都會在 DB server 端配一個 backend process / thread。Backend 佔 5-15 MB 記憶體、context switch 也有成本。當應用層連線數超過 DB 機器能負擔的數量，會出現三類問題：</p>
<ul>
<li><strong>記憶體吃光</strong>：500 個 backend × 10 MB = 5 GB、再加 shared buffer、可能直接 OOM</li>
<li><strong>Context switch 抖動</strong>：上百個 backend 競爭 CPU、上下文切換 overhead 變成主要消耗</li>
<li><strong>連線建立失敗</strong>：超過 <code>max_connections</code> 後、新請求拿不到連線、即使現有連線多數 idle</li>
</ul>
<p>問題的根因不是「連線多」、是「連線<strong>生命週期跟使用率不對齊</strong>」。應用層 connection pool 通常維持「每臺機器 N 個常駐連線、避免每個 request 重新建連」、但 100 臺機器各自 keep 10 個常駐就是 1000 個 idle 連線。</p>
<p>解法的方向不是「砍應用層連線數」（會讓 connection acquisition 變慢、影響 latency）、是「在 DB 跟應用層之間放一層 multiplexer」— 把多個應用層連線複用到少數 DB 連線上。這層中介就是 <a href="/blog/backend/knowledge-cards/connection-pooler/" data-link-title="Connection Pooler" data-link-desc="應用層跟資料庫之間的連線複用中介層、解水平擴展時的連線數放大問題">connection pooler</a>。</p>
<h2 id="connection-pooler-三大選項">Connection Pooler 三大選項</h2>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>部署模式</th>
          <th>主要適用 DB</th>
          <th>主要特點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PgBouncer</td>
          <td>Self-managed / sidecar</td>
          <td>PostgreSQL only</td>
          <td>輕量（C 寫的 single process）、三種 pooling 模式可選</td>
      </tr>
      <tr>
          <td>AWS RDS Proxy</td>
          <td>Managed</td>
          <td>RDS / Aurora (PG / MySQL)</td>
          <td>整合 IAM auth、自動 failover、計價 per vCPU</td>
      </tr>
      <tr>
          <td>ProxySQL</td>
          <td>Self-managed</td>
          <td>MySQL</td>
          <td>規則型 routing、可做 query rewriting、自動 failover</td>
      </tr>
  </tbody>
</table>
<h3 id="pgbouncer--三種-pooling-模式決定一切">PgBouncer — 三種 pooling 模式決定一切</h3>
<p>PgBouncer 的核心參數是 <code>pool_mode</code>：</p>
<ul>
<li><strong>Session mode</strong>：應用層 client 拿到的連線、跟 DB backend 1:1 綁定、整個 session 結束才釋放。其實沒做 multiplexing、只是 connection caching。</li>
<li><strong>Transaction mode</strong>：每個 transaction 結束、應用層 client 的連線釋放回 pool、下個 transaction 再分配 DB backend。multiplexing 比較強、但<strong>不支援 transaction-scoped state</strong>（如 <code>SET LOCAL</code>、prepared statement、temporary table）。</li>
<li><strong>Statement mode</strong>：每個 statement 結束就釋放、最強 multiplexing 但<strong>不支援 transaction</strong>。極少用、只在純 stateless query workload 適用。</li>
</ul>
<p>Transaction mode 是多數場景的 default。但要注意：應用層的 ORM / driver 可能默認用 prepared statement、跟 transaction mode 衝突。PostgreSQL 14+ 的 protocol-level prepared statement 才相容、JDBC / asyncpg 等需要特別配置。</p>
<h3 id="aws-rds-proxy--managed-換掉運維">AWS RDS Proxy — managed 換掉運維</h3>
<p>RDS Proxy 是 PgBouncer / ProxySQL 同類功能的 managed 版本：AWS 負責部署、HA、failover、IAM 整合。應用層連到 RDS Proxy endpoint、Proxy 在背後維持跟 RDS / Aurora 的連線池。</p>
<p>特點：</p>
<ul>
<li><strong>連線 share 模式類似 transaction mode</strong>：自動 detect 連線是否在 transaction、空閒時釋放</li>
<li><strong>IAM auth 整合</strong>：應用層用 IAM token、不用維護 DB password</li>
<li><strong>Failover 加速</strong>：DB failover 時 Proxy 維持應用層連線不斷、background 重連 new primary。Failover 期間應用層感受最小化。</li>
<li><strong>計價</strong>：per vCPU-hour、Aurora 約 $0.015/vCPU-hr、RDS 約 $0.02/vCPU-hr — 加在 RDS 計價上面</li>
</ul>
<p>不適用場景：很多 read-only / analytics workload 不需要 connection pooler、純讀 replica 直接連通常更便宜。RDS Proxy 是給「寫入混合」「連線抖動嚴重」這類場景。</p>
<h3 id="proxysql--mysql-規則型-routing">ProxySQL — MySQL 規則型 routing</h3>
<p>ProxySQL 是 MySQL 生態的 connection pooler、但比 PgBouncer 更全功能：</p>
<ul>
<li><strong>Query routing rules</strong>：可以按 query pattern 把 query 導去不同 backend（讀路徑去 replica、寫路徑去 primary、特定 query 強制 cache）</li>
<li><strong>Connection multiplexing</strong>：類似 PgBouncer transaction mode</li>
<li><strong>Query rewriting</strong>：可以攔截 query 改寫（debug / 漸進遷移 schema）</li>
<li><strong>Auto failover</strong>：監控 backend 健康、自動切流</li>
</ul>
<p>ProxySQL 的代價是學習曲線跟運維成本 — 規則設計需要對 query pattern 跟 DB topology 有掌控、設錯規則會把 query 導去錯誤 backend、debug 困難。</p>
<h2 id="選型對照">選型對照</h2>
<p>實務選型的關鍵變數是「DB 廠商 / managed 程度 / 規模 / 預算」：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>推薦</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>AWS RDS / Aurora、團隊不想自管</td>
          <td>RDS Proxy</td>
          <td>Managed、整合度高、failover 加速是 free value</td>
      </tr>
      <tr>
          <td>AWS RDS / Aurora、需要極致省成本</td>
          <td>PgBouncer（PG）/ ProxySQL（MySQL）on EC2</td>
          <td>比 RDS Proxy 便宜、但要自管 HA</td>
      </tr>
      <tr>
          <td>GCP Cloud SQL / 自管 PostgreSQL</td>
          <td>PgBouncer</td>
          <td>PG 生態事實標準、配置文件多</td>
      </tr>
      <tr>
          <td>Azure Database for PostgreSQL</td>
          <td>PgBouncer 或 Azure 內建 connection pooling</td>
          <td>Azure 部分 SKU 內建類似功能、檢查 vendor 文件</td>
      </tr>
      <tr>
          <td>MySQL 需要讀寫分離 + query routing</td>
          <td>ProxySQL</td>
          <td>規則型 routing 是 ProxySQL 強項</td>
      </tr>
      <tr>
          <td>不確定要不要 connection pooler</td>
          <td>先用 vendor 內建（RDS Proxy / PG managed pooler）跑一段、再評估自管</td>
          <td>降低初期決策成本</td>
      </tr>
  </tbody>
</table>
<h2 id="不裝-pooler-的判讀">不裝 pooler 的判讀</h2>
<p>Connection pooler 不是必要 — 在以下情境可以暫時不裝：</p>
<ul>
<li><strong>應用層機器數 &lt; 10</strong>：對 DB 連線總數壓力小、deferred 安裝 pooler 沒問題</li>
<li><strong>每臺機器連線數 &lt; 5</strong>：應用層 connection pool 已經很省、再加 pooler 改善有限</li>
<li><strong>DB 機器規格大、<code>max_connections</code> 充裕</strong>：高階 RDS instance 可開到 5000-10000 連線、有 buffer 之前不必加 pooler</li>
<li><strong>Workload 全是長 transaction</strong>：transaction mode pooler 在這種 workload 跟 session mode 沒差、收益低</li>
</ul>
<p>該裝 pooler 的訊號是相反：應用層機器數 ≥ 20、每臺連線數 ≥ 10、<code>max_connections</code> 使用率 ≥ 70%、或 P99 connection wait time 升高。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DB <code>pg_stat_activity</code> 顯示大量 idle 連線</td>
          <td>應用層 keep-alive 連線、實際使用率低</td>
          <td>加 connection pooler 把 idle 釋放回 DB</td>
      </tr>
      <tr>
          <td>應用層 connection acquisition 等待時間升高</td>
          <td>應用層 pool 太小、或 DB 連線數已撞 <code>max_connections</code></td>
          <td>加 pooler 把連線總數壓低、應用層 pool size 維持原樣</td>
      </tr>
      <tr>
          <td>DB failover 後應用層 5-10 分鐘錯誤率高</td>
          <td>應用層 connection pool 沒 detect 到 backend 切換</td>
          <td>RDS Proxy 的 failover 加速、或應用層 connection validation 加強</td>
      </tr>
      <tr>
          <td>Pooler 上線後出現「unexpected error」</td>
          <td>transaction mode 跟 prepared statement / SET LOCAL 衝突</td>
          <td>改 ORM 配置、用 protocol-level prepared statement 或避開 SET LOCAL</td>
      </tr>
      <tr>
          <td>應用層 N+1 query 仍然存在</td>
          <td>Pooler 沒解 N+1、它只解連線數放大</td>
          <td>回 <a href="/blog/backend/01-database/query-anti-patterns/" data-link-title="1.13 應用層查詢反模式與 Query 預算" data-link-desc="整理 N&#43;1、select *、缺索引、ORM lazy load、long transaction 等查詢反模式與每請求的 query 預算判讀">1.13 query 反模式</a> 修反模式</td>
      </tr>
  </tbody>
</table>
<h2 id="常見誤區">常見誤區</h2>
<p>把 connection pooler 當「N+1 解藥」。Pooler 解的是「連線數放大」、不是「query 數量過多」。N+1 query 在裝完 pooler 後仍然慢、只是 DB 不會因為連線爆掉而當機。兩個是正交問題、各自要解。</p>
<p>把 RDS Proxy 當「免費功能」。Proxy 的計價跟 RDS / Aurora 本體疊加、高 connection volume 場景 Proxy 成本可能可觀。要算實際的 cost-per-request、不是預設「managed 一定值得」。</p>
<p>把 transaction mode 配置當「裝完就好」。Prepared statement / SET LOCAL / temporary table 都會跟 transaction mode 衝突、ORM 預設行為要 audit 過、不然會在 production 出現難 debug 的「query 隨機失敗」。</p>
<h2 id="定位邊界">定位邊界</h2>
<p>本章專注「連線池放大的解法」。當問題進入擴展軸選擇（要垂直 vs 水平？stateful 前提？）、回 <a href="/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸</a>；進入 DB 本身的容量規劃（要多大規格 instance？要不要 read replica？）、進 <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>；進入 application-level connection 設計（per-request pool / persistent pool）、進 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1 高併發 SQL</a>。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>09 案例庫多數案例規模到 connection pool 已是 secondary concern、但兩個案例有對應參考：</p>
<ul>
<li><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 30 倍突發</a> — Zoom 把 stateful 資料層改用 DynamoDB、繞過 SQL connection pool 問題（KV 沒有 backend process 概念）。對照本章可問：若 Zoom 保留 SQL、connection pool 怎麼設計才撐得住 30 倍突發？</li>
<li><a href="/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/" data-link-title="9.C39 DoorDash：Aurora Postgres 寫入瓶頸 → CockroachDB 多主寫入" data-link-desc="DoorDash 從 Aurora Postgres 遷到 CockroachDB、解 1.6 M QPS 單主寫入瓶頸、外送平台爆量壓力下重做 OLTP 拓樸">9.C39 DoorDash：CockroachDB 多主寫入</a> — DoorDash 從 Aurora single-primary 換成 CockroachDB 多主、connection pool 設計從「集中在 primary」變成「分散在多 node」。對照本章可問：CockroachDB 是否仍需要 connection pooler？</li>
</ul>
<h2 id="跨模組路由">跨模組路由</h2>
<ol>
<li>與 <a href="/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸</a> 的交接：9.13 提出隱性成本、本章給具體解法。</li>
<li>與 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1 高併發 SQL 讀寫邊界</a> 的交接：1.1 講應用層 connection pool 設計、本章補 DB 端 pooler 中介層。</li>
<li>與 <a href="/blog/backend/01-database/vendors/" data-link-title="資料庫 Vendor 清單" data-link-desc="規劃 SQL、managed SQL、document、KV 與 distributed SQL 的服務頁撰寫順序與教學大綱">01 vendors</a> 的交接：各 DB vendor 的內建 pooler 能力詳見 vendor deep article。</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> 的交接：pooler 加上後、DB 容量規劃的單位從「連線數」變成「DB backend 數 + Pooler vCPU」。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>要看擴展軸選擇的完整 framing、回 <a href="/blog/backend/09-performance-capacity/scaling-axes/" data-link-title="9.13 擴展軸與 Stateless 前提" data-link-desc="整理垂直 / 水平擴展取捨、stateless vs stateful 前提、auto scaling 操作模型與兩種擴展的 hidden cost">9.13 擴展軸與 Stateless 前提</a>。要看 DB-side 高併發處理、進 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">1.1 高併發 SQL 讀寫邊界</a>。要看具體 vendor 的 pooler 文件、進對應 <a href="/blog/backend/01-database/vendors/" data-link-title="資料庫 Vendor 清單" data-link-desc="規劃 SQL、managed SQL、document、KV 與 distributed SQL 的服務頁撰寫順序與教學大綱">vendor deep article</a>。</p>
]]></content:encoded></item><item><title>9.C14 Standard Chartered：受監管銀行的 Aurora 4000 TPS 容量提升</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/standard-chartered-aurora-banking/</guid><description>&lt;p>這個案例的核心責任是說明「受監管產業」的容量規劃跟「網路服務」的本質差異。銀行交易系統的容量目標不只是「能撐多少」、還要同時滿足合規（資料駐留、稽核、加密、可恢復性）、跟一般工程性能優化的取捨完全不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Standard Chartered 在 Aurora 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/search/">AWS search results&lt;/a> 與相關 case study）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>遷移前&lt;/th>
 &lt;th>遷移後 (Aurora)&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>交易吞吐 (TPS)&lt;/td>
 &lt;td>（未公開、基線值）&lt;/td>
 &lt;td>4000 TPS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>吞吐倍數&lt;/td>
 &lt;td>1x baseline&lt;/td>
 &lt;td>10x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>受監管市場&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>7 個（首批遷移）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本下降&lt;/td>
 &lt;td>-&lt;/td>
 &lt;td>「顯著」（未公開具體數字）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要驅動&lt;/td>
 &lt;td>韌性 + 性能&lt;/td>
 &lt;td>-&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Amazon Aurora（PostgreSQL 或 MySQL 相容）、加密 at rest / in transit、多 AZ 部署、跨地區複製（受監管市場各自獨立）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>受監管銀行案例揭露三個合規驅動容量規劃的重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>資料駐留限制 = 容量規劃的單位是「per 市場」&lt;/strong>：7 個受監管市場代表 7 個獨立 cluster（資料不能跨境）、容量規劃變成「7 個獨立規劃 × 各自合規門檻」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的合規要求識別、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的地理分片。&lt;/li>
&lt;li>&lt;strong>「韌性 + 性能」並列、不是 trade-off&lt;/strong>：傳統工程文化常把可靠性跟性能視為對立、銀行業務要求兩者同時達標。Aurora 的多 AZ storage + replica 同時提供性能（讀分流）跟韌性（故障切換）、達成 &lt;em>韌性即性能&lt;/em> 的目標。對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">06.18 reliability metrics governance&lt;/a> 的可靠性指標。&lt;/li>
&lt;li>&lt;strong>遷移本身的合規驗證 = 容量規劃延伸&lt;/strong>：受監管系統遷移不只是技術測試、還要過合規審查（中央銀行 / 金融監管機關）、每個市場各自審。這個審查 lead time（數月）必須算進遷移時程。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a> 的合規驅動 migration。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「10x throughput」是 &lt;em>vs 舊系統&lt;/em>、不是 &lt;em>vs 競爭對手&lt;/em>。受監管銀行的舊系統通常是 1990s-2000s 的 mainframe 或自建 OLTP、性能本來就低。讀案例時要對標的是「自家改善幅度」、不是「絕對性能」。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>資料駐留是容量規劃的硬限制、不是優化選項&lt;/strong>：受監管市場必須各自獨立 cluster、不能用「全球單一 cluster」優化。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">00.4 traffic data scale&lt;/a> 的合規限制。&lt;/li>
&lt;li>&lt;strong>多 AZ + 跨地區複製是合規基線、不是優化&lt;/strong>：銀行業務 RPO / RTO 通常由監管要求（不能丟資料、必須 X 小時內恢復）、不是業務 SLA 選項。對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">06.7 DR rollback rehearsal&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移時程要算合規 lead time&lt;/strong>：每個受監管市場的審查可能 3-12 個月、合計遷移時程是「市場數 × 平均審查月份」、不是「技術遷移月份」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：Azure SQL Hyperscale + Azure regions、GCP Cloud SQL / Spanner + regional configurations、各家雲端的受監管雲端方案（AWS GovCloud、Azure Government、GCP Assured Workloads）都是對等候選。差異是各家對特定監管框架（PCI-DSS、ISO27001、各國金融法規）的認證覆蓋。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「受監管產業」的容量規劃跟「網路服務」的本質差異。銀行交易系統的容量目標不只是「能撐多少」、還要同時滿足合規（資料駐留、稽核、加密、可恢復性）、跟一般工程性能優化的取捨完全不同。</p>
<h2 id="觀察">觀察</h2>
<p>Standard Chartered 在 Aurora 的關鍵敘述（引自 <a href="https://aws.amazon.com/search/">AWS search results</a> 與相關 case study）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>遷移前</th>
          <th>遷移後 (Aurora)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>交易吞吐 (TPS)</td>
          <td>（未公開、基線值）</td>
          <td>4000 TPS</td>
      </tr>
      <tr>
          <td>吞吐倍數</td>
          <td>1x baseline</td>
          <td>10x</td>
      </tr>
      <tr>
          <td>受監管市場</td>
          <td>-</td>
          <td>7 個（首批遷移）</td>
      </tr>
      <tr>
          <td>成本下降</td>
          <td>-</td>
          <td>「顯著」（未公開具體數字）</td>
      </tr>
      <tr>
          <td>主要驅動</td>
          <td>韌性 + 性能</td>
          <td>-</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Amazon Aurora（PostgreSQL 或 MySQL 相容）、加密 at rest / in transit、多 AZ 部署、跨地區複製（受監管市場各自獨立）。</p>
<h2 id="判讀">判讀</h2>
<p>受監管銀行案例揭露三個合規驅動容量規劃的重點。</p>
<ol>
<li><strong>資料駐留限制 = 容量規劃的單位是「per 市場」</strong>：7 個受監管市場代表 7 個獨立 cluster（資料不能跨境）、容量規劃變成「7 個獨立規劃 × 各自合規門檻」。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的合規要求識別、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的地理分片。</li>
<li><strong>「韌性 + 性能」並列、不是 trade-off</strong>：傳統工程文化常把可靠性跟性能視為對立、銀行業務要求兩者同時達標。Aurora 的多 AZ storage + replica 同時提供性能（讀分流）跟韌性（故障切換）、達成 <em>韌性即性能</em> 的目標。對應 <a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">06.18 reliability metrics governance</a> 的可靠性指標。</li>
<li><strong>遷移本身的合規驗證 = 容量規劃延伸</strong>：受監管系統遷移不只是技術測試、還要過合規審查（中央銀行 / 金融監管機關）、每個市場各自審。這個審查 lead time（數月）必須算進遷移時程。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的合規驅動 migration。</li>
</ol>
<p>需要警惕：「10x throughput」是 <em>vs 舊系統</em>、不是 <em>vs 競爭對手</em>。受監管銀行的舊系統通常是 1990s-2000s 的 mainframe 或自建 OLTP、性能本來就低。讀案例時要對標的是「自家改善幅度」、不是「絕對性能」。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>資料駐留是容量規劃的硬限制、不是優化選項</strong>：受監管市場必須各自獨立 cluster、不能用「全球單一 cluster」優化。對應 <a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">00.4 traffic data scale</a> 的合規限制。</li>
<li><strong>多 AZ + 跨地區複製是合規基線、不是優化</strong>：銀行業務 RPO / RTO 通常由監管要求（不能丟資料、必須 X 小時內恢復）、不是業務 SLA 選項。對應 <a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">06.7 DR rollback rehearsal</a>。</li>
<li><strong>遷移時程要算合規 lead time</strong>：每個受監管市場的審查可能 3-12 個月、合計遷移時程是「市場數 × 平均審查月份」、不是「技術遷移月份」。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>。</li>
</ol>
<p>跨平台等效：Azure SQL Hyperscale + Azure regions、GCP Cloud SQL / Spanner + regional configurations、各家雲端的受監管雲端方案（AWS GovCloud、Azure Government、GCP Assured Workloads）都是對等候選。差異是各家對特定監管框架（PCI-DSS、ISO27001、各國金融法規）的認證覆蓋。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃受監管產業 OLTP → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想做合規驅動的容量規劃 → <a href="/blog/backend/00-service-selection/traffic-data-scale/" data-link-title="0.5 流量與資料量評估" data-link-desc="用流量形狀、資料成長、hot key、保留期限與尖峰模式評估後端需求規模">00.4 traffic data scale</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想理解韌性跟性能的同步達成 → <a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">06.18 reliability metrics governance</a></li>
<li>對照其他金融交易案例 → <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> / <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</a></li>
<li>想拆解跨 AZ failover RTO 量級與合規 anti-recommendation → <a href="/blog/backend/01-database/vendors/aurora/cross-az-failover-rto/" data-link-title="Aurora Cross-AZ Failover：RTO 量測、endpoint routing 與 application reconnect 契約" data-link-desc="Aurora cross-AZ failover lifecycle（detection / promotion / DNS update）、&lt; 30 秒 RTO、application DNS cache 跟 connection pool 對齊、Standard Chartered 受監管場景為什麼用獨立 cluster 而非 Global Database failover">Aurora 跨 AZ failover RTO</a></li>
<li>想評估全球資料常駐與多 region 部署 → <a href="/blog/backend/01-database/vendors/aurora/global-database-multi-region/" data-link-title="Aurora Global Database：跨 region async replication、&lt; 1 秒 lag 與合規 anti-recommendation" data-link-desc="Aurora Global Database 跨 region storage-level async replication、&lt; 1 秒 typical lag、planned vs unplanned failover RTO 數量級對比、Standard Chartered 合規禁止跨境複製為什麼讓 Global Database 變反指標">Aurora global database 多 region</a></li>
<li>想對照 distributed SQL（CockroachDB / Aurora DSQL / Spanner）的合規場景 → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/rds/aurora/customers/">Amazon Aurora Customer Stories</a></li>
<li><a href="https://aws.amazon.com/blogs/industries/amazon-aurora-for-core-banking-systems/">Amazon Aurora for Core Banking Systems</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-aurora-dsql-for-global-scale-financial-transactions/">Amazon Aurora DSQL for global-scale financial transactions</a></li>
</ul>
]]></content:encoded></item><item><title>9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/</guid><description>&lt;p>這個案例的核心責任是說明「售票搶購型 flash-sale」的負載形狀 — 跟現有所有案例都不同的極端形狀。售票開賣在精確時間點（例如 12:00:00）瞬間湧入數十萬使用者、5 分鐘內賣完、之後流量歸零。這種「t=0 起跳、t=300 結束」的負載沒有「峰值預測」可言、只有「瞬間吸收」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>拓元 Tixcraft 在 AWS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/tixcraft/">tixCraft Case Study&lt;/a> 與 &lt;a href="https://www.slideshare.net/slideshow/case-sharing-tixcraft-on-aws-reinvent-2015-recap/55681198">AWS re:Invent 2015 簡報&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>同時選位用戶&lt;/td>
 &lt;td>100,000+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>訂單峰值&lt;/td>
 &lt;td>每分鐘 70,000+ 訂單、單秒最高 2,500+ 訂單&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3 分鐘內售出&lt;/td>
 &lt;td>30,000+ 張票&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DynamoDB IOPS 範圍&lt;/td>
 &lt;td>20 → 135,000（2015/8/29 峰值）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資源擴張幅度&lt;/td>
 &lt;td>30 分鐘內從 6 台擴到 800 台（130x）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部署時間&lt;/td>
 &lt;td>1,600 工時 → 20 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>壓測規模&lt;/td>
 &lt;td>10,000 台 t2.micro、$130 / 小時&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>任務總成本&lt;/td>
 &lt;td>&amp;lt; 2 台 MacBook Pro（約 $4,200）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>vs 傳統基礎設施成本&lt;/td>
 &lt;td>0.26%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成立年份&lt;/td>
 &lt;td>2013 年底（雲原生）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合（依用戶提供的架構圖）：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>入口&lt;/strong>：Amazon Route 53（DNS）+ CloudFront + S3（靜態資源 static.tixcraft.com）&lt;/li>
&lt;li>&lt;strong>UI 層&lt;/strong>：Elastic Load Balancing → EC2 跨 3 個 Availability Zone（Tixcraft UI）&lt;/li>
&lt;li>&lt;strong>API 層&lt;/strong>：ELB → EC2 跨 3 個 AZ（API）+ ElastiCache 加速 session&lt;/li>
&lt;li>&lt;strong>資料層&lt;/strong>：DynamoDB 作為主要寫入目標（接 UI 寫入跟 API 寫入）&lt;/li>
&lt;li>&lt;strong>付款層&lt;/strong>：獨立的 EC2 Payment、連到 traditional server（合作金流、跑於企業 data center）&lt;/li>
&lt;li>&lt;strong>同步層&lt;/strong>：S3 Sync + EC2 Bridge 跟 corporate data center 的 backend 雙向同步&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>拓元案例最值得讀的、是它揭露三個 flash-sale 工程設計的非直覺事實。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>DynamoDB 作為寫入緩衝、不是 OLTP&lt;/strong>：搶票時的「訂單」先丟進 DynamoDB、傳統 server 用自己能承受的速度消費、即時生效在此架構下不是目標。架構上 DynamoDB 扮演 &lt;em>durable queue&lt;/em> 的角色、不是傳統 OLTP DB。這層解耦讓「前端可以擴 130 倍、後端不用同步擴」、避免後端被前端拖垮。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的 outbox / async delivery 概念、跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 transaction boundary 分離。&lt;/li>
&lt;li>&lt;strong>DynamoDB IOPS 從 20 衝到 135,000 = partition 設計能撐&lt;/strong>：這個 6,750 倍的彈性不是 DynamoDB 魔法、是 &lt;em>partition key 設計均勻&lt;/em> 的結果。partition key 不均、IOPS 上限是「最熱 partition 上限」、不是「總和」。對應 &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的同一判讀重點、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 hot partition 識別。&lt;/li>
&lt;li>&lt;strong>30 分鐘擴 130 倍 = 雲原生架構的存在證明&lt;/strong>：6 台 → 800 台不是手動操作、是 Auto Scaling Group + AMI prebuild + load balancer warmup 的組合。傳統 IDC 做不到。這層彈性是「30 秒內」flash-sale 的前置條件。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 autoscaling 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「售票搶購型 flash-sale」的負載形狀 — 跟現有所有案例都不同的極端形狀。售票開賣在精確時間點（例如 12:00:00）瞬間湧入數十萬使用者、5 分鐘內賣完、之後流量歸零。這種「t=0 起跳、t=300 結束」的負載沒有「峰值預測」可言、只有「瞬間吸收」。</p>
<h2 id="觀察">觀察</h2>
<p>拓元 Tixcraft 在 AWS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/tixcraft/">tixCraft Case Study</a> 與 <a href="https://www.slideshare.net/slideshow/case-sharing-tixcraft-on-aws-reinvent-2015-recap/55681198">AWS re:Invent 2015 簡報</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同時選位用戶</td>
          <td>100,000+</td>
      </tr>
      <tr>
          <td>訂單峰值</td>
          <td>每分鐘 70,000+ 訂單、單秒最高 2,500+ 訂單</td>
      </tr>
      <tr>
          <td>3 分鐘內售出</td>
          <td>30,000+ 張票</td>
      </tr>
      <tr>
          <td>DynamoDB IOPS 範圍</td>
          <td>20 → 135,000（2015/8/29 峰值）</td>
      </tr>
      <tr>
          <td>資源擴張幅度</td>
          <td>30 分鐘內從 6 台擴到 800 台（130x）</td>
      </tr>
      <tr>
          <td>部署時間</td>
          <td>1,600 工時 → 20 分鐘</td>
      </tr>
      <tr>
          <td>壓測規模</td>
          <td>10,000 台 t2.micro、$130 / 小時</td>
      </tr>
      <tr>
          <td>任務總成本</td>
          <td>&lt; 2 台 MacBook Pro（約 $4,200）</td>
      </tr>
      <tr>
          <td>vs 傳統基礎設施成本</td>
          <td>0.26%</td>
      </tr>
      <tr>
          <td>成立年份</td>
          <td>2013 年底（雲原生）</td>
      </tr>
  </tbody>
</table>
<p>服務組合（依用戶提供的架構圖）：</p>
<ul>
<li><strong>入口</strong>：Amazon Route 53（DNS）+ CloudFront + S3（靜態資源 static.tixcraft.com）</li>
<li><strong>UI 層</strong>：Elastic Load Balancing → EC2 跨 3 個 Availability Zone（Tixcraft UI）</li>
<li><strong>API 層</strong>：ELB → EC2 跨 3 個 AZ（API）+ ElastiCache 加速 session</li>
<li><strong>資料層</strong>：DynamoDB 作為主要寫入目標（接 UI 寫入跟 API 寫入）</li>
<li><strong>付款層</strong>：獨立的 EC2 Payment、連到 traditional server（合作金流、跑於企業 data center）</li>
<li><strong>同步層</strong>：S3 Sync + EC2 Bridge 跟 corporate data center 的 backend 雙向同步</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>拓元案例最值得讀的、是它揭露三個 flash-sale 工程設計的非直覺事實。</p>
<ol>
<li><strong>DynamoDB 作為寫入緩衝、不是 OLTP</strong>：搶票時的「訂單」先丟進 DynamoDB、傳統 server 用自己能承受的速度消費、即時生效在此架構下不是目標。架構上 DynamoDB 扮演 <em>durable queue</em> 的角色、不是傳統 OLTP DB。這層解耦讓「前端可以擴 130 倍、後端不用同步擴」、避免後端被前端拖垮。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的 outbox / async delivery 概念、跟 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 transaction boundary 分離。</li>
<li><strong>DynamoDB IOPS 從 20 衝到 135,000 = partition 設計能撐</strong>：這個 6,750 倍的彈性不是 DynamoDB 魔法、是 <em>partition key 設計均勻</em> 的結果。partition key 不均、IOPS 上限是「最熱 partition 上限」、不是「總和」。對應 <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> 的同一判讀重點、跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 hot partition 識別。</li>
<li><strong>30 分鐘擴 130 倍 = 雲原生架構的存在證明</strong>：6 台 → 800 台不是手動操作、是 Auto Scaling Group + AMI prebuild + load balancer warmup 的組合。傳統 IDC 做不到。這層彈性是「30 秒內」flash-sale 的前置條件。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 autoscaling 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
</ol>
<p>需要警惕的判讀盲點：</p>
<ul>
<li>「限流到底怎麼做」這個工程社群關心的問題、架構圖上看不到明確元件。可能是「DynamoDB 寫入排隊 = 隱性限流」、也可能是 ELB / WAF / 應用層限流。沒有公開資訊不要過度推測。</li>
<li>2015 年的數字、用的還是 t2.micro 跟舊版 DynamoDB throughput model。現在等效實作可能會用 DynamoDB on-demand、AWS WAF、CloudFront WAF rules、或 SeatGeek-style Virtual Waiting Room（見 <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</a>）。</li>
<li>「30,000 張 / 3 分鐘」是 <em>票房成績</em>、不是 <em>系統極限</em>。系統能撐遠不止這個量、只是票本身賣完了。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>flash-sale 的核心架構模式：寫入緩衝 + 慢速消費</strong>：前端把訂單塞進可彈性擴容的儲存（DynamoDB / Redis Stream / Kafka）、後端按自己能力消費。這個模式讓「短時間吸收洪峰」跟「實際處理」解耦。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 與 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a>。</li>
<li><strong>partition key 設計是 flash-sale 的命脈</strong>：搶票場景天然容易 hot partition（同一場演唱會 = 同一 event_id）、必須用 composite key（event_id + user_id_hash）或 write sharding（event_id + random_suffix）分散。對應 <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>。</li>
<li><strong>flash-sale 必須事先 ELB / Auto Scaling 預熱</strong>：開賣前 30-60 分鐘 pre-warm ELB、預先啟動最低額度的 EC2、避免 t=0 時冷啟動。對應 AWS 官方 <a href="https://aws.amazon.com/blogs/mt/top-considerations-for-flash-sale-events/">Flash Sale 工程指引</a>。</li>
<li><strong>付款層獨立、不被搶票流量影響</strong>：拓元把 Payment EC2 拉出來、直連傳統金流 server。讓「選位 + 下單」的高頻流量不會塞爆「付款」的低頻流量。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的關鍵路徑切分。</li>
<li><strong>限流（rate limiting）通常是隱性的、不一定看得到 component</strong>：DynamoDB 寫入排隊本身就是隱性限流；也可以加 WAF rate-based rule、ELB request throttling、或前置 Virtual Waiting Room 做明確限流（見 <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</a>）。</li>
</ol>
<p>跨平台等效：GCP Cloud Spanner / Bigtable + Cloud Pub/Sub 作 buffer + GKE autoscaling；Azure Cosmos DB + Service Bus + AKS；自建 PostgreSQL + Kafka + Kubernetes 都可以實作對等架構。差異是 vendor 整合度跟擴容速度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 flash-sale 緩衝架構 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想做 partition key 設計 → <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> + <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a></li>
<li>想做明確限流 / 排隊機制 → <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 Virtual Waiting Room</a></li>
<li>想預熱 ELB / Auto Scaling → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>對照其他售票市場 → <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</a>（印度市場、年售 2 億張）</li>
<li>想理解 flash-sale 場景的 partition key 反模式 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想評估 on-demand vs provisioned 在 flash-sale 的搭配 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/tixcraft/">tixCraft Case Study (AWS)</a></li>
<li><a href="https://www.slideshare.net/slideshow/case-sharing-tixcraft-on-aws-reinvent-2015-recap/55681198">tixCraft on AWS re:Invent 2015 Recap (SlideShare)</a></li>
<li><a href="https://www.youtube.com/watch?v=Bi-1xjXvKgs">tixCraft: Handling Millions of Ticketing Requests with AWS (YouTube)</a></li>
<li><a href="https://aws.amazon.com/blogs/mt/top-considerations-for-flash-sale-events/">Top considerations for Flash sale events (AWS Cloud Operations Blog)</a></li>
<li><a href="https://aws.amazon.com/blogs/database/handle-traffic-spikes-with-amazon-dynamodb-provisioned-capacity/">Handle traffic spikes with Amazon DynamoDB provisioned capacity</a></li>
</ul>
]]></content:encoded></item><item><title>MySQL InnoDB Tuning：為什麼一個 100 GB DB 在 64 GB RAM server 上 query 慢 5 倍</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/innodb-tuning/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/innodb-tuning/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/" data-link-title="MySQL" data-link-desc="高併發網路服務常用關聯式資料庫、Vitess / PlanetScale 分片生態、GitHub / Shopify / Facebook 規模驗證">MySQL&lt;/a> overview 的 implementation-layer deep article。Overview 已說明 MySQL 在 OLTP 譜系的定位、本文聚焦 &lt;em>InnoDB engine tuning&lt;/em> — 4 個影響最大的 knob 跟對應 production 行為。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="開場常見痛點">開場：常見痛點&lt;/h2>
&lt;p>一個 100 GB MySQL DB、64 GB RAM 的 server、p99 query latency 從 5ms 飆到 50ms。第一直覺是 server overload — 但 CPU &amp;lt; 30%、disk IO 50 IOPS。為什麼慢？&lt;/p>
&lt;p>打開 &lt;code>SHOW VARIABLES LIKE 'innodb_buffer_pool_size'&lt;/code>：&lt;code>134217728&lt;/code>（128 MB）。對 64 GB RAM server、buffer pool 只用了 128 MB、剩 99.9% 的 working set 每次 query 都要從 disk 讀。CPU 閒、disk 沒滿、是因為 &lt;em>MySQL 自己不用 RAM&lt;/em> — 用 InnoDB 預設值跑 100 GB DB 等於 disk-only 模式。&lt;/p>
&lt;p>這個案例展示 InnoDB tuning 的核心：MySQL 預設值是 &lt;em>為 16 GB RAM 設計&lt;/em>、production server RAM 越大、預設值離 optimal 越遠。&lt;/p>
&lt;h2 id="4-個-critical-knob">4 個 critical knob&lt;/h2>
&lt;p>對 90% production case、調這 4 個就解決大部分 InnoDB 性能問題：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Knob&lt;/th>
 &lt;th>預設&lt;/th>
 &lt;th>對 production 建議&lt;/th>
 &lt;th>影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>innodb_buffer_pool_size&lt;/code>&lt;/td>
 &lt;td>128 MB&lt;/td>
 &lt;td>系統 RAM 50-75%（dedicated server 75%）&lt;/td>
 &lt;td>讀效能（資料能否在 RAM）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>innodb_log_file_size&lt;/code>&lt;/td>
 &lt;td>48 MB（×2 file）&lt;/td>
 &lt;td>1-4 GB（依寫吞吐、8.0.30+ 改 &lt;code>innodb_redo_log_capacity&lt;/code>）&lt;/td>
 &lt;td>寫效能（flush 頻率）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>innodb_flush_log_at_trx_commit&lt;/code>&lt;/td>
 &lt;td>1 (full ACID)&lt;/td>
 &lt;td>1（金融 / 訂單）/ 2（高吞吐可容 1 秒 loss）&lt;/td>
 &lt;td>寫吞吐 vs durability&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>innodb_io_capacity&lt;/code> + &lt;code>_max&lt;/code>&lt;/td>
 &lt;td>200 / 2000&lt;/td>
 &lt;td>SSD: 2000 / 20000; NVMe: 10000 / 40000&lt;/td>
 &lt;td>flush 速度（適配儲存）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>其他 knob（&lt;code>innodb_thread_concurrency&lt;/code> / &lt;code>innodb_buffer_pool_instances&lt;/code> / &lt;code>innodb_read_io_threads&lt;/code> 等）也有影響、但對多數 case &lt;em>先把這 4 個調對&lt;/em> 比微調其他 20 個重要。&lt;/p>
&lt;h2 id="knob-1buffer-pool--把-working-set-拉進-ram">Knob 1：Buffer pool — 把 working set 拉進 RAM&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer-pool/" data-link-title="Buffer Pool" data-link-desc="說明資料庫如何用記憶體快取磁碟頁，以降低 I/O 並影響查詢效能">InnoDB buffer pool&lt;/a> 是 &lt;em>page cache&lt;/em> — 從 disk 讀過的 16 KB page 快取在 RAM、下次 query 直接 RAM 讀。Buffer pool 越大、cache hit ratio 越高、disk IO 越少。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/mysql/" data-link-title="MySQL" data-link-desc="高併發網路服務常用關聯式資料庫、Vitess / PlanetScale 分片生態、GitHub / Shopify / Facebook 規模驗證">MySQL</a> overview 的 implementation-layer deep article。Overview 已說明 MySQL 在 OLTP 譜系的定位、本文聚焦 <em>InnoDB engine tuning</em> — 4 個影響最大的 knob 跟對應 production 行為。</p></blockquote>
<hr>
<h2 id="開場常見痛點">開場：常見痛點</h2>
<p>一個 100 GB MySQL DB、64 GB RAM 的 server、p99 query latency 從 5ms 飆到 50ms。第一直覺是 server overload — 但 CPU &lt; 30%、disk IO 50 IOPS。為什麼慢？</p>
<p>打開 <code>SHOW VARIABLES LIKE 'innodb_buffer_pool_size'</code>：<code>134217728</code>（128 MB）。對 64 GB RAM server、buffer pool 只用了 128 MB、剩 99.9% 的 working set 每次 query 都要從 disk 讀。CPU 閒、disk 沒滿、是因為 <em>MySQL 自己不用 RAM</em> — 用 InnoDB 預設值跑 100 GB DB 等於 disk-only 模式。</p>
<p>這個案例展示 InnoDB tuning 的核心：MySQL 預設值是 <em>為 16 GB RAM 設計</em>、production server RAM 越大、預設值離 optimal 越遠。</p>
<h2 id="4-個-critical-knob">4 個 critical knob</h2>
<p>對 90% production case、調這 4 個就解決大部分 InnoDB 性能問題：</p>
<table>
  <thead>
      <tr>
          <th>Knob</th>
          <th>預設</th>
          <th>對 production 建議</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>innodb_buffer_pool_size</code></td>
          <td>128 MB</td>
          <td>系統 RAM 50-75%（dedicated server 75%）</td>
          <td>讀效能（資料能否在 RAM）</td>
      </tr>
      <tr>
          <td><code>innodb_log_file_size</code></td>
          <td>48 MB（×2 file）</td>
          <td>1-4 GB（依寫吞吐、8.0.30+ 改 <code>innodb_redo_log_capacity</code>）</td>
          <td>寫效能（flush 頻率）</td>
      </tr>
      <tr>
          <td><code>innodb_flush_log_at_trx_commit</code></td>
          <td>1 (full ACID)</td>
          <td>1（金融 / 訂單）/ 2（高吞吐可容 1 秒 loss）</td>
          <td>寫吞吐 vs durability</td>
      </tr>
      <tr>
          <td><code>innodb_io_capacity</code> + <code>_max</code></td>
          <td>200 / 2000</td>
          <td>SSD: 2000 / 20000; NVMe: 10000 / 40000</td>
          <td>flush 速度（適配儲存）</td>
      </tr>
  </tbody>
</table>
<p>其他 knob（<code>innodb_thread_concurrency</code> / <code>innodb_buffer_pool_instances</code> / <code>innodb_read_io_threads</code> 等）也有影響、但對多數 case <em>先把這 4 個調對</em> 比微調其他 20 個重要。</p>
<h2 id="knob-1buffer-pool--把-working-set-拉進-ram">Knob 1：Buffer pool — 把 working set 拉進 RAM</h2>
<p><a href="/blog/backend/knowledge-cards/buffer-pool/" data-link-title="Buffer Pool" data-link-desc="說明資料庫如何用記憶體快取磁碟頁，以降低 I/O 並影響查詢效能">InnoDB buffer pool</a> 是 <em>page cache</em> — 從 disk 讀過的 16 KB page 快取在 RAM、下次 query 直接 RAM 讀。Buffer pool 越大、cache hit ratio 越高、disk IO 越少。</p>
<p><strong>Sizing</strong>：</p>
<ul>
<li><em>Dedicated MySQL server</em>：RAM 70-80%（剩 20-30% 給 OS / MySQL 其他結構 / connection buffer）</li>
<li><em>Shared server</em>：RAM 30-50%（看其他 process 需求）</li>
<li><em>Container / Kubernetes</em>：對 container memory limit 70%（不是 host RAM）</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 64 GB RAM dedicated server</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">innodb_buffer_pool_size</span> <span class="o">=</span> <span class="s">48G</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">innodb_buffer_pool_instances</span> <span class="o">=</span> <span class="s">8  # 分 8 個 instance 降 mutex contention（每 instance 6 GB）</span></span></span></code></pre></div><p><strong>Buffer pool warm-up</strong>：MySQL 重啟後 buffer pool 是空的、要慢慢從 disk 把熱資料拉回 RAM。預設 5.7+ MySQL 啟動時 <em>dump buffer pool LRU list 到 disk</em>、重啟時 <em>自動 restore</em>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="na">innodb_buffer_pool_dump_at_shutdown</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">innodb_buffer_pool_load_at_startup</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">innodb_buffer_pool_dump_pct</span> <span class="o">=</span> <span class="s">75  # 只 dump 最 hot 的 75% page list</span></span></span></code></pre></div><p>沒這個 warm-up、重啟後第 1 個小時 query latency 都偏高、application 看到 p99 spike。</p>
<h2 id="knob-2redo-log--flush-頻率跟寫吞吐">Knob 2：Redo log — flush 頻率跟寫吞吐</h2>
<p>InnoDB 寫入 <em>先寫 redo log（順序寫）</em>、再非同步寫到 data file（隨機寫）。Redo log 滿了強迫 flush data file、flush 期間寫吞吐降。</p>
<p><code>innodb_log_file_size</code> 控制每個 log file 大小（預設 2 個 file）：</p>
<ul>
<li>5.7：預設 48 MB × 2 = 96 MB total</li>
<li>8.0：預設仍是 48 MB × 2、8.0.30+ 改用動態 <code>innodb_redo_log_capacity</code>（default 100 MB total）</li>
</ul>
<p>對 5K WPS server、預設容量可能 <em>每分鐘 flush 一次</em>、寫吞吐持續 stall。提高到 1-4 GB total、flush 改成每 30 分鐘一次、寫吞吐穩定。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="na">innodb_log_file_size</span> <span class="o">=</span> <span class="s">2G       # 大寫吞吐 server 設 1-4 GB</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">innodb_log_files_in_group</span> <span class="o">=</span> <span class="s">2   # 預設 2 個就夠</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">innodb_log_buffer_size</span> <span class="o">=</span> <span class="s">64M    # log 寫 disk 前的 RAM buffer</span></span></span></code></pre></div><p><strong>Trade-off</strong>：log file 越大、recovery 時間越長（crash 後 InnoDB 要 replay 全部 log）。1 GB log 通常 &lt; 1 分鐘 recovery、4 GB 可能 5 分鐘以上。SSD / NVMe 這個 trade-off 不嚴重、HDD 要注意。</p>
<p>MySQL 8.0+ 改進：log file 可動態調整（不用重啟）、且 <em>automatic redo log writer threads</em> 降低 mutex contention。</p>
<h2 id="knob-3flush-method--acid-vs-吞吐">Knob 3：Flush method — ACID vs 吞吐</h2>
<p><code>innodb_flush_log_at_trx_commit</code> 控制 <em>每個 transaction commit 時要不要 flush log 到 disk</em>：</p>
<ul>
<li><code>1</code>（預設）：每次 commit fsync log file → <em>zero data loss on crash</em></li>
<li><code>2</code>：每次 commit 寫 log file（但 OS-level cache、不 fsync）→ <em>server crash 不丟、OS crash 丟 1 秒</em></li>
<li><code>0</code>：每秒 fsync 一次 → <em>任何 crash 丟 1 秒</em></li>
</ul>
<p><code>sync_binlog</code> 對應 binlog（不是 InnoDB log）：</p>
<ul>
<li><code>1</code>（建議）：每次 commit fsync binlog</li>
<li><code>0</code>：依賴 OS sync、容易丟 binlog → replication / CDC 風險</li>
</ul>
<p><strong>Production 組合</strong>：</p>
<table>
  <thead>
      <tr>
          <th>用途</th>
          <th><code>innodb_flush_log_at_trx_commit</code></th>
          <th><code>sync_binlog</code></th>
          <th>寫吞吐</th>
          <th>Crash data loss</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>金融 / 訂單 / 支付</td>
          <td>1</td>
          <td>1</td>
          <td>baseline</td>
          <td>0</td>
      </tr>
      <tr>
          <td>一般 web 應用</td>
          <td>1</td>
          <td>1</td>
          <td>baseline</td>
          <td>0</td>
      </tr>
      <tr>
          <td>高寫吞吐 + 容忍 1 sec loss</td>
          <td>2</td>
          <td>1</td>
          <td>+30-50%</td>
          <td>OS crash 丟 1 秒</td>
      </tr>
      <tr>
          <td>Dev / test</td>
          <td>2</td>
          <td>0</td>
          <td>+50-100%</td>
          <td>不重要</td>
      </tr>
      <tr>
          <td>不要這樣設</td>
          <td>0</td>
          <td>0</td>
          <td>+100%</td>
          <td>任意 crash 丟資料</td>
      </tr>
  </tbody>
</table>
<p>多數 production 用 <code>1 + 1</code>、雖然慢但 <em>簡單可預測</em>。改成 <code>2 + 1</code> 之前要明確 <em>能容忍 1 秒 data loss</em>、且通常 review 過 Disaster Recovery Plan。</p>
<h2 id="knob-4io-capacity--適配儲存">Knob 4：IO capacity — 適配儲存</h2>
<p>InnoDB 後台 flush 速度受 <code>innodb_io_capacity</code> 限制：</p>
<ul>
<li><code>innodb_io_capacity</code>（一般）：後台 flush 目標 IOPS</li>
<li><code>innodb_io_capacity_max</code>（突發）：emergency flush 上限</li>
</ul>
<p><strong>對應儲存類型</strong>：</p>
<table>
  <thead>
      <tr>
          <th>儲存</th>
          <th>IOPS 能力</th>
          <th><code>innodb_io_capacity</code></th>
          <th><code>innodb_io_capacity_max</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>7200 RPM HDD</td>
          <td>~80 IOPS</td>
          <td>100</td>
          <td>200</td>
      </tr>
      <tr>
          <td>SSD (SATA)</td>
          <td>10K-50K IOPS</td>
          <td>2000</td>
          <td>20000</td>
      </tr>
      <tr>
          <td>NVMe SSD</td>
          <td>100K-500K IOPS</td>
          <td>10000</td>
          <td>40000</td>
      </tr>
      <tr>
          <td>EBS gp3</td>
          <td>3000-16000 IOPS</td>
          <td>5000</td>
          <td>16000</td>
      </tr>
      <tr>
          <td>EBS io2</td>
          <td>50K-256K IOPS</td>
          <td>20000</td>
          <td>60000</td>
      </tr>
  </tbody>
</table>
<p>預設 <code>200 / 2000</code> 是 <em>為 HDD 設計</em>、SSD / NVMe server 用預設值 = InnoDB 自我限速、flush 慢、寫入瓶頸。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># NVMe SSD server</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">innodb_io_capacity</span> <span class="o">=</span> <span class="s">10000</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">innodb_io_capacity_max</span> <span class="o">=</span> <span class="s">40000</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="na">innodb_flush_neighbors</span> <span class="o">=</span> <span class="s">0  # NVMe 不需要 group flush 相鄰 page</span></span></span></code></pre></div><h2 id="5-個-production-踩雷">5 個 Production 踩雷</h2>
<h3 id="1-buffer-pool-沒-warm-up--重啟後-1-小時-p99-飆">1. Buffer pool 沒 warm-up — 重啟後 1 小時 p99 飆</h3>
<p>MySQL 重啟（OS upgrade / config change / failover）後、buffer pool 是空的、所有 query 第一次都 disk 讀、p99 latency 飆 5-10x、application 看到 timeout。</p>
<p>修法：</p>
<ul>
<li>啟用 <code>innodb_buffer_pool_dump_at_shutdown=1</code> + <code>innodb_buffer_pool_load_at_startup=1</code></li>
<li>對 <em>沒 graceful shutdown</em> 的 crash（OOM / kernel panic）、buffer pool 沒 dump、warm-up 後第一個小時仍辛苦</li>
<li>重要 server 重啟前手動 dump：<code>SET GLOBAL innodb_buffer_pool_dump_now=ON</code></li>
<li>對於不能容忍 cold cache 的場景、failover 前 <em>先 pre-warm new primary</em>（用 query replay 把 hot data 拉到 buffer pool）</li>
</ul>
<h3 id="2-log-file-size-設太小--checkpoint-storm">2. Log file size 設太小 — checkpoint storm</h3>
<p><code>innodb_log_file_size=48M</code> 預設、高寫吞吐 server log 每分鐘 flush 一次、flush 期間 <em>checkpoint storm</em> — 寫吞吐降 50%、p99 暴增。錯誤訊號是 <code>innodb_log_waits</code> 持續 &gt; 0。</p>
<p>修法：</p>
<ul>
<li>監控 <code>SHOW STATUS LIKE 'Innodb_log_waits'</code> — 應該長期接近 0</li>
<li>提高 <code>innodb_log_file_size</code> 到 1-4 GB（依寫吞吐）</li>
<li>8.0+ 可動態調整、5.7 需要 <em>正常 shutdown</em> 後改、開啟前先 dump buffer pool（避免 cold cache）</li>
</ul>
<h3 id="3-sync_binlog0-換速度--replication-永久-broken-風險">3. <code>sync_binlog=0</code> 換速度 — replication 永久 broken 風險</h3>
<p>開發 / staging 改 <code>sync_binlog=0</code>（加快寫入）、後來複製到 production 配置、production 同樣 <code>sync_binlog=0</code>。OS crash 後 binlog 缺最後幾秒 transaction、replica 跟 primary GTID set diverge、replication broken、要 <em>重建 replica from base backup</em>（小時級 recovery）。</p>
<p>修法：</p>
<ul>
<li><em>Production 永遠用 <code>sync_binlog=1</code></em>、不要為了寫吞吐犧牲 binlog durability</li>
<li>開發 / staging 配置跟 production 隔離、不要直接 copy config</li>
<li>Replica 失聯後 <em>用 GTID 自動 re-attach</em>（不是 binlog position）— 仍然需要 binlog 完整、<code>sync_binlog=0</code> 仍是風險</li>
</ul>
<h3 id="4-io-scheduler--不是-innodb-tuning-但影響大">4. IO scheduler — 不是 InnoDB tuning 但影響大</h3>
<p>Linux <code>noop</code> / <code>deadline</code> / <code>cfq</code> IO scheduler 對 SSD / NVMe 影響大：</p>
<ul>
<li><code>cfq</code>（traditional spinning disk default）：對 SSD 嚴重 bottleneck</li>
<li><code>deadline</code>：對 SSD 較好、但有 latency cap</li>
<li><code>noop</code> / <code>none</code>：對 NVMe 最好（讓 device 自己處理 queue）</li>
</ul>
<p><strong>Production check</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">cat /sys/block/sda/queue/scheduler
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 應該顯示： [none] mq-deadline (NVMe)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 或：         noop deadline [cfq] (cfq 是錯的)</span></span></span></code></pre></div><p>不是 InnoDB knob、但影響 InnoDB IO behavior &gt; 30%。InnoDB tuning 前先確認 OS-level IO scheduler 對。</p>
<h3 id="5-undo-log-膨脹--purge-跟不上">5. Undo log 膨脹 — purge 跟不上</h3>
<p>Undo log 紀錄 <em>未來可能 rollback 需要的舊版本 row</em>。長 transaction（hours-level）讓 undo log 持續累積、不能 purge、最後 InnoDB tablespace 膨脹幾 GB、disk 滿。</p>
<p>訊號：</p>
<ul>
<li><code>SHOW ENGINE INNODB STATUS</code> 看 <code>History list length</code> 持續成長（正常 &lt; 1000、異常 millions）</li>
<li><code>information_schema.innodb_metrics</code> 的 <code>trx_rseg_history_len</code></li>
</ul>
<p>修法：</p>
<ul>
<li>找 long-running transaction：<code>SELECT * FROM information_schema.innodb_trx WHERE trx_started &lt; NOW() - INTERVAL 1 HOUR</code></li>
<li>KILL 該 transaction（謹慎、可能 application bug）</li>
<li>8.0+ 用 separate undo tablespace（<code>innodb_undo_tablespaces</code>）、不污染 main tablespace、且可以 truncate</li>
</ul>
<h2 id="容量規劃要點">容量規劃要點</h2>
<p>對 64 GB RAM、NVMe SSD、5K WPS、100 GB DB 的 server：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># my.cnf production-ready baseline</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">[mysqld]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Buffer pool (75% RAM)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="na">innodb_buffer_pool_size</span> <span class="o">=</span> <span class="s">48G</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="na">innodb_buffer_pool_instances</span> <span class="o">=</span> <span class="s">8</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="na">innodb_buffer_pool_dump_at_shutdown</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="na">innodb_buffer_pool_load_at_startup</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># Redo log</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="na">innodb_log_file_size</span> <span class="o">=</span> <span class="s">2G</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="na">innodb_log_files_in_group</span> <span class="o">=</span> <span class="s">2</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="na">innodb_log_buffer_size</span> <span class="o">=</span> <span class="s">64M</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Flush behavior</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="na">innodb_flush_log_at_trx_commit</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="na">sync_binlog</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="na">innodb_flush_method</span> <span class="o">=</span> <span class="s">O_DIRECT  # 跳過 OS page cache 避免 double cache</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># IO capacity (NVMe)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="na">innodb_io_capacity</span> <span class="o">=</span> <span class="s">10000</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="na">innodb_io_capacity_max</span> <span class="o">=</span> <span class="s">40000</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="na">innodb_flush_neighbors</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="na">innodb_lru_scan_depth</span> <span class="o">=</span> <span class="s">1024</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"># Concurrency</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="na">innodb_thread_concurrency</span> <span class="o">=</span> <span class="s">0  # 0 = no limit (8.0+ 推薦)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="na">innodb_read_io_threads</span> <span class="o">=</span> <span class="s">8</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="na">innodb_write_io_threads</span> <span class="o">=</span> <span class="s">8</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># 額外</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="na">innodb_file_per_table</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="na">innodb_strict_mode</span> <span class="o">=</span> <span class="s">1</span></span></span></code></pre></div><p>跨不同 server spec、<code>buffer_pool_size</code> / <code>io_capacity</code> 隨硬體調整、其他 knob 變動小。</p>
<h2 id="跟其他模組整合">跟其他模組整合</h2>
<h3 id="跟-replication-topology">跟 Replication topology</h3>
<p><code>sync_binlog=1</code> + <code>innodb_flush_log_at_trx_commit=1</code> 是 <em>durability baseline</em>、影響 <a href="/blog/backend/01-database/vendors/mysql/replication-topology/" data-link-title="MySQL Replication Topology：async / semi-sync / GTID 不是三選一、是三個 trade-off 軸的疊加" data-link-desc="MySQL replication 不是「選 async 還是 semi-sync」、是 *durability / latency / consistency* 三個 trade-off 軸的疊加；GTID 是跨 mode 的 infrastructure layer、不是第三種 mode。本文走 3 軸取捨模型 → async / semi-sync 行為對比 → GTID 替代 binlog-position 的好處 → 配置 step-by-step → 5 production 踩雷（lag 暴衝 / semi-sync 退回 async / GTID gap / Loss-Less semi-sync 真的 loss-less / chained replication 雪崩）→ 跟 Aurora MySQL / Vitess / ProxySQL / Orchestrator 整合">Replication Topology</a> 的 <em>primary durability</em>。Semi-sync 加在這基礎上提供 <em>跨 server durability</em>。</p>
<h3 id="跟-proxysql">跟 ProxySQL</h3>
<p>ProxySQL connection pool 降低 <em>MySQL connection 開銷</em>、但 <em>每個 connection</em> 仍消耗 8-10 MB RAM（thread stack + session buffer）。Buffer pool 設 75% RAM 後、剩 25% 給 connection / temporary buffer / OS。Connection 太多會擠掉 buffer pool。</p>
<p>詳見 <a href="/blog/backend/01-database/vendors/mysql/proxysql-config/" data-link-title="MySQL ProxySQL 配置：connection / query / route / response 四段 lifecycle 跟 query rule 設計" data-link-desc="ProxySQL 是 MySQL 生態的 connection pool &#43; query routing 標準。本文走 connection → query parse → route → response 四段 lifecycle、query rule engine 的 rule chain 設計、Hostgroup / Server / User 三層 schema、配置 step-by-step（讀寫分離 &#43; replica lag-aware routing）、5 production 踩雷（query rule 順序錯亂 / connection 漂移 / write 路由到 replica / runtime / disk schema drift / mirror traffic 副作用）、跟 Replication / Orchestrator / HAProxy 整合">ProxySQL 配置</a>。</p>
<h3 id="跟-aurora-mysql">跟 Aurora MySQL</h3>
<p>Aurora 改寫 InnoDB storage layer、上方 knob 大多 <em>Aurora 自動管理</em>：</p>
<ul>
<li>Buffer pool size：Aurora compute instance 自動配</li>
<li>Redo log：Aurora 自己的 distributed log、不用 <code>innodb_log_file_size</code></li>
<li><code>sync_binlog</code> / <code>innodb_flush_log_at_trx_commit</code>：Aurora storage layer 保證 durability、應用層 knob 影響小</li>
</ul>
<p>Aurora user 仍可 tune <code>innodb_buffer_pool_size</code> 等、但操作面從 InnoDB 內部議題變成 <em>Aurora instance class 選擇</em>。詳見 <a href="/blog/backend/01-database/vendors/aurora/" data-link-title="AWS Aurora" data-link-desc="AWS managed PostgreSQL / MySQL、storage / compute 分離、&#43;75% 效能改善的 production 證據">Aurora vendor page</a>。</p>
<h3 id="跟-osc-tool">跟 OSC tool</h3>
<p>InnoDB tuning 不直接影響 OSC 工具行為、但 <em>log file size 太小</em> 時 gh-ost / pt-osc 寫 ghost table 容易 trigger checkpoint storm、放慢整個 schema migration。詳見 <a href="/blog/backend/01-database/vendors/mysql/online-schema-change-tools/" data-link-title="MySQL Online Schema Change：gh-ost 跟 pt-online-schema-change 兩條完全不同的 ghost table 路徑" data-link-desc="MySQL ALTER TABLE 可能鎖整張表，production 需要 online schema change 流程。gh-ost（GitHub）跟 pt-online-schema-change（Percona）都用 ghost table 解決、但底層機制完全不同：pt-osc 用 trigger 同步、gh-ost 用 binlog stream 同步。本文走兩工具機制對照表 → trigger vs binlog 各自取捨 → 配置 step-by-step → 5 production 踩雷（trigger overhead / binlog 延遲 / FK constraint / hot trigger lock / 切換瞬間 deadlock）→ 何時用哪一個">Online Schema Change Tools</a>。</p>
<h2 id="觀測-metric">觀測 metric</h2>
<p><code>SHOW STATUS LIKE</code> + Performance Schema 提供：</p>
<ul>
<li><code>Innodb_buffer_pool_read_requests</code> / <code>_reads</code> → cache hit ratio = <code>1 - reads/read_requests</code>、應該 &gt; 99%</li>
<li><code>Innodb_log_waits</code> → checkpoint pressure、應該 = 0</li>
<li><code>Innodb_log_write_requests</code> / <code>_writes</code> → log buffer 效率</li>
<li><code>Innodb_rows_inserted</code> / <code>_updated</code> / <code>_read</code> → workload 形狀</li>
<li><code>Innodb_row_lock_waits</code> / <code>_time</code> → lock contention</li>
</ul>
<p>把這些丟進 <a href="/blog/backend/04-observability/vendors/datadog/" data-link-title="Datadog" data-link-desc="All-in-one SaaS 觀測平台、APM / Logs / Metrics / RUM / Security">Datadog</a> / <a href="/blog/backend/04-observability/vendors/prometheus/" data-link-title="Prometheus" data-link-desc="Pull-based metrics 主流 OSS、PromQL 與 alerting">Prometheus</a> 透過 <a href="https://github.com/prometheus/mysqld_exporter">mysqld_exporter</a> / <a href="https://www.percona.com/software/database-tools/percona-monitoring-and-management">Percona Monitoring</a> 持續 trend。</p>
<h2 id="相關連結">相關連結</h2>
<ul>
<li><a href="/blog/backend/01-database/vendors/mysql/" data-link-title="MySQL" data-link-desc="高併發網路服務常用關聯式資料庫、Vitess / PlanetScale 分片生態、GitHub / Shopify / Facebook 規模驗證">MySQL vendor overview</a></li>
<li><a href="/blog/backend/01-database/vendors/mysql/replication-topology/" data-link-title="MySQL Replication Topology：async / semi-sync / GTID 不是三選一、是三個 trade-off 軸的疊加" data-link-desc="MySQL replication 不是「選 async 還是 semi-sync」、是 *durability / latency / consistency* 三個 trade-off 軸的疊加；GTID 是跨 mode 的 infrastructure layer、不是第三種 mode。本文走 3 軸取捨模型 → async / semi-sync 行為對比 → GTID 替代 binlog-position 的好處 → 配置 step-by-step → 5 production 踩雷（lag 暴衝 / semi-sync 退回 async / GTID gap / Loss-Less semi-sync 真的 loss-less / chained replication 雪崩）→ 跟 Aurora MySQL / Vitess / ProxySQL / Orchestrator 整合">MySQL Replication Topology</a>（<code>sync_binlog</code> 跟 replication 互動）</li>
<li><a href="/blog/backend/01-database/vendors/mysql/proxysql-config/" data-link-title="MySQL ProxySQL 配置：connection / query / route / response 四段 lifecycle 跟 query rule 設計" data-link-desc="ProxySQL 是 MySQL 生態的 connection pool &#43; query routing 標準。本文走 connection → query parse → route → response 四段 lifecycle、query rule engine 的 rule chain 設計、Hostgroup / Server / User 三層 schema、配置 step-by-step（讀寫分離 &#43; replica lag-aware routing）、5 production 踩雷（query rule 順序錯亂 / connection 漂移 / write 路由到 replica / runtime / disk schema drift / mirror traffic 副作用）、跟 Replication / Orchestrator / HAProxy 整合">MySQL ProxySQL 配置</a>（connection 跟 buffer pool 爭 RAM）</li>
<li><a href="/blog/backend/01-database/vendors/aurora/" data-link-title="AWS Aurora" data-link-desc="AWS managed PostgreSQL / MySQL、storage / compute 分離、&#43;75% 效能改善的 production 證據">Aurora vendor page</a>（managed MySQL、InnoDB tuning 部分轉手）</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/" data-link-title="PostgreSQL autovacuum tuning：為什麼你的 autovacuum 永遠追不上 bloat" data-link-desc="MVCC 怎麼產生 dead tuple、autovacuum cost-based throttle 為什麼預設保守、per-table tuning 怎麼設、5 個 production 踩雷（cost_limit 太低 / 長 transaction blocks vacuum / anti-wraparound 在 peak / partition vacuum 滿 worker / index bloat 沒處理）、跟 partitioning &#43; monitoring 整合">PostgreSQL Autovacuum Tuning</a>（PG sibling、不同 engine 內部 tuning）</li>
<li>官方：<a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-default-se.html">InnoDB Configuration</a> / <a href="https://www.percona.com/blog/mysql-101-tuning-mysql-after-installation/">Percona Tuning Guide</a></li>
</ul>
]]></content:encoded></item><item><title>9.C16 SeatGeek：DynamoDB + Lambda 打造的虛擬等候室</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/</guid><description>&lt;p>這個案例的核心責任是說明「flash-sale 場景下、限流如何明確設計」。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的「DynamoDB 隱性緩衝」是姊妹案 — Tixcraft 用 DynamoDB 作為寫入緩衝吸收洪峰、SeatGeek 走更上游一層、在用戶到達系統前就明確排隊。兩種架構並存於票務業界、適合不同業務場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>SeatGeek Virtual Waiting Room 架構（引自 &lt;a href="https://aws.amazon.com/blogs/architecture/build-a-virtual-waiting-room-with-amazon-dynamodb-and-aws-lambda-at-seatgeek/">AWS Architecture Blog&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>元件&lt;/th>
 &lt;th>角色&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Protected Zone table&lt;/td>
 &lt;td>紀錄受保護資源的 metadata（哪個 event 受 waiting room 保護）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Counters table&lt;/td>
 &lt;td>紀錄「每分鐘發出多少 access token」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>User Connection table&lt;/td>
 &lt;td>紀錄訪客 token 與 WebSocket connection ID&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Queue table&lt;/td>
 &lt;td>把訪客 token 對映到 access token（排隊序號）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Bouncer Lambda&lt;/td>
 &lt;td>配發與失效 access token 的「守門員」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>API Gateway&lt;/td>
 &lt;td>接受外部請求、轉發 Bouncer&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>業務動機：取代「第三方 waiting room 服務」、原因是缺乏客製化（VIP 規則、優先級）跟 metrics 可見度。&lt;/p>
&lt;p>關鍵機制：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Token = 庫存單位&lt;/strong>：access token 總數 = 可售票數量。沒拿到 token 的用戶被導到 waiting room 頁面、看到排隊位置與預估等待時間。&lt;/li>
&lt;li>&lt;strong>FIFO 或 priority queue&lt;/strong>：可以按進入順序、也可以對 VIP 客戶優先發 token。&lt;/li>
&lt;li>&lt;strong>Token 失效機制&lt;/strong>：用戶完成購票 / 主動退出時、token 釋放回 pool、給下一位等候用戶。&lt;/li>
&lt;/ol>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>SeatGeek 案例揭露三個明確限流設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>隱性緩衝 vs 明確排隊是兩種架構取捨&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">Tixcraft 模式&lt;/a>「全部塞進 DynamoDB」、用戶以為下單成功、實際處理排隊。SeatGeek 模式「明確告訴你排隊位置」、用戶看得到等待時間。前者犧牲透明度換流量吸收、後者犧牲流量吸收換體驗。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.10 Production-Side 驗證&lt;/a> 的用戶體驗 vs 系統行為取捨。&lt;/li>
&lt;li>&lt;strong>WebSocket connection 是 stateful 容量單位&lt;/strong>：100 萬個 active waiting room 用戶 = 100 萬個 WebSocket connection、每個 connection 都吃記憶體跟 file descriptor。Lambda 沒辦法保持 WebSocket、需要 API Gateway WebSocket API 或 AppSync 配合。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 stateful service 容量規劃。&lt;/li>
&lt;li>&lt;strong>限流粒度 = 業務粒度&lt;/strong>：「每分鐘發 N 個 token」這個參數直接決定「每分鐘成交 N 張票」。N 太小、賣不完；N 太大、後端撐不住。N 不是技術參數、是業務 × 後端容量的協商結果。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 把容量規劃跟業務 KPI 對接。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「flash-sale 場景下、限流如何明確設計」。跟 <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> 的「DynamoDB 隱性緩衝」是姊妹案 — Tixcraft 用 DynamoDB 作為寫入緩衝吸收洪峰、SeatGeek 走更上游一層、在用戶到達系統前就明確排隊。兩種架構並存於票務業界、適合不同業務場景。</p>
<h2 id="觀察">觀察</h2>
<p>SeatGeek Virtual Waiting Room 架構（引自 <a href="https://aws.amazon.com/blogs/architecture/build-a-virtual-waiting-room-with-amazon-dynamodb-and-aws-lambda-at-seatgeek/">AWS Architecture Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>元件</th>
          <th>角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Protected Zone table</td>
          <td>紀錄受保護資源的 metadata（哪個 event 受 waiting room 保護）</td>
      </tr>
      <tr>
          <td>Counters table</td>
          <td>紀錄「每分鐘發出多少 access token」</td>
      </tr>
      <tr>
          <td>User Connection table</td>
          <td>紀錄訪客 token 與 WebSocket connection ID</td>
      </tr>
      <tr>
          <td>Queue table</td>
          <td>把訪客 token 對映到 access token（排隊序號）</td>
      </tr>
      <tr>
          <td>Bouncer Lambda</td>
          <td>配發與失效 access token 的「守門員」</td>
      </tr>
      <tr>
          <td>API Gateway</td>
          <td>接受外部請求、轉發 Bouncer</td>
      </tr>
  </tbody>
</table>
<p>業務動機：取代「第三方 waiting room 服務」、原因是缺乏客製化（VIP 規則、優先級）跟 metrics 可見度。</p>
<p>關鍵機制：</p>
<ol>
<li><strong>Token = 庫存單位</strong>：access token 總數 = 可售票數量。沒拿到 token 的用戶被導到 waiting room 頁面、看到排隊位置與預估等待時間。</li>
<li><strong>FIFO 或 priority queue</strong>：可以按進入順序、也可以對 VIP 客戶優先發 token。</li>
<li><strong>Token 失效機制</strong>：用戶完成購票 / 主動退出時、token 釋放回 pool、給下一位等候用戶。</li>
</ol>
<h2 id="判讀">判讀</h2>
<p>SeatGeek 案例揭露三個明確限流設計重點。</p>
<ol>
<li><strong>隱性緩衝 vs 明確排隊是兩種架構取捨</strong>：<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>「全部塞進 DynamoDB」、用戶以為下單成功、實際處理排隊。SeatGeek 模式「明確告訴你排隊位置」、用戶看得到等待時間。前者犧牲透明度換流量吸收、後者犧牲流量吸收換體驗。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.10 Production-Side 驗證</a> 的用戶體驗 vs 系統行為取捨。</li>
<li><strong>WebSocket connection 是 stateful 容量單位</strong>：100 萬個 active waiting room 用戶 = 100 萬個 WebSocket connection、每個 connection 都吃記憶體跟 file descriptor。Lambda 沒辦法保持 WebSocket、需要 API Gateway WebSocket API 或 AppSync 配合。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 stateful service 容量規劃。</li>
<li><strong>限流粒度 = 業務粒度</strong>：「每分鐘發 N 個 token」這個參數直接決定「每分鐘成交 N 張票」。N 太小、賣不完；N 太大、後端撐不住。N 不是技術參數、是業務 × 後端容量的協商結果。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 把容量規劃跟業務 KPI 對接。</li>
</ol>
<p>需要警惕的判讀盲點：</p>
<ul>
<li>AWS Architecture Blog 沒提具體流量數字（concurrent users、queue depth、throughput）。讀者無法直接套用到自家容量規劃、必須自己壓測。</li>
<li>DynamoDB 4 張表的設計 <em>看似簡單</em>、實際上每張表的 partition key / sort key 設計都要仔細想。複製這個架構不等於拿到 SeatGeek 的吞吐能力。</li>
<li>「token expiration」機制如果設計不好（例如用戶關閉瀏覽器、token 沒回收）、會導致「排隊很長但實際空著」、影響轉換率。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>明確 vs 隱性限流的選擇</strong>：高價值門票（演唱會、限量周邊）適合明確排隊（用戶願意等）；高頻低價值商品（FCFS 折扣）適合隱性緩衝（讓用戶快速完成）。</li>
<li><strong>Virtual Waiting Room 是 stateful service、要規劃連線容量</strong>：不是 stateless Lambda 一招到底、需要 WebSocket gateway + DynamoDB state store。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的混合架構。</li>
<li><strong>token 過期策略要寫進設計初稿</strong>：用戶離開、付款超時、瀏覽器當掉 — 三種狀況的 token 回收邏輯都不一樣、要明確設計。</li>
<li><strong>可觀測性是「自建 waiting room」勝過「第三方」的關鍵</strong>：SeatGeek 換掉第三方就是要 metrics 可見、知道每分鐘 token issue rate、queue depth distribution、token expiration rate、conversion funnel。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a>。</li>
</ol>
<p>跨平台等效：GCP Cloud Functions + Firestore + Pub/Sub；Azure Functions + Cosmos DB + SignalR；自建 Redis（INCR / TTL）+ WebSocket gateway（Soketi / Socket.IO + Redis adapter）都可以實作對等架構。AWS 還推出官方 <a href="https://aws.amazon.com/solutions/implementations/virtual-waiting-room-on-aws/">Virtual Waiting Room on AWS</a> Solutions、是 SeatGeek 模式的可重用版本。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計明確排隊限流 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>對照隱性緩衝模式 → <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></li>
<li>想做 conversion funnel 可觀測性 → <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> + <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a></li>
<li>想了解 stateful service 容量規劃 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/architecture/build-a-virtual-waiting-room-with-amazon-dynamodb-and-aws-lambda-at-seatgeek/">Build a Virtual Waiting Room with Amazon DynamoDB and AWS Lambda at SeatGeek</a></li>
<li><a href="https://aws.amazon.com/solutions/implementations/virtual-waiting-room-on-aws/">Virtual Waiting Room on AWS (Solutions)</a></li>
<li><a href="https://aws.amazon.com/blogs/apn/how-to-manage-peak-traffic-on-aws-using-queue-its-virtual-waiting-room/">How to manage peak traffic on AWS using Queue-it&rsquo;s virtual waiting room</a></li>
</ul>
]]></content:encoded></item><item><title>9.C17 BookMyShow：印度年售 2 億張票的資料架構現代化</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/bookmyshow-indian-ticketing-platform/</guid><description>&lt;p>這個案例的核心責任是說明「規模化 ticketing 平台」的長期工程議題 — 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的「單一搶票事件」不同、BookMyShow 是 &lt;em>每天都有上百個 flash-sale 事件&lt;/em> 的平台、年售 2 億張票、跨 5 個國家。容量問題從「單一峰值」變成「峰值的常態化」、加上「資料層怎麼跟得上業務變化」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>BookMyShow 在 AWS 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/blogs/business-intelligence/how-bookmyshow-saved-80-in-costs-by-migrating-to-an-aws-modern-data-architecture/">BookMyShow AWS Migration Blog&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>年售票量&lt;/td>
 &lt;td>2 億張 / 年（pre-COVID baseline）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>印度 + 斯里蘭卡 + 新加坡 + 印尼 + 中東&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移時程&lt;/td>
 &lt;td>4 個月完成&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>舊系統年數&lt;/td>
 &lt;td>15 年自建 analytics solution&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>儲存成本下降&lt;/td>
 &lt;td>90%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分析成本下降&lt;/td>
 &lt;td>80%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料整合&lt;/td>
 &lt;td>從 80 TB 多份副本 → 單一 source of truth&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>資料架構：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Data Lake&lt;/strong>：Amazon S3 統一儲存&lt;/li>
&lt;li>&lt;strong>Ingestion&lt;/strong>：Kafka consumers、AWS Glue ETL、AWS IoT Core（MQTT）&lt;/li>
&lt;li>&lt;strong>Processing&lt;/strong>：Amazon EMR（streaming permanent cluster + batch transient cluster）&lt;/li>
&lt;li>&lt;strong>Data Warehouse&lt;/strong>：Amazon Redshift + materialized views&lt;/li>
&lt;li>&lt;strong>Analytics&lt;/strong>：Amazon Athena（ad-hoc）+ Amazon QuickSight（dashboard）&lt;/li>
&lt;li>&lt;strong>ML&lt;/strong>：Amazon SageMaker（內容熱度、活動熱度、搜尋趨勢模型）&lt;/li>
&lt;li>&lt;strong>Orchestration&lt;/strong>：Amazon MWAA + AWS Step Functions&lt;/li>
&lt;/ul>
&lt;p>關鍵業務支撐：「sudden spikes with new movies or events launched」靠 serverless（S3、Glue、Athena、Step Functions、Lambda）自動擴容、無需人工介入。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>BookMyShow 案例揭露三個規模化 ticketing 平台的長期工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>單一搶票 → 常態多事件 = 架構從「為峰值設計」變「為流量分佈設計」&lt;/strong>：每天上百場電影 + 數十場演唱會 + 各種活動同時開票、每場都是 mini flash-sale。容量問題不再是「為一場演唱會準備」、而是「為每天上百個峰值同時準備」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 從單一 workload 變成 workload portfolio。&lt;/li>
&lt;li>&lt;strong>資料層比交易層更難擴&lt;/strong>：8 TB → 80 TB 過程中、舊 analytics 系統用 15 年才走到極限。交易層擴容靠 stateless EC2 + auto-scaling 相對容易、資料層 schema migration、ETL 重寫、報表回對都是長 lead time 工作。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema migration 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的 cost attribution。&lt;/li>
&lt;li>&lt;strong>跨國市場 = 多重合規約束&lt;/strong>：印度、新加坡、印尼、中東各自有資料駐留 / 加密 / 報稅規則。S3 + EMR + Redshift 的「資料分區」不只是性能議題、也是合規議題。對應 &lt;a href="https://tarrragon.github.io/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&lt;/a> 的合規容量規劃。&lt;/li>
&lt;/ol>
&lt;p>需要警惕的判讀盲點：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「規模化 ticketing 平台」的長期工程議題 — 跟 <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> 的「單一搶票事件」不同、BookMyShow 是 <em>每天都有上百個 flash-sale 事件</em> 的平台、年售 2 億張票、跨 5 個國家。容量問題從「單一峰值」變成「峰值的常態化」、加上「資料層怎麼跟得上業務變化」。</p>
<h2 id="觀察">觀察</h2>
<p>BookMyShow 在 AWS 的關鍵敘述（引自 <a href="https://aws.amazon.com/blogs/business-intelligence/how-bookmyshow-saved-80-in-costs-by-migrating-to-an-aws-modern-data-architecture/">BookMyShow AWS Migration Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>年售票量</td>
          <td>2 億張 / 年（pre-COVID baseline）</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>印度 + 斯里蘭卡 + 新加坡 + 印尼 + 中東</td>
      </tr>
      <tr>
          <td>遷移時程</td>
          <td>4 個月完成</td>
      </tr>
      <tr>
          <td>舊系統年數</td>
          <td>15 年自建 analytics solution</td>
      </tr>
      <tr>
          <td>儲存成本下降</td>
          <td>90%</td>
      </tr>
      <tr>
          <td>分析成本下降</td>
          <td>80%</td>
      </tr>
      <tr>
          <td>資料整合</td>
          <td>從 80 TB 多份副本 → 單一 source of truth</td>
      </tr>
  </tbody>
</table>
<p>資料架構：</p>
<ul>
<li><strong>Data Lake</strong>：Amazon S3 統一儲存</li>
<li><strong>Ingestion</strong>：Kafka consumers、AWS Glue ETL、AWS IoT Core（MQTT）</li>
<li><strong>Processing</strong>：Amazon EMR（streaming permanent cluster + batch transient cluster）</li>
<li><strong>Data Warehouse</strong>：Amazon Redshift + materialized views</li>
<li><strong>Analytics</strong>：Amazon Athena（ad-hoc）+ Amazon QuickSight（dashboard）</li>
<li><strong>ML</strong>：Amazon SageMaker（內容熱度、活動熱度、搜尋趨勢模型）</li>
<li><strong>Orchestration</strong>：Amazon MWAA + AWS Step Functions</li>
</ul>
<p>關鍵業務支撐：「sudden spikes with new movies or events launched」靠 serverless（S3、Glue、Athena、Step Functions、Lambda）自動擴容、無需人工介入。</p>
<h2 id="判讀">判讀</h2>
<p>BookMyShow 案例揭露三個規模化 ticketing 平台的長期工程重點。</p>
<ol>
<li><strong>單一搶票 → 常態多事件 = 架構從「為峰值設計」變「為流量分佈設計」</strong>：每天上百場電影 + 數十場演唱會 + 各種活動同時開票、每場都是 mini flash-sale。容量問題不再是「為一場演唱會準備」、而是「為每天上百個峰值同時準備」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 從單一 workload 變成 workload portfolio。</li>
<li><strong>資料層比交易層更難擴</strong>：8 TB → 80 TB 過程中、舊 analytics 系統用 15 年才走到極限。交易層擴容靠 stateless EC2 + auto-scaling 相對容易、資料層 schema migration、ETL 重寫、報表回對都是長 lead time 工作。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema migration 與 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的 cost attribution。</li>
<li><strong>跨國市場 = 多重合規約束</strong>：印度、新加坡、印尼、中東各自有資料駐留 / 加密 / 報稅規則。S3 + EMR + Redshift 的「資料分區」不只是性能議題、也是合規議題。對應 <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> 的合規容量規劃。</li>
</ol>
<p>需要警惕的判讀盲點：</p>
<ul>
<li>「年售 2 億張」是 <em>年度總和</em>、不是 <em>峰值</em>。實際單秒峰值（板球比賽決賽開票、寶萊塢新片首映）案例本身沒揭露。</li>
<li>案例聚焦在 <em>資料分析層</em> 的遷移、不是 <em>交易層</em> 的 flash-sale 設計。讀者若想學「單場 flash-sale 怎麼撐」、應該回 <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> 或 <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</a>。</li>
<li>「80% 成本下降」是 <em>vs 15 年舊系統</em>、不是 <em>vs 競爭對手</em>。舊系統的儲存效率、運維成本本來就低、改善幅度部分來自「現代化紅利」、不只是 AWS 服務本身。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>大規模 ticketing 平台要分「交易層」跟「資料層」兩條容量規劃</strong>：交易層為單一 event flash-sale 設計（<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</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</a> 模式）；資料層為「上千場活動的長期分析」設計（BookMyShow 模式）。兩者用不同服務、不同 SLO。</li>
<li><strong>跨國平台先解決資料駐留、再規劃跨國 analytics</strong>：印度資料不能搬到新加坡分析、合規必須各國資料本地處理、再彙整 metadata。對應 <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>。</li>
<li><strong>serverless data stack 是 ticketing 平台的長期方向</strong>：S3 + Glue + Athena + Step Functions 的成本曲線比 EMR cluster 平穩、沒事件時近乎 0、有事件時自動擴。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>遷移時程 4 個月 = 計畫密度極高</strong>：15 年資產 4 個月遷完不是常態、需要先把 <em>資料模型</em> canonical 化、再 batch 平行遷。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的 schema 對映先行。</li>
</ol>
<p>跨平台等效：GCP BigQuery + Dataflow + Cloud Storage + Pub/Sub 是對等 stack；Azure Synapse + Data Lake + Event Hubs；自建 Delta Lake + Spark + Kafka 都可以實作對等架構。差異是 vendor 整合度跟 serverless 透明度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃多事件 ticketing 平台 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想看單一 flash-sale 設計 → <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> + <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</a></li>
<li>想做跨國合規容量規劃 → <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> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想做大規模 migration → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> + <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify migration</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/business-intelligence/how-bookmyshow-saved-80-in-costs-by-migrating-to-an-aws-modern-data-architecture/">How BookMyShow saved 80% in costs by migrating to an AWS modern data architecture</a></li>
<li><a href="https://aws.amazon.com/architecture/analytics-big-data/">AWS Modern Data Architecture</a></li>
</ul>
]]></content:encoded></item><item><title>9.C18 Zoom：COVID 期間從 1000 萬到 3 億 DAU 的 30 倍突發</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zoom-covid-surge-dynamodb/</guid><description>&lt;p>這個案例的核心責任是說明「SaaS 類 surge」跟 &lt;a href="https://tarrragon.github.io/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 Pokemon GO&lt;/a> 的「product surge」差異。Zoom 的 30 倍成長不是「產品爆紅」、是「外部事件（COVID）逼全世界改變工作模式」、突發是 &lt;em>結構性&lt;/em> 的、不是回歸均值的暫時現象。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Zoom 在 2020 年 COVID 期間的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>日活參與者&lt;/td>
 &lt;td>1000 萬 → 3 億（2020 年 3 月）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成長倍數&lt;/td>
 &lt;td>30x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主資料層&lt;/td>
 &lt;td>Amazon DynamoDB（會議 metadata）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴容描述&lt;/td>
 &lt;td>「nearly infinitely with no performance issues」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「On the backend, they were able to manage this surge with Amazon DynamoDB for Zoom Meetings.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Zoom surge 揭露三個 SaaS 突發成長的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>SaaS surge 是結構性、不是暫時性&lt;/strong>：Pokemon GO 上線爆紅後流量會隨熱度消退、Zoom COVID 成長是「永久 baseline 上移」。容量規劃不能假設「過幾個月會回來」、必須假設「3 億 DAU 是新常態」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的長期 baseline 重新校準。&lt;/li>
&lt;li>&lt;strong>DynamoDB 「無限擴容」對 SaaS 元資料層特別適用&lt;/strong>：Zoom 會議 metadata（room ID、participant list、permission state）是典型 KV 工作負載、partition key（meeting_id）天然均勻、不會 hot partition。對應 &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 同樣的 partition 均勻優勢。&lt;/li>
&lt;li>&lt;strong>媒體串流不在 DynamoDB&lt;/strong>：Zoom 的影音流量是 P2P + edge servers、不經 DynamoDB。DynamoDB 只承擔「control plane」、不承擔「data plane」。這個分離是擴 30 倍的前提 — 控制面跟資料面解耦、控制面用 managed 服務、資料面用專屬基礎設施。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的關鍵路徑切分。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「nearly infinitely」是行銷敘述、不是工程承諾。實務上 Zoom 在 COVID 初期確實遇到 outage 與性能問題、後續才穩定。讀案例時要看 &lt;em>最終狀態&lt;/em> 跟 &lt;em>過程中的 incident&lt;/em>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>控制面跟資料面分離&lt;/strong>：高頻 metadata 操作放 managed KV（DynamoDB / Cosmos DB / Firestore）、大資料量串流放專屬基礎設施（CDN / WebRTC / 自管 servers）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>。&lt;/li>
&lt;li>&lt;strong>surge 後重新校準 SLO baseline&lt;/strong>：30x 成長之後、SLO 的「正常範圍」要更新、否則 monitoring 會誤報。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 SLO 演進。&lt;/li>
&lt;li>&lt;strong>長期 surge 觸發架構重新評估&lt;/strong>：DynamoDB 是「擴大量」的好選擇、但成本也跟著放大。當 baseline 從 1000 萬永久升到 3 億、原本的 on-demand 模式可能變得貴、要考慮 provisioned + auto-scaling 組合。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：Google Meet 也用 Spanner / Firestore、Microsoft Teams 用 Cosmos DB — 三家視訊會議都靠 managed KV 撐 metadata、是同一個架構模式的不同 vendor 實作。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「SaaS 類 surge」跟 <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 Pokemon GO</a> 的「product surge」差異。Zoom 的 30 倍成長不是「產品爆紅」、是「外部事件（COVID）逼全世界改變工作模式」、突發是 <em>結構性</em> 的、不是回歸均值的暫時現象。</p>
<h2 id="觀察">觀察</h2>
<p>Zoom 在 2020 年 COVID 期間的關鍵敘述（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>日活參與者</td>
          <td>1000 萬 → 3 億（2020 年 3 月）</td>
      </tr>
      <tr>
          <td>成長倍數</td>
          <td>30x</td>
      </tr>
      <tr>
          <td>主資料層</td>
          <td>Amazon DynamoDB（會議 metadata）</td>
      </tr>
      <tr>
          <td>擴容描述</td>
          <td>「nearly infinitely with no performance issues」</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「On the backend, they were able to manage this surge with Amazon DynamoDB for Zoom Meetings.」</p>
<h2 id="判讀">判讀</h2>
<p>Zoom surge 揭露三個 SaaS 突發成長的工程重點。</p>
<ol>
<li><strong>SaaS surge 是結構性、不是暫時性</strong>：Pokemon GO 上線爆紅後流量會隨熱度消退、Zoom COVID 成長是「永久 baseline 上移」。容量規劃不能假設「過幾個月會回來」、必須假設「3 億 DAU 是新常態」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的長期 baseline 重新校準。</li>
<li><strong>DynamoDB 「無限擴容」對 SaaS 元資料層特別適用</strong>：Zoom 會議 metadata（room ID、participant list、permission state）是典型 KV 工作負載、partition key（meeting_id）天然均勻、不會 hot partition。對應 <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> 同樣的 partition 均勻優勢。</li>
<li><strong>媒體串流不在 DynamoDB</strong>：Zoom 的影音流量是 P2P + edge servers、不經 DynamoDB。DynamoDB 只承擔「control plane」、不承擔「data plane」。這個分離是擴 30 倍的前提 — 控制面跟資料面解耦、控制面用 managed 服務、資料面用專屬基礎設施。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的關鍵路徑切分。</li>
</ol>
<p>需要警惕：「nearly infinitely」是行銷敘述、不是工程承諾。實務上 Zoom 在 COVID 初期確實遇到 outage 與性能問題、後續才穩定。讀案例時要看 <em>最終狀態</em> 跟 <em>過程中的 incident</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>控制面跟資料面分離</strong>：高頻 metadata 操作放 managed KV（DynamoDB / Cosmos DB / Firestore）、大資料量串流放專屬基礎設施（CDN / WebRTC / 自管 servers）。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>。</li>
<li><strong>surge 後重新校準 SLO baseline</strong>：30x 成長之後、SLO 的「正常範圍」要更新、否則 monitoring 會誤報。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 SLO 演進。</li>
<li><strong>長期 surge 觸發架構重新評估</strong>：DynamoDB 是「擴大量」的好選擇、但成本也跟著放大。當 baseline 從 1000 萬永久升到 3 億、原本的 on-demand 模式可能變得貴、要考慮 provisioned + auto-scaling 組合。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
</ol>
<p>跨平台等效：Google Meet 也用 Spanner / Firestore、Microsoft Teams 用 Cosmos DB — 三家視訊會議都靠 managed KV 撐 metadata、是同一個架構模式的不同 vendor 實作。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照 product surge → <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 Pokemon GO</a></li>
<li>想理解 control plane vs data plane → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
<li>想規劃 surge 後的 SLO → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.16 SLI / SLO 訊號</a></li>
<li>想評估 surge 下的 on-demand vs provisioned 切換 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
<li>想避免 surge 觸發 hot partition → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/innovators/zoom/">Zoom Video Communications on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB + EKS 上的遊戲後端</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/</guid><description>&lt;p>這個案例的核心責任是說明「遊戲後端 KV」跟「廣告 KV」「電商 KV」的業務語意差異。遊戲後端的 KV 工作負載特性是：玩家狀態（角色、裝備、戰績）必須次秒讀寫、跨 region 同步、防作弊 — 這層需求跟 &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的「廣告量測」或 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth&lt;/a> 的「AR 玩家位置」都不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Capcom 在 AWS 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/capcom/">Capcom Case Study&lt;/a> 與 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>遊戲 IP&lt;/td>
 &lt;td>Resident Evil、Street Fighter、Monster Hunter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>後端請求量&lt;/td>
 &lt;td>billions of requests&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>響應時間&lt;/td>
 &lt;td>single-digit millisecond&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>營運成本下降&lt;/td>
 &lt;td>30%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Amazon DynamoDB + Amazon EKS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工程資源再配置&lt;/td>
 &lt;td>從 DB 運維轉到遊戲品質與開發週期&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「Capcom uses Amazon DynamoDB to meet this demand with single-digit millisecond response times」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Capcom 案例揭露三個遊戲後端 KV 的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>遊戲後端 KV = 跨遊戲共用基礎設施&lt;/strong>：Resident Evil / Street Fighter / Monster Hunter 是不同類型遊戲（單機+多人 / 對戰 / 合作打怪）、卻共用 &lt;em>同一套後端 KV&lt;/em>。這個共用降低了單一遊戲的維運成本、也讓新遊戲上線時不用重做基礎設施。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 multi-tenant platform。&lt;/li>
&lt;li>&lt;strong>single-digit ms response time = 玩家體感「即時」的底線&lt;/strong>：戰鬥動作、技能釋放、玩家對戰都要次秒級反應、超過 10ms 就「卡」。這個延遲門檻反推 Capcom 必須用 sub-region cache（ElastiCache / 本地 game server）+ DynamoDB DAX、不能單靠 DynamoDB。對應 &lt;a href="https://tarrragon.github.io/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 &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase&lt;/a> 的延遲反推。&lt;/li>
&lt;li>&lt;strong>「工程資源從 DB 運維轉到遊戲品質」是 managed 服務的真實價值&lt;/strong>：Capcom 不是 IT 公司、是遊戲公司。把 DBA 時間從「Postgres patching、replication 設定、backup 排程」釋放到「遊戲機制設計、玩家行為分析」、才是 30% 成本下降的本質。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的人力成本工程化。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「billions of requests」沒指明時間單位（每秒、每天、每月）。讀案例時要找具體單位、不要直接套用到自家。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>遊戲後端 KV 用 DynamoDB / Cosmos DB / Bigtable&lt;/strong>：partition key 用 player_id 天然均勻、不會 hot partition。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema 設計。&lt;/li>
&lt;li>&lt;strong>EKS 跑 game server、不直接連 DynamoDB&lt;/strong>：game server 處理遊戲邏輯（戰鬥、配對、防作弊）、DynamoDB 處理持久狀態。中間用 DAX 或 ElastiCache 減少 DynamoDB 呼叫。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>。&lt;/li>
&lt;li>&lt;strong>多 IP / 多遊戲共用平台是降本核心&lt;/strong>：每個新遊戲不重做基礎設施、共用同一套 DynamoDB + EKS。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games&lt;/a> 的「single-tenant per game」對照 — 不同 IP 公司有不同取捨。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Bigtable + GKE + Memorystore、Azure Cosmos DB + AKS + Cache for Redis 都可實作對等架構。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「遊戲後端 KV」跟「廣告 KV」「電商 KV」的業務語意差異。遊戲後端的 KV 工作負載特性是：玩家狀態（角色、裝備、戰績）必須次秒讀寫、跨 region 同步、防作弊 — 這層需求跟 <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> 的「廣告量測」或 <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> 的「AR 玩家位置」都不同。</p>
<h2 id="觀察">觀察</h2>
<p>Capcom 在 AWS 的關鍵敘述（引自 <a href="https://aws.amazon.com/solutions/case-studies/capcom/">Capcom Case Study</a> 與 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>遊戲 IP</td>
          <td>Resident Evil、Street Fighter、Monster Hunter</td>
      </tr>
      <tr>
          <td>後端請求量</td>
          <td>billions of requests</td>
      </tr>
      <tr>
          <td>響應時間</td>
          <td>single-digit millisecond</td>
      </tr>
      <tr>
          <td>營運成本下降</td>
          <td>30%</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Amazon DynamoDB + Amazon EKS</td>
      </tr>
      <tr>
          <td>工程資源再配置</td>
          <td>從 DB 運維轉到遊戲品質與開發週期</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「Capcom uses Amazon DynamoDB to meet this demand with single-digit millisecond response times」。</p>
<h2 id="判讀">判讀</h2>
<p>Capcom 案例揭露三個遊戲後端 KV 的工程重點。</p>
<ol>
<li><strong>遊戲後端 KV = 跨遊戲共用基礎設施</strong>：Resident Evil / Street Fighter / Monster Hunter 是不同類型遊戲（單機+多人 / 對戰 / 合作打怪）、卻共用 <em>同一套後端 KV</em>。這個共用降低了單一遊戲的維運成本、也讓新遊戲上線時不用重做基礎設施。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 multi-tenant platform。</li>
<li><strong>single-digit ms response time = 玩家體感「即時」的底線</strong>：戰鬥動作、技能釋放、玩家對戰都要次秒級反應、超過 10ms 就「卡」。這個延遲門檻反推 Capcom 必須用 sub-region cache（ElastiCache / 本地 game server）+ DynamoDB DAX、不能單靠 DynamoDB。對應 <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</a> 的延遲反推。</li>
<li><strong>「工程資源從 DB 運維轉到遊戲品質」是 managed 服務的真實價值</strong>：Capcom 不是 IT 公司、是遊戲公司。把 DBA 時間從「Postgres patching、replication 設定、backup 排程」釋放到「遊戲機制設計、玩家行為分析」、才是 30% 成本下降的本質。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本工程化。</li>
</ol>
<p>需要警惕：「billions of requests」沒指明時間單位（每秒、每天、每月）。讀案例時要找具體單位、不要直接套用到自家。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>遊戲後端 KV 用 DynamoDB / Cosmos DB / Bigtable</strong>：partition key 用 player_id 天然均勻、不會 hot partition。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema 設計。</li>
<li><strong>EKS 跑 game server、不直接連 DynamoDB</strong>：game server 處理遊戲邏輯（戰鬥、配對、防作弊）、DynamoDB 處理持久狀態。中間用 DAX 或 ElastiCache 減少 DynamoDB 呼叫。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>。</li>
<li><strong>多 IP / 多遊戲共用平台是降本核心</strong>：每個新遊戲不重做基礎設施、共用同一套 DynamoDB + EKS。跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a> 的「single-tenant per game」對照 — 不同 IP 公司有不同取捨。</li>
</ol>
<p>跨平台等效：GCP Bigtable + GKE + Memorystore、Azure Cosmos DB + AKS + Cache for Redis 都可實作對等架構。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他遊戲後端 → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS</a>（cluster 隔離 vs 共用）</li>
<li>想設計遊戲 KV → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <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></li>
<li>想理解 sub-ms latency 反推 → <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</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a></li>
<li>想規劃遊戲 KV access pattern 與 single-table design → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a></li>
<li>想評估遊戲流量的 on-demand vs provisioned → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/capcom/">CAPCOM Case Study</a></li>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/big-data/powering-gaming-applications-with-amazon-dynamodb/">Powering Gaming Applications with Amazon DynamoDB</a></li>
</ul>
]]></content:encoded></item><item><title>Akamas</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/akamas/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/akamas/</guid><description>&lt;p>Akamas 的核心責任是把 workload、SLO constraint、runtime configuration 與雲端成本放進同一個最佳化迴圈。它適合 Kubernetes、VM、database、runtime 與雲端資源調校，重點在用實驗與約束條件產生 rightsizing、configuration tuning 與 capacity efficiency 建議。&lt;/p>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>Akamas 適合已經有可量測 workload 與成本壓力的服務。當團隊能說清楚 request rate、latency SLO、error budget、CPU / memory headroom、replica policy 與雲端費用目標，Akamas 可以把這些條件轉成 optimization objective，找出更好的配置組合。&lt;/p>
&lt;p>這個定位讓 Akamas 接到三個主章。它從 &lt;a href="https://tarrragon.github.io/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 容量規劃模型&lt;/a> 接收 headroom 與 growth curve，從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 接收 cost per request 與 cost curve，從 &lt;a href="https://tarrragon.github.io/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 Performance Improvement Loop&lt;/a> 接收 test、profile、fix、re-test 的閉環。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Akamas 的核心定位是 &lt;em>AI-driven autonomous optimization&lt;/em>、不是 monitoring、不是 cost reporting、也不是手動 rightsizing 工具。它用 ML 在 &lt;em>parameter space&lt;/em> 中找出可同時降 cost 並達到 SLO 的配置組合、目標是把 &lt;em>效能調校&lt;/em> 從 expert-driven 手工活、轉成可重跑的工程實驗。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth&lt;/a> 這類 FinOps cost tool 的差異是 &lt;em>動作面&lt;/em>。FinOps tool 看到 &lt;em>cost 已經發生&lt;/em>、把帳單拆 tag、推薦保留方案；Akamas 看 workload 在 SLO 邊界下能不能跑得更便宜、輸出的是 &lt;em>configuration change&lt;/em>、不是 invoice 切片。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">Datadog APM&lt;/a> / Prometheus 這類 observability stack 的差異是 &lt;em>決策面&lt;/em>。APM 告訴你 &lt;em>哪裡慢、哪個 endpoint p99 飆&lt;/em>；Akamas 接 APM / metrics 訊號當輸入、輸出 &lt;em>該怎麼改 JVM heap、HPA target、connection pool&lt;/em> 的 recommendation。Observability 是 &lt;em>看&lt;/em>、Akamas 是 &lt;em>動&lt;/em>。&lt;/p>
&lt;p>跟手動 tuning（SRE 拍腦袋、grid search、A/B configuration test）的差異是 &lt;em>參數空間規模&lt;/em>。Manual tuning 在 3-5 個參數還可控；JVM + container limit + HPA + DB pool + node packing 同時轉動時、組合爆炸、ML-driven search 才能在合理 budget 內收斂。&lt;/p></description><content:encoded><![CDATA[<p>Akamas 的核心責任是把 workload、SLO constraint、runtime configuration 與雲端成本放進同一個最佳化迴圈。它適合 Kubernetes、VM、database、runtime 與雲端資源調校，重點在用實驗與約束條件產生 rightsizing、configuration tuning 與 capacity efficiency 建議。</p>
<h2 id="定位">定位</h2>
<p>Akamas 適合已經有可量測 workload 與成本壓力的服務。當團隊能說清楚 request rate、latency SLO、error budget、CPU / memory headroom、replica policy 與雲端費用目標，Akamas 可以把這些條件轉成 optimization objective，找出更好的配置組合。</p>
<p>這個定位讓 Akamas 接到三個主章。它從 <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> 接收 headroom 與 growth curve，從 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 接收 cost per request 與 cost curve，從 <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 Performance Improvement Loop</a> 接收 test、profile、fix、re-test 的閉環。</p>
<h2 id="服務定位">服務定位</h2>
<p>Akamas 的核心定位是 <em>AI-driven autonomous optimization</em>、不是 monitoring、不是 cost reporting、也不是手動 rightsizing 工具。它用 ML 在 <em>parameter space</em> 中找出可同時降 cost 並達到 SLO 的配置組合、目標是把 <em>效能調校</em> 從 expert-driven 手工活、轉成可重跑的工程實驗。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage</a> / <a href="/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth</a> 這類 FinOps cost tool 的差異是 <em>動作面</em>。FinOps tool 看到 <em>cost 已經發生</em>、把帳單拆 tag、推薦保留方案；Akamas 看 workload 在 SLO 邊界下能不能跑得更便宜、輸出的是 <em>configuration change</em>、不是 invoice 切片。</p>
<p>跟 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">Datadog APM</a> / Prometheus 這類 observability stack 的差異是 <em>決策面</em>。APM 告訴你 <em>哪裡慢、哪個 endpoint p99 飆</em>；Akamas 接 APM / metrics 訊號當輸入、輸出 <em>該怎麼改 JVM heap、HPA target、connection pool</em> 的 recommendation。Observability 是 <em>看</em>、Akamas 是 <em>動</em>。</p>
<p>跟手動 tuning（SRE 拍腦袋、grid search、A/B configuration test）的差異是 <em>參數空間規模</em>。Manual tuning 在 3-5 個參數還可控；JVM + container limit + HPA + DB pool + node packing 同時轉動時、組合爆炸、ML-driven search 才能在合理 budget 內收斂。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Akamas optimization study 是否健康、最少看四件事：</p>
<ul>
<li><strong>Agent / collector 部署完整度</strong>：哪些 target（JVM / container / K8s / DB）裝了 Akamas agent 或接到 metrics source、metrics window 是否涵蓋 representative peak、是否漏 tail latency 與 GC pause</li>
<li><strong>Target system 邊界定義</strong>：optimization 是針對單一 service / 一組 microservice / 整個 K8s cluster、tunable parameter list 是否經 service owner 審核、不在 list 內的參數是否會被間接影響</li>
<li><strong>Optimization goal 對得上 business outcome</strong>：goal 是「降 cost 30%」還是「同 SLO 下 cost minimize」、是否同時聲明 latency / error budget / throughput 的下界、避免 ML 為達 cost target 把 latency 推到邊緣</li>
<li><strong>Safety bound 緊 / 鬆的取捨</strong>：bound 太緊收斂不到方案、bound 太鬆 production validation 會出事、是否有 staging tenant 跑完再 promote、autopilot 範圍是否限定 non-critical workload</li>
</ul>
<p>四項任一缺、就是 <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 Performance Improvement Loop</a> 邊界的待補項目、不是 Akamas 設定問題。</p>
<h2 id="適用場景">適用場景</h2>
<p>Kubernetes rightsizing 是 Akamas 的主要入口。多服務平台常見問題是 requests / limits、HPA target、replica floor、node pool 與 runtime 參數互相牽動；Akamas 的價值是把這些參數放進同一個優化空間，而非逐項手動調整。</p>
<p>Runtime 與 database tuning 適合需要穩定 SLO 的服務。JVM heap、Go runtime、PostgreSQL、MongoDB、Elasticsearch 或 Spark workload 會同時受配置、資料形狀與流量尖峰影響；optimization tool 可以用可重跑實驗保留調校證據。</p>
<p>FinOps 與 SRE 協作適合用 Akamas 建立共同語言。FinOps 關心浪費與預算，SRE 關心 latency、error rate 與可靠性；Akamas 類工具把節省幅度、性能風險與回退條件放在同一份 recommendation 裡，降低跨團隊溝通成本。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Akamas 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>優化目標</td>
          <td>把 cost、latency、throughput 與 SLO 一起建模</td>
          <td>明確 business objective 與風險上限</td>
      </tr>
      <tr>
          <td>參數空間</td>
          <td>支援 runtime、container、database 與雲端配置</td>
          <td>服務 owner 對參數語意的審核</td>
      </tr>
      <tr>
          <td>執行模式</td>
          <td>支援 human approval、pipeline 與自動化調校</td>
          <td>rollout guardrail、變更紀錄與回退</td>
      </tr>
      <tr>
          <td>證據保存</td>
          <td>recommendation 可以回寫實驗、約束與預期效益</td>
          <td>production validation 與長期 drift 追蹤</td>
      </tr>
  </tbody>
</table>
<p>優化目標價值來自約束透明。成本降低只有在 latency、availability 與 error budget 邊界內才成立，因此 Akamas 頁面要先問目標函數與 guardrail，再談節省幅度。</p>
<p>參數空間價值來自跨層調校。單看 CPU request 可能會誤判，因為 GC、DB connection、thread pool、replica policy 與 node packing 會一起改變 cost per request。</p>
<p>執行模式價值來自可控自動化。Human-in-the-loop 適合早期導入，pipeline mode 適合 release gate，autopilot 適合 guardrail、rollback 與 owner model 已成熟的環境。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>Akamas 和 Vantage 的主要差異是控制面。Vantage 偏 cost visibility、allocation、forecast 與報表；Akamas 偏把效能約束放進 configuration optimization，適合需要直接調整 capacity 與 runtime 參數的場景。</p>
<p>Akamas 和 CloudHealth 的主要差異是操作層級。CloudHealth 偏 enterprise FinOps governance、policy、showback / chargeback 與多雲管理；Akamas 偏 service-level optimization 與工程調校閉環。</p>
<p>Akamas 和 AWS Cost Explorer 的主要差異是範圍與自動化。Cost Explorer 是 AWS-native 成本分析入口；Akamas 可以把成本訊號跟 workload、SLO 與配置實驗接起來，適合需要跨層優化的服務。</p>
<h2 id="操作成本">操作成本</h2>
<p>Akamas 的主要成本是 optimization model 建立。團隊要定義目標、約束、可調參數、測試窗口、流量代表性與成功門檻，並讓 service owner 審核每個 recommendation 的業務風險。</p>
<p>導入成本會隨自動化程度上升。早期可以用 approval workflow 接 recommendation；進入 pipeline 或 autopilot 後，要補 change window、deploy marker、rollback、SLO guardrail、audit log 與 incident handoff。</p>
<p>資料品質會直接影響結果可信度。Metric 延遲、缺少 tail latency、成本 tag 錯誤、workload window 偏差或測試環境差異，都會讓 recommendation 的 confidence 下降。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Akamas 結果應回寫到 optimization evidence package。最小欄位包括 optimization goal、constraint、tunable parameters、workload window、baseline cost、baseline performance、recommended configuration、expected saving、risk note、validation result 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Akamas 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>optimization report、experiment result、recommendation</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>workload sample、test window、production validation</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>APM / metrics / cost dashboard / Akamas report</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>workload representativeness、metric freshness、tag coverage</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>SLO guardrail、repeatability、rollback readiness</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未覆蓋 cohort、未納入下游 quota、測試環境差異</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓成本調校可以被審查。Akamas recommendation 要能回答「節省來自哪個配置變更、哪個 SLO 保護這次變更、哪個訊號觸發回退」。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Akamas（AI optimization）</th>
          <th>FinOps tool（Vantage / CloudHealth）</th>
          <th>APM（Datadog / Prometheus）</th>
          <th>Manual tuning（SRE / 性能工程師）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主要動作</td>
          <td>產出 configuration change recommend</td>
          <td>拆帳單、報表、保留方案推薦</td>
          <td>顯示瓶頸位置與 metric</td>
          <td>拍腦袋 / grid search / A/B test</td>
      </tr>
      <tr>
          <td>決策訊號</td>
          <td>workload + SLO + cost 同模型</td>
          <td>帳單 + tag</td>
          <td>latency / saturation / error metric</td>
          <td>經驗 + ad-hoc benchmark</td>
      </tr>
      <tr>
          <td>適用參數空間</td>
          <td>多參數（JVM + container + HPA + DB）</td>
          <td>N/A（不動參數）</td>
          <td>N/A（不動參數）</td>
          <td>3-5 個參數還可控</td>
      </tr>
      <tr>
          <td>自動化程度</td>
          <td>human approval / pipeline / autopilot</td>
          <td>recommendation + dashboard、不自動執行</td>
          <td>alert + dashboard</td>
          <td>全人工</td>
      </tr>
      <tr>
          <td>風險邊界</td>
          <td>靠 safety bound + staging validation</td>
          <td>低（只動 commitment、不動 runtime）</td>
          <td>低（觀察、不動）</td>
          <td>靠人盯、容易遺漏 cross-parameter</td>
      </tr>
      <tr>
          <td>何時不適用</td>
          <td>參數空間小 / SLO 未明確 / metric 不全</td>
          <td>需要動 runtime 才能省的場景</td>
          <td>不解決「改什麼」、只解決「在哪裡」</td>
          <td>參數爆炸時 ROI 太差</td>
      </tr>
  </tbody>
</table>
<p>選 Akamas 的核心訴求是 <em>參數空間大 + workload 可重跑 + cost 壓力夠高、值得投入 optimization study setup 成本</em>。小規模 / 參數少 / SLO 不明、直接走 manual tuning 更快；只想看帳單拆解、走 FinOps tool；只想知道哪裡慢、走 APM。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Optimization study 的三要素</strong>：goal（目標函數、常見 <code>minimize cost subject to p99 latency &lt; X, error rate &lt; Y</code>）、parameter list（哪些 knob 可動、各自合法區間）、safety bound（哪些 metric 不能越界、越界即 reject candidate）。study setup 是 Akamas 最重的人力投入、value 來自 <em>把隱性調校 know-how 寫成可重跑配置</em>、不是 ML 本身。</p>
<p><strong>Live experiment vs offline study</strong>：offline study 用 staging 環境跑代表性 workload、安全但與 production 流量結構有偏差；live experiment 在 production 上小範圍試 candidate（例如 single canary pod）、訊號真實但需要嚴格 safety bound 與 rollback。多數團隊先 offline 找候選 region、再 live 收斂 — 不要一開始就 production autopilot。</p>
<p><strong>跟 K8s VPA / HPA 互補不互斥</strong>：HPA 處理 <em>replica 數量</em>、VPA 處理 <em>單 pod request / limit</em>、Akamas 處理 <em>參數組合 + 跨層協同</em>（含 JVM heap、HPA target、replica floor、node pool selection）。三者並用時要明確分工 — Akamas 不該跟 VPA 同時調 request，否則彼此推翻；常見作法是 Akamas 設 <em>baseline configuration</em>、VPA / HPA 在 baseline 上做即時微調。</p>
<p><strong>跟 observability stack integration</strong>：Akamas 接 Datadog / Prometheus / New Relic / Dynatrace 取 metrics、接 Kubernetes API 取 workload state、接 cloud billing API 取 cost。integration 品質直接決定 recommendation 信度 — metric 缺 tail latency 或 cost tag 不準、ML 會找到 <em>看起來省、實際出事</em> 的配置。對應 <a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.4 Performance Observability</a> 的訊號治理。</p>
<p><strong>安全邊界 — 不該全 autopilot production</strong>：critical workload（payment / auth / DB primary）即使 SLO bound 寫清楚也不該 autopilot、recommendation 要走 human approval + change window；non-critical workload（batch job / dev cluster / internal tool）autopilot 可接受。ML black-box 是 production safety 的本質風險、不是設定問題。</p>
<p><strong>ML 黑箱可解釋性</strong>：Akamas recommendation 給出 <em>why this configuration</em> 的 sensitivity analysis（哪個參數影響最大、哪個參數對 cost / latency 是 trade-off curve），但根因解釋仍弱於人類性能工程師的 mental model。Production 採用前、service owner 要能用自己的 domain knowledge 對 recommendation 做 sanity check、不是純靠 ML score 拍板。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Optimization goal 對不上 business outcome</strong>：goal 寫「降 cost 30%」但沒寫 latency / error budget 下界 — ML 把 cost 壓到 SLO 邊緣、production 上線就 incident、回頭補 safety bound + business KPI alignment</li>
<li><strong>Safety bound 太鬆 / 太緊</strong>：太鬆 candidate 過 staging 但 production validation 出事、太緊 study 跑不出有意義方案 — bound 應綁 production-observed p99 / error rate baseline + 20% 緩衝、不是拍數字</li>
<li><strong>ML black-box 沒辦法解釋</strong>：service owner 看不懂為何 recommendation 改某個 obscure JVM flag — 跑 sensitivity analysis、不接受 <em>無 domain rationale</em> 的 recommendation、視為 candidate 而非 final</li>
<li><strong>參數空間 leak 到 list 外</strong>：Akamas 改 JVM heap 但間接讓 GC 行為變、撞到沒納入的 thread pool — 補 cross-parameter dependency 到 list、或縮小 study scope</li>
<li><strong>Workload window 不代表 production</strong>：staging 跑 50% 流量、ML 找到的方案在 100% peak hour 出事 — workload sample 必須涵蓋 representative peak、不是平均值</li>
<li><strong>Autopilot 推到 critical service</strong>：non-critical workload 試出甜頭、團隊把 autopilot 推到 payment service、incident 後 rollback 困難 — autopilot 範圍要寫進政策、critical service 永遠 human approval</li>
<li><strong>Recommendation 跟 VPA 互推</strong>：Akamas 設 request = X、VPA 立刻調回 Y、循環 — Akamas baseline 跟 VPA scope 要分層、不要在同一個 dimension 兩個 controller 同時動</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>Akamas 目前在 09 案例庫中適合作為 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 的工具承接點。它可回寫到 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato TiDB → DynamoDB 遷移</a> 的成本下降 50% 取捨、<a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 EKS cluster</a> 的年省 1000 萬美金的 Kubernetes capacity 調校、<a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom 遊戲後端</a> 的營運成本下降 30%、以及 <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> 的需求降低時成本下降 25% 彈性曲線。</p>
<p>這些案例的重點是優化條件。Akamas 頁引用案例時，應把「某公司節省成本」轉成 workload window、SLO constraint、調整參數、驗證方式與回退條件 — 例如 Zomato 的 4x throughput / 90% latency 改善是同時優化目標、不是只看成本欄位。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<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></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a></li>
<li>上游：<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 Performance Improvement Loop</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage</a></li>
<li>官方：<a href="https://docs.akamas.io/akamas-docs/getting-started/introduction-to-akamas">Akamas documentation</a></li>
</ul>
]]></content:encoded></item><item><title>GoReplay</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/goreplay/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/goreplay/</guid><description>&lt;p>GoReplay 的核心責任是捕捉 production HTTP traffic，並把真實請求形狀重播到 staging、shadow environment 或新版本。它適合驗證 synthetic load 難以建模的 endpoint mix、header、payload size、burst pattern 與 long-tail 行為，重點在把 production reality 轉成可控 replay artifact。&lt;/p>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>GoReplay 適合在 synthetic workload 可信度偏低時使用。當 &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> 很難準確描述使用者路徑、payload 分布或 endpoint mix，GoReplay 可以從 production traffic 擷取真實樣本，再用 rate limit、filter、rewrite 與 output target 控制重播範圍。&lt;/p>
&lt;p>這個定位讓 GoReplay 接到 &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> 的 shadow traffic。它的價值在於保留 production 請求形狀；它的風險在於 PII、credential、side effect、下游容量與 capture host overhead 都要被治理。&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> / &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> 的 synthetic load 設計 mindset 完全不同。Scripted load 假設 &lt;em>測試者能描述使用者行為&lt;/em> — 寫 script、設 rate、跑 scenario；GoReplay 假設 &lt;em>production 才是 source of truth&lt;/em> — endpoint mix、header 分布、payload size、burst pattern 都從真實 traffic 抽樣、不靠人為建模。對 long-tail 行為（少見 endpoint、巨大 payload、特殊 header 組合）這個差異決定了 capacity 規劃的真實度。&lt;/p>
&lt;h2 id="最短判讀路徑">最短判讀路徑&lt;/h2>
&lt;p>判斷 GoReplay deployment 是否健康、最少看四件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Capture mode&lt;/strong>：用 &lt;code>raw&lt;/code> (libpcap-based)、&lt;code>pcap-file&lt;/code>（離線 replay 已存檔的 pcap）、&lt;code>file&lt;/code>（GoReplay 原生 gor format）哪一種？raw 對 production host 有 CPU / network overhead、pcap-file 適合事後 replay、file 適合 long-running capture buffer&lt;/li>
&lt;li>&lt;strong>Replay target&lt;/strong>：打到 staging full-stack、shadow service、還是 isolated sandbox？POST / PUT / DELETE 是否導到 dry-run path 或 idempotent mock？webhook / payment / notification 是否被攔截？&lt;/li>
&lt;li>&lt;strong>Rate adjustment&lt;/strong>：用原始 production rate replay，還是 2x / 10x / 0.1x？capacity 規劃通常需要 &lt;em>speed up&lt;/em> 來測未來流量、debug 通常需要 &lt;em>slow down&lt;/em> 跟單一請求追查&lt;/li>
&lt;li>&lt;strong>Middleware filter&lt;/strong>：PII / token / cookie / credential redaction 在哪一段做（capture 前、capture 後、replay 前）？是否走 GoReplay middleware binary（stdin / stdout pipeline）統一處理&lt;/li>
&lt;/ul>
&lt;h2 id="適用場景">適用場景&lt;/h2>
&lt;p>架構遷移驗證適合 GoReplay。DB、cache、search、API gateway 或 framework 重寫時，可以把真實 HTTP traffic replay 到新路徑，觀察 latency、error、resource saturation 與 response diff。&lt;/p></description><content:encoded><![CDATA[<p>GoReplay 的核心責任是捕捉 production HTTP traffic，並把真實請求形狀重播到 staging、shadow environment 或新版本。它適合驗證 synthetic load 難以建模的 endpoint mix、header、payload size、burst pattern 與 long-tail 行為，重點在把 production reality 轉成可控 replay artifact。</p>
<h2 id="定位">定位</h2>
<p>GoReplay 適合在 synthetic workload 可信度偏低時使用。當 <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> 很難準確描述使用者路徑、payload 分布或 endpoint mix，GoReplay 可以從 production traffic 擷取真實樣本，再用 rate limit、filter、rewrite 與 output target 控制重播範圍。</p>
<p>這個定位讓 GoReplay 接到 <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> 的 shadow traffic。它的價值在於保留 production 請求形狀；它的風險在於 PII、credential、side effect、下游容量與 capture host overhead 都要被治理。</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> / <a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a> 的 synthetic load 設計 mindset 完全不同。Scripted load 假設 <em>測試者能描述使用者行為</em> — 寫 script、設 rate、跑 scenario；GoReplay 假設 <em>production 才是 source of truth</em> — endpoint mix、header 分布、payload size、burst pattern 都從真實 traffic 抽樣、不靠人為建模。對 long-tail 行為（少見 endpoint、巨大 payload、特殊 header 組合）這個差異決定了 capacity 規劃的真實度。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 GoReplay deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Capture mode</strong>：用 <code>raw</code> (libpcap-based)、<code>pcap-file</code>（離線 replay 已存檔的 pcap）、<code>file</code>（GoReplay 原生 gor format）哪一種？raw 對 production host 有 CPU / network overhead、pcap-file 適合事後 replay、file 適合 long-running capture buffer</li>
<li><strong>Replay target</strong>：打到 staging full-stack、shadow service、還是 isolated sandbox？POST / PUT / DELETE 是否導到 dry-run path 或 idempotent mock？webhook / payment / notification 是否被攔截？</li>
<li><strong>Rate adjustment</strong>：用原始 production rate replay，還是 2x / 10x / 0.1x？capacity 規劃通常需要 <em>speed up</em> 來測未來流量、debug 通常需要 <em>slow down</em> 跟單一請求追查</li>
<li><strong>Middleware filter</strong>：PII / token / cookie / credential redaction 在哪一段做（capture 前、capture 後、replay 前）？是否走 GoReplay middleware binary（stdin / stdout pipeline）統一處理</li>
</ul>
<h2 id="適用場景">適用場景</h2>
<p>架構遷移驗證適合 GoReplay。DB、cache、search、API gateway 或 framework 重寫時，可以把真實 HTTP traffic replay 到新路徑，觀察 latency、error、resource saturation 與 response diff。</p>
<p>Long-tail workload 校正適合 GoReplay。Synthetic scenario 通常覆蓋主路徑，GoReplay 可以揭露少見 endpoint、特殊 header、巨大 payload、冷門 tenant 與尖峰 cohort。</p>
<p>事故後修補驗證適合 GoReplay。若事故由特定請求形狀觸發，capture sample 可以在修補環境重播，確認 latency、error 或 resource usage 是否回到可接受範圍。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>GoReplay 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>真實 traffic</td>
          <td>endpoint mix、payload、header 分布接近 production</td>
          <td>PII / credential 遮罩與權限治理</td>
      </tr>
      <tr>
          <td>HTTP replay</td>
          <td>對 HTTP API 路徑直接有效</td>
          <td>非 HTTP protocol 與加密流量處理</td>
      </tr>
      <tr>
          <td>Filter / rewrite</td>
          <td>可控制 host、path、header、rate</td>
          <td>side effect 隔離與 sandbox target</td>
      </tr>
      <tr>
          <td>Capture artifact</td>
          <td>可保存樣本做回歸驗證</td>
          <td>retention、存取控制與樣本代表性</td>
      </tr>
  </tbody>
</table>
<p>真實 traffic 價值來自分布保真。它能捕捉 synthetic script 容易漏掉的 query parameter、header、payload size 與 endpoint mix，但 capture sample 也會帶入 production 資料治理責任。</p>
<p>Filter / rewrite 價值來自安全邊界。Replay 前要改寫 target、移除 credential、遮罩 PII、限制 rate，並把寫入類請求導到 sandbox 或 dry-run path。</p>
<h2 id="跟其他方式的取捨">跟其他方式的取捨</h2>
<p>GoReplay 和 k6 / Gatling / Locust 的主要差異是流量來源。GoReplay 取 production sample，保真度高；scripted load test 取人工模型，可控性高。</p>
<p>GoReplay 和 service mesh mirroring 的主要差異是部署位置。GoReplay 在 host / network capture 層工作，適合沒有 mesh 的服務；service mesh mirroring 在 sidecar / proxy 層工作，適合已經落地 mesh 的平台。</p>
<p>GoReplay 和 AWS VPC Traffic Mirroring 的主要差異是應用語意。GoReplay 對 HTTP replay 更直接；VPC Traffic Mirroring 在網路層複製封包，侵入性低但應用層 rewrite、遮罩與 replay 控制需要額外處理。</p>
<h3 id="核心取捨表">核心取捨表</h3>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>GoReplay</th>
          <th>k6 / JMeter (synthetic)</th>
          <th>AWS VPC Traffic Mirroring</th>
          <th>Service Mesh Mirroring</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>流量來源</td>
          <td>Production sniff（real shape）</td>
          <td>Scripted scenario（builder&rsquo;s model）</td>
          <td>VPC 網路層封包複製</td>
          <td>Sidecar / proxy 層複製</td>
      </tr>
      <tr>
          <td>工作層級</td>
          <td>HTTP / L7（capture host）</td>
          <td>HTTP / L7（client-side script）</td>
          <td>L3-L4（packet level）</td>
          <td>L7（sidecar in-mesh）</td>
      </tr>
      <tr>
          <td>Rate adjust</td>
          <td>原生支援（0.1x - 10x）</td>
          <td>scenario 內 ramp / arrival rate</td>
          <td>全量、無 rate control</td>
          <td>mesh policy 控制</td>
      </tr>
      <tr>
          <td>Replay 控制</td>
          <td>filter / rewrite / middleware binary</td>
          <td>程式內 logic 完整可控</td>
          <td>需自寫 application-level rewriter</td>
          <td>mesh-level routing rule</td>
      </tr>
      <tr>
          <td>Long-tail 覆蓋</td>
          <td>強（real distribution）</td>
          <td>弱（取決於 scenario design）</td>
          <td>強（real distribution）但需後處理</td>
          <td>強（in-mesh real traffic）</td>
      </tr>
      <tr>
          <td>PII / 安全成本</td>
          <td>高（middleware 自己寫 redaction）</td>
          <td>低（fixture 由人控制）</td>
          <td>高（packet-level 難語意化遮罩）</td>
          <td>中（mesh policy 可協助）</td>
      </tr>
      <tr>
          <td>部署條件</td>
          <td>host agent + libpcap，需有權限 sniff interface</td>
          <td>無（client / load generator 機台即可）</td>
          <td>AWS-only、ENI mirroring 配額</td>
          <td>已落地 mesh（Istio / Linkerd）</td>
      </tr>
  </tbody>
</table>
<p>選 GoReplay 的核心訴求：<em>HTTP 應用層 replay + production shape 保真 + 沒落地 mesh</em>；若已用 mesh、優先看 mesh 內建 mirroring；若要跨 protocol（gRPC / 自家 binary）GoReplay 開源版受限、需考慮 Pro 版或 mesh 方案。</p>
<h2 id="操作成本">操作成本</h2>
<p>GoReplay 的主要成本是資料安全。Production request 可能包含 token、cookie、PII、payment payload、internal IDs 與 tenant 資料，capture、保存、重播與刪除都要有明確 owner。</p>
<p>Replay 成本來自下游副作用。POST、PUT、DELETE、webhook、email、payment、notification 與 queue publish 都要導到 sandbox、mock 或 idempotent dry-run，避免 replay 造成重複交易或通知。</p>
<p>Capture 成本來自主機資源。高流量服務上的 capture agent 會消耗 CPU、network 與 disk，正式啟用前要先量測 overhead，並設定 sampling、rate limit 與 stop condition。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>GoReplay 結果應回寫到 evidence package。最小欄位包括 capture source、capture time range、filter / rewrite rule、sample size、replay rate、target environment、data masking status、p95 / p99、error rate、resource saturation、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>GoReplay 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>capture command、sample hash、replay command</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>capture start / end、replay start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>APM / metrics / logs / diff 查詢連結</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>sample representativeness、masking status</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>replay rate、target parity、capture coverage</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未捕捉 protocol、資料遮罩限制、sandbox 差異</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓 replay 結論可審查。Reviewer 要能知道樣本來自哪段 production、經過哪些 filter、打到哪個 target，以及哪些 side effect 被 mock 或隔離。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Capture to file（pcap-like artifact）</strong>：用 <code>--output-file</code> 把 capture 寫成 GoReplay 原生 gor file（或讀 pcap）、之後用 <code>--input-file</code> 重複 replay。這個模式讓 <em>capture window</em> 跟 <em>replay run</em> 解耦 — capture 一次，可在不同 staging branch / 不同 rate / 不同 target 重播多次。對 regression 驗證跟「事故當時的 traffic shape」回放特別關鍵、但 file artifact 也成為 PII 儲存物、retention 跟存取控制要跟 production log 同級。</p>
<p><strong>Replay with rate adjustment（10x speed）</strong>：<code>--input-file-replay-speed 10</code>（gor format）或加 <code>--input-file-loop</code> 反覆播放。10x speed 對 capacity headroom 驗證直接有用 — 用真實 traffic shape 模擬「未來流量翻 10 倍」、避開 scripted scenario 自帶的人為偏差。反向用法 0.1x 跟 isolated request replay 適合排錯特定 endpoint 的 long-tail latency。注意 10x 會把下游 DB / cache / external API 同樣放大，sandbox target 容量要先評估。</p>
<p><strong>Middleware filter（PII redaction）</strong>：GoReplay middleware 是獨立 binary、用 stdin / stdout 跟 GoReplay process 串接、可寫任何語言。典型責任：JSON body 解析、Authorization / Cookie / Set-Cookie header strip、Email / phone / card number regex 遮罩、cross-request session ID rewriting（讓 staging 不撞 production session）。middleware 邏輯本身需要 code review、寫進版控、staging 測過再放到 production capture host。</p>
<p><strong>Pro version（GoReplay Pro - binary protocols）</strong>：開源版聚焦 HTTP/1.x；GoReplay Pro 支援 binary protocol（自家 protocol、protobuf-over-TCP、部分 gRPC pattern）跟 enterprise 維護 SLA。判斷點：若服務是純 HTTP REST 開源版夠用、若有 gRPC 或自家 binary 且不在 mesh 內、要評估 Pro 或改走 service mesh mirroring。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Capture loss / sample 不完整</strong>：libpcap 在高流量下會 drop packet、<code>gor stat</code> 的 capture stats 顯示 drop &gt; 1% 就不可信 — 加 capture host CPU、改用 PF_RING / AF_PACKET、或縮 capture filter 範圍（只 capture target port + sampling）</li>
<li><strong>TCP reassembly 失敗 / replay 結果亂碼</strong>：跨 packet 的 HTTP body 沒被正確組裝、常見於 MTU / TCP segment offload 設定異常 — 確認 capture interface 沒開 TSO / GRO、或用 application-level capture（HEC-style sidecar）取代 packet capture</li>
<li><strong>PII / secret 漏 redact 進 staging</strong>：middleware 規則沒覆蓋新加的 header / 新的 body schema — 建立 redaction allowlist（只放行已知 schema）而非 denylist、每次 schema 變更同步更新 middleware、staging 入口加 secret scanner 做 last-mile 攔截</li>
<li><strong>Replay 觸發下游真實副作用</strong>：POST / PUT 沒導 sandbox、webhook 真的打出去、payment 真的扣款 — replay target 預設 <em>deny all write</em>、白名單放行特定 idempotent endpoint、其餘走 mock 或 dry-run flag</li>
<li><strong>Replay rate 拖垮 capture host</strong>：同機 capture + replay、CPU / NIC 互相搶 — capture host 只負責 sniff + write to file、replay 機器獨立、用 gor file 解耦</li>
<li><strong>長時間 capture 寫爆 disk</strong>：未設 rotation 或 size limit — <code>--output-file</code> 加 size / time rotation、定期 archive 到 S3 + 過期刪除</li>
<li><strong>Staging 容量比 production 小、放大流量打爆</strong>：10x replay 沒先估下游 — capacity 規劃前先用 1x 暖機、觀察下游 saturation、再 ramp 到目標倍率</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>GoReplay 適合回寫 migration 與 production validation 案例。它可接 <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> 的 production-shaped load、<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> 的 cutover 前 replay、<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 這類資料庫整併前的 query pattern 驗證、<a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato TiDB → DynamoDB</a> 跨 DB 遷移的請求 pattern 重播，以及 <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365 MongoDB → Cosmos DB</a> 的全球分析平台遷移 query 驗證。</p>
<p>這些案例的重點是 production request shape。GoReplay 頁引用案例時，要把 case 轉成 capture window、filter、rewrite、target isolation、rate limit 與 diff / saturation metric — 例如 Zomato 遷 DB 時、replay 必須先 mask PII + 改寫 SQL 方言、不能直接把 TiDB query 打進 DynamoDB SDK。</p>
<p>Capacity 規劃用 real workload model 是這些案例的共通對照啟示。Tixcraft 的售票 spike、SeatGeek 的 waiting room cutover、Netflix 的 Aurora 整併、Microsoft 365 的全球 query 分布 — 共通點是 <em>scripted scenario 無法事先列舉所有 endpoint 跟 payload 組合</em>。GoReplay 的回應是把「使用者行為建模」這個工作丟回給 production traffic 本身、規劃者只負責決定 capture window、replay rate 跟 target boundary，不再試圖窮舉 scenario。這個 mindset 才是 GoReplay 跟 <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> 在 capacity 規劃流程中的真正分工點。</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/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/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></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/" data-link-title="AWS VPC Traffic Mirroring" data-link-desc="用 VPC 網路層封包鏡像觀察 production traffic 的低侵入 production validation 方式">AWS VPC Traffic Mirroring</a></li>
<li>知識卡：<a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic</a></li>
<li>官方：<a href="https://docs.goreplay.org/">GoReplay documentation</a></li>
</ul>
]]></content:encoded></item><item><title>9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/</guid><description>&lt;p>這個案例的核心責任是提供「同樣業務需求、不同 DB 技術」的具體對照數字。Zomato 帳單系統從 TiDB 遷移到 DynamoDB、留下三個關鍵改善百分比、是 DB 選型決策的少見 &lt;em>可量化&lt;/em> 對照樣本。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Zomato 帳單系統遷移的關鍵數字（引自 &lt;a href="https://aws.amazon.com/blogs/database/unlocking-performance-scalability-and-cost-efficiency-of-zomatos-billing-platform-by-switching-from-tidb-to-dynamodb/">AWS Database Blog&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>TiDB（遷移前）&lt;/th>
 &lt;th>DynamoDB（遷移後）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>微服務吞吐&lt;/td>
 &lt;td>2,000 RPM&lt;/td>
 &lt;td>8,000 RPM（4x）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>延遲降幅&lt;/td>
 &lt;td>baseline&lt;/td>
 &lt;td>-90%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本降幅&lt;/td>
 &lt;td>baseline&lt;/td>
 &lt;td>-50%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>每日事件量&lt;/td>
 &lt;td>10M（共用）&lt;/td>
 &lt;td>10M&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>餐廳合作夥伴&lt;/td>
 &lt;td>350,000+&lt;/td>
 &lt;td>350,000+&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵動機：TiDB 必須為「突發流量峰值」提前 over-provision、付出常態成本；DynamoDB on-demand 模式「pay only for what we use」、避免 over-provisioning。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Zomato 遷移揭露三個 DB 選型決策的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>NewSQL vs NoSQL 的取捨不只是 schema&lt;/strong>：TiDB 提供 SQL 介面跟 ACID、DynamoDB 提供 KV 介面跟最終一致性。Zomato 選 DynamoDB 是判斷「帳單事件本身可以接受 eventually consistent」、用一致性換性能跟成本。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的一致性取捨。&lt;/li>
&lt;li>&lt;strong>TiDB 必須 over-provision 是分散式 SQL 的常態&lt;/strong>：分散式 SQL 為了支援跨節點交易、必須有預留容量、否則峰值會出現 leader election storm 或 follower lag。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner&lt;/a> 的「節點數即容量」是同類取捨、Spanner 也必須預先 scale 節點。&lt;/li>
&lt;li>&lt;strong>2K → 8K RPM 是 4 倍、但延遲降 90% 才是真關鍵&lt;/strong>：吞吐改善可能來自架構優化、延遲改善才是 DB 本質差。從 baseline → 10% 通常代表少了 1-2 個 hop（例如 cross-region replication、coordinator round-trip）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為&lt;/a> 的 Little&amp;rsquo;s Law。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>「成本降 50%」是 &lt;em>當下流量下的對照&lt;/em>。如果未來流量繼續成長、DynamoDB 的 cost-per-request 成長率比 TiDB 自管 cluster 高 — 達到某規模後 TiDB 反而更便宜。讀遷移案例要看「在當下流量下划算」、不等於「永遠划算」。&lt;/li>
&lt;li>「90% 延遲降」可能只是 p50、p99 / p999 改善幅度通常較小。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>DB 遷移前先確認業務一致性需求&lt;/strong>：能接受 eventually consistent 的工作負載適合 KV / NoSQL；必須 strong consistency 的工作負載必須 SQL / NewSQL。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移評估要看「總成本曲線」、不是「當下 snapshot」&lt;/strong>：算未來 12-24 個月在預期流量下的成本對照、不是只算現在。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a>。&lt;/li>
&lt;li>&lt;strong>遷移過程要 dual-write + shadow read 驗證&lt;/strong>：避免新舊系統行為不一致導致業務問題。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence&lt;/a>。&lt;/li>
&lt;li>&lt;strong>on-demand vs provisioned 的選擇與業務流量形狀對應&lt;/strong>：突發流量適合 on-demand、可預測流量適合 provisioned。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的 on-demand 應用。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：MongoDB Atlas → DynamoDB、Cassandra → DynamoDB、PostgreSQL → Aurora、CockroachDB → Spanner 都是常見遷移路徑。每條路徑的取捨類似。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是提供「同樣業務需求、不同 DB 技術」的具體對照數字。Zomato 帳單系統從 TiDB 遷移到 DynamoDB、留下三個關鍵改善百分比、是 DB 選型決策的少見 <em>可量化</em> 對照樣本。</p>
<h2 id="觀察">觀察</h2>
<p>Zomato 帳單系統遷移的關鍵數字（引自 <a href="https://aws.amazon.com/blogs/database/unlocking-performance-scalability-and-cost-efficiency-of-zomatos-billing-platform-by-switching-from-tidb-to-dynamodb/">AWS Database Blog</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>TiDB（遷移前）</th>
          <th>DynamoDB（遷移後）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>微服務吞吐</td>
          <td>2,000 RPM</td>
          <td>8,000 RPM（4x）</td>
      </tr>
      <tr>
          <td>延遲降幅</td>
          <td>baseline</td>
          <td>-90%</td>
      </tr>
      <tr>
          <td>成本降幅</td>
          <td>baseline</td>
          <td>-50%</td>
      </tr>
      <tr>
          <td>每日事件量</td>
          <td>10M（共用）</td>
          <td>10M</td>
      </tr>
      <tr>
          <td>餐廳合作夥伴</td>
          <td>350,000+</td>
          <td>350,000+</td>
      </tr>
  </tbody>
</table>
<p>關鍵動機：TiDB 必須為「突發流量峰值」提前 over-provision、付出常態成本；DynamoDB on-demand 模式「pay only for what we use」、避免 over-provisioning。</p>
<h2 id="判讀">判讀</h2>
<p>Zomato 遷移揭露三個 DB 選型決策的判讀重點。</p>
<ol>
<li><strong>NewSQL vs NoSQL 的取捨不只是 schema</strong>：TiDB 提供 SQL 介面跟 ACID、DynamoDB 提供 KV 介面跟最終一致性。Zomato 選 DynamoDB 是判斷「帳單事件本身可以接受 eventually consistent」、用一致性換性能跟成本。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的一致性取捨。</li>
<li><strong>TiDB 必須 over-provision 是分散式 SQL 的常態</strong>：分散式 SQL 為了支援跨節點交易、必須有預留容量、否則峰值會出現 leader election storm 或 follower lag。這跟 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的「節點數即容量」是同類取捨、Spanner 也必須預先 scale 節點。</li>
<li><strong>2K → 8K RPM 是 4 倍、但延遲降 90% 才是真關鍵</strong>：吞吐改善可能來自架構優化、延遲改善才是 DB 本質差。從 baseline → 10% 通常代表少了 1-2 個 hop（例如 cross-region replication、coordinator round-trip）。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.1 壓測理論與系統行為</a> 的 Little&rsquo;s Law。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「成本降 50%」是 <em>當下流量下的對照</em>。如果未來流量繼續成長、DynamoDB 的 cost-per-request 成長率比 TiDB 自管 cluster 高 — 達到某規模後 TiDB 反而更便宜。讀遷移案例要看「在當下流量下划算」、不等於「永遠划算」。</li>
<li>「90% 延遲降」可能只是 p50、p99 / p999 改善幅度通常較小。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>DB 遷移前先確認業務一致性需求</strong>：能接受 eventually consistent 的工作負載適合 KV / NoSQL；必須 strong consistency 的工作負載必須 SQL / NewSQL。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a>。</li>
<li><strong>遷移評估要看「總成本曲線」、不是「當下 snapshot」</strong>：算未來 12-24 個月在預期流量下的成本對照、不是只算現在。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a>。</li>
<li><strong>遷移過程要 dual-write + shadow read 驗證</strong>：避免新舊系統行為不一致導致業務問題。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a>。</li>
<li><strong>on-demand vs provisioned 的選擇與業務流量形狀對應</strong>：突發流量適合 on-demand、可預測流量適合 provisioned。對應 <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> 的 on-demand 應用。</li>
</ol>
<p>跨平台等效：MongoDB Atlas → DynamoDB、Cassandra → DynamoDB、PostgreSQL → Aurora、CockroachDB → Spanner 都是常見遷移路徑。每條路徑的取捨類似。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想做 DB 遷移評估 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想理解一致性取捨 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> + <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想做總成本評估 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>對照其他 DB 遷移 → <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify Kafka→Pub/Sub</a></li>
<li>想拆 access pattern 對應的 DynamoDB schema → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a> + <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想評估搬遷後的 capacity mode → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/database/unlocking-performance-scalability-and-cost-efficiency-of-zomatos-billing-platform-by-switching-from-tidb-to-dynamodb/">Unlocking performance, scalability, and cost-efficiency of Zomato&rsquo;s Billing Platform by switching from TiDB to DynamoDB</a></li>
<li><a href="https://aws.amazon.com/blogs/opensource/how-zomato-boosted-performance-25-and-cut-compute-cost-30-migrating-trino-and-druid-workloads-to-aws-graviton/">How Zomato Boosted Performance 25% and Cut Compute Cost 30% Migrating Trino and Druid Workloads to AWS Graviton</a></li>
</ul>
]]></content:encoded></item><item><title>Service Mesh Mirroring</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/service-mesh-mirroring/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/service-mesh-mirroring/</guid><description>&lt;p>Service mesh mirroring 的核心責任是在 proxy 層複製 production traffic 到 shadow service，讓新版本接受真實請求形狀，同時把使用者回應留在原本路徑。它適合已經落地 Istio、Linkerd 或類似 mesh 的平台，重點在用 routing policy 控制 mirror ratio、target、隔離與觀測。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay&lt;/a> 比、Service Mesh Mirroring 在 &lt;em>proxy / sidecar&lt;/em> 層、是 K8s mesh-native 的 L7 HTTP request mirror、不需要 application 或 host 端 capture binary；GoReplay 在 &lt;em>application host&lt;/em> 層、適合無 mesh 的環境或要 capture artifact 離線 replay。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/" data-link-title="AWS VPC Traffic Mirroring" data-link-desc="用 VPC 網路層封包鏡像觀察 production traffic 的低侵入 production validation 方式">AWS VPC Traffic Mirroring&lt;/a> 比、Service Mesh Mirroring 在 L7（HTTP route / header / subset 可控）、VPC Traffic Mirroring 在 L3-L4 packet 層、見度更底層但缺 application 語意。三者組合常見於 K8s + 多 cloud 混合環境。&lt;/p>
&lt;h2 id="最短判讀路徑">最短判讀路徑&lt;/h2>
&lt;p>判斷 Service Mesh Mirroring 部署是否健康、最少看四件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Mesh implementation 對齊&lt;/strong>：用哪套 mesh（Istio / Linkerd / Envoy gateway / Consul Connect）、control plane 版本、sidecar injection coverage、跨 namespace policy 邊界是否清楚&lt;/li>
&lt;li>&lt;strong>VirtualService mirror config&lt;/strong>：mirror destination 是否限制在同 namespace / 同 cluster、mirror_percent 是否從 1% 漸進、route / header filter 是否排除 write-heavy 或 PII path&lt;/li>
&lt;li>&lt;strong>Target service capacity&lt;/strong>：shadow target deployment 是否有獨立 HPA、跟 primary 同 node pool 還是隔離、DB / cache / external API 是否導 mock 或 sandbox、不會 share connection pool 造成 primary 飽和&lt;/li>
&lt;li>&lt;strong>Response handling&lt;/strong>：mirrored response 是 fire-and-forget（Istio 預設）還是有 logging、shadow 端是否能辨識 mirrored request（&lt;code>X-Envoy-Internal&lt;/code> / custom header）、side effect（payment / notification / webhook）是否走 dry-run&lt;/li>
&lt;/ul>
&lt;p>四件事任一缺失、就是 &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> shadow traffic 治理的待補項目。&lt;/p></description><content:encoded><![CDATA[<p>Service mesh mirroring 的核心責任是在 proxy 層複製 production traffic 到 shadow service，讓新版本接受真實請求形狀，同時把使用者回應留在原本路徑。它適合已經落地 Istio、Linkerd 或類似 mesh 的平台，重點在用 routing policy 控制 mirror ratio、target、隔離與觀測。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay</a> 比、Service Mesh Mirroring 在 <em>proxy / sidecar</em> 層、是 K8s mesh-native 的 L7 HTTP request mirror、不需要 application 或 host 端 capture binary；GoReplay 在 <em>application host</em> 層、適合無 mesh 的環境或要 capture artifact 離線 replay。跟 <a href="/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/" data-link-title="AWS VPC Traffic Mirroring" data-link-desc="用 VPC 網路層封包鏡像觀察 production traffic 的低侵入 production validation 方式">AWS VPC Traffic Mirroring</a> 比、Service Mesh Mirroring 在 L7（HTTP route / header / subset 可控）、VPC Traffic Mirroring 在 L3-L4 packet 層、見度更底層但缺 application 語意。三者組合常見於 K8s + 多 cloud 混合環境。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Service Mesh Mirroring 部署是否健康、最少看四件事：</p>
<ul>
<li><strong>Mesh implementation 對齊</strong>：用哪套 mesh（Istio / Linkerd / Envoy gateway / Consul Connect）、control plane 版本、sidecar injection coverage、跨 namespace policy 邊界是否清楚</li>
<li><strong>VirtualService mirror config</strong>：mirror destination 是否限制在同 namespace / 同 cluster、mirror_percent 是否從 1% 漸進、route / header filter 是否排除 write-heavy 或 PII path</li>
<li><strong>Target service capacity</strong>：shadow target deployment 是否有獨立 HPA、跟 primary 同 node pool 還是隔離、DB / cache / external API 是否導 mock 或 sandbox、不會 share connection pool 造成 primary 飽和</li>
<li><strong>Response handling</strong>：mirrored response 是 fire-and-forget（Istio 預設）還是有 logging、shadow 端是否能辨識 mirrored request（<code>X-Envoy-Internal</code> / custom header）、side effect（payment / notification / webhook）是否走 dry-run</li>
</ul>
<p>四件事任一缺失、就是 <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> shadow traffic 治理的待補項目。</p>
<h2 id="定位">定位</h2>
<p>Service mesh mirroring 適合平台已經有 proxy control plane 的團隊。當 service-to-service traffic 都經過 sidecar 或 gateway，mirror policy 可以把部分 production request 複製到新版本，不需要在 application code 中加 capture / replay 邏輯。</p>
<p>這個定位讓 service mesh mirroring 接到 <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> 的 shadow traffic 與 canary perf check。它比 host capture 更貼近 service routing，但也依賴 mesh 的觀測、policy、資源隔離與治理能力。</p>
<h2 id="適用場景">適用場景</h2>
<p>新版本 shadow validation 適合 service mesh mirroring。平台可以把 1%、5% 或特定 route 的流量 mirror 到 shadow deployment，觀察新版本 CPU、memory、latency、DB read 與 error。</p>
<p>Service-to-service migration 適合 service mesh mirroring。當下游服務準備換 runtime、framework、DB client 或 cache client，mirror 可以讓新路徑吃到 production upstream pattern。</p>
<p>多 region / 多 version 對照適合 service mesh mirroring。Mesh policy 能按 namespace、host、route、header 或 subset 控制 mirror target，讓平台在小 blast radius 下收集 production-shaped evidence。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Service mesh mirroring 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Proxy 層控制</td>
          <td>mirror policy 不侵入 application code</td>
          <td>mesh control plane 治理與變更審核</td>
      </tr>
      <tr>
          <td>Service routing</td>
          <td>可按 host、route、subset 控制 target</td>
          <td>route 命名、ownership、policy drift</td>
      </tr>
      <tr>
          <td>Mesh observability</td>
          <td>request metric、trace、service graph 可對照</td>
          <td>shadow target 的獨立 dashboard</td>
      </tr>
      <tr>
          <td>漸進比例</td>
          <td>mirror ratio 可逐步放大</td>
          <td>下游容量與 stop condition</td>
      </tr>
  </tbody>
</table>
<p>Proxy 層控制價值來自一致性。當所有 service 都走 mesh，mirror policy 可以用同一套控制面管理，避免每個 application 自行實作 replay。</p>
<p>Mesh observability 價值來自對照能力。Shadow service 的 latency、error、resource saturation 與 dependency call 可以直接跟 primary path 對比，但 dashboard 要清楚標記 mirrored traffic，避免混入正式 SLO。</p>
<h2 id="跟其他方式的取捨">跟其他方式的取捨</h2>
<p>Service mesh mirroring 和 GoReplay 的主要差異是控制面。Service mesh mirroring 依賴既有 proxy / mesh，適合服務間流量；GoReplay 適合 HTTP capture artifact、離線 replay 與沒有 mesh 的環境。</p>
<p>Service mesh mirroring 和 AWS VPC Traffic Mirroring 的主要差異是語意層級。Mesh 在 L7 routing 層，能按 route、host、header 與 subset 控制；VPC mirroring 在網路層，能見度更底層但應用語意控制較少。</p>
<p>Service mesh mirroring 和 canary 的主要差異是使用者影響。Mirrored request 的回應不回給使用者，適合 capacity / correctness observation；canary 會讓真實使用者走新版本，適合最終放量。</p>
<h2 id="操作成本">操作成本</h2>
<p>Service mesh mirroring 的主要成本是下游容量。Shadow traffic 雖然不回應使用者，但仍會消耗 shadow service、DB、cache、third-party mock、queue 與 observability pipeline 的資源。</p>
<p>Policy 成本來自控制面治理。Mirror rule、route、subset、namespace、owner 與 rollout window 都要可審查；錯誤的 mirror policy 可能把過大比例流量導到未準備好的 target。</p>
<p>Side effect 成本來自 application 行為。Shadow service 要能辨識 mirrored request，並把 write、external API call、notification、payment 與 queue publish 導到 sandbox、mock 或 dry-run。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Service mesh mirroring 結果應回寫到 evidence package。最小欄位包括 mesh policy version、source service、route、mirror ratio、target subset、time range、shadow target resource、data / side effect isolation、p95 / p99、error rate、dependency saturation、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Service mesh mirroring 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>mesh policy、route config、deployment version</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>mirror start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>service graph、metrics、trace、logs</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>mirror ratio、route coverage、header filter</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>target parity、dependency isolation</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未 mirror route、side effect mock、mesh overhead</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓 mirror 實驗可關閉。Reviewer 要能看到 mirror policy 何時啟動、何時停止、覆蓋哪些 route、消耗哪些下游資源，以及 shadow target 是否接近 production。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Istio VirtualService mirror / mirror_percent</strong>：Istio 用 <code>VirtualService</code> 的 <code>mirror</code> 欄位指定 shadow destination、<code>mirrorPercentage</code>（v1.7+；舊版 <code>mirror_percent</code>）控制比例。production 操作慣例是從 1% 起步、每 30-60min 觀察 shadow target latency / error / saturation 再放大、達到 100% 後維持一週收 evidence 才 promote。route-level config 比 mesh-wide policy 安全、blast radius 限定在指定 host / path。</p>
<p><strong>Linkerd traffic split</strong>：Linkerd 用 SMI <code>TrafficSplit</code> CRD 或 native <code>HTTPRoute</code> 分流、走 <em>active-active</em> shadow 模式而非 fire-and-forget。Linkerd mirror 預設較輕量、proxy overhead 比 Istio 低、適合資源敏感的 K8s cluster；但 L7 policy 表達力不如 Istio EnvoyFilter。</p>
<p><strong>Envoy MirrorPolicy</strong>：直接寫 Envoy config（不透過 Istio control plane）時、<code>route.RouteAction.request_mirror_policies</code> 是底層 primitive。多 cluster 邊緣 gateway（Contour / Emissary-Ingress / Gloo）都是這層的 abstraction、適合不想引入 full Istio 但要 mirror 能力的場景。</p>
<p><strong>跟 Argo Rollouts canary 整合 — shadow deployment</strong>：Argo Rollouts 的 <code>analysis</code> step 可以接 mesh mirror — <em>shadow stage</em> 先用 mirror 收 evidence、<em>canary stage</em> 才放真實流量。對應 <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> 的「shadow 先於 canary」原則、避免把使用者當小白鼠。</p>
<p><strong>跟 <a href="/blog/backend/04-observability/vendors/datadog/" data-link-title="Datadog" data-link-desc="All-in-one SaaS 觀測平台、APM / Logs / Metrics / RUM / Security">Datadog</a> APM trace correlation</strong>：mirrored request 應該有獨立的 trace tag（<code>env:shadow</code> 或 <code>traffic.mirror:true</code>）、讓 Datadog APM / <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">observability stack</a> 能 filter 出 shadow path 的 p95 / error rate、不混入 primary SLO dashboard。trace propagation header 要保留、否則 distributed trace 斷在 mesh 邊界。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Mirror target capacity 不足 / shadow service OOM</strong>：shadow deployment 沒獨立 HPA、跟 primary 共用 node pool — 拆 node pool、shadow 設獨立 resource request、mirror_percent 從 1% 起步</li>
<li><strong>Mirrored response 漏處理（fire-and-forget 副作用）</strong>：Istio 預設丟棄 mirrored response、shadow 端的 error 沒被 collect — shadow service 自己 emit metric / log、不依賴 mirror response、加 <code>X-Shadow-Request</code> header 讓 shadow 端可辨識並走 dry-run 路徑</li>
<li><strong>PII / sensitive data 進 staging</strong>：mirrored request 帶真實 user token / payment info 打到 staging — header / body filter 走 EnvoyFilter 做 PII redaction、或在 mesh 邊界跑 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">data masking proxy</a> 再 mirror</li>
<li><strong>Side effect 真的發生（payment double charge / notification 真寄）</strong>：shadow service 沒辨識 mirrored request 就走正式邏輯 — 強制 shadow 端用 sandbox credential、external API client 走 mock / dry-run mode、write 改 read-only replica</li>
<li><strong>Mesh control plane 飽和 / mirror policy drift</strong>：mirror rule 散落各 namespace 沒 owner、policy version 不一致 — 走 GitOps（Argo CD / Flux）+ policy as code、定期 audit <code>kubectl get virtualservice -A</code></li>
<li><strong>Cross-cluster mirror blast radius 失控</strong>：mirror destination 指向其他 cluster 導致跨 cluster 流量爆增 — mirror destination 限 same-cluster、跨 cluster 要走獨立的 gateway 並設 quota</li>
<li><strong>Shadow trace 混進 SLO dashboard</strong>：APM 沒分 primary / shadow tag、p95 看起來變差但其實是 shadow 拖累 — trace tag <code>env:shadow</code> 強制、observability dashboard filter</li>
</ul>
<h2 id="何時改走其他服務">何時改走其他服務</h2>
<table>
  <thead>
      <tr>
          <th>需求形狀</th>
          <th>改走</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>無 mesh 環境 / 要 capture artifact 離線重播</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></td>
      </tr>
      <tr>
          <td>L3-L4 packet 層分析（IDS / network forensic）</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/" data-link-title="AWS VPC Traffic Mirroring" data-link-desc="用 VPC 網路層封包鏡像觀察 production traffic 的低侵入 production validation 方式">AWS VPC Traffic Mirroring</a></td>
      </tr>
      <tr>
          <td>合成負載 / load test 而非 production mirror</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> / <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>Production-side 整體治理</td>
          <td><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></td>
      </tr>
  </tbody>
</table>
<h2 id="不在本頁內的主題">不在本頁內的主題</h2>
<ul>
<li>Istio / Linkerd / Envoy 完整 install / 升級 / control plane HA 細節</li>
<li>Service mesh 安全模型（mTLS / SPIFFE / authorization policy）— 屬 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">7 security</a> 邊界</li>
<li>Mesh-level retry / timeout / circuit breaker 等 resilience pattern</li>
<li>Multi-cluster mesh federation（Istio multi-primary、Linkerd multicluster）</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>Service mesh mirroring 適合回寫平台遷移與新版本 shadow validation 案例。它可接 <a href="/blog/backend/05-deployment-platform/cases/miro-managed-eks-migration/" data-link-title="5.C5 Miro：Managed EKS 遷移" data-link-desc="從自維運平台轉向 managed EKS 的組織與技術協同案例。">Miro managed EKS migration</a>、<a href="/blog/backend/05-deployment-platform/cases/tradeshift-self-managed-k8s-to-eks/" data-link-title="5.C1 Tradeshift：self-managed Kubernetes 遷移到 EKS" data-link-desc="零停機平台遷移的分段策略案例。">Tradeshift self-managed K8s to EKS</a>、<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/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 EKS cluster</a> 的 single-tenant per game 跨 cluster 流量 shadow，以及 <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 100+ 微服務</a> 跨服務的 mirror 範圍治理。</p>
<p>這些案例的重點是 routing policy 與 blast radius。Service mesh mirroring 頁引用案例時，要把 case 轉成 route、mirror ratio、target subset、dependency isolation 與 abort condition — 例如 Riot Games 的 single-tenant 模式下、mirror policy 必須限制在 <em>同遊戲</em> cluster 內、不能跨 game 否則 blast radius 失控。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<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/05-deployment-platform/traffic-config-control-plane-boundary/" data-link-title="5.7 Traffic、Config 與 Control Plane Boundary" data-link-desc="說明流量、設定、secret、service discovery 與管理面如何分責任與回退。">5.6 Traffic, Config and Control Plane Boundary</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></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/" data-link-title="AWS VPC Traffic Mirroring" data-link-desc="用 VPC 網路層封包鏡像觀察 production traffic 的低侵入 production validation 方式">AWS VPC Traffic Mirroring</a></li>
<li>知識卡：<a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic</a></li>
</ul>
]]></content:encoded></item><item><title>Vantage</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vantage/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vantage/</guid><description>&lt;p>Vantage 是 &lt;em>modern multi-cloud FinOps SaaS&lt;/em>、2020 年由 Heroku ex-founder 創立。它的核心責任是把雲端帳單轉成工程團隊能追蹤的 cost report、allocation、forecast 與 efficiency metric。它跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth&lt;/a>、Apptio Cloudability、&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/" data-link-title="AWS Cost Explorer" data-link-desc="用 AWS-native 成本與用量分析建立 account、service、tag 與 usage type 的成本判讀入口">AWS Cost Explorer&lt;/a> 同層、但賣點是 &lt;em>developer-friendly UI + 直覺定價 + 多雲 connector 一鍵啟用&lt;/em> — 適合工程團隊自助而非走 FinOps 部門申請的組織。&lt;/p>
&lt;p>它適合多 account、多 provider、Kubernetes 與 shared infrastructure 成本需要分攤到 service、team、namespace、label 或 resource 的組織。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Vantage 的差異在 &lt;em>使用者體驗與切入角度&lt;/em>、指標本身跟同類工具相近。CloudHealth / Apptio 是傳統 enterprise FinOps platform、面向 procurement、CFO、FinOps governance team；Vantage 把入口換成工程團隊 — 報表能直接 share URL、UI 接近 observability dashboard、connector 走 self-service onboarding 而非 SOW + professional service。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth&lt;/a> 比、Vantage &lt;em>淺但快上手&lt;/em>、適合 100 - 1000 人工程組織自助 FinOps；CloudHealth 走 enterprise governance、policy engine、approval workflow 更深、適合 5000+ 員工跨 BU 治理。跟 Apptio Cloudability 比、定位類似 CloudHealth、但 Apptio 把成本接到 TBM（Technology Business Management）frame、適合需要把 IT 成本對到 business service / product P&amp;amp;L 的組織。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/" data-link-title="AWS Cost Explorer" data-link-desc="用 AWS-native 成本與用量分析建立 account、service、tag 與 usage type 的成本判讀入口">AWS Cost Explorer&lt;/a> 比、Cost Explorer 是 AWS-only 入口、免費但只有 AWS、跨 provider / Kubernetes / SaaS spend 看不到；Vantage 把 AWS + GCP + Azure + Snowflake + Databricks + Datadog + Fastly 等串成單一視圖。&lt;/p>
&lt;p>關鍵張力：&lt;em>modern SaaS 速度&lt;/em> ↔ &lt;em>enterprise governance 深度&lt;/em> 是 Vantage 的核心定位 trade-off。要 procurement-grade workflow、approval chain、custom data warehouse export 走 CloudHealth / Apptio；要工程 owner 直接打開 dashboard 看 cost trend、5 分鐘加新 connector 走 Vantage。&lt;/p></description><content:encoded><![CDATA[<p>Vantage 是 <em>modern multi-cloud FinOps SaaS</em>、2020 年由 Heroku ex-founder 創立。它的核心責任是把雲端帳單轉成工程團隊能追蹤的 cost report、allocation、forecast 與 efficiency metric。它跟 <a href="/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth</a>、Apptio Cloudability、<a href="/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/" data-link-title="AWS Cost Explorer" data-link-desc="用 AWS-native 成本與用量分析建立 account、service、tag 與 usage type 的成本判讀入口">AWS Cost Explorer</a> 同層、但賣點是 <em>developer-friendly UI + 直覺定價 + 多雲 connector 一鍵啟用</em> — 適合工程團隊自助而非走 FinOps 部門申請的組織。</p>
<p>它適合多 account、多 provider、Kubernetes 與 shared infrastructure 成本需要分攤到 service、team、namespace、label 或 resource 的組織。</p>
<h2 id="服務定位">服務定位</h2>
<p>Vantage 的差異在 <em>使用者體驗與切入角度</em>、指標本身跟同類工具相近。CloudHealth / Apptio 是傳統 enterprise FinOps platform、面向 procurement、CFO、FinOps governance team；Vantage 把入口換成工程團隊 — 報表能直接 share URL、UI 接近 observability dashboard、connector 走 self-service onboarding 而非 SOW + professional service。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth</a> 比、Vantage <em>淺但快上手</em>、適合 100 - 1000 人工程組織自助 FinOps；CloudHealth 走 enterprise governance、policy engine、approval workflow 更深、適合 5000+ 員工跨 BU 治理。跟 Apptio Cloudability 比、定位類似 CloudHealth、但 Apptio 把成本接到 TBM（Technology Business Management）frame、適合需要把 IT 成本對到 business service / product P&amp;L 的組織。跟 <a href="/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/" data-link-title="AWS Cost Explorer" data-link-desc="用 AWS-native 成本與用量分析建立 account、service、tag 與 usage type 的成本判讀入口">AWS Cost Explorer</a> 比、Cost Explorer 是 AWS-only 入口、免費但只有 AWS、跨 provider / Kubernetes / SaaS spend 看不到；Vantage 把 AWS + GCP + Azure + Snowflake + Databricks + Datadog + Fastly 等串成單一視圖。</p>
<p>關鍵張力：<em>modern SaaS 速度</em> ↔ <em>enterprise governance 深度</em> 是 Vantage 的核心定位 trade-off。要 procurement-grade workflow、approval chain、custom data warehouse export 走 CloudHealth / Apptio；要工程 owner 直接打開 dashboard 看 cost trend、5 分鐘加新 connector 走 Vantage。</p>
<h2 id="定位">定位</h2>
<p>Vantage 適合把 cost attribution 帶進容量規劃流程。當團隊已經能用 workload model 描述流量，下一步要知道每個 workload、namespace、database、cache、region 與 account 對成本曲線的影響，Vantage 可以把雲端費用整理成可查詢、可分組、可預測的報表。</p>
<p>這個定位讓 Vantage 接到三個主章。它從 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 接收 cost per request 與 over-provision waste，從 <a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a> 接收 dashboard 與 ownership 訊號，從 <a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因</a> 接收 tag、label 與 attribution vocabulary。</p>
<h2 id="適用場景">適用場景</h2>
<p>Showback 與 chargeback 是 Vantage 的主要入口。當平台成本散在 shared Kubernetes cluster、managed database、network egress、storage 與 support plan 裡，Cost Reports 可以把費用依 team、service、environment 或 business unit 切開，讓討論從總帳單轉成 owner action。</p>
<p>Kubernetes 成本分析適合用 Vantage 補足平台可見性。Namespace、label、service、pod、CPU、RAM、storage 與 GPU 維度能讓團隊看到 idle cost、resource efficiency 與 rightsizing recommendation，特別適合多租戶平台。</p>
<p>Forecast 與 anomaly review 適合日常成本治理。每月 forecast、cost trend、unexpected spike 與 budget drift 可以接到 engineering review，讓容量調整、release、marketing event 與成本變化在同一個時間軸上被討論。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Vantage 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cost allocation</td>
          <td>依 provider、account、resource、Kubernetes label 分攤</td>
          <td>tag / label policy、owner taxonomy</td>
      </tr>
      <tr>
          <td>Kubernetes 成本</td>
          <td>namespace、service、label 與 pod-level efficiency</td>
          <td>agent rollout、cluster mapping</td>
      </tr>
      <tr>
          <td>Forecast</td>
          <td>成本趨勢與月末預測可接 review 節奏</td>
          <td>事件註記、release marker、業務日曆</td>
      </tr>
      <tr>
          <td>工程入口</td>
          <td>報表可讓 service owner 直接查詢與追蹤</td>
          <td>action workflow、remediation ownership</td>
      </tr>
  </tbody>
</table>
<p>Cost allocation 價值來自 owner 明確。總帳單只能告訴組織花了多少錢；service-level report 才能讓工程團隊知道哪個 workload、region、database 或 network path 改變了成本。</p>
<p>Kubernetes 成本價值來自 shared cluster 拆分。多租戶平台常把多個服務塞進同一組 node pool；Vantage 類工具把 pod lifecycle 與底層基礎設施成本接起來，讓 namespace 或 label 變成成本討論單位。</p>
<p>Forecast 價值來自提前介入。成本 review 如果只看月底結果，容量浪費和異常用量已經發生；forecast 和 anomaly 讓團隊在月中就能調整 resource request、replica、reserved capacity 或 release plan。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Vantage deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Multi-cloud connector coverage</strong>：AWS / GCP / Azure / Snowflake / Datadog / Fastly 等 connector 是否都接上 — 缺一個就有成本盲區、缺了 Snowflake 反而比缺了 AWS 痛（query cost 沒人看）</li>
<li><strong>Cost Report 設計</strong>：是否依 service / team / environment / business unit 切出可 share 的 saved report、URL 是否進 wiki / Slack canonical 位置、誰每週看</li>
<li><strong>Anomaly Detection 設定</strong>：threshold 跟 baseline 是否 tune 過、false positive rate、anomaly 出現後是否有 owner 接、不是只進 email spam</li>
<li><strong>Report sharing 機制</strong>：cost report 是否走 read-only URL share 給工程 owner、不是把每個工程師都拉進 Vantage account；team 是否有 cost retrospective 節奏</li>
</ul>
<p>四件事任一缺失、就是 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 邊界的待補項目。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>Vantage 和 Akamas 的主要差異是決策深度。Vantage 讓團隊看清成本、分攤責任與找出浪費；Akamas 更進一步把 workload constraint 與 configuration tuning 接成 optimization loop。</p>
<p>Vantage 和 CloudHealth 的主要差異是組織重心。Vantage 偏工程團隊可直接使用的 cost reports、Kubernetes 成本與 resource-level 分析；CloudHealth 偏 enterprise FinOps governance、policy 與大組織流程。</p>
<p>Vantage 和 AWS Cost Explorer 的主要差異是範圍。AWS Cost Explorer 是 AWS-native 入口；Vantage 適合跨 provider、Kubernetes 與多 workspace 的成本視圖。</p>
<h3 id="核心取捨表">核心取捨表</h3>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Vantage</th>
          <th>CloudHealth</th>
          <th>Apptio Cloudability</th>
          <th>AWS Cost Explorer</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者重心</td>
          <td>工程 owner 自助</td>
          <td>FinOps / procurement team</td>
          <td>FinOps + business / product owner</td>
          <td>AWS account holder</td>
      </tr>
      <tr>
          <td>多雲覆蓋</td>
          <td>AWS + GCP + Azure + 主要 SaaS connector</td>
          <td>AWS + GCP + Azure 完整 + policy engine</td>
          <td>AWS + GCP + Azure + on-prem (TBM frame)</td>
          <td>AWS only</td>
      </tr>
      <tr>
          <td>Onboarding 速度</td>
          <td>快 — connector self-service、分鐘級</td>
          <td>慢 — SOW + professional service</td>
          <td>慢 — TBM mapping + implementation</td>
          <td>即用（AWS-native）</td>
      </tr>
      <tr>
          <td>報表分享</td>
          <td>強 — URL share、read-only viewer 免費</td>
          <td>中 — 走 RBAC、外部分享受限</td>
          <td>中 — 走 TBM portal</td>
          <td>弱 — 限 AWS console viewer</td>
      </tr>
      <tr>
          <td>Kubernetes cost</td>
          <td>強 — namespace / label / pod-level 內建</td>
          <td>中 — 整合需配置</td>
          <td>中</td>
          <td>弱</td>
      </tr>
      <tr>
          <td>Anomaly detection</td>
          <td>內建、threshold 可調</td>
          <td>內建 + policy 觸發</td>
          <td>內建</td>
          <td>基本（AWS Cost Anomaly Detection）</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>100-1000 人工程組織、cloud-native</td>
          <td>5000+ 員工跨 BU enterprise governance</td>
          <td>把 IT cost 對到 product P&amp;L 的組織</td>
          <td>純 AWS、預算敏感、初期治理</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>低-中 — report 為主、無深度 lock-in</td>
          <td>高 — policy / approval workflow 量多</td>
          <td>高 — TBM mapping 跟 business 整合</td>
          <td>零 — 本就免費內建</td>
      </tr>
  </tbody>
</table>
<p>選 Vantage 的核心訴求：<em>工程團隊自助 FinOps + 跨雲跨 SaaS 一張視圖 + UI / 報表 share 走 modern observability 體驗</em>、且不需要 enterprise approval workflow / TBM business mapping。需要重 governance 走 CloudHealth、需要 IT-to-business cost mapping 走 Apptio、純 AWS 預算敏感先用 Cost Explorer。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Cost Report builder</strong>：Vantage 的核心 primitive、走 <em>filter + group by + time range</em> 的 declarative model — 例如 <code>provider:aws AND service:ec2 AND tag:team=payments group by region</code>。Saved report 變團隊 canonical view、URL 可貼 wiki / Slack；scheduled report 走 email / Slack notification。實務上 <em>每個 service owner 都該有一張 saved report</em>、不是 FinOps team 中央集中看。</p>
<p><strong>Anomaly Detection</strong>：依 cost trend 統計 baseline、超過 threshold 觸發 anomaly。痛點是 <em>false positive</em>：deploy 新 service、月底 invoice timing、provider 計費延遲都會觸發。Tune 方向是 <em>排除 known event</em>（new connector 接入後 7 天 grace period）+ <em>調 sensitivity per service</em>（payment 可容忍 5% drift、ML training cluster 容忍 50%）。對應 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 的 anomaly governance frame。</p>
<p><strong>Resource ROI / efficiency metric</strong>：Vantage 把 cost 跟 utilization metric 對齊、算 <em>cost per unit</em>（cost / request、cost / GB stored、cost / GPU-hour）。意義是把 cost report 從 <em>absolute spend</em> 升級到 <em>efficiency frontier</em>、能識別 overprovision 跟 underutilization。需要 metric source 接上（Datadog / Prometheus / CloudWatch）、純帳單 data 算不出 ROI。</p>
<p><strong>Datadog / Slack integration</strong>：cost anomaly + scheduled report 推到 Slack channel、跟 incident channel 共用；Datadog 接成 metric source 後可在 Datadog dashboard 看 cost trend 跟 latency / error rate side-by-side、適合做 <em>cost-aware SLO review</em>。</p>
<p><strong>Vantage Network（vendor benchmark）</strong>：匿名化彙整 Vantage 客戶的 unit cost benchmark（每 GB S3 storage、每 RDS instance hour、每 Snowflake credit）、讓客戶看自己跟同產業比是貴是便宜。價值在 <em>negotiation leverage</em> — 跟 AWS / Snowflake 談 EDP / 多年合約時、benchmark 是議價素材。注意是匿名 aggregate、不是 vendor 個別揭露。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Multi-cloud tag drift</strong>：AWS 用 <code>team</code>、GCP 用 <code>Team</code>、Azure 用 <code>Team-Name</code>、Vantage report group by 後出現大量 <code>untagged</code> — 在 Vantage <em>Virtual Tag</em>（rule-based tag normalization）統一 mapping、或源頭走 tag policy enforcement（<a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_tag-policies.html">AWS Organizations tag policy</a>、GCP organization policy）</li>
<li><strong>Anomaly false positive 過多 / SOC-like alert fatigue</strong>：threshold 設太緊、month-end billing delay 沒排除 — 拉大 baseline window、加 grace period for new resource、per-service tune sensitivity</li>
<li><strong>Cost spike root cause 不明</strong>：總帳單漲了但 group by service / region / tag 都看不出來 — 切到 <em>Resource Report</em>（最細粒度、看 instance / volume / snapshot 個別 cost）找 outlier、或開 Vantage <em>Cost Diffs</em>（兩個 time window 對比 delta breakdown）</li>
<li><strong>Kubernetes cost agent 資料缺</strong>：agent 沒裝 / cluster role 權限不足 / metric server 沒啟用、namespace breakdown 全空 — 走 Vantage Kubernetes onboarding checklist 補 agent + RBAC + metric server、確認資料 24hr 內出現</li>
<li><strong>Connector 接上但資料沒進來</strong>：跨 account assume role 失敗、CUR（Cost and Usage Report）export 沒開、Snowflake account usage 權限缺 — 在 Vantage connector page 看 sync status 跟 error log、不是盲猜</li>
<li><strong>Report share URL 被外人猜到</strong>：read-only URL 預設 <em>unauthenticated</em>、share 給 contractor 後沒 revoke — 改用 <em>Authentication-required share</em> 或定期 rotate URL、敏感成本數字（payment processor cost / customer-specific dedicated infra）走 internal-only</li>
<li><strong>Forecast 不準 / 跟實際差太多</strong>：base period 太短 / 有 one-off event（migration backfill、disaster recovery test）、forecast model 抓不到 seasonality — 拉長 base period、標記 one-off event 排除、或改走 manual override forecast 給特定 service</li>
</ul>
<h2 id="操作成本">操作成本</h2>
<p>Vantage 的主要成本是 cost taxonomy 維護。Tag、label、account、workspace、cluster、namespace 與 service owner 要有穩定規則，Cost Reports 才能被工程團隊信任。</p>
<p>Kubernetes agent 導入需要平台協作。Cluster 權限、資料上傳、node / pod mapping、provider cost delay 與 double counting 防護，都需要平台團隊與 FinOps 團隊一起定義。</p>
<p>Remediation 成本在報表之後才開始。找到 idle cost、overprovisioned workload 或 unexpected egress 只是第一步，後續要有 ticket、owner、驗證、rollback 與 saving confirmation。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Vantage 結果應回寫到 cost attribution evidence package。最小欄位包括 report name、filter、grouping、time range、provider、owner dimension、baseline cost、forecast、anomaly、efficiency metric、action item 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Vantage 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>Cost Report、Kubernetes Efficiency Report、Resource Report</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>report window、billing period、forecast period</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>Vantage report URL、cloud billing query、dashboard</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>tag coverage、agent freshness、provider data delay</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>owner mapping、double counting check、trend repeatability</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未標記 resource、shared cost allocation rule、資料延遲</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是把成本問題交給正確 owner。Vantage report 要能回答「誰的 workload 產生成本、成本從何時開始改變、哪個維度最能解釋變化」。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>Vantage 目前適合作為 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 與 <a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 cost attribution</a> 的工具承接點。它可回寫到 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 EKS cluster</a> 的多 cluster 成本歸屬與年省 1000 萬美金驗證、<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 的 28% 成本下降跨 DB 整併、<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 modern data architecture</a> 的儲存 90% / 分析 80% 成本下降，以及 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 的 on-demand cost model 50% 降幅。</p>
<p>這些案例的重點是成本歸屬。Vantage 頁引用案例時，要把 report filter、owner dimension、成本變化、action item 與驗證結果寫清楚 — 例如 Netflix 的 28% 下降需要拆到 DB tier、replication topology 與 read replica 比例，避免停在帳單 dashboard 截圖。</p>
<p>Vantage 的客戶輪廓偏 <em>modern startup 與 mid-market</em> — 工程組織 100-1000 人、cloud-native first、沒有獨立 FinOps team、由 platform / SRE 兼任成本治理。這類組織的痛點是 <em>誰看 cost report、誰調 anomaly、誰負責 saving validation</em> 的工程節奏沒建立、governance policy 本身反而不缺。引用 Riot Games / Netflix / BookMyShow / Zomato 案例時、重點是把這些 enterprise-scale 的 attribution 機制轉譯成 mid-market 可執行的 weekly review 節奏、而非照搬全部 governance overhead。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a></li>
<li>跨模組：<a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth</a>、<a href="/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/" data-link-title="AWS Cost Explorer" data-link-desc="用 AWS-native 成本與用量分析建立 account、service、tag 與 usage type 的成本判讀入口">AWS Cost Explorer</a></li>
<li>官方：<a href="https://docs.vantage.sh/cost_reports">Vantage Cost Reports</a></li>
</ul>
]]></content:encoded></item><item><title>9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/</guid><description>&lt;p>這個案例的核心責任是補強 Azure 案例庫深度。Cosmos DB 過往只有 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth&lt;/a> 一篇、ASOS 提供 &lt;em>傳統零售場景 + 全球分散 + 季節性峰值&lt;/em> 的對照、跟 Minecraft Earth 的 &lt;em>AR 遊戲 + 玩家位置&lt;/em> 完全不同業務語意。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>ASOS 在 Azure 的關鍵數字（引自 &lt;a href="https://www.microsoft.com/en/customers/story/718983-asos-retail-and-consumer-goods-azure">ASOS Microsoft Customer Story&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>客戶數&lt;/td>
 &lt;td>1540 萬&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Black Friday 24 小時請求量&lt;/td>
 &lt;td>1.67 億&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Black Friday 請求峰值&lt;/td>
 &lt;td>3,500 req/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Black Friday 訂單峰值&lt;/td>
 &lt;td>33 orders/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>平均響應時間&lt;/td>
 &lt;td>48 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>商品 SKU&lt;/td>
 &lt;td>85,000、每週新增 5,000 件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構轉變&lt;/td>
 &lt;td>2016 年遷移到 microservices&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Azure Cosmos DB + microservices&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵業務驅動：「ASOS chose Azure Cosmos DB because of its global distribution and ability to handle heavy seasonal bursts like Black Friday」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>ASOS 案例揭露三個全球零售 KV 容量規劃重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Black Friday 24h 1.67 億 = 平均 1,930 req/sec、峰值 3,500 req/sec&lt;/strong>：峰值 / 平均 = 1.81 倍。這個比例顯示 Black Friday 「持續高峰」、不是「瞬間爆量」 — 24 小時內流量曲線相對平緩、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a> 的「5 分鐘賣完」是完全不同形狀。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 的負載形狀識別。&lt;/li>
&lt;li>&lt;strong>48ms 平均響應 = 全球分散下 Cosmos DB 的代表性數字&lt;/strong>：英國時尚電商、客戶遍及全球、Cosmos DB 在每個地區複製、讀取在最近 region 完成。這個 48ms 包含網路、DB、應用層 — DB 本身可能只佔 5-10ms、其他是網路與應用層。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 分解。&lt;/li>
&lt;li>&lt;strong>85K SKU + 每週新增 5K = 高更新頻率 catalog&lt;/strong>：商品資料不只是讀、還有頻繁更新（價格、庫存、推薦排序）。這層 write throughput 對 Cosmos DB partition key 設計（通常用 category_id 或 brand_id）至關重要。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 hot partition 識別。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：這是 2016 年的數字、過去 10 年 ASOS 應該成長很多。但 1.67 億 req/24h 跟 33 orders/sec 對許多新興電商仍是天花板級數字、可作為「中大型零售」對標。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 Azure 案例庫深度。Cosmos DB 過往只有 <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> 一篇、ASOS 提供 <em>傳統零售場景 + 全球分散 + 季節性峰值</em> 的對照、跟 Minecraft Earth 的 <em>AR 遊戲 + 玩家位置</em> 完全不同業務語意。</p>
<h2 id="觀察">觀察</h2>
<p>ASOS 在 Azure 的關鍵數字（引自 <a href="https://www.microsoft.com/en/customers/story/718983-asos-retail-and-consumer-goods-azure">ASOS Microsoft Customer Story</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶數</td>
          <td>1540 萬</td>
      </tr>
      <tr>
          <td>Black Friday 24 小時請求量</td>
          <td>1.67 億</td>
      </tr>
      <tr>
          <td>Black Friday 請求峰值</td>
          <td>3,500 req/sec</td>
      </tr>
      <tr>
          <td>Black Friday 訂單峰值</td>
          <td>33 orders/sec</td>
      </tr>
      <tr>
          <td>平均響應時間</td>
          <td>48 ms</td>
      </tr>
      <tr>
          <td>商品 SKU</td>
          <td>85,000、每週新增 5,000 件</td>
      </tr>
      <tr>
          <td>架構轉變</td>
          <td>2016 年遷移到 microservices</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Azure Cosmos DB + microservices</td>
      </tr>
  </tbody>
</table>
<p>關鍵業務驅動：「ASOS chose Azure Cosmos DB because of its global distribution and ability to handle heavy seasonal bursts like Black Friday」。</p>
<h2 id="判讀">判讀</h2>
<p>ASOS 案例揭露三個全球零售 KV 容量規劃重點。</p>
<ol>
<li><strong>Black Friday 24h 1.67 億 = 平均 1,930 req/sec、峰值 3,500 req/sec</strong>：峰值 / 平均 = 1.81 倍。這個比例顯示 Black Friday 「持續高峰」、不是「瞬間爆量」 — 24 小時內流量曲線相對平緩、跟 <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> 的「5 分鐘賣完」是完全不同形狀。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 的負載形狀識別。</li>
<li><strong>48ms 平均響應 = 全球分散下 Cosmos DB 的代表性數字</strong>：英國時尚電商、客戶遍及全球、Cosmos DB 在每個地區複製、讀取在最近 region 完成。這個 48ms 包含網路、DB、應用層 — DB 本身可能只佔 5-10ms、其他是網路與應用層。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency budget 分解。</li>
<li><strong>85K SKU + 每週新增 5K = 高更新頻率 catalog</strong>：商品資料不只是讀、還有頻繁更新（價格、庫存、推薦排序）。這層 write throughput 對 Cosmos DB partition key 設計（通常用 category_id 或 brand_id）至關重要。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 hot partition 識別。</li>
</ol>
<p>需要警惕：這是 2016 年的數字、過去 10 年 ASOS 應該成長很多。但 1.67 億 req/24h 跟 33 orders/sec 對許多新興電商仍是天花板級數字、可作為「中大型零售」對標。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Black Friday 類「持續高峰」適合 provisioned + scheduled scaling</strong>：跟 flash-sale 的「on-demand 吃彈性」不同、Black Friday 整天高、用 provisioned 比較划算。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的可預期峰值準備。</li>
<li><strong>全球零售用 Cosmos DB / DynamoDB Global Tables</strong>：客戶在哪、讀取就在哪、避免跨洲 latency。對應 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的全球分散取捨。</li>
<li><strong>微服務 + Cosmos DB 是電商現代化典型路徑</strong>：從單體 → 微服務、從關聯式 DB → multi-model NoSQL、是 2016 後零售業常見遷移。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 與 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a>。</li>
</ol>
<p>跨平台等效：AWS DynamoDB Global Tables + Lambda、GCP Firestore + Cloud Run 都可以實作對等架構。差異是 Cosmos DB 的 multi-model（同一服務支援 SQL、Mongo、Cassandra、Gremlin、Table API）、AWS 對應有 DynamoDB（KV/Document）+ Neptune（Graph）+ Keyspaces（Cassandra）等多個服務。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他可預期峰值 → <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 AWS Prime Day</a> / <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</a></li>
<li>對照 flash-sale-spike → <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></li>
<li>想對照其他 Cosmos DB 使用 → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a></li>
<li>想規劃全球電商 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想拆 Black Friday 容量背後的 RU 成本與 sizing → <a href="/blog/backend/01-database/vendors/cosmosdb/ru-cost-model-sizing/" data-link-title="Cosmos DB RU/s 成本模型 &#43; 容量規劃：RU 思維、payload、index、provisioned vs autoscale vs serverless" data-link-desc="從 CPU&#43;IOPS 思維轉到 RU 思維的學習曲線、依負載形狀選容量模式、payload &#43; index policy 對 RU 的影響、autoscale reactive 限制 — 從 ASOS Black Friday &#43; Minecraft Earth 1M RU/s 壓測切入">Cosmos DB RU 成本模型與 sizing</a></li>
<li>想做電商 partition key 設計 → <a href="/blog/backend/01-database/vendors/cosmosdb/partition-key-design/" data-link-title="Cosmos DB Partition Key Design：synthetic / composite / hierarchical &#43; 不可逆性硬約束" data-link-desc="Cosmos DB logical partition 10000 RU/s 上限、partition key 不可改、三種設計模式（synthetic / composite / hierarchical）、跟 DynamoDB / MongoDB 可逆性對比、latency budget 拆解 — 從 Minecraft Earth &#43; ASOS 切入">Cosmos DB partition key 設計</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.microsoft.com/en/customers/story/718983-asos-retail-and-consumer-goods-azure">ASOS – Online retailer uses cloud database to deliver world-class shopping experiences</a></li>
<li><a href="https://azure.microsoft.com/en-us/products/cosmos-db/">Azure Cosmos DB</a></li>
</ul>
]]></content:encoded></item><item><title>AWS VPC Traffic Mirroring</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/</guid><description>&lt;p>AWS VPC Traffic Mirroring 的核心責任是在 VPC 網路層複製 ENI traffic，讓團隊用低 application 侵入方式觀察 production flow。它適合封包級診斷、網路安全分析、流量樣本收集與部分 replay 前置資料蒐集，重點在明確定義 mirror source、filter、target、加密邊界與保存責任。&lt;/p>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>AWS VPC Traffic Mirroring 適合需要網路層能見度的 AWS workload。當 application code、service mesh 或 host capture 都不適合改動時，VPC 層 mirror 可以從 ENI 複製封包到 analysis appliance、IDS、packet capture 或自管處理服務。&lt;/p>
&lt;p>這個定位讓 AWS VPC Traffic Mirroring 接到 &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> 的 shadow traffic 前置觀測。它偏封包觀察與樣本收集，若要做應用層 replay、filter、rewrite 或 side effect 隔離，通常還需要 GoReplay、proxy、custom processor 或測試環境配合。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay&lt;/a> 比、VPC Traffic Mirroring 走 &lt;em>無侵入 L3 packet copy&lt;/em>、GoReplay 走 &lt;em>application-level HTTP capture / rewrite&lt;/em>；跟 &lt;a href="https://tarrragon.github.io/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&lt;/a> 比、VPC Mirror 在 ENI 層、Mesh Mirror 在 K8s pod 層；跟 AWS Network Firewall 比、Firewall 是 &lt;em>inline 阻擋&lt;/em>、Mirror 是 &lt;em>side-channel 觀察&lt;/em>、兩者目的不同但 packet path 相近。&lt;/p>
&lt;h2 id="最短判讀路徑">最短判讀路徑&lt;/h2>
&lt;p>判斷 VPC Traffic Mirroring deployment 是否健康、最少看四件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Source ENI selection&lt;/strong>：哪些 ENI 被 mirror（per-instance / per-subnet / 用 tag 自動選）、是否覆蓋瓶頸路徑上的關鍵節點（ALB target / NAT Gateway / RDS proxy / cross-AZ ENI）、漏掉哪個 ENI 就是 evidence 盲區&lt;/li>
&lt;li>&lt;strong>Filter rule 收斂&lt;/strong>：mirror filter 用 protocol / port / CIDR / direction 限定、避免「全 ENI 全 traffic」這種失控設定；filter 太寬會把 cross-AZ cost + target 處理量直接炸上去&lt;/li>
&lt;li>&lt;strong>Target NLB capacity&lt;/strong>：mirror target 是 ENI 或 NLB、target capacity（NLB flow / bandwidth）跟 source 流量比例要對得起來、target overload 會 drop 封包讓 evidence 失真&lt;/li>
&lt;li>&lt;strong>Sampling rate / packet length truncation&lt;/strong>：高流量服務不必 1:1 mirror、要設 &lt;code>packet_length&lt;/code> 截斷（只取 header）跟 mirror session ratio；忘設 sampling 等於整條 production 流量複製兩份、AWS bill 月底會出事&lt;/li>
&lt;/ul>
&lt;p>四件事任一缺失、就是 &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> 邊界的待補項目。&lt;/p></description><content:encoded><![CDATA[<p>AWS VPC Traffic Mirroring 的核心責任是在 VPC 網路層複製 ENI traffic，讓團隊用低 application 侵入方式觀察 production flow。它適合封包級診斷、網路安全分析、流量樣本收集與部分 replay 前置資料蒐集，重點在明確定義 mirror source、filter、target、加密邊界與保存責任。</p>
<h2 id="定位">定位</h2>
<p>AWS VPC Traffic Mirroring 適合需要網路層能見度的 AWS workload。當 application code、service mesh 或 host capture 都不適合改動時，VPC 層 mirror 可以從 ENI 複製封包到 analysis appliance、IDS、packet capture 或自管處理服務。</p>
<p>這個定位讓 AWS VPC Traffic Mirroring 接到 <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> 的 shadow traffic 前置觀測。它偏封包觀察與樣本收集，若要做應用層 replay、filter、rewrite 或 side effect 隔離，通常還需要 GoReplay、proxy、custom processor 或測試環境配合。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay</a> 比、VPC Traffic Mirroring 走 <em>無侵入 L3 packet copy</em>、GoReplay 走 <em>application-level HTTP capture / rewrite</em>；跟 <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> 比、VPC Mirror 在 ENI 層、Mesh Mirror 在 K8s pod 層；跟 AWS Network Firewall 比、Firewall 是 <em>inline 阻擋</em>、Mirror 是 <em>side-channel 觀察</em>、兩者目的不同但 packet path 相近。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 VPC Traffic Mirroring deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Source ENI selection</strong>：哪些 ENI 被 mirror（per-instance / per-subnet / 用 tag 自動選）、是否覆蓋瓶頸路徑上的關鍵節點（ALB target / NAT Gateway / RDS proxy / cross-AZ ENI）、漏掉哪個 ENI 就是 evidence 盲區</li>
<li><strong>Filter rule 收斂</strong>：mirror filter 用 protocol / port / CIDR / direction 限定、避免「全 ENI 全 traffic」這種失控設定；filter 太寬會把 cross-AZ cost + target 處理量直接炸上去</li>
<li><strong>Target NLB capacity</strong>：mirror target 是 ENI 或 NLB、target capacity（NLB flow / bandwidth）跟 source 流量比例要對得起來、target overload 會 drop 封包讓 evidence 失真</li>
<li><strong>Sampling rate / packet length truncation</strong>：高流量服務不必 1:1 mirror、要設 <code>packet_length</code> 截斷（只取 header）跟 mirror session ratio；忘設 sampling 等於整條 production 流量複製兩份、AWS bill 月底會出事</li>
</ul>
<p>四件事任一缺失、就是 <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> 邊界的待補項目。</p>
<h2 id="適用場景">適用場景</h2>
<p>網路層瓶頸定位適合 VPC Traffic Mirroring。當 latency、packet loss、TLS handshake、connection reset、NAT、load balancer 或 cross-AZ traffic 是疑點時，封包 mirror 能提供 application metrics 看不到的證據。</p>
<p>低侵入 traffic sampling 適合 VPC Traffic Mirroring。團隊可以在不改 application code 的情況下收集 production flow，作為 workload model、security analysis 或 replay pipeline 的輸入。</p>
<p>受管 AWS 網路環境適合 VPC Traffic Mirroring。當服務主要跑在 EC2 / ENI 可 mirror 的環境中，VPC 原生能力可以讓網路團隊用既有安全與觀測流程管理。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>AWS VPC Traffic Mirroring 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>網路層鏡像</td>
          <td>application 無侵入、封包級可見</td>
          <td>L7 解碼、filter、rewrite 與 replay</td>
      </tr>
      <tr>
          <td>AWS 原生</td>
          <td>VPC / ENI / filter / target 整合</td>
          <td>AWS 約束、跨帳號與跨 VPC 設計</td>
      </tr>
      <tr>
          <td>安全分析</td>
          <td>可接 IDS、packet analyzer、forensics</td>
          <td>PII / payload 保存與存取控制</td>
      </tr>
      <tr>
          <td>流量樣本</td>
          <td>可支援 workload model 校正</td>
          <td>加密 traffic 處理與樣本代表性</td>
      </tr>
  </tbody>
</table>
<p>網路層鏡像價值來自低侵入。團隊可以在不調整 application 或 service mesh 的情況下取得 flow evidence，但也要承擔 L7 語意不足的限制。</p>
<p>安全分析價值來自封包細節。對容量工程而言，封包證據能幫忙確認 connection、TLS、NAT、load balancer 與跨區流量成本；對資安而言，則能支援 IDS 與 forensic workflow。</p>
<h2 id="跟其他方式的取捨">跟其他方式的取捨</h2>
<p>AWS VPC Traffic Mirroring 和 GoReplay 的主要差異是層級。VPC mirroring 在 L3 / L4 觀察封包；GoReplay 更接近 HTTP application replay，對 request rewrite 與 target control 更直接。</p>
<p>AWS VPC Traffic Mirroring 和 service mesh mirroring 的主要差異是控制範圍。VPC mirroring 由網路層控制，適合低侵入封包觀察；service mesh mirroring 由 L7 route policy 控制，適合服務版本與 route 對照。</p>
<p>AWS VPC Traffic Mirroring 和 synthetic load test 的主要差異是用途。VPC mirroring 提供 production traffic evidence；synthetic load test 提供可控壓力。兩者常搭配：先用 mirror 校正 workload model，再用 k6 / Gatling / Locust 產生可控負載。</p>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>AWS VPC Traffic Mirroring</th>
          <th>GoReplay</th>
          <th>Service Mesh Mirroring</th>
          <th>AWS Network Firewall</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>鏡像層級</td>
          <td>L3 / L4 packet copy</td>
          <td>L7 HTTP capture + replay</td>
          <td>L7 pod-level（Istio / Linkerd）</td>
          <td>L3-L7 inline filter（非 mirror）</td>
      </tr>
      <tr>
          <td>Application 侵入</td>
          <td>無 — ENI 層、code 不改</td>
          <td>中 — 需 sidecar / capture host</td>
          <td>中 — service mesh 必須先佈</td>
          <td>無 — VPC gateway 層</td>
      </tr>
      <tr>
          <td>Replay 能力</td>
          <td>弱 — 需自接 packet replayer</td>
          <td>強 — 內建 request rewrite</td>
          <td>中 — mirror to shadow service</td>
          <td>無</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>network forensics / IDS / 容量分析</td>
          <td>HTTP regression / load replay</td>
          <td>K8s service-level shadow test</td>
          <td>inline 阻擋 / IDS / IPS</td>
      </tr>
      <tr>
          <td>加密 payload</td>
          <td>看不到 — TLS 仍密</td>
          <td>看得到 — application 解密後</td>
          <td>看得到 — mesh sidecar 已 TLS terminate</td>
          <td>partial — TLS inspection 需另設</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>per-ENI / cross-AZ traffic</td>
          <td>計算 + 儲存</td>
          <td>mesh overhead + shadow service</td>
          <td>per-GB processed</td>
      </tr>
  </tbody>
</table>
<h2 id="操作成本">操作成本</h2>
<p>AWS VPC Traffic Mirroring 的主要成本是資料治理。Mirror target 可能收到 payload、token、cookie、internal identifiers 與敏感資料，因此保存、查詢、保留期限、存取權與刪除責任要先定義。</p>
<p>網路成本來自複製 traffic。Mirror session 會增加網路流量與 target processing 成本，高流量服務要先估算 mirror ratio、filter、target capacity 與跨 AZ 費用。</p>
<p>加密成本來自 L7 可讀性。TLS traffic 在網路層 mirror 後通常仍是加密封包；若需要 application payload，要搭配解密點、proxy、key 管理或 application-level capture。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>AWS VPC Traffic Mirroring 結果應回寫到 evidence package。最小欄位包括 mirror source ENI、filter rule、mirror target、session number、time range、sampling / truncation、target capacity、payload handling、packet metrics、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>AWS VPC Traffic Mirroring 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>mirror session、filter、target config</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>mirror start / end</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>packet analyzer、flow logs、metrics link</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>filter coverage、sampling、encryption status</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>target capacity、source coverage</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>加密 payload、未 mirror ENI、L7 語意不足</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是把網路層觀察接回效能判斷。Reviewer 要能知道 mirror 覆蓋哪些 ENI、哪些封包被 filter、target 是否有 capacity，以及封包證據如何對應到 application latency 或 saturation。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Filter rule 設計</strong>：mirror filter 支援 source CIDR / dest CIDR / protocol / port range / direction（ingress / egress）、rule number 決定 evaluation 順序。production 慣例是 <em>最小覆蓋原則</em> — 先用 <code>port 443 + dest CIDR = ALB target group</code> 限定到關鍵 path、再依需要擴張。filter 寫太寬會把 control-plane heartbeat、health check、internal RPC 全部 mirror 進來、target 處理量瞬間爆掉。</p>
<p><strong>跟 IDS / packet analyzer 整合</strong>：mirror target 接 ENI 後常見的下游堆疊是 <em>Zeek</em>（前 Bro、生成 connection log / protocol log）、<em>Suricata</em>（rule-based IDS / IPS 偵測）、<em>Wireshark / tshark</em>（離線封包分析）。實務上 mirror → NLB → 自管 EC2 跑 Zeek 產 JSON log → 進 <a href="/blog/backend/07-security-data-protection/vendors/datadog-security/" data-link-title="Datadog Security" data-link-desc="Datadog observability platform 上的 security suite：Cloud SIEM &#43; CSPM &#43; CWS &#43; AAP &#43; Sensitive Data Scanner、跟 observability 同 plane">Datadog</a> / Splunk 做 correlation。容量工程關心 connection reset 跟 retransmit、資安關心 protocol anomaly、共用同一份 mirror feed。</p>
<p><strong>Replay 到 staging cluster</strong>：mirror feed 不能直接 replay（沒有 stateful 重組），但可以接 packet replayer（tcpreplay / GoReplay packet mode）把樣本送到 staging。要注意 <em>side effect 隔離</em> — staging 的 DB / external API 不應該真的執行寫入、否則 mirror 變成 production fanout。</p>
<p><strong>Traffic analysis platform 整合</strong>：mirror 取得的 packet evidence 通常進 <a href="/blog/backend/07-security-data-protection/vendors/datadog-security/" data-link-title="Datadog Security" data-link-desc="Datadog observability platform 上的 security suite：Cloud SIEM &#43; CSPM &#43; CWS &#43; AAP &#43; Sensitive Data Scanner、跟 observability 同 plane">Datadog Network Performance Monitoring</a> 做 NPM dashboard、或進 Splunk Stream app 做 SIEM correlation。整合的關鍵是 <em>時間軸對齊</em> — packet timestamp、application log、metrics 三者要同步、否則 root cause 拼不回去。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Target NLB capacity 不夠 / drop packet</strong>：mirror traffic 量超過 NLB flow limit、packet 被 silently drop — 拆 mirror session 到多個 target、開 NLB flow log 看 drop reason、必要時改用 Gateway Load Balancer</li>
<li><strong>Filter rule 太寬導致流量爆</strong>：「mirror 所有 traffic」設定上線後 target ENI 跟 cross-AZ bandwidth 雙重炸 — 立刻關掉 session、改用 dest CIDR / port 收斂、加 <code>packet_length</code> 截斷只取 header</li>
<li><strong>Cross-AZ mirror cost 暴增</strong>：source ENI 跟 target 在不同 AZ、每個封包複製都收 cross-AZ traffic 費 — target NLB 部署到每個 AZ、用 AZ-affinity routing、或把 mirror target 限定在 source 同 AZ</li>
<li><strong>TLS payload 看不到</strong>：mirror 拿到加密封包、L7 內容無法分析 — 把解密點移到 ALB / NLB-TLS termination、或在 application 層加 capture（不再用 VPC mirror）</li>
<li><strong>Mirror session 漏掉新 instance</strong>：autoscaling 起新 instance 沒自動加入 mirror — 用 mirror target by tag、Terraform / CloudFormation 把 mirror session 寫進 ASC launch template</li>
<li><strong>Packet timestamp 不對齊 application log</strong>：mirror packet 時間是 source ENI capture 時間、不是 application processing 時間、做 latency 分析會偏差 — 用 packet 5-tuple + request ID 對齊 application log、不要直接相減 timestamp</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>AWS VPC Traffic Mirroring 適合回寫網路與平台層效能案例。它可接 <a href="/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &#43; 1000 Pods/sec 創建吞吐">9.C34 GCP 130K node GKE cluster</a> 的大規模網路觀測需求（雖在 GCP、但網路證據的層次拆解可類比）、<a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair GCP burst capacity</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> 的 pre-event network evidence、<a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 EKS cluster</a> 跨 cluster 的網路流量觀測、以及 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys DynamoDB 15-region</a> 的 99.999% 可用性下封包層 evidence 補強。</p>
<p>這些案例的重點是網路層 evidence。VPC Traffic Mirroring 頁引用案例時，要把 case 轉成 mirror source、filter、target capacity、packet metric、cross-AZ cost 與 L7 correlation — 例如 Riot Games 35ms 延遲門檻下、cross-AZ traffic mirror 本身會增加成本、必須先用 filter 收斂到關鍵 ENI。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<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/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/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay</a></li>
<li>平行：<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></li>
<li>知識卡：<a href="/blog/backend/knowledge-cards/shadow-traffic/" data-link-title="Shadow Traffic" data-link-desc="把 production traffic 複製到新版本驗證、但不返回結果給用戶的測試模式">Shadow Traffic</a></li>
<li>官方：<a href="https://docs.aws.amazon.com/vpc/latest/mirroring/what-is-traffic-mirroring.html">AWS VPC Traffic Mirroring documentation</a></li>
</ul>
]]></content:encoded></item><item><title>CloudHealth</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/cloudhealth/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/cloudhealth/</guid><description>&lt;p>CloudHealth 的核心責任是把大型組織的 cloud spend、governance、policy、allocation 與 optimization workflow 放進同一個 FinOps 管理平面。它適合 account、team、business unit、provider 與採購流程複雜的組織，重點在讓成本治理、合規要求與工程 owner 能共用同一套成本事實。2018 年被 VMware 收購、2023 年隨 VMware 進入 Broadcom 旗下；現屬 Broadcom 的 enterprise FinOps 旗艦產品。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>CloudHealth 跟 AWS Cost Explorer / Azure Cost Management 那種單雲原生工具的差異在 &lt;em>跨雲一致 schema + enterprise FinOps operating model&lt;/em>、單雲帳單細節反而是原生工具更深。Cost Explorer 在 AWS-only 場景的 granularity 更深、但跨 Azure / GCP 帳單對齊、成本中心 chargeback、policy 治理就需要 CloudHealth 這類 multi-cloud platform。&lt;/p>
&lt;p>跟 Vantage 比、CloudHealth 走 &lt;em>enterprise governance-first&lt;/em>、Vantage 走 &lt;em>engineering-friendly dashboard-first&lt;/em>。Vantage 對小到中型 cloud-native 團隊更快上手、但 chargeback 流程、policy violation queue、approval workflow 都不是它的主場。跟 Apptio Cloudability（IBM 收購）比、兩者定位最接近、都吃 large enterprise FinOps 市場；CloudHealth 的差異是 VMware / Broadcom ecosystem 整合（vCenter / Tanzu / on-prem hybrid），Cloudability 強在 TBM（Technology Business Management）財務分攤模型成熟度。&lt;/p>
&lt;p>關鍵張力：&lt;em>Broadcom 收購後的 product roadmap 不確定性&lt;/em> ↔ &lt;em>enterprise FinOps ecosystem 深度&lt;/em>。Broadcom 對 VMware portfolio 的價格調整、partner 縮編、support tier 變動 2024-2025 持續發生；客戶要評估 &lt;em>退場成本（chargeback rule + tag taxonomy 量大）vs 短期 license 漲幅&lt;/em>、不是只看當下功能。&lt;/p>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>CloudHealth 適合 enterprise FinOps 與 cloud governance。當組織需要跨 AWS、Azure、Google Cloud、Kubernetes、shared services 與成本中心建立 showback、chargeback、policy 與 optimization workflow，CloudHealth 類平台可以提供集中式成本管理與治理視角。&lt;/p>
&lt;p>這個定位讓 CloudHealth 接到三個主章。它從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 接收 cost curve 與 over-provision waste，從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性&lt;/a> 接收成本 dashboard 需求，從 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因&lt;/a> 接收 owner、tag 與 attribution 規則。&lt;/p>
&lt;h2 id="適用場景">適用場景&lt;/h2>
&lt;p>多雲成本治理是 CloudHealth 的主要入口。大型企業常有不同 cloud provider、不同採購合約、不同 account 結構與不同團隊成熟度；CloudHealth 可以把成本、資產、policy 與權限治理收斂到 FinOps 工作流程。&lt;/p></description><content:encoded><![CDATA[<p>CloudHealth 的核心責任是把大型組織的 cloud spend、governance、policy、allocation 與 optimization workflow 放進同一個 FinOps 管理平面。它適合 account、team、business unit、provider 與採購流程複雜的組織，重點在讓成本治理、合規要求與工程 owner 能共用同一套成本事實。2018 年被 VMware 收購、2023 年隨 VMware 進入 Broadcom 旗下；現屬 Broadcom 的 enterprise FinOps 旗艦產品。</p>
<h2 id="服務定位">服務定位</h2>
<p>CloudHealth 跟 AWS Cost Explorer / Azure Cost Management 那種單雲原生工具的差異在 <em>跨雲一致 schema + enterprise FinOps operating model</em>、單雲帳單細節反而是原生工具更深。Cost Explorer 在 AWS-only 場景的 granularity 更深、但跨 Azure / GCP 帳單對齊、成本中心 chargeback、policy 治理就需要 CloudHealth 這類 multi-cloud platform。</p>
<p>跟 Vantage 比、CloudHealth 走 <em>enterprise governance-first</em>、Vantage 走 <em>engineering-friendly dashboard-first</em>。Vantage 對小到中型 cloud-native 團隊更快上手、但 chargeback 流程、policy violation queue、approval workflow 都不是它的主場。跟 Apptio Cloudability（IBM 收購）比、兩者定位最接近、都吃 large enterprise FinOps 市場；CloudHealth 的差異是 VMware / Broadcom ecosystem 整合（vCenter / Tanzu / on-prem hybrid），Cloudability 強在 TBM（Technology Business Management）財務分攤模型成熟度。</p>
<p>關鍵張力：<em>Broadcom 收購後的 product roadmap 不確定性</em> ↔ <em>enterprise FinOps ecosystem 深度</em>。Broadcom 對 VMware portfolio 的價格調整、partner 縮編、support tier 變動 2024-2025 持續發生；客戶要評估 <em>退場成本（chargeback rule + tag taxonomy 量大）vs 短期 license 漲幅</em>、不是只看當下功能。</p>
<h2 id="定位">定位</h2>
<p>CloudHealth 適合 enterprise FinOps 與 cloud governance。當組織需要跨 AWS、Azure、Google Cloud、Kubernetes、shared services 與成本中心建立 showback、chargeback、policy 與 optimization workflow，CloudHealth 類平台可以提供集中式成本管理與治理視角。</p>
<p>這個定位讓 CloudHealth 接到三個主章。它從 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 接收 cost curve 與 over-provision waste，從 <a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a> 接收成本 dashboard 需求，從 <a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因</a> 接收 owner、tag 與 attribution 規則。</p>
<h2 id="適用場景">適用場景</h2>
<p>多雲成本治理是 CloudHealth 的主要入口。大型企業常有不同 cloud provider、不同採購合約、不同 account 結構與不同團隊成熟度；CloudHealth 可以把成本、資產、policy 與權限治理收斂到 FinOps 工作流程。</p>
<p>Showback / chargeback 適合用 CloudHealth 建立財務語言。成本中心、部門、產品線、環境與專案需要穩定分攤規則，才能讓工程決策接到預算管理、採購承諾與年度規劃。</p>
<p>Optimization workflow 適合用 CloudHealth 管理組織節奏。Rightsizing、reserved capacity、idle resource、tag compliance 與 policy violation 都需要 owner、例外、核准、驗證與追蹤，enterprise 平台的價值在於流程一致。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>CloudHealth 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>組織治理</td>
          <td>支援多 account、多團隊、成本中心與 policy</td>
          <td>FinOps operating model、owner taxonomy</td>
      </tr>
      <tr>
          <td>成本分攤</td>
          <td>支援 showback / chargeback 與 shared cost rule</td>
          <td>tag hygiene、成本中心對照表</td>
      </tr>
      <tr>
          <td>最佳化流程</td>
          <td>支援 rightsizing、commitment 與 policy action</td>
          <td>工程驗證、變更排程、saving confirmation</td>
      </tr>
      <tr>
          <td>Enterprise 整合</td>
          <td>適合採購、財務、平台與工程共同使用</td>
          <td>權限模型、報表治理、例外處理</td>
      </tr>
  </tbody>
</table>
<p>組織治理價值來自一致流程。單一工程團隊可以靠雲端原生工具追成本；大型組織需要 policy、role、approval、exception 與 audit trail 才能讓成本治理長期運作。</p>
<p>成本分攤價值來自可對帳。Showback / chargeback 要能讓財務、平台與服務 owner 對同一筆費用得到相同解釋，shared platform cost、discount、support fee 與 commitment benefit 都要有分攤規則。</p>
<p>最佳化流程價值來自閉環管理。Rightsizing recommendation 只有在 owner 接手、服務驗證、變更落地與 saving confirmation 完成後，才會變成實際成本改善。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 CloudHealth deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Multi-cloud connector 完整性</strong>：AWS（CUR / billing role）、Azure（EA / MCA billing role）、GCP（BigQuery billing export）、Kubernetes（kube-state-metrics + Prometheus）連接器是否都接通、是否有 daily ingestion lag、是否漏 account / subscription</li>
<li><strong>FinOps team workflow 落地</strong>：policy queue、recommendation queue、approval flow 是否有實際 owner（不只是 dashboard 看一看）、weekly / monthly FinOps cadence 是否進到工程 sprint 跟財務 close cycle</li>
<li><strong>Chargeback 規則可對帳</strong>：business unit / cost center / application / environment 的分攤公式是否文件化、shared service（platform team / CI runner / observability stack）的 split rule 是否被各 BU 接受、月底財務 close 對得起來</li>
<li><strong>Reserved Instance / Savings Plan 管理</strong>：commitment coverage（已 commit 比例）、utilization（已用比例）、expiration alert、跨 account 的 commitment sharing 是否有 owner 主動經營、不是買完就放著</li>
</ul>
<p>四件事任一缺失、就是 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 邊界的待補項目。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>CloudHealth</th>
          <th>Vantage</th>
          <th>AWS Cost Explorer</th>
          <th>Apptio Cloudability</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Multi-cloud</td>
          <td>強 — AWS / Azure / GCP / K8s</td>
          <td>強 — 加 Snowflake / Datadog 整合</td>
          <td>弱 — AWS-only</td>
          <td>強 — 三大雲 + on-prem</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>陡 — enterprise model 複雜</td>
          <td>緩 — engineer 友善 dashboard</td>
          <td>緩 — AWS console 內建</td>
          <td>陡 — TBM 模型門檻高</td>
      </tr>
      <tr>
          <td>Chargeback</td>
          <td>強 — policy + approval flow 完整</td>
          <td>中 — report-driven、流程靠外掛</td>
          <td>弱 — 報表為主、無 workflow</td>
          <td>強 — TBM 財務分攤是主場</td>
      </tr>
      <tr>
          <td>部署模型</td>
          <td>SaaS only</td>
          <td>SaaS only</td>
          <td>AWS console 內建</td>
          <td>SaaS only</td>
      </tr>
      <tr>
          <td>適合規模</td>
          <td>Enterprise（多 BU + 多雲）</td>
          <td>Startup ~ Mid（cloud-native）</td>
          <td>AWS single-account ~ Org</td>
          <td>Enterprise（重財務治理）</td>
      </tr>
      <tr>
          <td>計費模型</td>
          <td>% of cloud spend + minimum</td>
          <td>Per-cloud-account tier</td>
          <td>Free（AWS 內建）</td>
          <td>% of cloud spend + minimum</td>
      </tr>
      <tr>
          <td>Roadmap 風險</td>
          <td>Broadcom 收購後不確定</td>
          <td>獨立公司、roadmap 穩定</td>
          <td>AWS 自家、roadmap 跟雲同步</td>
          <td>IBM 收購後整合中</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>高 — chargeback rule + tag 量大</td>
          <td>低 — report 可重建</td>
          <td>無 — AWS-native 切換無痛</td>
          <td>高 — TBM 模型重 migrate</td>
      </tr>
  </tbody>
</table>
<p>選 CloudHealth 的核心訴求：<em>enterprise scale + 多雲 + 已有 VMware / Broadcom ecosystem</em>、且能投入 FinOps team 維護 chargeback rule、policy queue、commitment management lifecycle。中小型 cloud-native 走 Vantage 更快；AWS-only 直接用 Cost Explorer + Cost Anomaly Detection；重財務 TBM 整合走 Apptio Cloudability。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>CloudHealth 和 Vantage 的主要差異是治理深度。Vantage 偏工程友善報表與 Kubernetes cost visibility；CloudHealth 偏 enterprise FinOps operating model、policy 與大組織分攤流程。</p>
<p>CloudHealth 和 Akamas 的主要差異是最佳化方式。CloudHealth 偏成本治理與推薦流程；Akamas 偏把 SLO 約束與 configuration tuning 放進 optimization engine。</p>
<p>CloudHealth 和 AWS Cost Explorer 的主要差異是多雲與流程。Cost Explorer 適合 AWS-native 成本分析；CloudHealth 適合跨 provider、跨成本中心與跨團隊治理。</p>
<h2 id="操作成本">操作成本</h2>
<p>CloudHealth 的主要成本是組織模型維護。Business unit、cost center、application、environment、owner、account 與 tag policy 需要持續治理，平台才能提供穩定報表。</p>
<p>流程成本會高於單純報表工具。Recommendation 需要進入 approval、exception、change management、validation 與 financial close process；這些流程讓工具適合大型組織，也要求更高維運紀律。</p>
<p>資料品質成本會集中在標籤與 shared cost。未標記資源、跨團隊 shared service、commitment benefit 分攤與 marketplace charge 都會影響成本歸屬信任度。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Reserved Instance 與 Savings Plan management</strong>：CloudHealth 把 commitment 視為 portfolio、不是單筆採購。Coverage（已 commit 比例）、utilization（已用比例）、break-even（攤平時間）三個指標要持續追、跟業務 roadmap 對齊；新服務上線前先 model 預期用量、commit 太多反而 lock-in 浪費、太少又付 on-demand 溢價。跨 account / linked account 的 commitment sharing 要明確 owner、不然 platform team 買的 RI 被 product team 吃掉、財務分攤回不去。</p>
<p><strong>Chargeback / showback 流程</strong>：showback 是 <em>讓 BU 看到自己花多少</em>、chargeback 是 <em>讓 BU 帳本上真的扣這筆</em>。chargeback 需要財務簽核、需要每月 close cycle、需要 dispute 機制；CloudHealth 的 chargeback rule 改動要走 approval、不能 admin 自己改完就上線、會直接影響 BU 月結。</p>
<p><strong>Multi-cloud asset inventory</strong>：CloudHealth 不只是帳單工具、也作 asset inventory — EC2 / RDS / VM / GKE node / Azure SQL 等資源的 owner、tag、environment、policy state 在同一視角。這個能力是 enterprise CMDB integration 的入口、也能反向支援 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">7 security posture</a> 的 untagged / unauthorized resource 偵測。</p>
<p><strong>跟 Datadog / SIEM integration</strong>：CloudHealth 的 cost data 可以 export 到 <a href="/blog/backend/04-observability/vendors/datadog/" data-link-title="Datadog" data-link-desc="All-in-one SaaS 觀測平台、APM / Logs / Metrics / RUM / Security">Datadog</a> 作 SRE cost-aware alert（service 突然花費暴衝 → 通常是 retry storm / runaway job），也可送 SIEM 作 untagged resource / cross-account spend anomaly 偵測。整合的價值不是把 CloudHealth 當另一個 observability tool、而是讓 cost signal 進到工程值班的視野。</p>
<p><strong>Broadcom 收購後 product roadmap 變動風險</strong>：2023 Broadcom 完成 VMware 收購後、CloudHealth 經歷 license model 調整、partner program 變動、support tier 重整。對既有大客戶來說 license 漲幅、SLA 條款、roadmap 透明度都進入再評估期；新客戶選型時 <em>退場成本評估</em> 要先做、不能假設 platform 五年不變。Broadcom 對 enterprise 客戶仍會維持產品線、但中小客戶可能感受到 support 縮減。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Multi-cloud tag 不一致</strong>：AWS 用 <code>Environment=prod</code>、Azure 用 <code>env=production</code>、GCP 用 <code>env-tier=prod</code> — CloudHealth 報表看起來三套不同 — 統一 tag taxonomy（cost center / application / environment / owner）寫進 cloud governance policy、用 cloud-native enforcement（AWS Tag Policy / Azure Policy / GCP Org Policy）擋未標記資源</li>
<li><strong>Chargeback 對不上帳</strong>：BU 看到的金額 ≠ 財務 close 的金額 — shared service split rule 沒被簽核、commitment benefit attribution 跑掉、marketplace charge 沒分攤 — 走 monthly close reconciliation、把 rule 鎖定後才開 dispute window</li>
<li><strong>Reserved Instance 浪費</strong>：commit 買了沒用滿（utilization &lt; 80%）— 跨 account share 沒開、或業務 roadmap 改了沒同步 commitment team — 開 cross-account RI sharing、commitment review 進 monthly FinOps cadence</li>
<li><strong>新雲帳號接不進來</strong>：connector 一直 ingestion failure — IAM role / EA permission / BigQuery export 沒設好、或 organization 結構改了 CloudHealth 沒同步 — 走 onboarding checklist、新 account 自動化納管</li>
<li><strong>Recommendation 一直沒人 action</strong>：rightsizing queue 累積幾百筆沒處理 — 沒有 owner、或 recommendation 沒對應到實際 service team — 用 tag 反查 owner、把 recommendation 進 sprint backlog 而非 FinOps 自己追</li>
<li><strong>Broadcom 收購後 support / price 變動</strong>：renewal 漲幅突然 30-50%、support tier 被降級 — 早一年開始評估替代方案（Vantage / Apptio / 雲原生組合）、把 chargeback rule 跟 tag taxonomy 抽象到不綁 vendor 的格式</li>
</ul>
<h2 id="evidence-package">Evidence Package</h2>
<p>CloudHealth 結果應回寫到 FinOps governance evidence package。最小欄位包括 business unit、cost center、application、provider、account、policy、recommendation、expected saving、approval state、implementation state、verified saving 與 exception。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>CloudHealth 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>cost report、policy report、recommendation queue</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>billing period、review cycle、saving validation window</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>CloudHealth report、cloud billing query、policy detail</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>tag compliance、account coverage、allocation rule</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>owner mapping、approval status、verified saving</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>shared service rule、manual exception、provider delay</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是支援治理審查。CloudHealth report 要能回答「這筆成本屬於誰、哪條 policy 觸發、誰核准例外、變更是否真的帶來 savings」。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>CloudHealth 目前適合作為 enterprise FinOps 與多雲治理案例的工具承接點。它可回寫到 <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 個受監管市場跨地區治理與成本中心分攤需求、<a href="/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/" data-link-title="9.C33 Maersk &#43; Bosch：傳統產業在 Azure AKS 上的微服務治理" data-link-desc="全球海運 Maersk 跟 Bosch 智慧建築把 AKS 當微服務治理基礎、釋放工程資源做業務功能">9.C33 Maersk + Bosch on Azure AKS</a> 的傳統產業多 BU 治理一致性、<a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair hybrid burst</a> 的 on-prem + GCP 雙來源帳單合併、以及 <a href="/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/" data-link-title="9.C35 Snap：GCP &#43; KeyDB 在 multi-cloud 架構下的低延遲快取" data-link-desc="Snap 用 GCP 上的 KeyDB cluster 減少跨 cloud cache 延遲、用 TPU 訓練廣告推薦模型">9.C35 Snap multi-cloud</a> 的 GCP + AWS 跨雲成本對照。</p>
<p>這些案例的重點是組織能力。CloudHealth 頁引用案例時，要把案例拆成 governance model、owner taxonomy、policy action、engineering validation 與 financial reporting — 例如 Standard Chartered 的 7 市場分割要回到 per-market policy + 合規 tag、不是單一全球 report、而非停在雲端帳單下降。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a></li>
<li>跨模組：<a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage</a></li>
<li>官方：<a href="https://news.broadcom.com/apj/releases/broadcom-announces-new-cloudhealth-user-experience-for-greater-cloud-spend-management-across-enterprise-teams">Broadcom CloudHealth announcement</a></li>
</ul>
]]></content:encoded></item><item><title>9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/</guid><description>&lt;p>這個案例的核心責任是說明「hybrid cloud burst」模式 — 平日跑自家 data center、峰值事件靠雲端補容量。這跟全部上雲（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15 Tixcraft&lt;/a>）或全部自管的兩種極端都不同、是大企業常見的折衷路徑。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Wayfair 在 GCP 的關鍵敘述（引自 &lt;a href="https://cloud.google.com/customers/wayfair">Wayfair Case Study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>商品數量&lt;/td>
 &lt;td>22 M+ 個 SKU&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>供應商數量&lt;/td>
 &lt;td>16,000+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>員工數&lt;/td>
 &lt;td>17,000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>北美 + 歐洲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值事件&lt;/td>
 &lt;td>Way Day（年度大促）、Black Friday、Cyber Monday&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>COVID Q2 2020 業績&lt;/td>
 &lt;td>美國淨營收成長 +82.5%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構模式&lt;/td>
 &lt;td>Hybrid（on-prem + GCP burst）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：BigQuery（資料倉儲）、Cloud Dataproc（資料處理）、Cloud Pub/Sub（資料注入）、Looker（dashboard）、Cloud DLP（合規）、C2 processors（高性能 compute）。&lt;/p>
&lt;p>關鍵敘述：「Our automation systems signal the cloud to scale on demand」「We were able to reduce and eventually eliminate the need for change freezes leading up to big events」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Wayfair 揭露三個 hybrid cloud burst 模式的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Hybrid burst 是「容量規劃成本平衡」的折衷&lt;/strong>：自家 data center 平日跑得便宜、峰值事件不夠用；全部上雲峰值好辦但平日成本高。Hybrid 模式讓 baseline 用便宜的、峰值用彈性的、總成本曲線最平。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的長期 TCO 規劃。&lt;/li>
&lt;li>&lt;strong>「Change freeze 不再需要」是 burst 模式的真正價值&lt;/strong>：傳統零售 IT 為了 Black Friday 通常 2-3 個月前就 freeze code change、確保穩定。Wayfair 在 GCP burst 上線後、能在峰值前繼續正常 release — 因為新功能可以單獨 deploy 到 GCP、不影響 on-prem 主系統。對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate&lt;/a> 的非凍結式變更管理。&lt;/li>
&lt;li>&lt;strong>資料平面（BigQuery / Dataproc）是 hybrid 的主場、交易平面仍在 on-prem&lt;/strong>：Wayfair 把「分析、報表、推薦模型」放 GCP、「核心交易、訂單處理、庫存」仍在自家。這個切分是 hybrid 的常見做法 — 計算密集的工作上雲、業務核心保留自管。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的核心 OLTP 跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的分析資料層分離。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>Wayfair 案例 &lt;em>沒有&lt;/em> 提具體 TPS、latency、capacity scale 數字 — 行銷敘述居多、工程細節較少。讀此類案例要對 &lt;em>策略&lt;/em> 做學習、不要套用具體數字。&lt;/li>
&lt;li>「82.5% 美國淨營收成長」是 &lt;em>業績&lt;/em>、不是 &lt;em>系統指標&lt;/em>。系統能撐業績、但兩者不是同一件事。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「hybrid cloud burst」模式 — 平日跑自家 data center、峰值事件靠雲端補容量。這跟全部上雲（<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>）或全部自管的兩種極端都不同、是大企業常見的折衷路徑。</p>
<h2 id="觀察">觀察</h2>
<p>Wayfair 在 GCP 的關鍵敘述（引自 <a href="https://cloud.google.com/customers/wayfair">Wayfair Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>商品數量</td>
          <td>22 M+ 個 SKU</td>
      </tr>
      <tr>
          <td>供應商數量</td>
          <td>16,000+</td>
      </tr>
      <tr>
          <td>員工數</td>
          <td>17,000</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>北美 + 歐洲</td>
      </tr>
      <tr>
          <td>峰值事件</td>
          <td>Way Day（年度大促）、Black Friday、Cyber Monday</td>
      </tr>
      <tr>
          <td>COVID Q2 2020 業績</td>
          <td>美國淨營收成長 +82.5%</td>
      </tr>
      <tr>
          <td>架構模式</td>
          <td>Hybrid（on-prem + GCP burst）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：BigQuery（資料倉儲）、Cloud Dataproc（資料處理）、Cloud Pub/Sub（資料注入）、Looker（dashboard）、Cloud DLP（合規）、C2 processors（高性能 compute）。</p>
<p>關鍵敘述：「Our automation systems signal the cloud to scale on demand」「We were able to reduce and eventually eliminate the need for change freezes leading up to big events」。</p>
<h2 id="判讀">判讀</h2>
<p>Wayfair 揭露三個 hybrid cloud burst 模式的工程重點。</p>
<ol>
<li><strong>Hybrid burst 是「容量規劃成本平衡」的折衷</strong>：自家 data center 平日跑得便宜、峰值事件不夠用；全部上雲峰值好辦但平日成本高。Hybrid 模式讓 baseline 用便宜的、峰值用彈性的、總成本曲線最平。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的長期 TCO 規劃。</li>
<li><strong>「Change freeze 不再需要」是 burst 模式的真正價值</strong>：傳統零售 IT 為了 Black Friday 通常 2-3 個月前就 freeze code change、確保穩定。Wayfair 在 GCP burst 上線後、能在峰值前繼續正常 release — 因為新功能可以單獨 deploy 到 GCP、不影響 on-prem 主系統。對應 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate</a> 的非凍結式變更管理。</li>
<li><strong>資料平面（BigQuery / Dataproc）是 hybrid 的主場、交易平面仍在 on-prem</strong>：Wayfair 把「分析、報表、推薦模型」放 GCP、「核心交易、訂單處理、庫存」仍在自家。這個切分是 hybrid 的常見做法 — 計算密集的工作上雲、業務核心保留自管。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的核心 OLTP 跟 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的分析資料層分離。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>Wayfair 案例 <em>沒有</em> 提具體 TPS、latency、capacity scale 數字 — 行銷敘述居多、工程細節較少。讀此類案例要對 <em>策略</em> 做學習、不要套用具體數字。</li>
<li>「82.5% 美國淨營收成長」是 <em>業績</em>、不是 <em>系統指標</em>。系統能撐業績、但兩者不是同一件事。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Hybrid burst 適合「業務核心 on-prem 已穩定 + 季節性 / 事件型峰值」的企業</strong>：對於全新雲原生 startup、直接全上雲更簡單；對於有 15-20 年自建系統的大企業、hybrid 是穩妥路徑。</li>
<li><strong>資料平面先上雲、交易平面後上</strong>：BI、ML、推薦這類「計算密集 + 資料量大 + 容忍延遲」適合先上 GCP / AWS / Azure；OLTP 後續再評估。對應 <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</a> 的資料層先行模式。</li>
<li><strong>automation signal + 雲端 burst 是「change freeze」的解法</strong>：監控訊號 → 自動 trigger 雲端容量 → 平滑釋放 → 不影響 on-prem 主系統的部署節奏。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a>。</li>
</ol>
<p>跨平台等效：AWS Outposts + AWS Direct Connect、Azure Arc + ExpressRoute、Equinix + 各雲商 PrivateLink 都是 hybrid burst 的基礎設施。差異是各家 hybrid 策略成熟度。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 hybrid cloud burst → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a></li>
<li>想做資料平面遷移 → <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</a> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>對照全雲原生 → <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></li>
<li>想取消 change freeze → <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">06.8 release gate</a> + <a href="/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">06.17 feature flag governance</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/customers/wayfair">Wayfair Case Study (Google Cloud)</a></li>
<li><a href="https://cloud.google.com/blog/topics/customers">Way Day 2019 burst capacity</a></li>
</ul>
]]></content:encoded></item><item><title>AWS Cost Explorer</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/</guid><description>&lt;p>AWS Cost Explorer 的核心責任是提供 AWS-native 的成本、用量、forecast、reservation 與 rightsizing 分析入口。它適合 AWS-first 團隊把帳單變化拆到 account、service、region、tag、usage type 與 time range，並把成本訊號接回容量規劃與服務 owner review。&lt;/p>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>AWS Cost Explorer 適合做 AWS 成本分析的 baseline。當團隊需要回答「哪個服務、帳號、tag 或 usage type 造成成本變化」，Cost Explorer 可以直接使用 AWS billing data 產生圖表、report、forecast 與 API 查詢。&lt;/p>
&lt;p>這個定位讓 AWS Cost Explorer 接到三個主章。它從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 接收 cost per request 與 cost curve，從 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性&lt;/a> 接收成本 dashboard 需求，從 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因&lt;/a> 接收 tag 與 ownership 規則。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage&lt;/a> 等 multi-cloud FinOps 平台比、Cost Explorer 走 &lt;em>AWS-native + free&lt;/em>：不另收費（API 查詢按 request 收 USD 0.01）、跟 Billing Console + CUR + Budgets + Anomaly Detection 同一 IAM 邊界、tag 與 Cost Category 設定直接從 billing data 拉。換來的限制是 &lt;em>只看 AWS&lt;/em>、跨雲 / Kubernetes pod-level / SaaS license 都要外接。&lt;/p>
&lt;h2 id="最短判讀路徑">最短判讀路徑&lt;/h2>
&lt;p>判斷 Cost Explorer 是否健康發揮、最少看四件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Cost Explorer view 是否有 saved report&lt;/strong>：team-level saved report（依 service / linked account / tag 拆）、月度 review checklist、有沒有人定期看 trend、view 是否進 dashboard share&lt;/li>
&lt;li>&lt;strong>CUR（Cost &amp;amp; Usage Report）設定&lt;/strong>：是否啟用 CUR 2.0 / Data Exports、S3 bucket 是否打開 Athena / QuickSight 查詢、hourly granularity 是否開、resource ID 是否開（沒開的話 tag-based allocation 拆不到 instance level）&lt;/li>
&lt;li>&lt;strong>Budgets + Anomaly Detection alert routing&lt;/strong>：service-level / account-level budget threshold、Cost Anomaly Detection monitor 是否分 service / linked account 設定、alert 接到 Slack / PagerDuty / email、誰負責 triage&lt;/li>
&lt;li>&lt;strong>Tag policy + Cost Category 治理&lt;/strong>：哪些 cost allocation tag 已啟用（在 Billing Console activate 才會進 CUR）、untagged resource 比例、Cost Category rule 是否覆蓋多帳號合併、誰維護 rule lifecycle&lt;/li>
&lt;/ul>
&lt;p>四件事任一缺失就是 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 邊界的待補項目 — CUR 沒開就只能看 console aggregated view、CUR 開了沒接 Athena / QuickSight 就只能看 Console 介面、不能跟 release / capacity 資料 join。&lt;/p></description><content:encoded><![CDATA[<p>AWS Cost Explorer 的核心責任是提供 AWS-native 的成本、用量、forecast、reservation 與 rightsizing 分析入口。它適合 AWS-first 團隊把帳單變化拆到 account、service、region、tag、usage type 與 time range，並把成本訊號接回容量規劃與服務 owner review。</p>
<h2 id="定位">定位</h2>
<p>AWS Cost Explorer 適合做 AWS 成本分析的 baseline。當團隊需要回答「哪個服務、帳號、tag 或 usage type 造成成本變化」，Cost Explorer 可以直接使用 AWS billing data 產生圖表、report、forecast 與 API 查詢。</p>
<p>這個定位讓 AWS Cost Explorer 接到三個主章。它從 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 接收 cost per request 與 cost curve，從 <a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a> 接收成本 dashboard 需求，從 <a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因</a> 接收 tag 與 ownership 規則。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth</a> / <a href="/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage</a> 等 multi-cloud FinOps 平台比、Cost Explorer 走 <em>AWS-native + free</em>：不另收費（API 查詢按 request 收 USD 0.01）、跟 Billing Console + CUR + Budgets + Anomaly Detection 同一 IAM 邊界、tag 與 Cost Category 設定直接從 billing data 拉。換來的限制是 <em>只看 AWS</em>、跨雲 / Kubernetes pod-level / SaaS license 都要外接。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Cost Explorer 是否健康發揮、最少看四件事：</p>
<ul>
<li><strong>Cost Explorer view 是否有 saved report</strong>：team-level saved report（依 service / linked account / tag 拆）、月度 review checklist、有沒有人定期看 trend、view 是否進 dashboard share</li>
<li><strong>CUR（Cost &amp; Usage Report）設定</strong>：是否啟用 CUR 2.0 / Data Exports、S3 bucket 是否打開 Athena / QuickSight 查詢、hourly granularity 是否開、resource ID 是否開（沒開的話 tag-based allocation 拆不到 instance level）</li>
<li><strong>Budgets + Anomaly Detection alert routing</strong>：service-level / account-level budget threshold、Cost Anomaly Detection monitor 是否分 service / linked account 設定、alert 接到 Slack / PagerDuty / email、誰負責 triage</li>
<li><strong>Tag policy + Cost Category 治理</strong>：哪些 cost allocation tag 已啟用（在 Billing Console activate 才會進 CUR）、untagged resource 比例、Cost Category rule 是否覆蓋多帳號合併、誰維護 rule lifecycle</li>
</ul>
<p>四件事任一缺失就是 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 邊界的待補項目 — CUR 沒開就只能看 console aggregated view、CUR 開了沒接 Athena / QuickSight 就只能看 Console 介面、不能跟 release / capacity 資料 join。</p>
<h2 id="適用場景">適用場景</h2>
<p>AWS 月度成本 review 是 Cost Explorer 的主要入口。團隊可以依 service、linked account、region、tag、cost category、purchase option 或 usage type 檢視趨勢，找出 EC2、RDS、S3、NAT Gateway、Data Transfer 或 managed service 的成本變化。</p>
<p>Forecast 與 trend review 適合用 Cost Explorer 連到容量規劃。月中 forecast、daily cost trend、commitment utilization 與 reservation recommendation 可以讓平台團隊提前調整 autoscaling、instance family、reserved capacity 或 service 配置。</p>
<p>Programmatic cost query 適合接內部 dashboard。Cost Explorer API 可以把成本與用量資料拉到 release dashboard、capacity review、service scorecard 或 FinOps workflow，讓工程團隊在自己熟悉的介面看成本訊號。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>AWS Cost Explorer 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>AWS baseline</td>
          <td>直接使用 AWS billing data 與 Cost Management 入口</td>
          <td>Tag policy、Cost Category 設計</td>
      </tr>
      <tr>
          <td>Report</td>
          <td>支援 service、account、region、tag、usage type 分析</td>
          <td>owner mapping、business context</td>
      </tr>
      <tr>
          <td>Forecast</td>
          <td>支援成本預測與趨勢判讀</td>
          <td>release marker、event calendar</td>
      </tr>
      <tr>
          <td>API</td>
          <td>支援把 cost query 接到內部工具</td>
          <td>cache、權限控管、查詢成本治理</td>
      </tr>
  </tbody>
</table>
<p>AWS baseline 價值來自資料來源直接。Cost Explorer 使用 AWS 成本與用量資料，適合作為其他 FinOps 工具導入前的共同對帳入口。</p>
<p>Report 價值來自快速拆解。當某月成本上升，工程團隊可以先用 service、usage type、region 與 tag 找出最大變動，再決定是否需要更細的 workload-level 或 Kubernetes-level 工具。</p>
<p>API 價值來自流程整合。把 cost query 接到 release note、incident review 或 capacity planning dashboard，能讓成本變化跟部署、流量與容量決策同時被檢視。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>AWS Cost Explorer 和 Vantage 的主要差異是範圍。Cost Explorer 是 AWS-native 成本入口；Vantage 適合跨 provider、Kubernetes 成本與工程團隊自助報表。</p>
<p>AWS Cost Explorer 和 CloudHealth 的主要差異是治理層級。Cost Explorer 適合 AWS account 與 service-level 分析；CloudHealth 適合 enterprise FinOps policy、showback / chargeback 與多雲治理。</p>
<p>AWS Cost Explorer 和 Akamas 的主要差異是行動模型。Cost Explorer 提供成本與用量事實；Akamas 把成本、SLO 與配置調校接成 optimization loop。</p>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>AWS Cost Explorer</th>
          <th>CloudHealth</th>
          <th>Vantage</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>範圍</td>
          <td>AWS-only</td>
          <td>Multi-cloud（AWS / Azure / GCP / SaaS）</td>
          <td>Multi-cloud + Kubernetes pod-level + SaaS</td>
      </tr>
      <tr>
          <td>計費</td>
          <td>Free（API 按 request 微收）</td>
          <td>Per-cloud-spend % 或 fixed tier</td>
          <td>Per-cloud-spend % 或 fixed tier</td>
      </tr>
      <tr>
          <td>治理層級</td>
          <td>Account / service / tag / usage type</td>
          <td>Enterprise FinOps policy、showback chargeback</td>
          <td>Engineering self-serve、業務團隊自助查詢</td>
      </tr>
      <tr>
          <td>Kubernetes</td>
          <td>EKS service-level、不到 pod / namespace</td>
          <td>Container module 補位</td>
          <td>內建 Kubernetes cost allocation</td>
      </tr>
      <tr>
          <td>退場成本</td>
          <td>低 — 跟 AWS billing 同源、隨時可切</td>
          <td>中 — policy / showback rule 量多</td>
          <td>中 — query 跟 dashboard 量多</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>AWS-first、預算敏感、團隊小</td>
          <td>Enterprise、多雲、需要 chargeback</td>
          <td>Cloud-native、跨雲、engineering 自助 FinOps</td>
      </tr>
  </tbody>
</table>
<p>選 Cost Explorer 的核心訴求：<em>AWS-only + free + 跟 Billing / Budgets / Anomaly Detection 同 IAM 邊界</em>。當需求出現 <em>跨雲對帳</em> / <em>Kubernetes pod-level chargeback</em> / <em>SaaS license 整合</em>、就改走 CloudHealth / Vantage。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Cost Anomaly Detection</strong>：基於 ML 的 cost spike 偵測、按 service / linked account / cost category / tag 建 monitor、anomaly score 超 threshold 就 alert。實務治理：先用 <em>AWS services</em> monitor 全 service 跑 2-4 週看 baseline、再針對高變動 service（EC2 / Data Transfer / S3）建 dedicated monitor 拉緊 threshold、alert 接 SNS → Slack / PagerDuty。false positive 主要來自 release event 或 batch job、用 dimensional filter（exclude 特定 usage type / region）+ subscribe threshold 調 absolute USD + percentage 雙條件。</p>
<p><strong>Budgets + Forecast</strong>：Budget 可設 monthly / quarterly / annual、threshold 走 actual 跟 forecast 兩條 — forecast 達 80% 先 warn、actual 達 100% 才 page。Forecast 基於過去 historical pattern + linear extrapolation、新 workload / peak event 前要手動調整或關 forecast alert 避免噪音。Budget action 可以自動執行 IAM policy / SCP（例如 dev account 超預算自動 detach attach role）、但 production 別開、誤殺風險高。</p>
<p><strong>CUR (Cost &amp; Usage Report) + S3 + Athena / QuickSight</strong>：CUR 是 hourly granularity、含 resource ID、reserved instance / savings plan attribution、cost allocation tag 全欄位的 raw billing data、寫到 S3 bucket（Parquet 格式）。標準 pipeline：CUR → S3 → Glue Crawler → Athena → QuickSight dashboard、或直接拉到 BigQuery / Snowflake 跟其他維度 join（release calendar / SLO / traffic）。CUR 2.0 / Data Exports 是新版、欄位 schema 穩定、recommend 新部署直接走 CUR 2.0。</p>
<p><strong>Reserved Instance + Savings Plan recommendation</strong>：Cost Explorer 內建 RI / SP recommendation engine、看 past 7 / 30 / 60 day usage、推薦 commitment term（1yr / 3yr）+ payment option（All Upfront / Partial / No Upfront）+ break-even point。實務做法：先看 <em>Compute Savings Plan</em>（覆蓋 EC2 / Fargate / Lambda）的 baseline、再看 <em>EC2 Instance Savings Plan</em>（鎖 family + region）加深、最後看 RI 鎖 specific instance type — 三層疊加可達 60-70% saving、但 commitment 風險也疊加、要對齊 capacity planning。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>Tag-based allocation 拆不到 instance / 比例異常</strong>：cost allocation tag 沒在 Billing Console activate（即使 EC2 tag 有設、billing 沒看到）— 進 Billing Console → Cost Allocation Tags → activate、要等 24hr CUR 才回填。Untagged resource 比例 &gt; 10% 直接代表 tag policy 沒落地、補 AWS Config rule 或 SCP 強制 tag。</li>
<li><strong>CUR delivery lag / 資料對不上 Console</strong>：CUR delivery 是 daily、月底結算後 finalized 還要等 1-3 天、月中看 CUR 跟 Console 有 % 差是正常 — 月中 review 用 Console、月底結算用 CUR finalized。如果 CUR 過了 48hr 還沒 delivery、檢查 S3 bucket policy 跟 CUR report status。</li>
<li><strong>Anomaly Detection false positive 多</strong>：threshold 設太嚴（absolute USD 太低 / percentage 太敏感）、或 monitor scope 太寬（包含 dev / sandbox account）— 拆 monitor 按 environment 分、production 抓 absolute USD + percentage 雙條件、dev 降低敏感度或關。</li>
<li><strong>Forecast 跳水 / 跳漲不合理</strong>：forecast 用 linear extrapolation、月中 spike / drop 會被放大、release 前 / peak event 前 forecast 不準 — 用 actual + Budget threshold 校正、別只看 forecast 決策。</li>
<li><strong>API rate limit / 查詢費用爆增</strong>：內部 dashboard 沒 cache 直接打 Cost Explorer API、每 request USD 0.01 月底結算 USD 數千 — cache 層 1hr TTL、time range 對齊 daily granularity、別 per-minute polling。</li>
<li><strong>Cost Category rule 衝突 / unallocated 過多</strong>：rule 設有 overlap 但 priority 沒設、或 rule 沒覆蓋新 service — Cost Category 走 explicit priority + default rule、新 service launch 進 owner checklist。</li>
</ul>
<h2 id="操作成本">操作成本</h2>
<p>Cost Explorer 的主要成本是資料治理。Tag、Cost Category、account structure、reservation sharing 與 owner mapping 要先整理，報表才會對工程團隊有行動意義。</p>
<p>API 整合需要查詢治理。程式化查詢要控制權限、頻率、cache、time range 與 paginated request 成本，避免內部 dashboard 造成額外查詢浪費。</p>
<p>成本解釋需要補業務 context。Cost Explorer 可以指出哪個 service 或 usage type 變貴；真正的工程判斷還要接 release、traffic、peak event、data retention、capacity policy 與 SLO 變化。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>AWS Cost Explorer 結果應回寫到 AWS cost evidence package。最小欄位包括 report name、group by、filter、time range、account、service、region、tag、usage type、forecast、recommendation、owner 與 action item。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>AWS Cost Explorer 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>Cost Explorer report、Cost Explorer API、RI / rightsizing recommendation</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>billing period、daily trend、forecast period</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>AWS Console report、API query、internal dashboard</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>tag coverage、Cost Category rule、data freshness</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>owner mapping、trend repeatability、billing delay</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>shared cost rule、multi-cloud gap、Kubernetes pod-level gap</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓 AWS 成本 review 可以重跑。Cost Explorer report 要能回答「查詢條件是什麼、成本變化在哪個維度、誰負責處理、下次如何確認改善」。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>AWS Cost Explorer 目前適合作為 AWS-first 成本案例的 baseline 工具。它可回寫到 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 的跨 DB 整併與 28% 成本下降驗證、<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 modern data architecture</a> 的 80 TB 多副本 → 單一 source of truth + 80% 分析成本下降、<a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 的 on-demand vs over-provisioned 對照、以及 <a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair GCP burst</a> 的 hybrid 模式 AWS-side baseline 釐清（即使是跨雲案例、AWS 側的 review 仍可用 Cost Explorer 跑）。</p>
<p>這些案例的重點是成本訊號到工程行動的轉換。Cost Explorer 頁引用案例時，要把 report 維度、變化原因、服務 owner、容量調整與驗證方式寫成可重跑流程 — Netflix 28% 下降要對應 Aurora cluster 數、IO-Optimized 切換時機與 reader replica 配比。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a></li>
<li>跨模組：<a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">04 可觀測性成本歸因</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage</a></li>
<li>官方：<a href="https://docs.aws.amazon.com/cost-management/latest/userguide/ce-what-is.html">AWS Cost Explorer documentation</a></li>
</ul>
]]></content:encoded></item><item><title>9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 +75%、成本 -28%</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/</guid><description>&lt;p>這個案例的核心責任是說明 Netflix 在 AWS 上的「資料庫統一」決策、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS 多集群&lt;/a> 形成對照。Riot 走「single-tenant per workload、246 個 cluster」、Netflix 走「跨 application 統一 Aurora、減少 DB 種類」 — 兩條路徑都是大規模平台的 &lt;em>合理&lt;/em> 選擇、但工程哲學完全不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Netflix 在 Aurora 整合的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/blogs/database/netflix-consolidates-relational-database-infrastructure-on-amazon-aurora-achieving-up-to-75-improved-performance/">Netflix consolidates relational database infrastructure on Amazon Aurora&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>效能提升&lt;/td>
 &lt;td>up to 75%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本下降&lt;/td>
 &lt;td>28%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>月串流時數&lt;/td>
 &lt;td>billions of hours&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>global&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>整合範圍&lt;/td>
 &lt;td>多套 relational DB → Aurora&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務架構&lt;/td>
 &lt;td>全球分散式 microservices&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容器編排&lt;/td>
 &lt;td>Amazon EKS&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Netflix 整體 AWS 使用：「Netflix uses AWS to deliver billions of hours of content monthly and runs its analytics platform for optimum performance of its global service. AWS enables Netflix to quickly deploy thousands of servers and terabytes of storage within minutes.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Netflix Aurora 整合揭露三個大規模平台 DB 治理重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>「DB 種類太多」本身是規模化的成本&lt;/strong>：Netflix 過往用 PostgreSQL、MySQL、Oracle 等不同 RDB、每個都需要不同 DBA 知識、不同備份、不同 monitoring 流程。整合到 Aurora 不只是「換 DB」、是「降低運維 surface area」、釋放工程資源。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的人力成本工程化、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &amp;#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &amp;#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom&lt;/a> 同類訴求。&lt;/li>
&lt;li>&lt;strong>75% performance improvement 是 Aurora storage layer 的本質優勢&lt;/strong>：Aurora 把 storage 跟 compute 分離、storage 用分散式 log-based 設計、replication 在 storage 層處理、不在 compute 層 — 這讓 read replica 不會受 master 寫入壓力影響、性能曲線比傳統 RDB 平滑。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的儲存層 vs 計算層分離。&lt;/li>
&lt;li>&lt;strong>Netflix 的 DB 工作負載大多是「微服務私有 store」&lt;/strong>：Netflix 微服務各自有自己的 Aurora cluster、不共用 — 跟 monolith 「一個大 DB 撐全部」相反。這層架構讓「DB 容量規劃」變成「每個微服務的容量規劃」、複雜度分散。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 service decomposition、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&amp;#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&amp;#43; 個微服務承載 8 倍峰值流量、跨 200&amp;#43; 城市">9.C7 Lyft 微服務&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Netflix 在 AWS 上的「資料庫統一」決策、跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS 多集群</a> 形成對照。Riot 走「single-tenant per workload、246 個 cluster」、Netflix 走「跨 application 統一 Aurora、減少 DB 種類」 — 兩條路徑都是大規模平台的 <em>合理</em> 選擇、但工程哲學完全不同。</p>
<h2 id="觀察">觀察</h2>
<p>Netflix 在 Aurora 整合的關鍵敘述（引自 <a href="https://aws.amazon.com/blogs/database/netflix-consolidates-relational-database-infrastructure-on-amazon-aurora-achieving-up-to-75-improved-performance/">Netflix consolidates relational database infrastructure on Amazon Aurora</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>效能提升</td>
          <td>up to 75%</td>
      </tr>
      <tr>
          <td>成本下降</td>
          <td>28%</td>
      </tr>
      <tr>
          <td>月串流時數</td>
          <td>billions of hours</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>global</td>
      </tr>
      <tr>
          <td>整合範圍</td>
          <td>多套 relational DB → Aurora</td>
      </tr>
      <tr>
          <td>微服務架構</td>
          <td>全球分散式 microservices</td>
      </tr>
      <tr>
          <td>容器編排</td>
          <td>Amazon EKS</td>
      </tr>
  </tbody>
</table>
<p>Netflix 整體 AWS 使用：「Netflix uses AWS to deliver billions of hours of content monthly and runs its analytics platform for optimum performance of its global service. AWS enables Netflix to quickly deploy thousands of servers and terabytes of storage within minutes.」</p>
<h2 id="判讀">判讀</h2>
<p>Netflix Aurora 整合揭露三個大規模平台 DB 治理重點。</p>
<ol>
<li><strong>「DB 種類太多」本身是規模化的成本</strong>：Netflix 過往用 PostgreSQL、MySQL、Oracle 等不同 RDB、每個都需要不同 DBA 知識、不同備份、不同 monitoring 流程。整合到 Aurora 不只是「換 DB」、是「降低運維 surface area」、釋放工程資源。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本工程化、跟 <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> 同類訴求。</li>
<li><strong>75% performance improvement 是 Aurora storage layer 的本質優勢</strong>：Aurora 把 storage 跟 compute 分離、storage 用分散式 log-based 設計、replication 在 storage 層處理、不在 compute 層 — 這讓 read replica 不會受 master 寫入壓力影響、性能曲線比傳統 RDB 平滑。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的儲存層 vs 計算層分離。</li>
<li><strong>Netflix 的 DB 工作負載大多是「微服務私有 store」</strong>：Netflix 微服務各自有自己的 Aurora cluster、不共用 — 跟 monolith 「一個大 DB 撐全部」相反。這層架構讓「DB 容量規劃」變成「每個微服務的容量規劃」、複雜度分散。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 service decomposition、跟 <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 微服務</a>。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「effective 75% improvement」是 <em>跨多個 workload 的最大改善幅度</em>、不是「每個 workload 都 +75%」。實際每個 workload 改善幅度從 10% 到 75% 不等。</li>
<li>Netflix 數據層遠不止 Aurora — 還有 Cassandra（playback metadata）、EVCache（cache layer）、Iceberg（data warehouse）。Aurora 主要是「需要 ACID 的 OLTP 工作負載」、不是「all-purpose store」。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>DB 種類整合是規模化的必要工程</strong>：每多一種 DB 就多一套運維 surface。在能合理 consolidate 的時候整合、降低 ops 複雜度。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 vendor diversity 取捨。</li>
<li><strong>storage / compute 分離是 OLTP 擴容的關鍵</strong>：Aurora、Spanner、TiDB 都採類似設計、是現代 cloud DB 的共同特徵。對應 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的 storage layer 設計。</li>
<li><strong>微服務私有 store 比共用 DB 容量規劃簡單</strong>：每個服務各自管 DB 容量、跨服務 contention 變成 <em>network 議題</em> 而非 <em>DB lock 議題</em>。</li>
<li><strong>大規模平台必須區分「OLTP 用 Aurora」「analytics 用 data lake」「KV 用 DynamoDB」「cache 用 EVCache」</strong>：Netflix 用各種 DB、不是一招打天下。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 polyglot persistence。</li>
</ol>
<p>跨平台等效：GCP Spanner（替代 OLTP）+ Bigtable（替代 KV）+ BigQuery（替代 analytics）；Azure Cosmos DB（替代多 model）+ SQL Hyperscale + Synapse — 各雲商提供類似 stack。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他大規模平台 → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS</a>（不同 consolidation 策略）</li>
<li>想理解 Aurora 設計 → <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> + <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a></li>
<li>想做 polyglot persistence 選型 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
<li>想做 DB consolidation 規劃 → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想理解 +75% 的 storage / compute 解耦根因 → <a href="/blog/backend/01-database/vendors/aurora/storage-architecture/" data-link-title="Aurora Storage Architecture：quorum-based 分散式 log 與韌性即性能設計" data-link-desc="Aurora storage / compute 分離、6-way 跨 AZ replication、4-of-6 write / 3-of-6 read quorum、韌性投資自動 amortize 成 read 性能、DraftKings 6ms 寫 / &lt;1ms 讀 production reference">Aurora 儲存層架構</a></li>
<li>想規劃自管 PostgreSQL / MySQL 遷入 Aurora 的步驟 → <a href="/blog/backend/01-database/vendors/aurora/migrate-from-self-managed-pg-mysql/" data-link-title="從自管 PostgreSQL / MySQL 遷到 Aurora：operational redesign migration playbook" data-link-desc="PostgreSQL / MySQL → Aurora 的 Type C operational redesign hybrid playbook、6 規格面（Driver / Diff audit / Phase plan / Evidence / Cutover / Cleanup）、Standard Chartered 合規 lead time 模型、Netflix 非 all-purpose store 邊界">從自管 PostgreSQL/MySQL 遷入 Aurora</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/database/netflix-consolidates-relational-database-infrastructure-on-amazon-aurora-achieving-up-to-75-improved-performance/">Netflix consolidates relational database infrastructure on Amazon Aurora, achieving up to 75% improved performance</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/innovators/netflix/">Netflix on AWS</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/netflix-case-study/">Netflix Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/</guid><description>&lt;p>這個案例的核心責任是說明 B2B SaaS 平台的容量規劃跟 C2C 案例的本質差異。Genesys 服務的是 &lt;em>客戶服務中心&lt;/em> — 客戶停線 = 全終端使用者打不通電話、客戶會失去信任。99.999% 可用性（年停機 5 分鐘）對 B2B 客服 SaaS 是合約義務、不是行銷敘述。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Genesys Cloud 在 DynamoDB 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/genesys-dynamodb-case-study/">Genesys DynamoDB Case Study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>客戶組織&lt;/td>
 &lt;td>8,000+ 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務國家&lt;/td>
 &lt;td>100+ 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主 region&lt;/td>
 &lt;td>15 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>衛星 region&lt;/td>
 &lt;td>5 個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性&lt;/td>
 &lt;td>99.999%（截至 2024-07-31 的 12 個月）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務數&lt;/td>
 &lt;td>數百個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料層&lt;/td>
 &lt;td>DynamoDB 為預設、用其他要 justify&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵架構決策（引述 Chief Architect Rob Gevers）：「Amazon DynamoDB is our primary data layer by default, and teams have to justify the use of something else.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Genesys 案例揭露三個 B2B SaaS 平台容量規劃重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>B2B 可用性目標跟 C2C 不同&lt;/strong>：B2C 大型網站可能接受 99.9%（年停機 8.76 小時）、B2B SaaS 經常合約規定 99.95% 或 99.99%、客服平台類甚至要 99.999%（年停機 5 分鐘）。每多一個 9、容量規劃跟運維成本指數成長。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 SLO 等級設計。&lt;/li>
&lt;li>&lt;strong>「DynamoDB 為預設、用其他要 justify」是規模化平台的工程治理&lt;/strong>：跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix&lt;/a> 整合到 Aurora 是同樣訴求、不同實作 — Genesys 選 DynamoDB 為基準是因為「Multi-region active-active」+「自動 scaling」+「99.999% SLA」的組合最容易達成 5 個 9 目標。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 DB 預設選型。&lt;/li>
&lt;li>&lt;strong>15 主 region + 5 衛星 region = 全球客戶就近接入&lt;/strong>：客戶服務有強烈延遲敏感（agent 操作介面卡 1 秒、客服效率掉一半）、必須在客戶所在地有 region。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster&lt;/a> 的延遲驅動 region 部署同類思維。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的地理分散規劃。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>「99.999% over 12 months」是 &lt;em>截至特定時間點的歷史值&lt;/em>、不代表「未來持續達成」。可用性是滾動指標、不是恆久承諾。&lt;/li>
&lt;li>案例 &lt;em>沒有&lt;/em> 提具體 QPS / RPS、訊息量、延遲分布。讀者要對 &lt;em>策略&lt;/em> 學習、具體數字需要自己壓測。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 B2B SaaS 平台的容量規劃跟 C2C 案例的本質差異。Genesys 服務的是 <em>客戶服務中心</em> — 客戶停線 = 全終端使用者打不通電話、客戶會失去信任。99.999% 可用性（年停機 5 分鐘）對 B2B 客服 SaaS 是合約義務、不是行銷敘述。</p>
<h2 id="觀察">觀察</h2>
<p>Genesys Cloud 在 DynamoDB 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/genesys-dynamodb-case-study/">Genesys DynamoDB Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶組織</td>
          <td>8,000+ 個</td>
      </tr>
      <tr>
          <td>服務國家</td>
          <td>100+ 個</td>
      </tr>
      <tr>
          <td>主 region</td>
          <td>15 個</td>
      </tr>
      <tr>
          <td>衛星 region</td>
          <td>5 個</td>
      </tr>
      <tr>
          <td>可用性</td>
          <td>99.999%（截至 2024-07-31 的 12 個月）</td>
      </tr>
      <tr>
          <td>微服務數</td>
          <td>數百個</td>
      </tr>
      <tr>
          <td>資料層</td>
          <td>DynamoDB 為預設、用其他要 justify</td>
      </tr>
  </tbody>
</table>
<p>關鍵架構決策（引述 Chief Architect Rob Gevers）：「Amazon DynamoDB is our primary data layer by default, and teams have to justify the use of something else.」</p>
<h2 id="判讀">判讀</h2>
<p>Genesys 案例揭露三個 B2B SaaS 平台容量規劃重點。</p>
<ol>
<li><strong>B2B 可用性目標跟 C2C 不同</strong>：B2C 大型網站可能接受 99.9%（年停機 8.76 小時）、B2B SaaS 經常合約規定 99.95% 或 99.99%、客服平台類甚至要 99.999%（年停機 5 分鐘）。每多一個 9、容量規劃跟運維成本指數成長。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 SLO 等級設計。</li>
<li><strong>「DynamoDB 為預設、用其他要 justify」是規模化平台的工程治理</strong>：跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a> 整合到 Aurora 是同樣訴求、不同實作 — Genesys 選 DynamoDB 為基準是因為「Multi-region active-active」+「自動 scaling」+「99.999% SLA」的組合最容易達成 5 個 9 目標。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 DB 預設選型。</li>
<li><strong>15 主 region + 5 衛星 region = 全球客戶就近接入</strong>：客戶服務有強烈延遲敏感（agent 操作介面卡 1 秒、客服效率掉一半）、必須在客戶所在地有 region。跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a> 的延遲驅動 region 部署同類思維。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的地理分散規劃。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「99.999% over 12 months」是 <em>截至特定時間點的歷史值</em>、不代表「未來持續達成」。可用性是滾動指標、不是恆久承諾。</li>
<li>案例 <em>沒有</em> 提具體 QPS / RPS、訊息量、延遲分布。讀者要對 <em>策略</em> 學習、具體數字需要自己壓測。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>B2B SaaS 平台優先選 multi-region active-active 資料層</strong>：DynamoDB Global Tables、Cosmos DB Multi-Region Write、Spanner multi-region 都是候選。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的全球一致性取捨。</li>
<li><strong>「預設 DB」原則簡化 onboarding</strong>：新團隊不用評估十種 DB、預設用 X、特殊需求再 justify。減少團隊認知負擔、加速產品開發。對應 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a> 的 DB 整合。</li>
<li><strong>99.999% 必須有 redundancy 在每一層</strong>：DNS、load balancer、application、database、storage 都要跨 region active-active。任何一層 single-region 就破壞整體 SLO。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 跟 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06 可靠性驗證模組</a>。</li>
<li><strong>多 region 是成本 vs 可用性的硬取捨</strong>：15 個 region 的成本約是 1 個 region 的 15 倍 — 對 B2B SaaS 是合理投資、對 B2C 通常不划算。</li>
</ol>
<p>跨平台等效：Azure Cosmos DB Multi-Region Write、GCP Spanner multi-region、Cassandra multi-DC 都可實作對等架構。差異是 region 數量、SLA 承諾、跨 region 延遲。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計 B2B SaaS 可用性 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> + <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">06.6 SLO 與 Error Budget 政策</a></li>
<li>想設計多 region 資料層 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想做 DB 統一治理 → <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想規劃跨 region 容量 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a></li>
<li>想理解 DynamoDB 99.999% 背後的 partition / GSI 設計 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a> + <a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">DynamoDB GSI / LSI 設計</a></li>
<li>想對應 global tables 多 region 寫衝突 → <a href="/blog/backend/01-database/vendors/dynamodb/global-tables-conflict/" data-link-title="DynamoDB Global Tables：multi-region active-active、LWW conflict 與 cross-device sync 正向用例" data-link-desc="Global Tables 不只是 conflict 痛點、也是 cross-device sync / global read / DR failover 的正向工程方案；本文展開 B2B SaaS vs B2C 業務 driver、LWW conflict resolution、reconciliation pipeline，含 Genesys 99.999% 跨 15 region 跟 Disney&#43; 跨裝置同步的對照">DynamoDB global tables 寫衝突</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/genesys-dynamodb-case-study/">Genesys Achieves 99.999% Availability Using Amazon DynamoDB</a></li>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
</ul>
]]></content:encoded></item><item><title>9.C25 Tubi：從 ScyllaDB 遷到 ElastiCache、ML feature store 達 sub-10ms p99</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tubi-elasticache-ml-feature-store/</guid><description>&lt;p>這個案例的核心責任是說明「ML feature store 的延遲敏感層」工程選型。即時推薦（首頁 carousel、播放後下一支）需要在 100ms 內生成、ML inference 之前的 feature lookup 通常吃 30-50ms — 把 lookup 壓到 10ms 以下、整個推薦延遲才有預算空間。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Tubi 在 ElastiCache 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/elasticache/customers/">ElastiCache Customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>工作負載&lt;/td>
 &lt;td>ML inference feature store&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>p99 延遲&lt;/td>
 &lt;td>&amp;lt; 10 ms&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移路徑&lt;/td>
 &lt;td>ScyllaDB → ElastiCache for Redis&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業務場景&lt;/td>
 &lt;td>串流推薦（free streaming service）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Tubi 案例揭露三個 ML feature store 容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>feature store 是 ML inference 的 critical path&lt;/strong>：每個推薦請求都要查 N 個 feature（user_profile、item_metadata、recent_interactions、similar_users 等）、每個 feature 查詢都吃 latency budget。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的多 stage budget 分解。&lt;/li>
&lt;li>&lt;strong>ScyllaDB → ElastiCache 是「持久 KV → 純 cache」的權衡&lt;/strong>：ScyllaDB 是 Cassandra-compatible 高吞吐 KV、提供 durability；ElastiCache 是 in-memory cache、可以 cache miss。Tubi 選 cache 是判斷「feature 可以重新計算」、durability 不必、純 in-memory 更快。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache vs durable store 選型。&lt;/li>
&lt;li>&lt;strong>p99 才是 ML 系統的容量門檻&lt;/strong>：ML 系統的 user-perceived latency 是 &lt;em>最後完成的 inference&lt;/em>、不是平均。p50 快沒用、p99 慢用戶就看到 loading spinner。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery&lt;/a> 的 latency percentile 分析、跟 &lt;a href="https://tarrragon.github.io/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 &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase&lt;/a> 的長尾延遲議題同類。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>「sub-10ms p99」沒指明 &lt;em>p999 / p9999&lt;/em>。p9999 通常比 p99 高一個量級、會出現在實際 user-perceived 體驗。&lt;/li>
&lt;li>ElastiCache 的 sub-10ms 是 &lt;em>cache hit 路徑&lt;/em> — cache miss 路徑會回到 ScyllaDB 或重新計算、延遲可能 100ms+。容量規劃要考慮 cache hit rate 跟 miss recovery 兩條路徑。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>ML feature store 用「兩層 cache」設計&lt;/strong>：L1 是 in-process cache（最熱的 features）、L2 是 ElastiCache / Memcached（次熱）、L3 才是持久 store（ScyllaDB / DynamoDB / S3 + Parquet）。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 cache hierarchy。&lt;/li>
&lt;li>&lt;strong>feature 可重算 → 用 cache、feature 必須持久 → 用 store&lt;/strong>：判斷依據是「重算成本」跟「資料一致性需求」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary&lt;/a>。&lt;/li>
&lt;li>&lt;strong>p99 / p999 反推單個 stage latency 上限&lt;/strong>：每個 stage（network、cache lookup、feature aggregation、model inference、response serialization）給一個 latency budget、總和等於整體 SLO。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a>、跟 &lt;a href="https://tarrragon.github.io/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 &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3 Coinbase&lt;/a> 同樣的反推思維。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：AWS ElastiCache for Redis / Valkey / MemoryDB、GCP Memorystore for Redis、Azure Cache for Redis 都可實作對等架構。專為 ML feature store 設計的還有 Feast / Tecton / Hopsworks 等開源 + 商業方案、底層常用 Redis-compatible store。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「ML feature store 的延遲敏感層」工程選型。即時推薦（首頁 carousel、播放後下一支）需要在 100ms 內生成、ML inference 之前的 feature lookup 通常吃 30-50ms — 把 lookup 壓到 10ms 以下、整個推薦延遲才有預算空間。</p>
<h2 id="觀察">觀察</h2>
<p>Tubi 在 ElastiCache 的關鍵敘述（引自 <a href="https://aws.amazon.com/elasticache/customers/">ElastiCache Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>工作負載</td>
          <td>ML inference feature store</td>
      </tr>
      <tr>
          <td>p99 延遲</td>
          <td>&lt; 10 ms</td>
      </tr>
      <tr>
          <td>遷移路徑</td>
          <td>ScyllaDB → ElastiCache for Redis</td>
      </tr>
      <tr>
          <td>業務場景</td>
          <td>串流推薦（free streaming service）</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<p>Tubi 案例揭露三個 ML feature store 容量設計重點。</p>
<ol>
<li><strong>feature store 是 ML inference 的 critical path</strong>：每個推薦請求都要查 N 個 feature（user_profile、item_metadata、recent_interactions、similar_users 等）、每個 feature 查詢都吃 latency budget。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的多 stage budget 分解。</li>
<li><strong>ScyllaDB → ElastiCache 是「持久 KV → 純 cache」的權衡</strong>：ScyllaDB 是 Cassandra-compatible 高吞吐 KV、提供 durability；ElastiCache 是 in-memory cache、可以 cache miss。Tubi 選 cache 是判斷「feature 可以重新計算」、durability 不必、純 in-memory 更快。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache vs durable store 選型。</li>
<li><strong>p99 才是 ML 系統的容量門檻</strong>：ML 系統的 user-perceived latency 是 <em>最後完成的 inference</em>、不是平均。p50 快沒用、p99 慢用戶就看到 loading spinner。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a> 的 latency percentile 分析、跟 <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</a> 的長尾延遲議題同類。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「sub-10ms p99」沒指明 <em>p999 / p9999</em>。p9999 通常比 p99 高一個量級、會出現在實際 user-perceived 體驗。</li>
<li>ElastiCache 的 sub-10ms 是 <em>cache hit 路徑</em> — cache miss 路徑會回到 ScyllaDB 或重新計算、延遲可能 100ms+。容量規劃要考慮 cache hit rate 跟 miss recovery 兩條路徑。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>ML feature store 用「兩層 cache」設計</strong>：L1 是 in-process cache（最熱的 features）、L2 是 ElastiCache / Memcached（次熱）、L3 才是持久 store（ScyllaDB / DynamoDB / S3 + Parquet）。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 cache hierarchy。</li>
<li><strong>feature 可重算 → 用 cache、feature 必須持久 → 用 store</strong>：判斷依據是「重算成本」跟「資料一致性需求」。對應 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a>。</li>
<li><strong>p99 / p999 反推單個 stage latency 上限</strong>：每個 stage（network、cache lookup、feature aggregation、model inference、response serialization）給一個 latency budget、總和等於整體 SLO。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a>、跟 <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</a> 同樣的反推思維。</li>
</ol>
<p>跨平台等效：AWS ElastiCache for Redis / Valkey / MemoryDB、GCP Memorystore for Redis、Azure Cache for Redis 都可實作對等架構。專為 ML feature store 設計的還有 Feast / Tecton / Hopsworks 等開源 + 商業方案、底層常用 Redis-compatible store。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 ML feature store → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a></li>
<li>想做 p99 / p999 反推 → <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</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.4 Saturation Discovery</a></li>
<li>對照其他 cache 案例 → <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>（配對引擎）</li>
<li>想理解 cache hierarchy → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/elasticache/customers/">Amazon ElastiCache Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/build-an-ultra-low-latency-online-feature-store-for-real-time-inferencing-using-amazon-elasticache-for-redis/">Build an ultra-low latency online feature store for real-time inferencing using Amazon ElastiCache for Redis</a></li>
</ul>
]]></content:encoded></item><item><title>9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/</guid><description>&lt;p>這個案例的核心責任是說明「行動支付類 SaaS」的訊息工作負載特性。PayPay 是日本最大行動支付（pre-IPO 估值 70 億美金級）、訊息功能需要在每筆交易後即時通知（付款成功、收款、優惠券）、單一用戶每天可能收到數十條訊息、加總到平台級別就是每日上億訊息。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>PayPay 在 DynamoDB 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>每日訊息量&lt;/td>
 &lt;td>3 億訊息&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要工作負載&lt;/td>
 &lt;td>行動支付通知 + 訊息功能&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可靠性敘述&lt;/td>
 &lt;td>「Super reliable and performed consistently」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Amazon DynamoDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>日本&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>PayPay 案例揭露三個行動支付訊息系統的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>支付通知是「不可丟失 + 不可延遲」雙重需求&lt;/strong>：用戶付完款 30 秒沒收到通知會懷疑系統壞了、會打客服 / 重複扣款。這層需求比 OTA 推播嚴格、必須有 durable queue + retry + 重複偵測。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的 idempotency 設計。&lt;/li>
&lt;li>&lt;strong>DynamoDB 在「訊息事件」這類負載特別適合&lt;/strong>：每則訊息有獨立 message_id（partition key 天然均勻）、TTL 機制可以自動清理過期訊息（避免 storage 爆炸）。對應 &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的 partition 均勻優勢、跟 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary&lt;/a> 的 TTL 議題。&lt;/li>
&lt;li>&lt;strong>3 億 / 天 ≈ 3,500 訊息 / 秒平均&lt;/strong>：聽起來不大、但這是 &lt;em>平均&lt;/em>。月底、雙 11 類大促、新年紅包等場景、單秒峰值可能達 10x-50x。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a> 的峰均比評估。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「super reliable」是行銷語言、不是工程承諾。讀此類短篇案例要把行銷敘述折扣、重點看 &lt;em>服務組合&lt;/em> 與 &lt;em>規模量級&lt;/em>。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>訊息系統設計區分「通知」跟「訊息」&lt;/strong>：通知（payment received）是 transactional、不可丟失；訊息（marketing）可以丟失部分、重點是 throughput。兩者用不同 SLO、不同 storage。對應 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> 的訊息分類。&lt;/li>
&lt;li>&lt;strong>TTL 自動清理避免 storage 成本爆炸&lt;/strong>：3 億 / 天 × 30 天 = 90 億筆記錄、不清理會撐死 storage 預算。對應 &lt;a href="https://tarrragon.github.io/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組&lt;/a> 的 TTL 設計。&lt;/li>
&lt;li>&lt;strong>訊息推送的下游（APNs、FCM、SMS gateway）是隱性瓶頸&lt;/strong>：DynamoDB 寫入可以撐 3K msg/sec、但 APNs 一天的 quota 是有限的。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a> 的依賴鏈分析。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：GCP Firestore + Cloud Messaging、Azure Cosmos DB + Notification Hubs 都是對等架構。差異是 vendor 整合度跟全球分發能力。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>想設計行動支付訊息 → &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>&lt;/li>
&lt;li>對照其他 KV 高吞吐 → &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> / &lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/li>
&lt;li>想做訊息系統容量規劃 → &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> + &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling&lt;/a>&lt;/li>
&lt;li>想避免訊息熱點打爆單一 partition → &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &amp;#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式&lt;/a>&lt;/li>
&lt;li>想評估訊息系統的 capacity mode → &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://aws.amazon.com/solutions/case-studies/paypay/">PayPay on AWS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「行動支付類 SaaS」的訊息工作負載特性。PayPay 是日本最大行動支付（pre-IPO 估值 70 億美金級）、訊息功能需要在每筆交易後即時通知（付款成功、收款、優惠券）、單一用戶每天可能收到數十條訊息、加總到平台級別就是每日上億訊息。</p>
<h2 id="觀察">觀察</h2>
<p>PayPay 在 DynamoDB 的關鍵敘述（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每日訊息量</td>
          <td>3 億訊息</td>
      </tr>
      <tr>
          <td>主要工作負載</td>
          <td>行動支付通知 + 訊息功能</td>
      </tr>
      <tr>
          <td>可靠性敘述</td>
          <td>「Super reliable and performed consistently」</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Amazon DynamoDB</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>日本</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀">判讀</h2>
<p>PayPay 案例揭露三個行動支付訊息系統的工程重點。</p>
<ol>
<li><strong>支付通知是「不可丟失 + 不可延遲」雙重需求</strong>：用戶付完款 30 秒沒收到通知會懷疑系統壞了、會打客服 / 重複扣款。這層需求比 OTA 推播嚴格、必須有 durable queue + retry + 重複偵測。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的 idempotency 設計。</li>
<li><strong>DynamoDB 在「訊息事件」這類負載特別適合</strong>：每則訊息有獨立 message_id（partition key 天然均勻）、TTL 機制可以自動清理過期訊息（避免 storage 爆炸）。對應 <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> 的 partition 均勻優勢、跟 <a href="/blog/backend/02-cache-redis/cache-copy-freshness-boundary/" data-link-title="2.7 Cache Copy Boundary 與 Freshness" data-link-desc="說明快取何時只是可重建副本，何時會影響交易、權限或配額正確性。">02.4 cache copy freshness boundary</a> 的 TTL 議題。</li>
<li><strong>3 億 / 天 ≈ 3,500 訊息 / 秒平均</strong>：聽起來不大、但這是 <em>平均</em>。月底、雙 11 類大促、新年紅包等場景、單秒峰值可能達 10x-50x。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a> 的峰均比評估。</li>
</ol>
<p>需要警惕：「super reliable」是行銷語言、不是工程承諾。讀此類短篇案例要把行銷敘述折扣、重點看 <em>服務組合</em> 與 <em>規模量級</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>訊息系統設計區分「通知」跟「訊息」</strong>：通知（payment received）是 transactional、不可丟失；訊息（marketing）可以丟失部分、重點是 throughput。兩者用不同 SLO、不同 storage。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 的訊息分類。</li>
<li><strong>TTL 自動清理避免 storage 成本爆炸</strong>：3 億 / 天 × 30 天 = 90 億筆記錄、不清理會撐死 storage 預算。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的 TTL 設計。</li>
<li><strong>訊息推送的下游（APNs、FCM、SMS gateway）是隱性瓶頸</strong>：DynamoDB 寫入可以撐 3K msg/sec、但 APNs 一天的 quota 是有限的。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的依賴鏈分析。</li>
</ol>
<p>跨平台等效：GCP Firestore + Cloud Messaging、Azure Cosmos DB + Notification Hubs 都是對等架構。差異是 vendor 整合度跟全球分發能力。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想設計行動支付訊息 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>對照其他 KV 高吞吐 → <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> / <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</a></li>
<li>想做訊息系統容量規劃 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.2 Workload Modeling</a></li>
<li>想避免訊息熱點打爆單一 partition → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a></li>
<li>想評估訊息系統的 capacity mode → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/paypay/">PayPay on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>9.C27 Disney+：DynamoDB 撐每日數十億動作的觀看歷史</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/</guid><description>&lt;p>這個案例的核心責任是說明「串流平台 metadata 層」的工作負載 — 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL&lt;/a> 的「live streaming 直播容量」是同產業不同議題。Disney+ 的 metadata 層處理「播了什麼、看到哪、下次推薦什麼」、是串流平台的「control plane」、不是「data plane」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Disney+ 在 DynamoDB 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>每日動作量&lt;/td>
 &lt;td>billions of actions daily&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要工作負載&lt;/td>
 &lt;td>content metadata + watch list management&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Amazon DynamoDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>global&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每個用戶動作（播放、暫停、跳過、加入 watchlist、評分）都是一次 DynamoDB 寫入。每次打開 app 又是多次讀（自己的 watchlist、最近播放、繼續觀看）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Disney+ 案例揭露三個串流平台 metadata 層的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>「每日數十億動作」= read + write 都要撐&lt;/strong>：跟 &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads&lt;/a> 的 18:1 讀寫比不同、串流 metadata 通常接近 5:1 read-heavy（每動作 1 寫、每 session 5 讀）。partition key 設計通常用 user_id、天然均勻、不會 hot partition。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema design。&lt;/li>
&lt;li>&lt;strong>新片發布是 predictable-peak&lt;/strong>：Marvel / Star Wars / Disney 動畫 新片上線首日、metadata 流量可衝 3-5 倍 — 因為「全平台用戶同時打開該片頁面」。這比一般 Black Friday 集中、像 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL&lt;/a> 的集中型流量。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a> 的內容發布事件容量規劃。&lt;/li>
&lt;li>&lt;strong>watchlist + 播放進度需要跨裝置即時同步&lt;/strong>：用戶在手機看到一半、晚上回家用電視繼續、進度必須跨裝置同步。這層需求對 DynamoDB Global Tables（multi-region active-active）特別適合。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的最終一致性可接受場景。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「billions of actions daily」沒指明具體數字（10 億、100 億 還是 數十億？）。讀此類短篇案例只能取「量級對標」、不能套用具體數字。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>串流平台分「metadata 層」「content delivery 層」&lt;/strong>：metadata（watchlist、播放進度、推薦）用 DynamoDB / Cosmos DB；content（video file）用 CDN + S3 / object storage。兩者完全分開、互不影響。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 control plane vs data plane、跟 &lt;a href="https://tarrragon.github.io/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&lt;/a> 的同類思維。&lt;/li>
&lt;li>&lt;strong>新片發布像 mini Black Friday、要 pre-scaling&lt;/strong>：發布時間已知、流量倍數可預估（根據前幾部）、可以提前 1-2 天 pre-scale DynamoDB capacity。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備&lt;/a>。&lt;/li>
&lt;li>&lt;strong>DynamoDB Global Tables 是跨裝置同步的有效方案&lt;/strong>：用戶在不同 region 登入同帳號、寫入會自動同步到其他 region。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &amp;#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys&lt;/a> 的 multi-region active-active。&lt;/li>
&lt;/ol>
&lt;p>跨平台等效：Netflix 同類 metadata 用 Cassandra + EVCache（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix&lt;/a> 提及）、HBO Max 用 Aurora、Apple TV+ 用 FoundationDB + Cassandra — 各家串流的 metadata 技術棧不同、但「分層解耦」的工程哲學一致。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「串流平台 metadata 層」的工作負載 — 跟 <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</a> 的「live streaming 直播容量」是同產業不同議題。Disney+ 的 metadata 層處理「播了什麼、看到哪、下次推薦什麼」、是串流平台的「control plane」、不是「data plane」。</p>
<h2 id="觀察">觀察</h2>
<p>Disney+ 在 DynamoDB 的關鍵敘述（引自 <a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB Customers</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每日動作量</td>
          <td>billions of actions daily</td>
      </tr>
      <tr>
          <td>主要工作負載</td>
          <td>content metadata + watch list management</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Amazon DynamoDB</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>global</td>
      </tr>
  </tbody>
</table>
<p>每個用戶動作（播放、暫停、跳過、加入 watchlist、評分）都是一次 DynamoDB 寫入。每次打開 app 又是多次讀（自己的 watchlist、最近播放、繼續觀看）。</p>
<h2 id="判讀">判讀</h2>
<p>Disney+ 案例揭露三個串流平台 metadata 層的工程重點。</p>
<ol>
<li><strong>「每日數十億動作」= read + write 都要撐</strong>：跟 <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> 的 18:1 讀寫比不同、串流 metadata 通常接近 5:1 read-heavy（每動作 1 寫、每 session 5 讀）。partition key 設計通常用 user_id、天然均勻、不會 hot partition。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema design。</li>
<li><strong>新片發布是 predictable-peak</strong>：Marvel / Star Wars / Disney 動畫 新片上線首日、metadata 流量可衝 3-5 倍 — 因為「全平台用戶同時打開該片頁面」。這比一般 Black Friday 集中、像 <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</a> 的集中型流量。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的內容發布事件容量規劃。</li>
<li><strong>watchlist + 播放進度需要跨裝置即時同步</strong>：用戶在手機看到一半、晚上回家用電視繼續、進度必須跨裝置同步。這層需求對 DynamoDB Global Tables（multi-region active-active）特別適合。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的最終一致性可接受場景。</li>
</ol>
<p>需要警惕：「billions of actions daily」沒指明具體數字（10 億、100 億 還是 數十億？）。讀此類短篇案例只能取「量級對標」、不能套用具體數字。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>串流平台分「metadata 層」「content delivery 層」</strong>：metadata（watchlist、播放進度、推薦）用 DynamoDB / Cosmos DB；content（video file）用 CDN + S3 / object storage。兩者完全分開、互不影響。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 control plane vs data plane、跟 <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</a> 的同類思維。</li>
<li><strong>新片發布像 mini Black Friday、要 pre-scaling</strong>：發布時間已知、流量倍數可預估（根據前幾部）、可以提前 1-2 天 pre-scale DynamoDB capacity。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a>。</li>
<li><strong>DynamoDB Global Tables 是跨裝置同步的有效方案</strong>：用戶在不同 region 登入同帳號、寫入會自動同步到其他 region。對應 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a> 的 multi-region active-active。</li>
</ol>
<p>跨平台等效：Netflix 同類 metadata 用 Cassandra + EVCache（<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a> 提及）、HBO Max 用 Aurora、Apple TV+ 用 FoundationDB + Cassandra — 各家串流的 metadata 技術棧不同、但「分層解耦」的工程哲學一致。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他串流案例 → <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</a>（live）/ <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 NTT DOCOMO Lemino</a></li>
<li>想理解 metadata 層 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a></li>
<li>想做內容發布 pre-scaling → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</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</a></li>
<li>想做跨裝置同步設計 → <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys multi-region</a></li>
<li>想拆 metadata 的 single-table 與 GSI 設計 → <a href="/blog/backend/01-database/vendors/dynamodb/single-table-design-pattern/" data-link-title="DynamoDB Single-Table Design：從適用度前置判讀到 access pattern 反推 PK/SK" data-link-desc="DynamoDB single-table 設計不是「資料表越少越好」，而是 access pattern 反推 PK/SK 跟 GSI；本文先做 DynamoDB 適用度 4 軸前置判讀（PK 天然均勻 / control plane vs data plane / consistency / access pattern 穩定），再展開設計流程、failure modes 與 durable queue 正向用例">DynamoDB single-table design</a> + <a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">DynamoDB GSI / LSI 設計</a></li>
<li>想做跨 region metadata 一致性 → <a href="/blog/backend/01-database/vendors/dynamodb/global-tables-conflict/" data-link-title="DynamoDB Global Tables：multi-region active-active、LWW conflict 與 cross-device sync 正向用例" data-link-desc="Global Tables 不只是 conflict 痛點、也是 cross-device sync / global read / DR failover 的正向工程方案；本文展開 B2B SaaS vs B2C 業務 driver、LWW conflict resolution、reconciliation pipeline，含 Genesys 99.999% 跨 15 region 跟 Disney&#43; 跨裝置同步的對照">DynamoDB global tables 寫衝突</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/dynamodb/customers/">Amazon DynamoDB Customers</a></li>
<li><a href="https://aws.amazon.com/blogs/database/amazon-dynamodb-use-cases-for-media-and-entertainment-customers/">Amazon DynamoDB use cases for media and entertainment customers</a></li>
</ul>
]]></content:encoded></item><item><title>9.C28 FanDuel：體育直播 + 投注的雙重峰值</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/</guid><description>&lt;p>這個案例的核心責任是說明「雙重峰值對齊」的工程取捨。FanDuel 同時運營體育直播（live streaming）跟體育投注（betting）、兩個工作負載在 &lt;em>同一場 NFL Super Bowl&lt;/em> 同時達到峰值、但 SLO 完全不同 — 直播容忍 30 秒延遲、投注必須毫秒內成交。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>FanDuel 在 AWS 的關鍵敘述（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel Case Study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>月活客戶&lt;/td>
 &lt;td>3.5 M+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>美國 20+ 州 + 加拿大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值擴容倍數&lt;/td>
 &lt;td>5-10x（NFL Super Bowl 等大型賽事）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>AWS Local Zones + Wavelength + Outposts&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>峰值類型&lt;/td>
 &lt;td>直播 + 投注雙峰&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「seamlessly scale capacity 5–10 times as required for large sporting events, such as the NFL Super Bowl」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>FanDuel 案例揭露三個雙重峰值對齊的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>直播跟投注是兩種完全不同 SLO&lt;/strong>：直播容忍秒級延遲（用 CDN + ABR 串流）、投注必須毫秒級成交（Super Bowl 進球瞬間、賠率變動、用戶投注必須在賠率變化前完成）。兩個服務必須各自獨立擴容、各自獨立 SLO。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的多 SLO 對齊。&lt;/li>
&lt;li>&lt;strong>AWS Local Zones / Wavelength / Outposts 是地理 + 監管雙重需求&lt;/strong>：美國博彩受各州監管、資料必須留在州內 → 用 Local Zones 在每個州就近部署；4G/5G 用戶投注延遲敏感 → 用 Wavelength 在電信商機房內運算；on-prem 需求 → 用 Outposts。對應 &lt;a href="https://tarrragon.github.io/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&lt;/a> 的受監管雙重需求、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games&lt;/a> 的延遲反推 region。&lt;/li>
&lt;li>&lt;strong>5-10x 是「同類事件中的最高倍率」&lt;/strong>：Super Bowl 是 NFL 賽季最大事件、不是常態。平日 baseline → 季後賽 2-3x → 季冠軍賽 4-5x → Super Bowl 5-10x。容量規劃要按事件級別分段、不是一律 10x。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的事件型容量分級。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p>
&lt;ul>
&lt;li>AWS 案例 &lt;em>沒有&lt;/em> 提具體 betting transaction TPS、concurrent streams、延遲分布。讀者要對 &lt;em>策略&lt;/em> 學習、不要套用具體數字。&lt;/li>
&lt;li>「5-10x」是 &lt;em>峰值倍數&lt;/em>、不是 &lt;em>peak 持續時間&lt;/em>。Super Bowl 的關鍵 30 分鐘可能 8-10x、其他 3 小時可能 3-5x。&lt;/li>
&lt;/ul>
&lt;h2 id="策略">策略&lt;/h2>
&lt;p>可重用的工程做法：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「雙重峰值對齊」的工程取捨。FanDuel 同時運營體育直播（live streaming）跟體育投注（betting）、兩個工作負載在 <em>同一場 NFL Super Bowl</em> 同時達到峰值、但 SLO 完全不同 — 直播容忍 30 秒延遲、投注必須毫秒內成交。</p>
<h2 id="觀察">觀察</h2>
<p>FanDuel 在 AWS 的關鍵敘述（引自 <a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月活客戶</td>
          <td>3.5 M+</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>美國 20+ 州 + 加拿大</td>
      </tr>
      <tr>
          <td>峰值擴容倍數</td>
          <td>5-10x（NFL Super Bowl 等大型賽事）</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>AWS Local Zones + Wavelength + Outposts</td>
      </tr>
      <tr>
          <td>峰值類型</td>
          <td>直播 + 投注雙峰</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「seamlessly scale capacity 5–10 times as required for large sporting events, such as the NFL Super Bowl」。</p>
<h2 id="判讀">判讀</h2>
<p>FanDuel 案例揭露三個雙重峰值對齊的工程重點。</p>
<ol>
<li><strong>直播跟投注是兩種完全不同 SLO</strong>：直播容忍秒級延遲（用 CDN + ABR 串流）、投注必須毫秒級成交（Super Bowl 進球瞬間、賠率變動、用戶投注必須在賠率變化前完成）。兩個服務必須各自獨立擴容、各自獨立 SLO。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的多 SLO 對齊。</li>
<li><strong>AWS Local Zones / Wavelength / Outposts 是地理 + 監管雙重需求</strong>：美國博彩受各州監管、資料必須留在州內 → 用 Local Zones 在每個州就近部署；4G/5G 用戶投注延遲敏感 → 用 Wavelength 在電信商機房內運算；on-prem 需求 → 用 Outposts。對應 <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> 的受監管雙重需求、跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a> 的延遲反推 region。</li>
<li><strong>5-10x 是「同類事件中的最高倍率」</strong>：Super Bowl 是 NFL 賽季最大事件、不是常態。平日 baseline → 季後賽 2-3x → 季冠軍賽 4-5x → Super Bowl 5-10x。容量規劃要按事件級別分段、不是一律 10x。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的事件型容量分級。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>AWS 案例 <em>沒有</em> 提具體 betting transaction TPS、concurrent streams、延遲分布。讀者要對 <em>策略</em> 學習、不要套用具體數字。</li>
<li>「5-10x」是 <em>峰值倍數</em>、不是 <em>peak 持續時間</em>。Super Bowl 的關鍵 30 分鐘可能 8-10x、其他 3 小時可能 3-5x。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>不同 SLO 的工作負載分開部署、不要混在同一 service</strong>：betting 跟 streaming 在 FanDuel 必然是兩個獨立微服務、各自有 dedicated infrastructure。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 service decomposition、跟 <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</a> 同思維。</li>
<li><strong>多層 edge（Local Zone / Wavelength / Outposts）服務不同延遲需求</strong>：Local Zone 服務「州內合規」需求、Wavelength 服務「電信網內超低延遲」、Outposts 服務「on-prem 監管」需求。三者組合對應跨州博彩業務。</li>
<li><strong>事件型容量規劃分級</strong>：建立 event tier 體系（regular game / playoff / championship / super bowl），每 tier 對應不同 pre-scale 倍數。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的容量分級。</li>
</ol>
<p>跨平台等效：Azure 提供類似 stack（Stack Edge + Edge Zones + Azure for Operators）、GCP 有 Network Edge + Distributed Cloud。差異是各家 edge 覆蓋深度跟電信商合作。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他事件型峰值 → <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>（賽事高潮 AI 預測）/ <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</a></li>
<li>想設計多 SLO 對齊 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a></li>
<li>想做受監管多地區部署 → <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> + <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a></li>
<li>想做 edge / Local Zone 規劃 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
<li>想理解雙峰下 Aurora storage / replica scaling → <a href="/blog/backend/01-database/vendors/aurora/storage-architecture/" data-link-title="Aurora Storage Architecture：quorum-based 分散式 log 與韌性即性能設計" data-link-desc="Aurora storage / compute 分離、6-way 跨 AZ replication、4-of-6 write / 3-of-6 read quorum、韌性投資自動 amortize 成 read 性能、DraftKings 6ms 寫 / &lt;1ms 讀 production reference">Aurora 儲存層架構</a> + <a href="/blog/backend/01-database/vendors/aurora/read-replica-scaling/" data-link-title="Aurora Read Replica Scaling：15 replica 上限、lag profile、headroom 預留與 fleet 治理" data-link-desc="Aurora 15 replica 上限、共享 storage 為什麼能養大量 replica、事件型容量分級表、DraftKings headroom 預留判讀、FanDuel 雙 SLO 並行、fleet 治理 3 條 driver（business sharding / microservice / 合規）">Aurora read replica scaling</a></li>
<li>想評估 distributed SQL 在 betting 場景的 fit → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel Case Study</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/fanduel-cloudfront-case-study/">FanDuel CloudFront Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>9.C29 NTT DOCOMO Lemino：3 個月達 500 萬 MAU 的串流後端</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/ntt-docomo-lemino-japanese-streaming/</guid><description>&lt;p>這個案例的核心責任是說明「電信商級新串流服務」如何用雲端服務快速 launch + scale。Lemino 是 NTT DOCOMO 在 2023-04 推出的串流服務、3 個月達 5M MAU、工程工時下降 90% — 這個「不用大量工程師」的營運模式靠的是 managed services 組合、不是自建。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>NTT DOCOMO Lemino 在 AWS 的關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/ntt-docomo-lemino/">Lemino Case Study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>3 個月 MAU&lt;/td>
 &lt;td>500 萬&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同時直播頻道&lt;/td>
 &lt;td>30 channels（規劃擴到 50）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DynamoDB 請求峰值&lt;/td>
 &lt;td>tens of thousands req/sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工程工時下降&lt;/td>
 &lt;td>90%（vs 自建）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>啟動年份&lt;/td>
 &lt;td>2023-04&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：AWS Media Services（Elemental Link、MediaConnect、MediaLive、MediaPackage）、Amazon Aurora、Amazon DynamoDB、DynamoDB Accelerator (DAX)、Amazon OpenSearch Service。&lt;/p>
&lt;p>關鍵敘述：採用 DynamoDB 的原因 — 「connection limits became bottlenecks when experiencing a rapid increase in access」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Lemino 案例揭露三個現代串流服務啟動的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>「connection limit 是 RDB 的隱性 bottleneck」是 OLTP 在 surge 下的典型問題&lt;/strong>：傳統 RDB（PostgreSQL、MySQL）每個連線吃記憶體跟 process / thread、connection pool 上限通常 1K-5K 個。當突發流量湧入、第一個爆的不是 CPU 也不是 disk、是 &lt;em>連線數量&lt;/em>。DynamoDB 的 HTTP API 模型沒有 connection state、天然解決這個問題。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 connection pool 議題、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato&lt;/a> 遷移動機同類。&lt;/li>
&lt;li>&lt;strong>AWS Media Services 是「電視台級」串流基礎設施&lt;/strong>：Elemental Link（encoding）、MediaConnect（transport）、MediaLive（live encoding）、MediaPackage（packaging + DRM）— 這套 stack 過往是電視台才買得起的硬體設備、AWS 把它變成 pay-per-use 服務。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 vendor-specific 串流服務評估。&lt;/li>
&lt;li>&lt;strong>90% 工程工時下降 = 走 managed 路線的真正價值&lt;/strong>：傳統電信商 launch 串流服務、要養 50-100 個 SRE + DBA + network 工程師、Lemino 用 managed 服務只需 5-10 個。差距不在「能不能 launch」、在「launch 後的維運成本」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &amp;#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &amp;#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom&lt;/a> 的同類訴求。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「tens of thousands req/sec」可能指 2 萬或 8 萬、差距 4 倍。「3 個月 5M MAU」很亮眼、但 NTT DOCOMO 自身有 8000 萬+ 電信用戶可以推、不是純自然成長。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「電信商級新串流服務」如何用雲端服務快速 launch + scale。Lemino 是 NTT DOCOMO 在 2023-04 推出的串流服務、3 個月達 5M MAU、工程工時下降 90% — 這個「不用大量工程師」的營運模式靠的是 managed services 組合、不是自建。</p>
<h2 id="觀察">觀察</h2>
<p>NTT DOCOMO Lemino 在 AWS 的關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/ntt-docomo-lemino/">Lemino Case Study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>3 個月 MAU</td>
          <td>500 萬</td>
      </tr>
      <tr>
          <td>同時直播頻道</td>
          <td>30 channels（規劃擴到 50）</td>
      </tr>
      <tr>
          <td>DynamoDB 請求峰值</td>
          <td>tens of thousands req/sec</td>
      </tr>
      <tr>
          <td>工程工時下降</td>
          <td>90%（vs 自建）</td>
      </tr>
      <tr>
          <td>啟動年份</td>
          <td>2023-04</td>
      </tr>
  </tbody>
</table>
<p>服務組合：AWS Media Services（Elemental Link、MediaConnect、MediaLive、MediaPackage）、Amazon Aurora、Amazon DynamoDB、DynamoDB Accelerator (DAX)、Amazon OpenSearch Service。</p>
<p>關鍵敘述：採用 DynamoDB 的原因 — 「connection limits became bottlenecks when experiencing a rapid increase in access」。</p>
<h2 id="判讀">判讀</h2>
<p>Lemino 案例揭露三個現代串流服務啟動的工程重點。</p>
<ol>
<li><strong>「connection limit 是 RDB 的隱性 bottleneck」是 OLTP 在 surge 下的典型問題</strong>：傳統 RDB（PostgreSQL、MySQL）每個連線吃記憶體跟 process / thread、connection pool 上限通常 1K-5K 個。當突發流量湧入、第一個爆的不是 CPU 也不是 disk、是 <em>連線數量</em>。DynamoDB 的 HTTP API 模型沒有 connection state、天然解決這個問題。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 connection pool 議題、跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 遷移動機同類。</li>
<li><strong>AWS Media Services 是「電視台級」串流基礎設施</strong>：Elemental Link（encoding）、MediaConnect（transport）、MediaLive（live encoding）、MediaPackage（packaging + DRM）— 這套 stack 過往是電視台才買得起的硬體設備、AWS 把它變成 pay-per-use 服務。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 vendor-specific 串流服務評估。</li>
<li><strong>90% 工程工時下降 = 走 managed 路線的真正價值</strong>：傳統電信商 launch 串流服務、要養 50-100 個 SRE + DBA + network 工程師、Lemino 用 managed 服務只需 5-10 個。差距不在「能不能 launch」、在「launch 後的維運成本」。對應 <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> 的同類訴求。</li>
</ol>
<p>需要警惕：「tens of thousands req/sec」可能指 2 萬或 8 萬、差距 4 倍。「3 個月 5M MAU」很亮眼、但 NTT DOCOMO 自身有 8000 萬+ 電信用戶可以推、不是純自然成長。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>新串流服務優先選 DynamoDB / Cosmos DB / Bigtable 撐 metadata 層</strong>：避免 connection limit、避免 schema migration、避免 DBA 維運成本。</li>
<li><strong>AWS Media Services / GCP Media CDN / Azure Media Services 是新進入者快速 launch 的捷徑</strong>：不要重造串流 stack、直接用 vendor 提供的。</li>
<li><strong>DAX 是 DynamoDB 讀 cache 的標準解法</strong>：當讀峰值持續高（例如熱門節目首播、Hotstar 等級）、加 DAX 減少 DynamoDB 讀次數、降低成本。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a>。</li>
<li><strong>小團隊 + managed services 是電信商雲端轉型的範本</strong>：傳統電信商過去靠人海戰術、現在改靠 managed + 工程紀律。</li>
</ol>
<p>跨平台等效：GCP 提供 Media CDN + Anvato，Azure 提供 Media Services + Azure Front Door — 各家都有完整串流 stack。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他串流案例 → <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</a>（live 直播）/ <a href="/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">9.C27 Disney+</a>（VOD metadata）</li>
<li>想理解 connection limit 議題 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato 遷移</a></li>
<li>想做 DAX / cache 加速 → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> + <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 ML feature store</a></li>
<li>想規劃 managed-only 串流 stack → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想做串流 metadata 的 partition / GSI 設計 → <a href="/blog/backend/01-database/vendors/dynamodb/partition-key-antipatterns/" data-link-title="DynamoDB Partition Key 反模式與 Write Sharding：composite key 修復跟 mode × partition 交叉判讀" data-link-desc="DynamoDB partition 上限 1000 WCU 是 hot partition 的根因；composite key（event_id &#43; shard suffix）跟 calculated shard（hash % N）兩種修法、mode × partition 在 provisioned / on-demand 不同表現，以及 9.C15 Tixcraft 6750x 擴展的工程細節">DynamoDB partition key 反模式</a> + <a href="/blog/backend/01-database/vendors/dynamodb/gsi-lsi-design/" data-link-title="DynamoDB GSI 與 LSI 設計：access pattern 補位、projection、consistency 跟 DAX 補位" data-link-desc="GSI / LSI 是 single-table 沒覆蓋的 access pattern 補位、不是萬靈丹；本文涵蓋 projection 三型選擇、sparse index、GSI 自己會 hot partition、DAX 讀峰值補位的觸發條件（含 Capcom 是 derive vs Lemino 是 case fact 的分層）">DynamoDB GSI / LSI 設計</a></li>
<li>想評估 on-demand vs provisioned 給直播 / VOD 用 → <a href="/blog/backend/01-database/vendors/dynamodb/on-demand-vs-provisioned/" data-link-title="DynamoDB On-Demand vs Provisioned：6 軸決策、auto-scaling 邊界與 cost crossover" data-link-desc="capacity mode 選擇不是單軸 peak/avg ratio；本文展開 6 軸決策（peak/avg / 讀寫比 trend / surge 暫時 vs 永久 baseline / predictable-peak vs flash-sale / DBA 工時釋放 / vendor vs 自管 cost crossover），含 Zomato 50% 成本下降、Zoom 30x permanent surge、Amazon Ads sustained workload 等 case 分軸 anchor">DynamoDB on-demand vs provisioned</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/solutions/case-studies/ntt-docomo-lemino/">NTT Docomo Rebuilds Infrastructure for Lemino Streaming Service Launch</a></li>
<li><a href="https://aws.amazon.com/media/direct-to-consumer-d2c-streaming/">Direct to Consumer &amp; Streaming on AWS</a></li>
</ul>
]]></content:encoded></item><item><title>Datadog Continuous Profiler</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/</guid><description>&lt;p>Datadog Continuous Profiler 的核心責任是把 production profile 接到 SaaS APM、deployment marker、service tag 與 release regression workflow。它適合已經使用 Datadog APM / metrics / logs 的團隊，重點在讓 slow request、resource saturation、deploy version 與 profile diff 能在同一個操作介面中對齊。&lt;/p>
&lt;h2 id="定位">定位&lt;/h2>
&lt;p>Datadog Continuous Profiler 是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/datadog/" data-link-title="Datadog" data-link-desc="All-in-one SaaS 觀測平台、APM / Logs / Metrics / RUM / Security">Datadog&lt;/a> APM 的 &lt;em>production profiling&lt;/em> add-on、跟 Datadog Logs / Metrics / Traces 同 plane、共用 service tag、env tag、version tag 與 query bar。它的核心責任是把 production profile 接到 SaaS APM、deployment marker、service tag 與 release regression workflow，讓 slow request、resource saturation、deploy version 與 profile diff 能在同一個操作介面中對齊。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca&lt;/a> 這類 OSS profiler 比、Datadog Continuous Profiler 走 &lt;em>ecosystem-bundled&lt;/em> 路線 — profiler 本身不獨立計費、跟 APM host 一起進 business unit 預算、profile data 直接跟 trace_id、deploy marker、log query 在同一介面 cross-link。OSS profiler 走 &lt;em>standalone deployment&lt;/em>、profile store 自管（ClickHouse / object storage）、跟 observability 其他 plane 要自己 wire（grafana correlation、自寫 trace_id mapping）。差異在 &lt;em>跨 signal 的 query continuity 跟組織計費歸屬&lt;/em>、flame graph 本身的視覺呈現相近。&lt;/p>
&lt;p>這個定位讓 Datadog Continuous Profiler 接到 &lt;a href="https://tarrragon.github.io/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 Performance Improvement Loop&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling&lt;/a>。它的價值在於降低 profile diff 的交接成本；它的代價在於 SaaS 成本、agent 設定、資料保留與 vendor 約束。&lt;/p></description><content:encoded><![CDATA[<p>Datadog Continuous Profiler 的核心責任是把 production profile 接到 SaaS APM、deployment marker、service tag 與 release regression workflow。它適合已經使用 Datadog APM / metrics / logs 的團隊，重點在讓 slow request、resource saturation、deploy version 與 profile diff 能在同一個操作介面中對齊。</p>
<h2 id="定位">定位</h2>
<p>Datadog Continuous Profiler 是 <a href="/blog/backend/04-observability/vendors/datadog/" data-link-title="Datadog" data-link-desc="All-in-one SaaS 觀測平台、APM / Logs / Metrics / RUM / Security">Datadog</a> APM 的 <em>production profiling</em> add-on、跟 Datadog Logs / Metrics / Traces 同 plane、共用 service tag、env tag、version tag 與 query bar。它的核心責任是把 production profile 接到 SaaS APM、deployment marker、service tag 與 release regression workflow，讓 slow request、resource saturation、deploy version 與 profile diff 能在同一個操作介面中對齊。</p>
<p>跟 <a href="/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope</a> / <a href="/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca</a> 這類 OSS profiler 比、Datadog Continuous Profiler 走 <em>ecosystem-bundled</em> 路線 — profiler 本身不獨立計費、跟 APM host 一起進 business unit 預算、profile data 直接跟 trace_id、deploy marker、log query 在同一介面 cross-link。OSS profiler 走 <em>standalone deployment</em>、profile store 自管（ClickHouse / object storage）、跟 observability 其他 plane 要自己 wire（grafana correlation、自寫 trace_id mapping）。差異在 <em>跨 signal 的 query continuity 跟組織計費歸屬</em>、flame graph 本身的視覺呈現相近。</p>
<p>這個定位讓 Datadog Continuous Profiler 接到 <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 Performance Improvement Loop</a> 與 <a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling</a>。它的價值在於降低 profile diff 的交接成本；它的代價在於 SaaS 成本、agent 設定、資料保留與 vendor 約束。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Datadog Continuous Profiler deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Agent / SDK profiling 是否真的 enabled</strong>：Datadog Agent 跑著不等於 profiler 開了 — 各語言要在 SDK init 加 <code>profiling_enabled=true</code> 或環境變數 <code>DD_PROFILING_ENABLED=true</code>、Go / Java / Python / Node / Ruby / .NET 的開啟方式跟覆蓋的 profile type（CPU / heap / goroutine / lock / wall time）各不同</li>
<li><strong>Service / version / env tag 紀律</strong>：profile 沒有 <code>service</code> + <code>env</code> + <code>version</code> tag 就無法 diff、release marker 也對不上 — CI 要把 git SHA 或 release tag 注入 <code>DD_VERSION</code>、deploy pipeline 要打 deployment marker API</li>
<li><strong>Sampling rate 跟 production coverage</strong>：profiler 預設 60s 採一次、低流量服務或 short-lived 任務可能 sample 不到 hot path — 對 ultra-low latency / burst workload 要評估 sampling 是否還抓得到 regression signal</li>
<li><strong>Profile ingestion cost / retention</strong>：profile 是按 APM host 計費、但 profile event 量隨 service 數量 + sampling rate 漲、retention 預設 7 天（custom retention 另計）— 大型 deployment 要做 service-level enable/disable governance</li>
</ul>
<h2 id="適用場景">適用場景</h2>
<p>Release regression 定位適合 Datadog Continuous Profiler。當 canary 或 release candidate 的 p99、CPU、memory 或 cost per request 退化，團隊可以用 deployment marker 對比 release 前後 profile，找出變寬的 call stack。</p>
<p>APM-to-profile drilldown 適合 Datadog Continuous Profiler。慢 request 可以從 service、endpoint、trace 或 span 往下切到 profile，讓工程師知道 latency 是 DB、network、runtime、serialization、lock 還是 CPU hot path。</p>
<p>多語言 SaaS 團隊適合 Datadog Continuous Profiler。團隊如果同時維護 Go、Java、Python、Ruby、Node.js 或 .NET 服務，SaaS profiler 可以用統一 tag、dashboard 與權限模型管理。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Datadog 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>APM 整合</td>
          <td>trace、service、endpoint、profile 可串接</td>
          <td>service tag 與 deploy label 紀律</td>
      </tr>
      <tr>
          <td>Deployment marker</td>
          <td>release 前後 profile diff 容易建立</td>
          <td>release pipeline 與版本標記整合</td>
      </tr>
      <tr>
          <td>SaaS 操作</td>
          <td>低自管成本、跨團隊易查詢</td>
          <td>成本治理、資料保留與 vendor 約束</td>
      </tr>
      <tr>
          <td>多語言支援</td>
          <td>多 runtime 用同一套操作介面</td>
          <td>各語言 agent overhead 與覆蓋差異</td>
      </tr>
  </tbody>
</table>
<p>APM 整合價值來自上下文連續。Metrics 告訴你 CPU 上升，trace 告訴你 endpoint 變慢，profile 告訴你哪段 code path 變貴；Datadog 的優勢是把這些訊號放進同一個查詢與 dashboard 流程。</p>
<p>Deployment marker 價值來自 release gate。Profile diff 如果能對齊 commit、version、environment 與 canary cohort，就能成為 <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> 的 evidence。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Datadog Continuous Profiler</th>
          <th>Pyroscope</th>
          <th>Parca</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>部署模型</td>
          <td>SaaS only、跟 Datadog Agent / APM 綁</td>
          <td>OSS self-host / Grafana Cloud SaaS</td>
          <td>OSS self-host（Polar Signals SaaS 選）</td>
      </tr>
      <tr>
          <td>計費模型</td>
          <td>跟 APM host 計費（profile 不獨立 metering）</td>
          <td>OSS 免費 / Grafana Cloud 按 ingestion</td>
          <td>OSS 免費 / SaaS 按 host</td>
      </tr>
      <tr>
          <td>Profile 採集方式</td>
          <td>Language SDK（pull 採樣）</td>
          <td>SDK + eBPF agent</td>
          <td>eBPF-first、language-agnostic</td>
      </tr>
      <tr>
          <td>Trace correlation</td>
          <td>強 — trace_id 自動 link 到 flame graph</td>
          <td>中 — 要自己 wire OTel trace_id</td>
          <td>弱 — 偏 eBPF profile、trace 整合較淺</td>
      </tr>
      <tr>
          <td>視覺 / Workflow</td>
          <td>APM service view + Profile diff + Code Hotspot in IDE</td>
          <td>Grafana flame graph + diff、跟 Loki / Tempo 同 UI</td>
          <td>Parca UI 簡潔、偏單純 profile 探索</td>
      </tr>
      <tr>
          <td>多語言支援</td>
          <td>Go / Java / Python / Node / Ruby / .NET / PHP 官方 SDK</td>
          <td>同 + 社群 SDK；eBPF 補 native binary</td>
          <td>eBPF-only、不挑語言但 symbol 解析較吃力</td>
      </tr>
      <tr>
          <td>Vendor lock-in</td>
          <td>高 — profile 跟 APM workflow 綁、退場要重建 dashboard</td>
          <td>低 — OSS、profile 格式相對開放</td>
          <td>低 — OSS、pprof 格式相容</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>Datadog-heavy org、APM / log / metric 已用</td>
          <td>Grafana stack 已用、要省 license</td>
          <td>eBPF-first、low-overhead always-on</td>
      </tr>
  </tbody>
</table>
<p>選 Datadog Continuous Profiler 的核心訴求：<em>Datadog 已是 observability backbone</em> + 要 <em>APM trace ↔ profile drilldown 是 first-class workflow</em> + 接受 SaaS 計費 + 接受 SDK overhead trade-off。如果 Datadog 不是既有平台、單純為了 profiling 引入 Datadog 通常成本不划算、改走 Pyroscope / Parca。</p>
<p>跟一次性 runtime profiler（<code>pprof</code>、<code>async-profiler</code> 手動跑）的差異是時間維度。一次性 profiler 適合本機或 incident 當下調查；continuous profiler 適合 baseline、release diff 與長期退化治理 — 兩者互補、不互斥。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>APM trace ↔ profile correlation</strong>：Datadog SDK 把 <code>trace_id</code> 注入 profile sample 的 label、APM trace view 上每個 span 可以直接點到「執行這段 span 時的 flame graph」。意義是 <em>p99 latency 異常 trace 不只看 span 等待時間、能直接看到該 span 期間 CPU / lock / allocation 真正花在哪段 code</em>。需要 SDK 版本支援 + trace context propagation 正確接上、舊版 SDK 或自寫 instrumentation 容易斷鏈。</p>
<p><strong>Endpoint profiling</strong>：profile 按 HTTP endpoint / RPC method 切片、不只看 service 整體 hot path。意義是 <em>新加的 endpoint 即便 traffic 小、也能單獨看它的 CPU / allocation cost</em>、不會被 service 主流量稀釋。對 multi-tenant API、A/B test endpoint、internal admin endpoint 的退化偵測特別有用。</p>
<p><strong>Code Hotspot in IDE</strong>：Datadog IDE plugin（IntelliJ / VS Code）把 production profile 的 hot line 直接 overlay 到 source code、工程師 review PR 時能看到「這個 function 在 production 佔 service CPU 12%」。降低 <em>看 flame graph → 找 source 對應行</em> 的 cognitive cost。對應 <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 Performance Improvement Loop</a> 中「production signal → code change」的 feedback loop 縮短。</p>
<p><strong>Profile diff（baseline vs candidate）</strong>：Datadog 內建 diff view、選兩個 time window 或兩個 version tag、直接看 flame graph 哪些 frame 變寬 / 變窄。是 <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> 的核心 evidence — canary 跑完 30min、自動拉 baseline vs candidate diff 報告、超過 threshold 阻擋 promote。</p>
<p><strong>Notebooks correlation</strong>：Datadog Notebooks 可以把 profile flame graph、APM trace、metric chart、log query 排在同一份文件。incident post-mortem 跟 release review 寫一份 notebook 比散落多個 dashboard tab 更可追溯、也接 <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>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>SDK overhead 在 production 過高</strong>：profiler 預設 overhead &lt; 2% CPU、但 wall-time profiling / allocation profiling 全開可能到 5%+ — canary 一台量測、按 profile type 分別 enable、不要全部一次開</li>
<li><strong>Sampling rate 太低 / false negative</strong>：short-lived job（&lt; 60s）或 low-traffic service 可能整個生命週期沒被 sample 到、看不到 hot path — 改成事件觸發 profile（on-demand profiling API）或拉高該 service 的 sampling rate</li>
<li><strong>Profile 沒有 version tag / 無法 diff</strong>：deploy pipeline 沒注入 <code>DD_VERSION</code>、release marker 對不上 — 補 CI 環境變數、用 <code>dd-trace</code> SDK 自動讀 git commit SHA、跑 staging 驗證 diff view 能顯示 version</li>
<li><strong>Trace ↔ profile drilldown 斷鏈</strong>：SDK 版本太舊、或 trace context 在非同步 / queue handler 沒 propagate — 升 SDK + 補 trace context propagation、用一條已知慢 trace 驗證能不能跳到 flame graph</li>
<li><strong>Profiling cost spike</strong>：新 service 開啟 profiling、或某 service profile event 暴增（exception 路徑反覆採樣）— 看 Datadog usage dashboard 的 profile host hour、對嫌疑 service 暫關 profiling 觀察 cost 曲線、再 tune sampling rate</li>
<li><strong>Flame graph symbol 解析失敗 / 顯示 <code>?</code> frame</strong>：缺 debug symbol、stripped binary、或語言 runtime 版本不支援 — 補 build 時保留 symbol、確認 SDK 版本 vs runtime 版本對應表</li>
<li><strong>Lock profile 看不出 contention</strong>：某些語言（Go / Java）的 lock profiling 需要額外 flag（<code>DD_PROFILING_BLOCK_ENABLED</code> / <code>DD_PROFILING_LOCK_ENABLED</code>）— 預設沒開、要明確 enable 才看得到 lock contention flame graph</li>
</ul>
<h2 id="操作成本">操作成本</h2>
<p>Datadog Continuous Profiler 的主要成本是資料量與保留。Profile sample、tag cardinality、service 數量、environment 數量與 retention 都會影響費用與查詢體驗。</p>
<p>Agent 成本來自 runtime 差異。不同語言的 profiler 支援、overhead、可觀測維度與限制不同，導入時要用 canary service 量測 CPU、memory、latency 與 profile completeness。</p>
<p>Vendor 成本來自資料與 workflow 綁定。當 profile diff、release marker、APM drilldown 與 incident workflow 都在 Datadog 中，後續切換平台需要重新建立 tag schema、dashboard、retention 與 gate integration。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Datadog Continuous Profiler 結果應回寫到 evidence package。最小欄位包括 service、version、environment、deploy marker、profile type、time range、comparison baseline、profile diff link、overhead estimate、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Datadog 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>profiler view、profile diff、APM link</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>baseline / candidate profile window</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>Datadog profile、trace、dashboard link</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>service tag、version tag、sampling status</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>production coverage、agent overhead</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>runtime coverage、tag drift、retention limit</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓 release regression 可追溯。Reviewer 要能從 failed gate 直接打開 profile diff，看出哪個 service、version、endpoint 或 call stack 造成資源成本變化。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>Datadog Continuous Profiler 適合回寫 release regression 與 APM 整合案例。它可接 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 的 profile noise 降低、<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> 的 low-latency 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 exchange</a> 的 z1d 單執行緒 hot path 分析、<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 100+ 微服務</a> 的 per-service profile diff，以及 <a href="/blog/backend/04-observability/cases/datadog-otel-migration-practice/" data-link-title="4.C7 Datadog：OTel 相容遷移實務" data-link-desc="APM 採集從專有代理轉向 OTel 相容模式的治理案例。">Datadog OTel migration practice</a> 的 observability pipeline 整合。</p>
<p>這些案例的重點是上下文對齊。Datadog Profiler 頁引用案例時，要把 case 轉成 service tag、deploy marker、profile diff、trace drilldown 與 release gate evidence — 例如 Coinbase sub-ms 目標下、profile 必須對齊 RAFT consensus 跟 placement group 拓樸、才能解釋 hot path 為何在某些 epoch 才出現。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<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 Performance Improvement Loop</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a></li>
<li>跨模組：<a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca</a></li>
<li>官方：<a href="https://docs.datadoghq.com/profiler/">Datadog Continuous Profiler documentation</a></li>
</ul>
]]></content:encoded></item><item><title>9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/</guid><description>&lt;p>這個案例的核心責任是填補 Azure data-architecture 維度缺口、並提供「MongoDB → Cosmos DB」這個跨產品遷移的官方範本。Microsoft 365 是全球最大 SaaS 之一（月活十億級）、其使用分析平台的容量需求是 planet-scale。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Microsoft 365 在 Cosmos DB 的關鍵敘述（引自 &lt;a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Microsoft 365 boosts usage analytics with Azure Cosmos DB&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>內容&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>用戶規模&lt;/td>
 &lt;td>Microsoft 365 全球用戶（十億級 MAU）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工作負載&lt;/td>
 &lt;td>使用分析（usage analytics）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷出技術&lt;/td>
 &lt;td>MongoDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷入技術&lt;/td>
 &lt;td>Azure Cosmos DB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移動機&lt;/td>
 &lt;td>「globally-distributed, multi-model」「virtually unlimited elastic scalability」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵敘述：「The team decided to replace MongoDB with Azure Cosmos DB, a fully managed globally-distributed, multi-model database service designed for global distribution and virtually unlimited elastic scalability.」&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Microsoft 365 案例揭露三個全球 SaaS 分析平台的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>MongoDB → Cosmos DB 是「相容 API + 升級擴展性」的遷移路徑&lt;/strong>：Cosmos DB 提供 MongoDB API 相容、應用層程式幾乎不用改、但底層儲存改用 Cosmos DB 的分散式架構。這層遷移成本遠低於改寫 application 到 native Cosmos DB SQL API、適合大規模既有系統。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a>、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato&lt;/a> 形成對照。&lt;/li>
&lt;li>&lt;strong>分析平台 vs 交易平台的 DB 取捨不同&lt;/strong>：交易平台優先 latency + consistency（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner&lt;/a>）、分析平台優先 throughput + global distribution + cost。Cosmos DB 5 個 consistency level 讓分析場景可以選 weakest（eventual / session），換最大 throughput。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth&lt;/a> 同思維。&lt;/li>
&lt;li>&lt;strong>Microsoft 自家產品 dogfood Cosmos DB&lt;/strong>：跟 Amazon Prime Day 用自家 DynamoDB（&lt;a href="https://tarrragon.github.io/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&lt;/a>）、Google 自家用 Spanner（&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10&lt;/a>）一樣 — 雲商旗艦 DB 都會用在自家旗艦產品。讀此類 dogfood 案例的權重應該高、因為「雲商自己賭身家」。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是填補 Azure data-architecture 維度缺口、並提供「MongoDB → Cosmos DB」這個跨產品遷移的官方範本。Microsoft 365 是全球最大 SaaS 之一（月活十億級）、其使用分析平台的容量需求是 planet-scale。</p>
<h2 id="觀察">觀察</h2>
<p>Microsoft 365 在 Cosmos DB 的關鍵敘述（引自 <a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Microsoft 365 boosts usage analytics with Azure Cosmos DB</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用戶規模</td>
          <td>Microsoft 365 全球用戶（十億級 MAU）</td>
      </tr>
      <tr>
          <td>工作負載</td>
          <td>使用分析（usage analytics）</td>
      </tr>
      <tr>
          <td>遷出技術</td>
          <td>MongoDB</td>
      </tr>
      <tr>
          <td>遷入技術</td>
          <td>Azure Cosmos DB</td>
      </tr>
      <tr>
          <td>遷移動機</td>
          <td>「globally-distributed, multi-model」「virtually unlimited elastic scalability」</td>
      </tr>
  </tbody>
</table>
<p>關鍵敘述：「The team decided to replace MongoDB with Azure Cosmos DB, a fully managed globally-distributed, multi-model database service designed for global distribution and virtually unlimited elastic scalability.」</p>
<h2 id="判讀">判讀</h2>
<p>Microsoft 365 案例揭露三個全球 SaaS 分析平台的工程重點。</p>
<ol>
<li><strong>MongoDB → Cosmos DB 是「相容 API + 升級擴展性」的遷移路徑</strong>：Cosmos DB 提供 MongoDB API 相容、應用層程式幾乎不用改、但底層儲存改用 Cosmos DB 的分散式架構。這層遷移成本遠低於改寫 application 到 native Cosmos DB SQL API、適合大規模既有系統。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>、跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 形成對照。</li>
<li><strong>分析平台 vs 交易平台的 DB 取捨不同</strong>：交易平台優先 latency + consistency（<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a>）、分析平台優先 throughput + global distribution + cost。Cosmos DB 5 個 consistency level 讓分析場景可以選 weakest（eventual / session），換最大 throughput。對應 <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> 同思維。</li>
<li><strong>Microsoft 自家產品 dogfood Cosmos DB</strong>：跟 Amazon Prime Day 用自家 DynamoDB（<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</a>）、Google 自家用 Spanner（<a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10</a>）一樣 — 雲商旗艦 DB 都會用在自家旗艦產品。讀此類 dogfood 案例的權重應該高、因為「雲商自己賭身家」。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>案例 <em>沒有</em> 提具體 throughput、latency、cost 數字。Microsoft 內部數字通常不公開、跟 AWS / GCP 案例的數字密度差很多。</li>
<li>「MongoDB 不夠用」是行銷話術。實際是 <em>MongoDB 在某些 workload pattern 下不夠用</em>、不是普遍結論。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>MongoDB-compatible Cosmos DB 是大規模遷移的捷徑</strong>：應用層改動少、底層擴展性升級。但要驗證 <em>特定 query pattern</em> 在兩邊行為一致。對應 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">01.3 schema migration rollout evidence</a> 的 dual-write 驗證。</li>
<li><strong>分析平台用 weakest acceptable consistency</strong>：session consistency 或 eventual consistency 通常夠用、能換到 3-10x throughput。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的一致性取捨。</li>
<li><strong>dogfood 是 vendor selection 的重要訊號</strong>：vendor 自家是否用在 production-critical workload、能告訴你「他們對自己服務的信任度」。</li>
<li><strong>Multi-model 是 Cosmos DB 的差異化價值</strong>：同一個服務可以用 SQL API / MongoDB API / Cassandra API / Gremlin / Table API、避免多個 DB 服務並存。</li>
</ol>
<p>跨平台等效：AWS DynamoDB（KV）+ DocumentDB（MongoDB-compatible）、GCP Firestore（document）+ Spanner（SQL）+ Bigtable（KV）— 各家用不同產品覆蓋 multi-model、Cosmos DB 是少數「單一產品支援多 model」。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他 Cosmos DB 案例 → <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> / <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS Black Friday</a></li>
<li>對照其他 dogfood 案例 → <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 AWS Prime Day</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想做 MongoDB-compatible 遷移 → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想理解 multi-model 取捨 → <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> + <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a></li>
<li>想對比 Cosmos DB MongoDB API vs SQL API 的選型 → <a href="/blog/backend/01-database/vendors/cosmosdb/mongodb-api-vs-sql-api/" data-link-title="Cosmos DB MongoDB API vs SQL API：遷移路徑、dogfood signal、multi-model、跨雲 hedging" data-link-desc="從『MongoDB API 跟 SQL API 哪個快』推進到 vendor selection 的四層問題：三型遷移路徑、dogfood signal 怎麼讀、multi-model 差異化、跨雲 hedging — 從 Microsoft 365 dogfood 案例切入">Cosmos DB MongoDB API vs SQL API</a></li>
<li>想做 RU 成本模型與容量 sizing → <a href="/blog/backend/01-database/vendors/cosmosdb/ru-cost-model-sizing/" data-link-title="Cosmos DB RU/s 成本模型 &#43; 容量規劃：RU 思維、payload、index、provisioned vs autoscale vs serverless" data-link-desc="從 CPU&#43;IOPS 思維轉到 RU 思維的學習曲線、依負載形狀選容量模式、payload &#43; index policy 對 RU 的影響、autoscale reactive 限制 — 從 ASOS Black Friday &#43; Minecraft Earth 1M RU/s 壓測切入">Cosmos DB RU 成本模型</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Microsoft 365 boosts usage analytics with Azure Cosmos DB</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/a-technical-overview-of-azure-cosmos-db/">A technical overview of Azure Cosmos DB</a></li>
</ul>
]]></content:encoded></item><item><title>Pyroscope</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/pyroscope/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/pyroscope/</guid><description>&lt;p>Pyroscope 的核心責任是提供開源 continuous profiling backend，讓團隊用 Grafana 生態保存、查詢、比較與視覺化 production profile。它適合偏 OSS-first、已使用 Grafana / Prometheus / Tempo / Loki 的團隊，重點在把 CPU、memory、allocation 與 profile diff 放進可自管 observability stack。Pyroscope 原為獨立 OSS 專案、&lt;em>2023 年被 Grafana Labs 收購&lt;/em>、現分兩條產品線：&lt;em>Grafana Pyroscope&lt;/em>（OSS、Apache 2.0、self-host）與 &lt;em>Grafana Cloud Profiles&lt;/em>（商業 SaaS、走 Grafana Cloud 計費）。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Pyroscope 在 continuous profiling 賽道上的差異點是 &lt;em>Grafana Labs 整合 + 多語言 SDK 覆蓋&lt;/em>、而不是 profiling 演算法本身。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca&lt;/a> 比、Parca 走 &lt;em>pprof + Prometheus-style label&lt;/em> 的 CNCF / eBPF infrastructure profiling 路線、focus 在 system-wide 一次抓全機；Pyroscope 走 &lt;em>per-language SDK + Grafana stack 整合&lt;/em> 的 developer-facing 路線、focus 在 application-level flame graph 與 release diff。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler&lt;/a> 比、Datadog 走 &lt;em>SaaS all-in-one + APM 同 trace context&lt;/em>、profiling 自動跟 trace span 關聯；Pyroscope 走 &lt;em>self-host 可選 + Grafana 跨 signal&lt;/em>、整合靠 Grafana dashboard 跟 explore link 而非 product-level deep linking。&lt;/p>
&lt;p>這個定位讓 Pyroscope 接到 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling&lt;/a> 與 &lt;a href="https://tarrragon.github.io/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 Performance Improvement Loop&lt;/a>。它的價值在於 OSS / Grafana 整合與可自管；它的代價在於 storage、retention、agent rollout 與營運責任要由團隊承擔。&lt;/p>
&lt;h2 id="最短判讀路徑">最短判讀路徑&lt;/h2>
&lt;p>判斷 Pyroscope deployment 是否健康、最少看四件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Agent / SDK setup&lt;/strong>：是用 &lt;em>language SDK&lt;/em>（in-process profiler、跟 application code 一起部署）還是 &lt;em>Grafana Alloy / Pyroscope agent&lt;/em>（out-of-process、適合 binary-only 或無法改 code 的 workload）— 兩條路 overhead、覆蓋率、tag 注入方式都不同&lt;/li>
&lt;li>&lt;strong>Push or pull model&lt;/strong>：SDK 預設 &lt;em>push&lt;/em>（application 主動把 profile sample 推到 Pyroscope server）、Alloy / agent 可走 &lt;em>pull&lt;/em>（scrape pprof endpoint、跟 Prometheus 同模型）— push 適合 short-lived job / serverless、pull 適合 long-running service + Kubernetes service discovery&lt;/li>
&lt;li>&lt;strong>Grafana integration&lt;/strong>：是否在 Grafana datasource 設好 Pyroscope、explore 是否能跨 trace / log / profile 跳轉（Tempo trace → Pyroscope profile by service+span）、dashboard 是否內嵌 flame graph panel&lt;/li>
&lt;li>&lt;strong>Tag schema discipline&lt;/strong>：service / version / region / environment / pod 是否一致命名、deploy event 是否打 label 讓 baseline / candidate 比較可成立&lt;/li>
&lt;/ul>
&lt;p>四件事任一缺失、profile 就只是「能看 flame graph」而非「release gate evidence」、無法支撐 &lt;a href="https://tarrragon.github.io/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 Performance Improvement Loop&lt;/a> 的 diff workflow。&lt;/p></description><content:encoded><![CDATA[<p>Pyroscope 的核心責任是提供開源 continuous profiling backend，讓團隊用 Grafana 生態保存、查詢、比較與視覺化 production profile。它適合偏 OSS-first、已使用 Grafana / Prometheus / Tempo / Loki 的團隊，重點在把 CPU、memory、allocation 與 profile diff 放進可自管 observability stack。Pyroscope 原為獨立 OSS 專案、<em>2023 年被 Grafana Labs 收購</em>、現分兩條產品線：<em>Grafana Pyroscope</em>（OSS、Apache 2.0、self-host）與 <em>Grafana Cloud Profiles</em>（商業 SaaS、走 Grafana Cloud 計費）。</p>
<h2 id="服務定位">服務定位</h2>
<p>Pyroscope 在 continuous profiling 賽道上的差異點是 <em>Grafana Labs 整合 + 多語言 SDK 覆蓋</em>、而不是 profiling 演算法本身。跟 <a href="/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca</a> 比、Parca 走 <em>pprof + Prometheus-style label</em> 的 CNCF / eBPF infrastructure profiling 路線、focus 在 system-wide 一次抓全機；Pyroscope 走 <em>per-language SDK + Grafana stack 整合</em> 的 developer-facing 路線、focus 在 application-level flame graph 與 release diff。跟 <a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler</a> 比、Datadog 走 <em>SaaS all-in-one + APM 同 trace context</em>、profiling 自動跟 trace span 關聯；Pyroscope 走 <em>self-host 可選 + Grafana 跨 signal</em>、整合靠 Grafana dashboard 跟 explore link 而非 product-level deep linking。</p>
<p>這個定位讓 Pyroscope 接到 <a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling</a> 與 <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 Performance Improvement Loop</a>。它的價值在於 OSS / Grafana 整合與可自管；它的代價在於 storage、retention、agent rollout 與營運責任要由團隊承擔。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Pyroscope deployment 是否健康、最少看四件事：</p>
<ul>
<li><strong>Agent / SDK setup</strong>：是用 <em>language SDK</em>（in-process profiler、跟 application code 一起部署）還是 <em>Grafana Alloy / Pyroscope agent</em>（out-of-process、適合 binary-only 或無法改 code 的 workload）— 兩條路 overhead、覆蓋率、tag 注入方式都不同</li>
<li><strong>Push or pull model</strong>：SDK 預設 <em>push</em>（application 主動把 profile sample 推到 Pyroscope server）、Alloy / agent 可走 <em>pull</em>（scrape pprof endpoint、跟 Prometheus 同模型）— push 適合 short-lived job / serverless、pull 適合 long-running service + Kubernetes service discovery</li>
<li><strong>Grafana integration</strong>：是否在 Grafana datasource 設好 Pyroscope、explore 是否能跨 trace / log / profile 跳轉（Tempo trace → Pyroscope profile by service+span）、dashboard 是否內嵌 flame graph panel</li>
<li><strong>Tag schema discipline</strong>：service / version / region / environment / pod 是否一致命名、deploy event 是否打 label 讓 baseline / candidate 比較可成立</li>
</ul>
<p>四件事任一缺失、profile 就只是「能看 flame graph」而非「release gate evidence」、無法支撐 <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 Performance Improvement Loop</a> 的 diff workflow。</p>
<h2 id="適用場景">適用場景</h2>
<p>自管 profiling backend 適合 Pyroscope。團隊若有資料主權、成本控制、內網部署或 OSS-first 要求，可以用 Pyroscope 保存 profile，降低 profile sample 外送帶來的治理成本。</p>
<p>Profile diff workflow 適合 Pyroscope。Release candidate、canary、baseline review 或 incident after-action 都可以用時間區間比較，找出 CPU、memory 或 allocation 的相對變化。</p>
<p>Grafana stack 整合適合 Pyroscope。若服務已經有 Grafana dashboard，profile link 可以放進 latency、CPU、memory、cost 或 release dashboard，讓 SRE 從聚合訊號跳到 callstack。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Pyroscope 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>OSS / self-host</td>
          <td>profile 資料可自管</td>
          <td>backend storage、retention、upgrade</td>
      </tr>
      <tr>
          <td>Grafana 整合</td>
          <td>dashboard、explore、profile link 容易串接</td>
          <td>tag schema 與 dashboard discipline</td>
      </tr>
      <tr>
          <td>Profile diff</td>
          <td>時間區間與版本對比直觀</td>
          <td>deploy label 與 baseline 管理</td>
      </tr>
      <tr>
          <td>多語言 agent</td>
          <td>常見 runtime 可導入</td>
          <td>agent overhead 與覆蓋差異量測</td>
      </tr>
  </tbody>
</table>
<p>OSS / self-host 價值來自控制權。Profile 可能包含 function name、package path、tenant-specific code path 或敏感 business logic，自管能讓資料保存與存取控制更貼近內部規範。</p>
<p>Grafana 整合價值來自操作連續性。當 CPU dashboard、latency dashboard 與 deploy annotation 都在 Grafana 中，Pyroscope 能讓工程師從圖表直接切到 flame graph。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>Pyroscope 和 Datadog Continuous Profiler 的主要差異是平台責任。Pyroscope 偏 OSS / self-host / Grafana stack；Datadog 偏 SaaS all-in-one 與 APM product workflow。</p>
<p>Pyroscope 和 Parca 的主要差異是生態定位。Pyroscope 偏 Grafana profiling backend 與 developer-facing flame graph；Parca 偏 eBPF / infrastructure-wide profiling 與 CNCF 生態。</p>
<p>Pyroscope 和一次性 profiler 的主要差異是可比較性。一次性 profiler 擅長局部調查；Pyroscope 擅長讓 profile 成為 release baseline 與 incident evidence。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Pyroscope（Grafana）</th>
          <th>Parca</th>
          <th>Datadog Continuous Profiler</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>部署模型</td>
          <td>OSS self-host / Grafana Cloud Profiles SaaS</td>
          <td>OSS self-host（CNCF Sandbox）</td>
          <td>SaaS only</td>
      </tr>
      <tr>
          <td>Profile 來源</td>
          <td>language SDK + Alloy / agent（push 為主）</td>
          <td>pprof scrape（pull）+ Parca Agent（eBPF）</td>
          <td>Datadog Agent + language tracer 整合</td>
      </tr>
      <tr>
          <td>語言覆蓋</td>
          <td>Go / Python / Java / Ruby / .NET / Rust / Node</td>
          <td>任何能輸出 pprof 的 runtime + eBPF system-wide</td>
          <td>Go / Python / Java / Ruby / .NET / Node</td>
      </tr>
      <tr>
          <td>Tag / label</td>
          <td>Prometheus-style label + 自訂 tag</td>
          <td>Prometheus-style label</td>
          <td>Datadog tag（跟 APM 共用）</td>
      </tr>
      <tr>
          <td>Diff workflow</td>
          <td>時間區間 + label 對比 + flame graph diff UI</td>
          <td>時間區間 + label 對比</td>
          <td>自動跟 deploy event + trace span 關聯</td>
      </tr>
      <tr>
          <td>整合方向</td>
          <td>Grafana（Tempo / Loki / Mimir 互跳）</td>
          <td>Prometheus / Grafana（弱整合）</td>
          <td>Datadog APM / Logs / Metrics 同 plane</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>Grafana-first、OSS-friendly、release diff 主流程</td>
          <td>infrastructure-wide eBPF profiling、CNCF 生態</td>
          <td>Datadog 已是主 observability、要 APM 連動</td>
      </tr>
  </tbody>
</table>
<p>選 Pyroscope 的核心訴求：<em>已用 Grafana stack + 多語言服務組合 + 要 OSS self-host 選項或預算敏感</em>、profile 主要用途是 release diff / incident hot-path 定位、不需要 APM-level 自動 trace 關聯。</p>
<h2 id="操作成本">操作成本</h2>
<p>Pyroscope 的主要成本是自管 backend。Profile ingest、storage、retention、compaction、backup、upgrade 與 dashboard ownership 都需要團隊負責。</p>
<p>Tag 成本來自查詢維度。service、version、region、environment、runtime、pod、tenant 這些 label 能提高定位能力，也會增加 cardinality、儲存與查詢成本。</p>
<p>Agent 成本來自 rollout 與 overhead。導入時要先選代表性服務，量測 profiler 對 CPU、memory、latency 的影響，再逐步擴大到 critical path。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Pyroscope 結果應回寫到 evidence package。最小欄位包括 service、version、environment、profile type、baseline window、candidate window、profile diff link、tag set、retention policy、overhead estimate、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Pyroscope 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>profile query、flame graph、diff link</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>baseline / candidate profile window</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>Grafana / Pyroscope explore link</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>tag completeness、sampling status</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>production coverage、agent overhead</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未覆蓋 runtime、tag drift、retention gap</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是讓 profile diff 成為 release artifact。Reviewer 要能從 release gate 打開 Pyroscope diff，確認變化來自 code path、runtime 行為、負載變化或 baseline drift。</p>
<h2 id="進階主題">進階主題</h2>
<p><strong>Grafana Cloud Profiles</strong>：商業 SaaS 版本、走 Grafana Cloud 計費（per-series 或 per-profile bytes）、適合不想自管 storage / retention / compaction 的團隊。跟 OSS Pyroscope 共用 SDK 跟 query API、可在 OSS 起步、規模到一定程度再遷移到 Cloud、避免廠商一開始就鎖死。</p>
<p><strong>Flame graph diff</strong>：Pyroscope 的核心 release workflow — 選 baseline window（release 前 24hr）跟 candidate window（release 後 24hr）、UI 把兩張 flame graph 差異標紅綠、可直接看到哪個 function 變慢 / 變快。判讀要點是 <em>baseline window 要排除部署當下的 warm-up / cache miss spike</em>、否則 diff 噪音蓋過真實 regression。</p>
<p><strong>多語言 SDK 覆蓋</strong>：Pyroscope 官方 SDK 覆蓋 Go / Python / Java / Ruby / .NET / Rust / Node.js — Go SDK 用 <code>runtime/pprof</code> 包裝、Java 走 async-profiler、Python 走 <code>py-spy</code> 風格 sampling profiler、Node.js 走 V8 sampling。各 SDK overhead 不一致（Java async-profiler ~1%、Python py-spy ~3-5%）、選型時要看代表性服務量測再 rollout、不能假設「都很低」。</p>
<p><strong>Adhoc profiling</strong>：當 production SDK 沒裝、或想對 batch job / CLI tool 做一次性 profile、可用 Pyroscope CLI 上傳 <em>standalone pprof file</em>（<code>pyroscope adhoc</code> 或 <code>profilecli</code>）— 補位「標準 pprof endpoint 不夠用、但又不想長期 instrument」的情境。對 ad-hoc incident investigation 跟 batch job postmortem 特別有用。</p>
<p><strong>Grafana Alloy 整合</strong>：Grafana Alloy（前 Grafana Agent）內建 Pyroscope receiver、可同時 scrape Prometheus metrics + tail Loki log + push Tempo trace + scrape Pyroscope profile、單一 agent 跨 four signal、降低 sidecar 數量跟維運成本。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>SDK overhead 過高 / latency p99 上升</strong>：profile sample rate 太高、或 Java async-profiler 在低 CPU host 競爭 schedule — 降 sample rate、staging 量測 CPU / latency delta 確認 &lt; 3% 再 promote</li>
<li><strong>Push agent 跟 pull agent 取捨錯</strong>：short-lived job 用 pull 結果還沒被 scrape 就 exit、long-running service 用 push 結果 Pyroscope server 過載 — short-lived / serverless 走 SDK push、long-running + Kubernetes service discovery 走 Alloy pull</li>
<li><strong>Label cardinality 爆 / storage 跟查詢都慢</strong>：tag 加了 pod name / request ID / user ID 等高 cardinality 維度 — 限制 tag 為 service / version / region / environment / cluster 等低 cardinality、高基數維度走 trace / log 別放 profile</li>
<li><strong>Baseline / candidate diff 全是噪音</strong>：baseline window 沒對齊流量模式（off-peak vs peak）、或 deploy label 沒打 — 要求 release pipeline 自動寫 <code>version</code> / <code>deploy_id</code> label、diff window 跨完整流量週期（24hr or 7day）</li>
<li><strong>Grafana datasource 連不到 / explore 跳轉失敗</strong>：datasource URL 設錯、或 service / span tag 不一致 — Tempo trace 用的 <code>service.name</code> 要跟 Pyroscope <code>service</code> label 對齊、否則 cross-signal 跳轉斷裂</li>
<li><strong>Storage / retention 失控</strong>：profile 保留太久、SmartStore-like 冷儲存沒設 — Pyroscope OSS 支援 object storage（S3 / GCS）backend、長 retention 必開、不然 PV 會爆</li>
</ul>
<h2 id="何時改走其他服務">何時改走其他服務</h2>
<table>
  <thead>
      <tr>
          <th>需求形狀</th>
          <th>改走</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>已用 Datadog APM、要 trace ↔ profile 自動關聯</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler</a></td>
      </tr>
      <tr>
          <td>要 eBPF system-wide / infrastructure profiling</td>
          <td><a href="/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca</a></td>
      </tr>
      <tr>
          <td>不想自管 backend、但要 Grafana stack</td>
          <td>Grafana Cloud Profiles（商業 SaaS、同 SDK）</td>
      </tr>
  </tbody>
</table>
<h2 id="案例回寫">案例回寫</h2>
<p>Pyroscope 適合回寫 OSS observability 與 release diff 案例。它可接 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 的 profile noise 降低、<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> 的 hot path 定位、<a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS multi-cluster</a> 的 single-tenant per game profile 隔離、<a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom 遊戲後端</a> 的 30% 成本下降 hot path 分析，以及 <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> 的 baseline / candidate profile diff。</p>
<p>這些案例的重點是可比較 profile。Pyroscope 頁引用案例時，要把 case 轉成 tag schema、baseline window、candidate window、flame graph diff 與 release gate evidence — 例如 Riot Games 246 cluster 的 tag schema 必須涵蓋 game / region / cluster 三維、才能避免「跨遊戲混合 profile」的歸因錯誤。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<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 Performance Improvement Loop</a></li>
<li>上游：<a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 效能可觀測性</a></li>
<li>跨模組：<a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca</a></li>
<li>官方：<a href="https://grafana.com/docs/pyroscope/latest/">Grafana Pyroscope documentation</a></li>
</ul>
]]></content:encoded></item><item><title>9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/</guid><description>&lt;p>這個案例的核心責任是補強 GCP 案例庫的「商業應用」深度、並提供拉丁美洲電商規模對標。Mercado Libre 是拉丁美洲最大電商（市值 600 億美金級）、業務涵蓋 18 個國家、是區域型平台的容量規劃範本。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Mercado Libre 在 GCP 的關鍵敘述（引自 &lt;a href="https://cloud.google.com/customers/mercado-libre">Mercado Libre Customer Story&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>客戶數&lt;/td>
 &lt;td>1 億&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>商品數&lt;/td>
 &lt;td>1.5 億（3 個試點國家）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業務影響&lt;/td>
 &lt;td>數百萬美金 incremental revenue（Vertex AI Search）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要 GCP 服務&lt;/td>
 &lt;td>Vertex AI Search、BigQuery&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料即時性&lt;/td>
 &lt;td>near real-time&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務地理&lt;/td>
 &lt;td>拉丁美洲&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵能力：「Vertex AI Search across 150 million items in three pilot countries that is helping its 100 million customers find the products they love faster」、「BigQuery to design a robust data architecture that ensures the availability of data in near real-time」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Mercado Libre 揭露三個區域電商容量規劃重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>區域電商 ≠ 全球電商&lt;/strong>：拉丁美洲 18 個國家、各自有獨立貨幣、稅務、物流、合規規則。容量規劃單位通常是「per country」、不是「per region」。對應 &lt;a href="https://tarrragon.github.io/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&lt;/a> 的市場分割、跟 &lt;a href="https://tarrragon.github.io/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&lt;/a> 的跨國平台對照。&lt;/li>
&lt;li>&lt;strong>Vertex AI Search = 「搜尋」當作 ML 服務、不是 Elasticsearch&lt;/strong>：傳統電商搜尋靠 Elasticsearch / OpenSearch + 自訓 ranker、Mercado Libre 用 vendor managed Vertex AI Search、把「商品搜尋 + 推薦排序」當作 ML 黑盒。這個取捨用「不可調參」換「快速上線」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 build vs buy、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify&lt;/a> 的 managed 轉向同類思維。&lt;/li>
&lt;li>&lt;strong>「數百萬美金 incremental revenue」是 ML 容量規劃的真實 ROI&lt;/strong>：搜尋改善 → 轉換率 → 訂單 → 收入、ML 投資的 cost 才能合理化。容量規劃不只看「能撐多大流量」、也要看「擴容能否帶業務 ROI」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency&lt;/a> 的成本工程化。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 GCP 案例庫的「商業應用」深度、並提供拉丁美洲電商規模對標。Mercado Libre 是拉丁美洲最大電商（市值 600 億美金級）、業務涵蓋 18 個國家、是區域型平台的容量規劃範本。</p>
<h2 id="觀察">觀察</h2>
<p>Mercado Libre 在 GCP 的關鍵敘述（引自 <a href="https://cloud.google.com/customers/mercado-libre">Mercado Libre Customer Story</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>客戶數</td>
          <td>1 億</td>
      </tr>
      <tr>
          <td>商品數</td>
          <td>1.5 億（3 個試點國家）</td>
      </tr>
      <tr>
          <td>業務影響</td>
          <td>數百萬美金 incremental revenue（Vertex AI Search）</td>
      </tr>
      <tr>
          <td>主要 GCP 服務</td>
          <td>Vertex AI Search、BigQuery</td>
      </tr>
      <tr>
          <td>資料即時性</td>
          <td>near real-time</td>
      </tr>
      <tr>
          <td>服務地理</td>
          <td>拉丁美洲</td>
      </tr>
  </tbody>
</table>
<p>關鍵能力：「Vertex AI Search across 150 million items in three pilot countries that is helping its 100 million customers find the products they love faster」、「BigQuery to design a robust data architecture that ensures the availability of data in near real-time」。</p>
<h2 id="判讀">判讀</h2>
<p>Mercado Libre 揭露三個區域電商容量規劃重點。</p>
<ol>
<li><strong>區域電商 ≠ 全球電商</strong>：拉丁美洲 18 個國家、各自有獨立貨幣、稅務、物流、合規規則。容量規劃單位通常是「per country」、不是「per region」。對應 <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> 的市場分割、跟 <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</a> 的跨國平台對照。</li>
<li><strong>Vertex AI Search = 「搜尋」當作 ML 服務、不是 Elasticsearch</strong>：傳統電商搜尋靠 Elasticsearch / OpenSearch + 自訓 ranker、Mercado Libre 用 vendor managed Vertex AI Search、把「商品搜尋 + 推薦排序」當作 ML 黑盒。這個取捨用「不可調參」換「快速上線」。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 build vs buy、跟 <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a> 的 managed 轉向同類思維。</li>
<li><strong>「數百萬美金 incremental revenue」是 ML 容量規劃的真實 ROI</strong>：搜尋改善 → 轉換率 → 訂單 → 收入、ML 投資的 cost 才能合理化。容量規劃不只看「能撐多大流量」、也要看「擴容能否帶業務 ROI」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的成本工程化。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「1.5 億商品 in 3 pilot countries」是 <em>試點規模</em>、不是全平台。全平台商品總數應該更大、但案例沒揭露。</li>
<li>BigQuery「near real-time」沒指明 latency（秒級、分鐘級）。BigQuery 傳統是 minutes-level、不是 sub-second、對「即時」的定義要謹慎。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>區域電商的容量規劃是「per country × peak_factor」</strong>：不是「per region」聚合、要按國家分別規劃。每個國家自己的 Black Friday / Cyber Monday / 雙 11 / 6.18 等本地大促時間都不同。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a>。</li>
<li><strong>「商品搜尋」適合用 managed AI search</strong>：除非有自家強大的 ML team + 大量訓練資料、否則 Vertex AI Search / OpenSearch Service 等 managed 比自建 ranker 划算。</li>
<li><strong>BigQuery 是 LatAm / 新興市場數據平台的標配</strong>：能處理 PB 級資料、無需 cluster 管理、適合中等工程資源的團隊。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的 data 平台選型、跟 <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</a> 的 Redshift + Athena 對照。</li>
<li><strong>ML ROI 直接 ＝ 業務指標</strong>：transaction conversion rate、AOV、recommendation CTR 都是 ML 容量規劃的下游 KPI。</li>
</ol>
<p>跨平台等效：AWS Personalize + Redshift + Glue、Azure AI Search + Synapse 都是對等候選。差異是 vendor 整合度跟模型的可調參空間。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他大規模電商 → <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS Black Friday</a> / <a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair burst</a></li>
<li>想規劃跨國容量 → <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> + <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</a></li>
<li>想做 ML feature serving → <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 ML feature store</a></li>
<li>想做 build vs buy 決策 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/customers/mercado-libre">Mercado Libre Customer Story</a></li>
<li><a href="https://cloud.google.com/blog/products/data-analytics/how-mercado-libre-uses-real-time-analytics-for-on-time-delivery">How Mercado Libre uses real-time analytics for on-time delivery</a></li>
</ul>
]]></content:encoded></item><item><title>PostgreSQL autovacuum tuning：為什麼你的 autovacuum 永遠追不上 bloat</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL&lt;/a> overview 的 implementation-layer deep article。Overview 已說明 PostgreSQL MVCC 的 vacuum 必要性、本文聚焦 &lt;em>autovacuum 在 production write-heavy workload 為什麼追不上&lt;/em> 的根因 + 各維度 tuning。&lt;/p>&lt;/blockquote>
&lt;h2 id="你的-autovacuum-永遠追不上-bloat--為什麼">你的 autovacuum 永遠追不上 bloat — 為什麼&lt;/h2>
&lt;p>write-heavy table 的常見故事：上線時表 10GB、3 個月後 30GB、6 個月 80GB；DBA 看 &lt;code>pg_stat_user_tables&lt;/code> 發現 &lt;code>n_dead_tup&lt;/code> 比 &lt;code>n_live_tup&lt;/code> 還多、&lt;code>pg_stat_progress_vacuum&lt;/code> 顯示 autovacuum 一直在跑、但 dead tuple 從沒清乾淨。表本身才 5M row、實際磁碟卻佔 80GB。&lt;/p>
&lt;p>這不是 PostgreSQL bug、是 autovacuum &lt;em>cost-based throttling 預設保守&lt;/em> 的設計意圖 — autovacuum 不該影響 OLTP query 性能、所以每跑一段就 sleep。預設 &lt;code>autovacuum_vacuum_cost_limit=200&lt;/code> + &lt;code>autovacuum_vacuum_cost_delay=2ms&lt;/code> 在 write-heavy 表（每秒幾千 UPDATE）下、清理速度 &lt;em>永遠慢於&lt;/em> dead tuple 產生速度。預設配置適合 read-heavy / write-light workload；OLTP write-heavy 必須調。&lt;/p>
&lt;h2 id="mvcc-跟-dead-tuplevacuum-在解什麼">MVCC 跟 dead tuple：vacuum 在解什麼&lt;/h2>
&lt;p>PostgreSQL MVCC：每次 UPDATE 都是 &lt;em>insert new row + mark old row as deleted&lt;/em>；DELETE 是 &lt;em>mark as deleted、不立刻釋放空間&lt;/em>。dead tuple 在 disk 上佔位、但不能被 query 讀到。autovacuum 的責任：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>回收 dead tuple 空間&lt;/strong> 供新 row reuse（不縮 table 大小、是 free space map）&lt;/li>
&lt;li>&lt;strong>更新 visibility map&lt;/strong> 讓 index-only scan 跳過 heap fetch&lt;/li>
&lt;li>&lt;strong>凍結老 row 的 xid&lt;/strong>（freeze）避免 xid wraparound 災難&lt;/li>
&lt;li>&lt;strong>重整 index B-tree&lt;/strong> 標記 dead pointer（不刪 index page）&lt;/li>
&lt;/ol>
&lt;p>Vacuum 不縮表 — 真要縮要跑 &lt;code>VACUUM FULL&lt;/code>（全表 exclusive lock、production 不能跑）或 &lt;code>pg_repack&lt;/code>（online repack tool）。預期 vacuum 只能 &lt;em>讓表停止長大&lt;/em>、不能 &lt;em>讓表變小&lt;/em>。&lt;/p>
&lt;h2 id="tuningcost-based-throttle-跟-trigger-threshold">Tuning：cost-based throttle 跟 trigger threshold&lt;/h2>
&lt;h3 id="cost-based-throttle全-instance">Cost-based throttle（全 instance）&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># postgresql.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="na">autovacuum_vacuum_cost_limit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">2000 # 預設 200、production 拉 5-10 倍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="na">autovacuum_vacuum_cost_delay&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">2ms # 預設 2ms、不太需要動&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="na">autovacuum_max_workers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">6 # 預設 3、CPU 多時拉到 6-10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="na">maintenance_work_mem&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">1GB # 預設 64MB、單一 vacuum 用的記憶體&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>直覺：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL</a> overview 的 implementation-layer deep article。Overview 已說明 PostgreSQL MVCC 的 vacuum 必要性、本文聚焦 <em>autovacuum 在 production write-heavy workload 為什麼追不上</em> 的根因 + 各維度 tuning。</p></blockquote>
<h2 id="你的-autovacuum-永遠追不上-bloat--為什麼">你的 autovacuum 永遠追不上 bloat — 為什麼</h2>
<p>write-heavy table 的常見故事：上線時表 10GB、3 個月後 30GB、6 個月 80GB；DBA 看 <code>pg_stat_user_tables</code> 發現 <code>n_dead_tup</code> 比 <code>n_live_tup</code> 還多、<code>pg_stat_progress_vacuum</code> 顯示 autovacuum 一直在跑、但 dead tuple 從沒清乾淨。表本身才 5M row、實際磁碟卻佔 80GB。</p>
<p>這不是 PostgreSQL bug、是 autovacuum <em>cost-based throttling 預設保守</em> 的設計意圖 — autovacuum 不該影響 OLTP query 性能、所以每跑一段就 sleep。預設 <code>autovacuum_vacuum_cost_limit=200</code> + <code>autovacuum_vacuum_cost_delay=2ms</code> 在 write-heavy 表（每秒幾千 UPDATE）下、清理速度 <em>永遠慢於</em> dead tuple 產生速度。預設配置適合 read-heavy / write-light workload；OLTP write-heavy 必須調。</p>
<h2 id="mvcc-跟-dead-tuplevacuum-在解什麼">MVCC 跟 dead tuple：vacuum 在解什麼</h2>
<p>PostgreSQL MVCC：每次 UPDATE 都是 <em>insert new row + mark old row as deleted</em>；DELETE 是 <em>mark as deleted、不立刻釋放空間</em>。dead tuple 在 disk 上佔位、但不能被 query 讀到。autovacuum 的責任：</p>
<ol>
<li><strong>回收 dead tuple 空間</strong> 供新 row reuse（不縮 table 大小、是 free space map）</li>
<li><strong>更新 visibility map</strong> 讓 index-only scan 跳過 heap fetch</li>
<li><strong>凍結老 row 的 xid</strong>（freeze）避免 xid wraparound 災難</li>
<li><strong>重整 index B-tree</strong> 標記 dead pointer（不刪 index page）</li>
</ol>
<p>Vacuum 不縮表 — 真要縮要跑 <code>VACUUM FULL</code>（全表 exclusive lock、production 不能跑）或 <code>pg_repack</code>（online repack tool）。預期 vacuum 只能 <em>讓表停止長大</em>、不能 <em>讓表變小</em>。</p>
<h2 id="tuningcost-based-throttle-跟-trigger-threshold">Tuning：cost-based throttle 跟 trigger threshold</h2>
<h3 id="cost-based-throttle全-instance">Cost-based throttle（全 instance）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># postgresql.conf</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="na">autovacuum_vacuum_cost_limit</span> <span class="o">=</span> <span class="s">2000          # 預設 200、production 拉 5-10 倍</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="na">autovacuum_vacuum_cost_delay</span> <span class="o">=</span> <span class="s">2ms            # 預設 2ms、不太需要動</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="na">autovacuum_max_workers</span> <span class="o">=</span> <span class="s">6                    # 預設 3、CPU 多時拉到 6-10</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="na">maintenance_work_mem</span> <span class="o">=</span> <span class="s">1GB                    # 預設 64MB、單一 vacuum 用的記憶體</span></span></span></code></pre></div><p>直覺：</p>
<ul>
<li><code>cost_limit</code> 是每個 cycle 能消費多少「cost」、cost 由 page read / dirty / hit 加總；拉高 = 每次 cycle 處理更多 page</li>
<li>拉 <code>cost_limit</code> 比 <code>cost_delay</code> 直接 — delay 太低（&lt; 1ms）OS scheduler 抖動就無效</li>
<li><code>max_workers</code> 限同時跑的 vacuum；partition 多時容易爆滿、要拉</li>
<li><code>maintenance_work_mem</code> 影響 index vacuum 速度、SSD 環境 1-2GB 是 sweet spot</li>
</ul>
<h3 id="per-table-override精準到-hot-table">Per-table override（精準到 hot table）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 對 hot write-heavy 表加強
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="n">autovacuum_vacuum_scale_factor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">.</span><span class="mi">05</span><span class="p">,</span><span class="w">      </span><span class="c1">-- 預設 0.2、5% dead 就觸發
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span><span class="w">  </span><span class="n">autovacuum_vacuum_threshold</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1000</span><span class="p">,</span><span class="w">          </span><span class="c1">-- 預設 50、絕對值底線
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="w">  </span><span class="n">autovacuum_vacuum_cost_limit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5000</span><span class="p">,</span><span class="w">         </span><span class="c1">-- 該表獨立 cost_limit
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span><span class="w">  </span><span class="n">autovacuum_analyze_scale_factor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">.</span><span class="mi">05</span><span class="p">,</span><span class="w">      </span><span class="c1">-- analyze 也跟著
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="w">  </span><span class="n">autovacuum_freeze_max_age</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">100000000</span><span class="w">        </span><span class="c1">-- anti-wraparound 提前
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="c1">-- 對 append-only 表（log table）降頻
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">audit_log</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">  </span><span class="n">autovacuum_vacuum_scale_factor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span><span class="w">        </span><span class="c1">-- 50% dead 才觸發（極少 UPDATE / DELETE）
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span><span class="w">  </span><span class="n">autovacuum_freeze_max_age</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1000000000</span><span class="w">       </span><span class="c1">-- freeze 延後
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span><span class="p">);</span></span></span></code></pre></div><p>關鍵：<em>hot table 比 default 緊、cold table 比 default 鬆</em>、不要把所有表用同套配置。Production cluster 通常 5-20 個 hot table 需要 per-table tuning。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1write-heavy-hot-tableautovacuum-永遠跑不完">Case 1：write-heavy hot table，autovacuum 永遠跑不完</h3>
<p><strong>徵兆</strong>：<code>pg_stat_user_tables.n_dead_tup</code> 持續高於 <code>n_live_tup</code>、<code>pg_stat_progress_vacuum</code> 顯示某表 vacuum 跑了 6+ 小時還在 <code>scanning heap</code>、表 size 持續長大。</p>
<p><strong>根因</strong>：default <code>cost_limit=200</code> 對該表 write rate（~5000 UPDATE/s）下、vacuum 處理速度 &lt; dead tuple 產生速度；單次 autovacuum 跑完整表要 12 小時、但表 5% bloat 觸發又啟動下一輪。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>對該表 <code>ALTER TABLE ... SET (autovacuum_vacuum_cost_limit = 10000)</code> — 該表 vacuum 不受全 instance 限制</li>
<li><code>maintenance_work_mem</code> 拉到 2GB（單 vacuum）</li>
<li>短期：手動 <code>VACUUM (VERBOSE, ANALYZE) events;</code> 在 maintenance window 跑、catch up</li>
<li>長期：考慮 partitioning — partition 後 vacuum 只動最近 partition、不掃整表</li>
</ol>
<h3 id="case-2長-transaction-卡住-vacuum-的-xmin-horizon">Case 2：長 transaction 卡住 vacuum 的 xmin horizon</h3>
<p><strong>徵兆</strong>：autovacuum 看似有跑、但 <code>n_dead_tup</code> 不降；<code>pg_stat_activity</code> 看到一個跑了 8 小時的 SELECT（report query 或 idle in transaction）。</p>
<p><strong>根因</strong>：vacuum 只能回收「不會被任何 active transaction 看到」的 dead tuple；長 transaction 的 xmin 鎖死 vacuum 能回收的範圍、即使 autovacuum 不停跑、能回收的 row 數為 0。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>預防</strong>：application 端用 <code>statement_timeout</code> + <code>idle_in_transaction_session_timeout</code>（30 分鐘）強制終止 long transaction</li>
<li><strong>偵測</strong>：<code>SELECT pid, now() - xact_start FROM pg_stat_activity WHERE state = 'idle in transaction'</code> 定期掃</li>
<li><strong>臨時</strong>：kill 長 transaction（<code>pg_cancel_backend(pid)</code> / <code>pg_terminate_backend(pid)</code>）、autovacuum 下次跑就能回收</li>
<li><strong>架構</strong>：報表 query 跑在 standby、不要在 primary 開 long transaction</li>
</ol>
<h3 id="case-3anti-wraparound-vacuum-在-peak-觸發">Case 3：Anti-wraparound vacuum 在 peak 觸發</h3>
<p><strong>徵兆</strong>：production 流量高峰時 PostgreSQL CPU 100%、<code>pg_stat_progress_vacuum</code> 顯示 anti-wraparound vacuum 正在跑、application latency 暴漲；log 出現 <code>database &quot;myapp&quot; must be vacuumed within X transactions</code>。</p>
<p><strong>根因</strong>：autovacuum_freeze_max_age（預設 200M）到了、PostgreSQL <em>強制</em> 跑 anti-wraparound vacuum（即使在 peak）；這個 vacuum <em>不受 cost_limit 限制</em>、跑到完才停、表大時要幾小時、跟 OLTP query 搶 IO。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>預防</strong>：<code>autovacuum_freeze_max_age</code> 拉到 1B（10 億）、給 freeze 更多時間在 off-peak 自然發生</li>
<li><strong>per-table freeze</strong>：hot table 設 <code>autovacuum_freeze_max_age = 100M</code>（提前在 off-peak freeze）、cold table 設 800M（避免不必要 freeze）</li>
<li><strong>緊急</strong>：手動跑 <code>VACUUM (FREEZE, VERBOSE) table_name;</code> 在 maintenance window 預先 freeze</li>
<li><strong>監測</strong>：<code>SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r' ORDER BY age(relfrozenxid) DESC LIMIT 20;</code> 看哪些表逼近 wraparound</li>
</ol>
<h3 id="case-4partition-table-把-autovacuum_max_workers-跑滿">Case 4：Partition table 把 autovacuum_max_workers 跑滿</h3>
<p><strong>徵兆</strong>：partition 後（時間 partition、12 個月分區）、autovacuum 跑很慢、<code>pg_stat_activity</code> 看到 3 個 autovacuum worker 都在跑 partition 表、其他 hot table queue 等很久。</p>
<p><strong>根因</strong>：<code>autovacuum_max_workers=3</code> 預設、每個 partition 算獨立 table；100 個 partition 中 50 個都需要 vacuum、worker 滿、其他 table 排隊。</p>
<p><strong>修法</strong>：</p>
<ol>
<li>拉 <code>autovacuum_max_workers</code> 到 6-10（依 CPU core 數）</li>
<li>cold partition 設 <code>autovacuum_enabled = false</code>（已不寫的舊 partition）、減少 worker 競爭</li>
<li>partition 數量本身要克制 — 100+ partition 是訊號該重新評估 partition strategy</li>
</ol>
<h3 id="case-5index-bloat-沒被-vacuum-處理">Case 5：Index bloat 沒被 vacuum 處理</h3>
<p><strong>徵兆</strong>：表 vacuum 跑完了、<code>n_dead_tup</code> 為 0、但 index size 持續長大；query 用該 index 越來越慢、跟 sequential scan 差不多。</p>
<p><strong>根因</strong>：autovacuum 只處理 <em>heap</em>（table data）跟 <em>index leaf pages</em>；index B-tree 內部結構 fragmentation 不被 vacuum 處理。dead pointer 留在 index leaf page、查詢仍 traverse 過、IO 多。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><code>REINDEX CONCURRENTLY</code> 線上重建 index（PG 12+）、不鎖表</li>
<li>監測 index bloat：<code>pgstattuple_approx</code> extension 或 <code>pg_repack</code></li>
<li>預防：B-tree index 設計避免 high cardinality + 大量 UPDATE 同欄位（typical 場景：status column update）；考慮 <em>partial index</em> 或 <em>hash index</em>（PG 10+ logged）</li>
<li>大量 bloat index 用 <code>pg_repack</code> 重建（不需要 superuser、不鎖表）</li>
</ol>
<h2 id="容量規劃">容量規劃</h2>
<p>vacuum capacity 用 <em>跟得上 dead tuple 產生速度</em> 衡量：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>估算方式</th>
          <th>警戒</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>dead tuple 產生 rate</td>
          <td><code>UPDATE/s + DELETE/s + ~10% INSERT/s（HOT update miss）</code></td>
          <td>跟 vacuum rate 對比</td>
      </tr>
      <tr>
          <td>vacuum 處理 rate</td>
          <td><code>cost_limit / cost_delay × page_size</code>、~MB/s 數量級</td>
          <td>跟 dead tuple rate 對比</td>
      </tr>
      <tr>
          <td>autovacuum_max_workers</td>
          <td>partition 數 + hot table 數 / 3-5</td>
          <td>100+ partition 必須拉 worker</td>
      </tr>
      <tr>
          <td>maintenance_work_mem</td>
          <td>1-2GB / vacuum worker</td>
          <td>全 worker 跑時的記憶體上限要 sizing</td>
      </tr>
      <tr>
          <td>anti-wraparound 觸發頻率</td>
          <td>預設 200M xid、write-heavy ~ 1-2 週觸發一次</td>
          <td>拉到 1B 後 ~ 2-3 月一次</td>
      </tr>
      <tr>
          <td>Bloat ratio</td>
          <td><code>pg_stat_user_tables.n_dead_tup / n_live_tup</code></td>
          <td>&gt; 50% 表示 vacuum 追不上</td>
      </tr>
  </tbody>
</table>
<p>實務 default：</p>
<ul>
<li>OLTP write-heavy（事件 / 訂單）：cost_limit 2000-5000、scale_factor 0.05、freeze_max_age 100M</li>
<li>OLTP read-heavy（user / config）：default 即可</li>
<li>Append-only log：scale_factor 0.5、freeze_max_age 800M、<code>autovacuum_enabled = false</code> for cold partition</li>
</ul>
<h2 id="整合--下一步">整合 / 下一步</h2>
<h3 id="跟-partitioning-整合">跟 <a href="/blog/backend/01-database/vendors/postgresql/declarative-partitioning/" data-link-title="PostgreSQL declarative partitioning：partition 不是切表、是讓 planner pruning" data-link-desc="Declarative partitioning 的真實價值是 query planner pruning &#43; maintenance scope 縮小、不是「把大表切小」；RANGE / LIST / HASH 取捨、partition key 選法、5 個 production 踩雷（key 選錯不 prune / unique 不 enforce 跨 partition / ATTACH 鎖太久 / partition 數爆 / DETACH 不 reclaim 空間）、跟 autovacuum &#43; index 設計整合">partitioning</a> 整合</h3>
<p>partitioning 是 vacuum 問題的長期解：</p>
<ul>
<li>大表（&gt; 100GB）vacuum 時間隨 size 線性、partition 後 vacuum 只動最近 partition</li>
<li>Cold partition <code>autovacuum_enabled = false</code> 完全停掉、新數據只在 hot partition</li>
<li>缺點：partition 數量爆炸時、autovacuum_max_workers 也要拉</li>
</ul>
<h3 id="跟-monitoring-整合">跟 monitoring 整合</h3>
<p>關鍵 metric：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- bloat 比例
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">relname</span><span class="p">,</span><span class="w"> </span><span class="n">n_dead_tup</span><span class="p">,</span><span class="w"> </span><span class="n">n_live_tup</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">       </span><span class="n">round</span><span class="p">(</span><span class="n">n_dead_tup</span><span class="p">::</span><span class="nb">numeric</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="k">nullif</span><span class="p">(</span><span class="n">n_live_tup</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">dead_pct</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">pg_stat_user_tables</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">n_live_tup</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">1000</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">n_dead_tup</span><span class="w"> </span><span class="k">DESC</span><span class="w"> </span><span class="k">LIMIT</span><span class="w"> </span><span class="mi">20</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="c1">-- vacuum 進度
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">pg_stat_progress_vacuum</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="c1">-- xid wraparound 距離
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">datname</span><span class="p">,</span><span class="w"> </span><span class="n">age</span><span class="p">(</span><span class="n">datfrozenxid</span><span class="p">)</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">pg_database</span><span class="w"> </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">age</span><span class="w"> </span><span class="k">DESC</span><span class="p">;</span></span></span></code></pre></div><p>Prometheus alert 三條：<code>dead_pct &gt; 30</code>、<code>vacuum_running_seconds &gt; 3600</code>、<code>xid_age &gt; 500000000</code>。</p>
<h3 id="跟-backup-window">跟 backup window</h3>
<p>VACUUM FREEZE 在 backup 前跑能減少 backup size（freeze tuple 不需要 special handling）：</p>
<ol>
<li>每週 maintenance window 跑 <code>VACUUM (FREEZE, ANALYZE) hot_table</code> — 預先 freeze + 更新 stats</li>
<li>backup 前避免長 transaction、確保 vacuum 能跑</li>
</ol>
<h3 id="下一步議題">下一步議題</h3>
<ul>
<li><strong>HOT update 跟 fillfactor</strong>：UPDATE 同頁可重用空間、fillfactor 80 為 hot table 留 20% buffer</li>
<li><strong><code>pg_repack</code> vs <code>VACUUM FULL</code></strong>：online vs offline、長期維護工具選擇</li>
<li><strong>PostgreSQL 14+ parallel vacuum</strong>：index vacuum 平行化、大表受益明顯</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>上游 vendor 頁：<a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL</a></li>
<li>上游 chapter：<a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">High Concurrency Access</a> — vacuum 是 concurrency 治理一環</li>
<li>平行 deep article：<a href="/blog/backend/01-database/vendors/postgresql/patroni-ha/" data-link-title="PostgreSQL Patroni HA：從 leader 失聯到 client 重連的 5 段 failover lifecycle" data-link-desc="Patroni 把 PostgreSQL HA 拆成 detection / election / promotion / reconfiguration / recovery 五段 lifecycle、每段都有獨立配置跟 failure mode；DCS quorum &#43; watchdog 防 split-brain、async/sync replication 取捨、5 個 production 踩雷、跟 PgBouncer / HAProxy / cert-manager 整合">Patroni HA</a> / <a href="/blog/backend/01-database/vendors/postgresql/declarative-partitioning/" data-link-title="PostgreSQL declarative partitioning：partition 不是切表、是讓 planner pruning" data-link-desc="Declarative partitioning 的真實價值是 query planner pruning &#43; maintenance scope 縮小、不是「把大表切小」；RANGE / LIST / HASH 取捨、partition key 選法、5 個 production 踩雷（key 選錯不 prune / unique 不 enforce 跨 partition / ATTACH 鎖太久 / partition 數爆 / DETACH 不 reclaim 空間）、跟 autovacuum &#43; index 設計整合">Declarative Partitioning</a> / <a href="/blog/backend/01-database/vendors/postgresql/mvcc-lock-model/" data-link-title="PostgreSQL MVCC &#43; Lock Model：為什麼 PG 比 MySQL 少 deadlock、但 vacuum 是別的代價" data-link-desc="PG 用 *MVCC-heavy &#43; 少 explicit lock* 的並行控制、跟 MySQL InnoDB 的 *lock-based*（record / gap / next-key）相反。本文走 MVCC 機制（tuple version &#43; xmin/xmax &#43; visibility）、PG 4 種 lock（row-level / table-level / advisory / predicate）、預測 SERIALIZABLE 行為、5 production 踩雷（idle transaction 卡 vacuum / SELECT FOR UPDATE 跨 transaction / advisory lock 沒釋放 / bloat 不是 vacuum 問題 / predicate lock 在 SSI 下 rollback）、跟 MySQL lock-contention sibling 對比">MVCC + Lock Model</a>（為什麼會有 dead tuple、跟 lock 互動）</li>
<li>Methodology：<a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">Vendor 深度技術文章的寫作方法論</a></li>
</ul>
]]></content:encoded></item><item><title>Parca</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/parca/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/parca/</guid><description>&lt;p>Parca 的核心責任是用開源 continuous profiling 與 eBPF 路線建立 infrastructure-wide profile evidence。它適合需要低侵入、跨 process、跨 service、偏平台層的 profiling 團隊，重點在用 always-on profile 找出 CPU、memory、runtime 與 kernel / user space 的資源熱點。&lt;/p>
&lt;h2 id="服務定位">服務定位&lt;/h2>
&lt;p>Parca 是 Polar Signals 主導的 OSS continuous profiling、特色是 &lt;em>eBPF-based 採集 + pprof 標準格式 + Prometheus-style 拉取與 label 模型&lt;/em>。它跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope&lt;/a> 是 OSS 同類、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler&lt;/a> 則是 OSS / 自管 vs SaaS / APM 整合的差異。eBPF agent 直接從 kernel 採 stack trace、不需要 application 改 code 或注入 runtime agent；pprof 格式讓既有 Go / Java / Python 工具鏈可以直接讀；Prometheus-style scrape 讓 Parca server 跟 metrics 用同一套 service discovery 與 label。&lt;/p>
&lt;h2 id="最短判讀路徑">最短判讀路徑&lt;/h2>
&lt;p>判斷 Parca 部署是否能撐起 platform-wide profiling、最少看四件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>eBPF agent deploy&lt;/strong>：Parca Agent 走 DaemonSet 跑在每個 node、需要 kernel ≥ 4.18（CO-RE / BTF）、&lt;code>SYS_ADMIN&lt;/code> 或 &lt;code>PERF_EVENT&lt;/code> capability、host PID namespace。受管 Kubernetes（GKE / EKS / AKS）的 worker node 是否允許這個權限是第一個判讀點&lt;/li>
&lt;li>&lt;strong>Parca server scrape&lt;/strong>：server 跟 agent 走 pull-based、Prometheus-style ServiceMonitor / scrape config、label 跟 metrics 同模型（namespace / pod / container / node）。scrape interval、retention、storage backend（FrostDB 內建 / object storage）要明確&lt;/li>
&lt;li>&lt;strong>pprof query&lt;/strong>：profile 以 pprof format 存、Parca UI 提供 flame graph 與 compare view、也可 export pprof file 給 &lt;code>go tool pprof&lt;/code> 或其他既有工具離線分析&lt;/li>
&lt;li>&lt;strong>Grafana integration&lt;/strong>：Parca 提供 datasource plugin、profile 可以跟 metrics / log / trace 在 Grafana 同一頁 correlate、配 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope&lt;/a> 或 Tempo 形成 observability 對齊&lt;/li>
&lt;/ul>
&lt;p>四件事任一缺失、就是 profiling control plane 還沒上線的待補項目。&lt;/p></description><content:encoded><![CDATA[<p>Parca 的核心責任是用開源 continuous profiling 與 eBPF 路線建立 infrastructure-wide profile evidence。它適合需要低侵入、跨 process、跨 service、偏平台層的 profiling 團隊，重點在用 always-on profile 找出 CPU、memory、runtime 與 kernel / user space 的資源熱點。</p>
<h2 id="服務定位">服務定位</h2>
<p>Parca 是 Polar Signals 主導的 OSS continuous profiling、特色是 <em>eBPF-based 採集 + pprof 標準格式 + Prometheus-style 拉取與 label 模型</em>。它跟 <a href="/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope</a> 是 OSS 同類、跟 <a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler</a> 則是 OSS / 自管 vs SaaS / APM 整合的差異。eBPF agent 直接從 kernel 採 stack trace、不需要 application 改 code 或注入 runtime agent；pprof 格式讓既有 Go / Java / Python 工具鏈可以直接讀；Prometheus-style scrape 讓 Parca server 跟 metrics 用同一套 service discovery 與 label。</p>
<h2 id="最短判讀路徑">最短判讀路徑</h2>
<p>判斷 Parca 部署是否能撐起 platform-wide profiling、最少看四件事：</p>
<ul>
<li><strong>eBPF agent deploy</strong>：Parca Agent 走 DaemonSet 跑在每個 node、需要 kernel ≥ 4.18（CO-RE / BTF）、<code>SYS_ADMIN</code> 或 <code>PERF_EVENT</code> capability、host PID namespace。受管 Kubernetes（GKE / EKS / AKS）的 worker node 是否允許這個權限是第一個判讀點</li>
<li><strong>Parca server scrape</strong>：server 跟 agent 走 pull-based、Prometheus-style ServiceMonitor / scrape config、label 跟 metrics 同模型（namespace / pod / container / node）。scrape interval、retention、storage backend（FrostDB 內建 / object storage）要明確</li>
<li><strong>pprof query</strong>：profile 以 pprof format 存、Parca UI 提供 flame graph 與 compare view、也可 export pprof file 給 <code>go tool pprof</code> 或其他既有工具離線分析</li>
<li><strong>Grafana integration</strong>：Parca 提供 datasource plugin、profile 可以跟 metrics / log / trace 在 Grafana 同一頁 correlate、配 <a href="/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope</a> 或 Tempo 形成 observability 對齊</li>
</ul>
<p>四件事任一缺失、就是 profiling control plane 還沒上線的待補項目。</p>
<h2 id="定位">定位</h2>
<p>Parca 適合平台團隊建立 profiling control plane。當問題橫跨 Kubernetes cluster、node pool、multi-service path 或 shared runtime 成本，Parca 能從更接近 infrastructure 的角度收集 profile。</p>
<p>這個定位讓 Parca 接到 <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/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling</a>。它的價值在於低侵入與平台廣度；它的代價在於 eBPF 支援、symbolization、storage、權限與平台維運責任。</p>
<h2 id="適用場景">適用場景</h2>
<p>Infrastructure-wide profiling 適合 Parca。平台團隊可以觀察 cluster、node、namespace、service 與 process 的 CPU 熱點，找出共同 library、runtime、sidecar、agent 或 kernel path 的成本。</p>
<p>Kubernetes 平台適合 Parca。當服務在多 namespace、多 workload、多 node pool 上運作，Parca 可以把 profile 維度接到 pod、container、node、namespace 與 label。</p>
<p>低侵入 profiling 適合 Parca。eBPF-based profiling 可以降低 application instrumentation 成本，讓團隊先取得廣域視角，再對特定服務加更細的 runtime profiler 或 APM 整合。</p>
<h2 id="選型判準">選型判準</h2>
<table>
  <thead>
      <tr>
          <th>判準</th>
          <th>Parca 的價值</th>
          <th>需要補的能力</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>eBPF / low overhead</td>
          <td>低侵入取得廣域 profile</td>
          <td>kernel / runtime 支援與權限治理</td>
      </tr>
      <tr>
          <td>Platform-wide</td>
          <td>node、namespace、service 維度可對照</td>
          <td>Kubernetes label 與 ownership discipline</td>
      </tr>
      <tr>
          <td>Open source</td>
          <td>profiling platform 可自管</td>
          <td>storage、retention、upgrade</td>
      </tr>
      <tr>
          <td>Compare / diff</td>
          <td>profile compare 支援退化定位</td>
          <td>deploy label、baseline 與 symbolization</td>
      </tr>
  </tbody>
</table>
<p>eBPF / low overhead 價值來自平台廣度。團隊可以先觀察整個基礎設施的 CPU 熱點，再決定哪些服務需要更深入的 application-level profiling。</p>
<p>Platform-wide 價值來自共同成本治理。Sidecar、agent、logging library、serialization library 或 runtime upgrade 的成本可能散在多個服務中，Parca 這類工具能把分散成本聚合回平台決策。</p>
<h2 id="跟其他工具的取捨">跟其他工具的取捨</h2>
<p>Parca 和 Datadog Continuous Profiler 的主要差異是平台模型。Parca 偏開源、自管、eBPF 與 infra-wide profiling；Datadog 偏 SaaS、APM drilldown、deployment marker 與產品化 workflow。</p>
<p>Parca 和 Pyroscope 的主要差異是視角。Pyroscope 偏 Grafana / application profiling backend；Parca 偏 eBPF、Kubernetes / infrastructure-level profiling 與平台團隊治理。</p>
<p>Parca 和 language runtime profiler 的主要差異是導入方式。Runtime profiler 能提供語言特定維度；Parca 能先提供低侵入廣域 profile，但 symbolization 與語言細節需要額外治理。</p>
<h2 id="核心取捨表">核心取捨表</h2>
<table>
  <thead>
      <tr>
          <th>取捨維度</th>
          <th>Parca</th>
          <th>Pyroscope</th>
          <th>Datadog Continuous Profiler</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>採集方式</td>
          <td>eBPF agent（kernel-level、unwound）</td>
          <td>eBPF + SDK 雙路、語言 SDK 較豐富</td>
          <td>APM agent 內建、語言 SDK 整合</td>
      </tr>
      <tr>
          <td>Profile format</td>
          <td>pprof（Google 標準）</td>
          <td>自家 + pprof export</td>
          <td>Datadog proprietary、可 export pprof</td>
      </tr>
      <tr>
          <td>採集模型</td>
          <td>Pull-based、Prometheus-style scrape</td>
          <td>Push or pull（Grafana Agent）</td>
          <td>Push to Datadog backend</td>
      </tr>
      <tr>
          <td>Label 模型</td>
          <td>Prometheus label（namespace / pod）</td>
          <td>Grafana label</td>
          <td>Datadog tag</td>
      </tr>
      <tr>
          <td>部署模型</td>
          <td>Self-hosted OSS + Polar Signals SaaS</td>
          <td>Self-hosted OSS + Grafana Cloud SaaS</td>
          <td>SaaS only</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>FrostDB 內建 / object storage</td>
          <td>自家 storage / Grafana backend</td>
          <td>Datadog managed</td>
      </tr>
      <tr>
          <td>APM 整合</td>
          <td>弱 — 走 Grafana correlation</td>
          <td>中 — Grafana stack 整合</td>
          <td>強 — trace ↔ profile drilldown 內建</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>Platform team 自管、Prometheus stack</td>
          <td>Grafana stack 已用、應用層 profiling</td>
          <td>已用 Datadog、APM-first、SaaS-only 可</td>
      </tr>
  </tbody>
</table>
<h2 id="進階主題">進階主題</h2>
<p><strong>Polar Signals Cloud</strong>：Parca 上游公司 Polar Signals 提供 managed SaaS — agent 一樣走 OSS、server / storage / UI 託管。適合不想養 Parca server 又要 OSS agent 路線的團隊。差異點是 ingestion cost 跟 retention 由 SaaS 計費、license / data residency 要看合約。</p>
<p><strong>Prometheus 同 label model</strong>：Parca 的 service discovery、scrape config 跟 label 跟 Prometheus 幾乎同形 — 既有 ServiceMonitor、relabel rule、Kubernetes SD 可以直接複用。意義是 profile 維度跟 metric 維度天然對齊、<code>namespace=foo, service=bar</code> 在兩邊都成立、cross-signal correlation 不需要再 mapping。</p>
<p><strong>Compare profiles（diff before/after deploy）</strong>：Parca UI 支援選 baseline window 跟 candidate window 做 flame graph diff、顏色標示哪個 stack frame 變胖變瘦。配 <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 Performance Improvement Loop</a> 的 deploy marker、可以把「這次發版讓 CPU +15%」直接歸因到具體 frame。</p>
<p><strong>Continuous profiling vs sampling-only</strong>：傳統 profiler 是「出問題時手動跑 30 秒」、Parca 是「always-on、低頻率持續採」。差異是 <em>事後回溯能力</em> — incident 發生時直接拉時間區間的 profile、不用重現問題；sampling-only 工具在偶發 spike 時抓不到現場。代價是 storage 跟 agent overhead 要長期治理。</p>
<h2 id="操作成本">操作成本</h2>
<p>Parca 的主要成本是平台維運。Agent / scraper、server、storage、retention、symbolization、upgrade 與 Kubernetes 權限都需要平台團隊負責。</p>
<p>Symbolization 成本來自可讀性。Profile 如果缺 symbol、debug info、build ID 或 source mapping，flame graph 會變成難以行動的 address / binary offset，因此 build pipeline 要保留符號資訊策略。</p>
<p>權限成本來自 eBPF 與 node visibility。低層 profiling 需要足夠 host / kernel 權限，受管 Kubernetes、security policy、multi-tenant cluster 與 compliance 要先評估。</p>
<h2 id="evidence-package">Evidence Package</h2>
<p>Parca 結果應回寫到 evidence package。最小欄位包括 cluster、namespace、service、node pool、profile type、baseline window、candidate window、compare link、symbolization status、agent overhead、known gap 與 owner。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Parca 證據來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>Parca query、compare view、flame graph</td>
      </tr>
      <tr>
          <td>Time range</td>
          <td>baseline / candidate profile window</td>
      </tr>
      <tr>
          <td>Query link</td>
          <td>Parca UI / dashboard / metrics link</td>
      </tr>
      <tr>
          <td>Data quality</td>
          <td>label completeness、symbolization status</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>cluster coverage、agent overhead</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>未覆蓋 node、symbol 缺失、kernel 限制</td>
      </tr>
  </tbody>
</table>
<p>Evidence package 的核心用途是把平台層 profile 變成容量決策。Reviewer 要能看到成本來自 application code、runtime、sidecar、kernel path 還是 shared library，並把結果回寫到 owner。</p>
<h2 id="排錯與失敗快速判讀">排錯與失敗快速判讀</h2>
<ul>
<li><strong>eBPF agent 起不來 / kernel 不支援</strong>：舊 kernel（&lt; 4.18）或缺 BTF / CO-RE 支援、受管 Kubernetes 不開 <code>SYS_ADMIN</code> — 先確認 node OS image、必要時換 distribution 或升級 worker node pool</li>
<li><strong>Profile storage 暴增</strong>：scrape interval 太密 + retention 沒設 + label cardinality 爆炸（把 request-id 放進 label）— 降頻、限 retention window、把高 cardinality 維度移出 profile label</li>
<li><strong>Symbol resolution 失敗 / flame graph 全是 address</strong>：build pipeline 沒保留 debug info、stripped binary、容器 image 不含符號 — 在 build 階段保留 debug symbol、用 separate debuginfo 上傳 Parca debuginfod、或在 image 保留 unstripped binary</li>
<li><strong>JIT 語言（Java / Node.js）stack 不完整</strong>：eBPF 看到的是 native frame、JIT-compiled frame 需要額外 perf map / JVMTI agent — 補語言層 profiler 或開 JIT symbol dump</li>
<li><strong>Agent overhead 影響 production</strong>：sample rate 預設 19 Hz、特定 workload 可能仍敏感 — 在 noisy neighbor 敏感的 node pool 降頻或排除特定 namespace</li>
<li><strong>多 cluster scrape 中心化太重</strong>：單一 Parca server 拉 N 個 cluster 變瓶頸 — 改 federation 模型、每 cluster 一個 Parca server、上層做 query aggregation</li>
</ul>
<h2 id="案例回寫">案例回寫</h2>
<p>Parca 適合回寫平台層與 multi-service 成本案例。它可接 <a href="/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &#43; 1000 Pods/sec 創建吞吐">9.C34 GCP 130K node GKE cluster</a> 的 cluster-scale profiling 需求、<a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS multi-cluster</a> 的 246 cluster 平台成本治理、<a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 的 shared platform noise 降低、<a href="/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/" data-link-title="9.C33 Maersk &#43; Bosch：傳統產業在 Azure AKS 上的微服務治理" data-link-desc="全球海運 Maersk 跟 Bosch 智慧建築把 AKS 當微服務治理基礎、釋放工程資源做業務功能">9.C33 Maersk + Bosch Azure AKS</a> 的傳統產業多 BU 平台層歸因，以及 <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom DynamoDB + EKS</a> 跨遊戲共用後端的 profile 切分。</p>
<p>這些案例的重點是平台視角。Parca 頁引用案例時，要把 case 轉成 cluster / namespace / service label、compare window、symbolization、shared library cost 與 owner routing — 例如 GCP 130K-node 規模下，Parca 自身的 storage / scrape capacity 也成為 profile target、不只是觀測 application。</p>
<p>兩個典型用途值得單獨點名：</p>
<ul>
<li><strong>Performance regression detection</strong>：發版前後拉 compare profile、把「這次 release 讓 P99 CPU +18%」歸因到具體 stack frame。配 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games EKS multi-cluster</a> 的 246 cluster 規模、單一 service rollout 在 always-on profile 下可秒級看出 hot path 變化、不需要等 SRE 跑手動 pprof</li>
<li><strong>Cost engineering</strong>：把 CPU profile 折算成 node 成本、找出 shared library / runtime / sidecar 的 hidden cost。配 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 的 platform consolidation 思路、profile 證據可以決定要不要重寫熱點、換 library、還是接受成本</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<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/improvement-loop/" data-link-title="9.9 Performance Improvement Loop" data-link-desc="壓測 → profile → fix → re-test → release gate 的閉環">9.9 Performance Improvement Loop</a></li>
<li>跨模組：<a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler</a></li>
<li>平行：<a href="/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope</a></li>
<li>官方：<a href="https://www.parca.dev/docs/">Parca documentation</a></li>
</ul>
]]></content:encoded></item><item><title>9.C32 Clearent：Azure SQL Hyperscale 撐每年 5 億筆支付交易</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/</guid><description>&lt;p>這個案例的核心責任是補強 Azure DB-OLTP 維度缺口。Clearent 是美國的中型支付處理商、跟 &lt;a href="https://tarrragon.github.io/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 跨市場銀行 OLTP&lt;/a> 形成對照 — 一個是合規驅動的跨市場分割、一個是單一規模的高吞吐處理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Clearent 在 Azure SQL Hyperscale 的關鍵敘述（引自 &lt;a href="https://www.microsoft.com/en/customers/story/774969-clearent-banking-capital-markets-azure">Clearent Customer Story&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>年交易量&lt;/td>
 &lt;td>5 億筆&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客戶基礎&lt;/td>
 &lt;td>各種規模 merchants（中小型為主）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>Azure SQL Database Hyperscale 服務級&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構模式&lt;/td>
 &lt;td>modern microservices architecture&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴展能力&lt;/td>
 &lt;td>「scale automatically and almost infinitely」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>並發特性&lt;/td>
 &lt;td>「tens of thousands of users 同時存取」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>業務驅動&lt;/td>
 &lt;td>「unite all its information in one place」+ 「faster insights」&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵特性：Azure SQL Hyperscale 把 storage 跟 compute 分離、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora&lt;/a> 的 Aurora 是同類設計。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Clearent 案例揭露三個 Hyperscale 設計的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>5 億筆 / 年 ≈ 1500 筆 / 秒平均、但 peak 可能 10-50x&lt;/strong>：支付交易有日內 / 月內 / 季內節律。早上 9-11 點商家對帳高峰、下午 12-1 點消費高峰、晚上 6-8 點消費高峰、月底結算高峰。容量規劃必須按 peak 訂、不是平均。對應 &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> 的 peak/avg ratio 跟 &lt;a href="https://tarrragon.github.io/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 容量規劃模型&lt;/a>。&lt;/li>
&lt;li>&lt;strong>Hyperscale = storage / compute 解耦&lt;/strong>：傳統 SQL Server primary 對 storage 跟 CPU / RAM 綁定、擴 storage 就要換更大 instance、不便。Hyperscale 把 storage 拉到分散式 log service、可以獨立擴 storage（最高 100 TB）、compute 獨立擴。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner&lt;/a> 的同類分離思維、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora&lt;/a>。&lt;/li>
&lt;li>&lt;strong>「unite all information in one place」是支付業的特殊需求&lt;/strong>：merchants 需要對帳、退款、清算、稅務報表都即時可查、不能 OLAP 分開。Hyperscale 的 read scale-out（最多 4 個 secondary replica）讓即時報表跑在 OLTP DB 上不影響交易吞吐。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：「scale automatically and almost infinitely」是行銷敘述。實際 Hyperscale 有上限（100 TB storage、Gen5 series 80 vCore）、超過要 sharding 應用層分散。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 Azure DB-OLTP 維度缺口。Clearent 是美國的中型支付處理商、跟 <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 跨市場銀行 OLTP</a> 形成對照 — 一個是合規驅動的跨市場分割、一個是單一規模的高吞吐處理。</p>
<h2 id="觀察">觀察</h2>
<p>Clearent 在 Azure SQL Hyperscale 的關鍵敘述（引自 <a href="https://www.microsoft.com/en/customers/story/774969-clearent-banking-capital-markets-azure">Clearent Customer Story</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>年交易量</td>
          <td>5 億筆</td>
      </tr>
      <tr>
          <td>客戶基礎</td>
          <td>各種規模 merchants（中小型為主）</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>Azure SQL Database Hyperscale 服務級</td>
      </tr>
      <tr>
          <td>架構模式</td>
          <td>modern microservices architecture</td>
      </tr>
      <tr>
          <td>擴展能力</td>
          <td>「scale automatically and almost infinitely」</td>
      </tr>
      <tr>
          <td>並發特性</td>
          <td>「tens of thousands of users 同時存取」</td>
      </tr>
      <tr>
          <td>業務驅動</td>
          <td>「unite all its information in one place」+ 「faster insights」</td>
      </tr>
  </tbody>
</table>
<p>關鍵特性：Azure SQL Hyperscale 把 storage 跟 compute 分離、跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> 的 Aurora 是同類設計。</p>
<h2 id="判讀">判讀</h2>
<p>Clearent 案例揭露三個 Hyperscale 設計的工程重點。</p>
<ol>
<li><strong>5 億筆 / 年 ≈ 1500 筆 / 秒平均、但 peak 可能 10-50x</strong>：支付交易有日內 / 月內 / 季內節律。早上 9-11 點商家對帳高峰、下午 12-1 點消費高峰、晚上 6-8 點消費高峰、月底結算高峰。容量規劃必須按 peak 訂、不是平均。對應 <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> 的 peak/avg ratio 跟 <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>。</li>
<li><strong>Hyperscale = storage / compute 解耦</strong>：傳統 SQL Server primary 對 storage 跟 CPU / RAM 綁定、擴 storage 就要換更大 instance、不便。Hyperscale 把 storage 拉到分散式 log service、可以獨立擴 storage（最高 100 TB）、compute 獨立擴。對應 <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> 的同類分離思維、跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a>。</li>
<li><strong>「unite all information in one place」是支付業的特殊需求</strong>：merchants 需要對帳、退款、清算、稅務報表都即時可查、不能 OLAP 分開。Hyperscale 的 read scale-out（最多 4 個 secondary replica）讓即時報表跑在 OLTP DB 上不影響交易吞吐。</li>
</ol>
<p>需要警惕：「scale automatically and almost infinitely」是行銷敘述。實際 Hyperscale 有上限（100 TB storage、Gen5 series 80 vCore）、超過要 sharding 應用層分散。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Hyperscale 跟 Aurora 是同類設計、選型按生態</strong>：Azure 生態用 Hyperscale、AWS 生態用 Aurora、GCP 用 AlloyDB / Spanner。三家底層工程哲學一致（log-structured storage、storage / compute 分離）、選哪家取決於 application 已在哪個 cloud。</li>
<li><strong>微服務 + 共用 OLTP 是支付業常見架構</strong>：服務拆細、但 OLTP 仍是 single source of truth、共用一個 Hyperscale cluster。這跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix microservice 各自 Aurora</a> 不同 — Netflix 每微服務 <em>自己</em> Aurora、Clearent 微服務共用 Hyperscale。取捨：Clearent 的「對帳一致性」需求讓共用更划算。</li>
<li><strong>支付業容量規劃以 peak 為主</strong>：不能用平均 RPS 規劃、要按單日 / 單秒 peak。歷史 peak × 預期成長 × headroom 是基本公式（<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>）。</li>
</ol>
<p>跨平台等效：AWS Aurora Serverless v2、GCP AlloyDB、Spanner、PostgreSQL 自管 + Patroni 都可實作對等架構。差異是 vendor managed 程度跟 OLAP / OLTP 統一視覺。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他 OLTP 案例 → <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> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> / <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></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> + <a href="/blog/backend/09-performance-capacity/peak-event-readiness/" data-link-title="9.11 高峰事件準備" data-link-desc="活動、季節性流量、推廣事件的 capacity readiness 流程">9.11 高峰事件準備</a></li>
<li>想理解 storage / compute 分離 → <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>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.microsoft.com/en/customers/story/774969-clearent-banking-capital-markets-azure">Clearent scales its modern microservices architecture to handle 500 million payment transactions a year</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/announcing-azure-sql-database-hyperscale-public-preview/">Announcing Azure SQL Database Hyperscale</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/get-high-performance-scaling-for-your-azure-database-workloads-with-hyperscale/">Get high-performance scaling for your Azure database workloads with Hyperscale</a></li>
</ul>
]]></content:encoded></item><item><title>PostgreSQL declarative partitioning：partition 不是切表、是讓 planner pruning</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/declarative-partitioning/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/declarative-partitioning/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL&lt;/a> overview 的 implementation-layer deep article。Overview 已說明大表（&amp;gt; 1TB）需要 partitioning、本文聚焦 &lt;em>partition 真實價值在哪、為什麼多數人第一次 partition 都做錯&lt;/em>。&lt;/p>&lt;/blockquote>
&lt;h2 id="partition-不是把大表切小是讓-planner-pruning--縮小-maintenance-scope">Partition 不是「把大表切小」、是「讓 planner pruning + 縮小 maintenance scope」&lt;/h2>
&lt;p>剛開始學 partitioning 的人多半從「表太大、切小一點」直覺出發；切了之後發現 — &lt;em>query 變慢&lt;/em>（planner 還在看所有 partition）、&lt;em>INSERT 變慢&lt;/em>（trigger / partition routing overhead）、&lt;em>backup 沒變短&lt;/em>（總資料量沒變）。直覺錯了：partition 的工程價值來自兩個機制、跟「切小」沒直接關係：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Query planner pruning&lt;/strong>：planner 在 planning 階段 &lt;em>跳過&lt;/em> 不可能命中 partition key 的 partition、查詢只 scan 相關 partition；前提是 &lt;em>WHERE 條件含 partition key&lt;/em>、否則 planner 看完所有 partition、效能反而比單表差&lt;/li>
&lt;li>&lt;strong>Maintenance scope 縮小&lt;/strong>：vacuum / index rebuild / DROP / archive 只動單一 partition、不掃整表；vacuum 12 小時變 30 分鐘 / DROP 老資料 0.01 秒、是 partition 真正回本的地方&lt;/li>
&lt;/ol>
&lt;p>partition 是 &lt;em>為了 maintenance 跟 planner pruning&lt;/em> 設計、不是「表變小」設計。漏掉這個 framing、partition 配置會錯。&lt;/p>
&lt;h2 id="range--list--hashpartition-策略對應業務形狀">RANGE / LIST / HASH：partition 策略對應業務形狀&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- RANGE: 時間序列、log、event（最常見）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">event_time&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">timestamptz&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">payload&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">jsonb&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PARTITION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">RANGE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">event_time&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events_2026_05&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PARTITION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">OF&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">events&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">FOR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-05-01&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TO&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2026-06-01&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- LIST: tenant ID / region / status enum
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">tenant_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PARTITION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">LIST&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tenant_id&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders_tenant_premium&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PARTITION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">OF&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">FOR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">IN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1001&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1002&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1003&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">-- HASH: 均勻散落（無自然 partition key）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">bigint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NOT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">NULL&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">...&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PARTITION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">HASH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">CREATE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">TABLE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users_0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">PARTITION&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">OF&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">FOR&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">VALUES&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MODULUS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">REMAINDER&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>策略選擇關鍵：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL</a> overview 的 implementation-layer deep article。Overview 已說明大表（&gt; 1TB）需要 partitioning、本文聚焦 <em>partition 真實價值在哪、為什麼多數人第一次 partition 都做錯</em>。</p></blockquote>
<h2 id="partition-不是把大表切小是讓-planner-pruning--縮小-maintenance-scope">Partition 不是「把大表切小」、是「讓 planner pruning + 縮小 maintenance scope」</h2>
<p>剛開始學 partitioning 的人多半從「表太大、切小一點」直覺出發；切了之後發現 — <em>query 變慢</em>（planner 還在看所有 partition）、<em>INSERT 變慢</em>（trigger / partition routing overhead）、<em>backup 沒變短</em>（總資料量沒變）。直覺錯了：partition 的工程價值來自兩個機制、跟「切小」沒直接關係：</p>
<ol>
<li><strong>Query planner pruning</strong>：planner 在 planning 階段 <em>跳過</em> 不可能命中 partition key 的 partition、查詢只 scan 相關 partition；前提是 <em>WHERE 條件含 partition key</em>、否則 planner 看完所有 partition、效能反而比單表差</li>
<li><strong>Maintenance scope 縮小</strong>：vacuum / index rebuild / DROP / archive 只動單一 partition、不掃整表；vacuum 12 小時變 30 分鐘 / DROP 老資料 0.01 秒、是 partition 真正回本的地方</li>
</ol>
<p>partition 是 <em>為了 maintenance 跟 planner pruning</em> 設計、不是「表變小」設計。漏掉這個 framing、partition 配置會錯。</p>
<h2 id="range--list--hashpartition-策略對應業務形狀">RANGE / LIST / HASH：partition 策略對應業務形狀</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- RANGE: 時間序列、log、event（最常見）
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="n">id</span><span class="w"> </span><span class="nb">bigint</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">  </span><span class="n">event_time</span><span class="w"> </span><span class="n">timestamptz</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="n">payload</span><span class="w"> </span><span class="n">jsonb</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">RANGE</span><span class="w"> </span><span class="p">(</span><span class="n">event_time</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events_2026_05</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="k">OF</span><span class="w"> </span><span class="n">events</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">  </span><span class="k">FOR</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;2026-05-01&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;2026-06-01&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="c1">-- LIST: tenant ID / region / status enum
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">orders</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">  </span><span class="n">id</span><span class="w"> </span><span class="nb">bigint</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">  </span><span class="n">tenant_id</span><span class="w"> </span><span class="nb">int</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">  </span><span class="p">...</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">LIST</span><span class="w"> </span><span class="p">(</span><span class="n">tenant_id</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">orders_tenant_premium</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="k">OF</span><span class="w"> </span><span class="n">orders</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">  </span><span class="k">FOR</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="k">IN</span><span class="w"> </span><span class="p">(</span><span class="mi">1001</span><span class="p">,</span><span class="w"> </span><span class="mi">1002</span><span class="p">,</span><span class="w"> </span><span class="mi">1003</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w"></span><span class="c1">-- HASH: 均勻散落（無自然 partition key）
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">  </span><span class="n">user_id</span><span class="w"> </span><span class="nb">bigint</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">  </span><span class="p">...</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">HASH</span><span class="w"> </span><span class="p">(</span><span class="n">user_id</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">users_0</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="k">OF</span><span class="w"> </span><span class="n">users</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">  </span><span class="k">FOR</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="k">WITH</span><span class="w"> </span><span class="p">(</span><span class="n">MODULUS</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="n">REMAINDER</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span></span></span></code></pre></div><p>策略選擇關鍵：</p>
<ul>
<li><strong>RANGE</strong> 適合 <em>時間 / 有序值</em> — query 多半帶 <code>WHERE event_time &gt;= X</code>、prune 效率最高；archive / drop 老資料是 <code>DROP PARTITION</code> 0.01 秒</li>
<li><strong>LIST</strong> 適合 <em>離散 enum / tenant</em> — query 帶 <code>WHERE tenant_id = X</code> prune；缺點是 tenant 增長要手動 ALTER ADD PARTITION</li>
<li><strong>HASH</strong> 適合 <em>均勻分散、沒自然 key</em> — query 多半 by-PK lookup、HASH 讓單 partition 大小均勻；prune 只在 <code>WHERE hash_key = X</code> 等值查詢觸發</li>
</ul>
<h3 id="選錯-partition-key-是最常見的錯誤">選錯 partition key 是最常見的錯誤</h3>
<p>例：events 表用 <code>user_id</code> HASH partition、但 query 多半 <code>WHERE event_time BETWEEN ...</code>、<code>user_id</code> 不在 WHERE — planner 沒法 prune、掃所有 partition、效能比單表更差（多了 partition routing overhead）。</p>
<p>partition key <em>必須</em> 對應 query 最常用的 WHERE filter；錯了就退化成 <em>維護面有好處、查詢面有壞處</em> 的尷尬狀態。</p>
<h2 id="partition-pruningplanner-怎麼決定跳過">Partition pruning：planner 怎麼決定跳過</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">EXPLAIN</span><span class="w"> </span><span class="p">(</span><span class="k">ANALYZE</span><span class="p">,</span><span class="w"> </span><span class="n">BUFFERS</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">events</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">event_time</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="s1">&#39;2026-05-01&#39;</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">event_time</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="s1">&#39;2026-05-15&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="c1">-- 期望輸出包含：
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1">--  Append (cost=...)
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">--    -&gt; Seq Scan on events_2026_05  (cost=...)
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">-- (只 scan 一個 partition、其他 partition pruned)</span></span></span></code></pre></div><p>pruning 觸發條件：</p>
<ol>
<li>WHERE 含 partition key 的 <em>constant expression</em>（<code>WHERE x = 5</code> 觸發；<code>WHERE x = some_function()</code> 不觸發 planning-time prune、但 PG 11+ execution-time prune 可救）</li>
<li>PG 11+ 支援 <em>execution-time pruning</em> — query plan 內含 partition key、runtime 才知道值（prepared statement / NestedLoop join）</li>
<li>partition key 不在 WHERE 時 — <em>全部 partition 掃</em>、是反指標、表示 partition strategy 不對</li>
</ol>
<h3 id="partition-wise-join--aggregate-pg-11">Partition-wise join / aggregate (PG 11+)</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SET</span><span class="w"> </span><span class="n">enable_partitionwise_join</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">on</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">SET</span><span class="w"> </span><span class="n">enable_partitionwise_aggregate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">on</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="c1">-- 兩個同 partition 策略的表 JOIN 時、planner 可 partition-wise 平行做
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">events</span><span class="w"> </span><span class="n">e</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="n">events_metadata</span><span class="w"> </span><span class="n">m</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">  </span><span class="k">ON</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="n">event_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">m</span><span class="p">.</span><span class="n">event_time</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">  </span><span class="k">WHERE</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="n">event_time</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="s1">&#39;2026-05-01&#39;</span><span class="p">;</span></span></span></code></pre></div><p>需要兩個表 <em>partition strategy 完全一致</em>（同 partition key + 同 partition boundary）— 設計時對齊、後期不容易調整。</p>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1partition-key-選錯query-變慢">Case 1：partition key 選錯，query 變慢</h3>
<p><strong>徵兆</strong>：partition 後特定查詢從 200ms 變成 2000ms；EXPLAIN 顯示 <code>Append</code> 下面所有 partition 都被 scan、沒 partition 被 prune。</p>
<p><strong>根因</strong>：partition by <code>user_id</code> HASH、但 query 多用 <code>WHERE created_at BETWEEN X AND Y</code>；planner 不知道 user 在哪個 partition、必須掃全部。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>驗證 step</strong>：partition 前先 <code>pg_stat_statements</code> 看 top 10 query 的 WHERE pattern、partition key 必須對應其中 80% 流量的 filter</li>
<li><strong>修正</strong>：DROP partition strategy、改 partition by <code>created_at</code> RANGE；遷移用 <code>pg_dump --section=data</code> per-partition 重灌</li>
<li><strong>避免</strong>：partitioning 不可逆、設計階段 query pattern 沒看清楚不要動</li>
</ol>
<h3 id="case-2cross-partition-unique-constraint-不-enforce">Case 2：cross-partition unique constraint 不 enforce</h3>
<p><strong>徵兆</strong>：partition 後發現 application code 寫死 duplicate user_email、但 unique constraint 沒擋；DB 內有同 email 多筆。</p>
<p><strong>根因</strong>：PostgreSQL partition table 的 <code>UNIQUE</code> constraint <em>必須包含 partition key</em> — <code>UNIQUE (email)</code> 在 partition by <code>tenant_id</code> 的表上 <em>無法 enforce</em>（PostgreSQL 拒建）；workaround 用 <code>UNIQUE (email, tenant_id)</code>、但業務語意是「email 全域唯一」、PG 無法保證。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>架構</strong>：跨 partition 唯一性必須在 <em>application 層</em> enforce（lock + check 模式）</li>
<li><strong>替代</strong>：用 <em>non-partitioned</em> 表存唯一性目標（user_email_registry）、做寫入前 lookup</li>
<li><strong>設計階段檢查</strong>：partition by X、unique constraint 必須含 X；若業務要求 unique 不含 X、partition strategy 錯</li>
</ol>
<h3 id="case-3attach-partition-鎖表太久">Case 3：ATTACH PARTITION 鎖表太久</h3>
<p><strong>徵兆</strong>：新 month partition <code>ATTACH PARTITION</code> 跑 30 秒、期間整個 events 表 read 阻塞、application timeout 大量。</p>
<p><strong>根因</strong>：<code>ATTACH PARTITION</code> 預設加 <code>ACCESS EXCLUSIVE</code> lock 在 parent table、scan 整個新 partition 驗證 CHECK constraint；大 partition + 沒 CHECK constraint 預先驗證 → 鎖時間爆。</p>
<p><strong>修法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- 1. 先把要 attach 的 partition 加 CHECK constraint，用 NOT VALID 不掃描
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events_2026_06</span><span class="w"> </span><span class="k">ADD</span><span class="w"> </span><span class="k">CONSTRAINT</span><span class="w"> </span><span class="n">events_2026_06_range</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="k">CHECK</span><span class="w"> </span><span class="p">(</span><span class="n">event_time</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="s1">&#39;2026-06-01&#39;</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">event_time</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="s1">&#39;2026-07-01&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">VALID</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w"></span><span class="c1">-- 2. VALIDATE 用 SHARE UPDATE EXCLUSIVE lock、允許讀寫
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events_2026_06</span><span class="w"> </span><span class="n">VALIDATE</span><span class="w"> </span><span class="k">CONSTRAINT</span><span class="w"> </span><span class="n">events_2026_06_range</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="c1">-- 3. ATTACH 不再需要 scan（CHECK 已 VALIDATE 過）
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events</span><span class="w"> </span><span class="n">ATTACH</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="n">events_2026_06</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">  </span><span class="k">FOR</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;2026-06-01&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;2026-07-01&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="c1">-- ATTACH 變 instant</span></span></span></code></pre></div><h3 id="case-4partition-數爆炸planner-planning-time-爆">Case 4：partition 數爆炸，planner planning time 爆</h3>
<p><strong>徵兆</strong>：partition 累積到 500+（daily partition 跑 1-2 年）、簡單 query EXPLAIN 顯示 planning_time 從 1ms 漲到 200ms、application response 變慢。</p>
<p><strong>根因</strong>：partition 越多 planner 要評估的 partition 越多、即使有 pruning、planning 階段也要 walk 全部 partition table；500+ partition 是 planning overhead 明顯的閾值。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>架構</strong>：partition granularity 對應 retention — 不要 daily partition 留 2 年（→ weekly / monthly）</li>
<li><strong>archive 老 partition</strong>：DETACH 老 partition、轉成 cold storage 表、planner 不再看</li>
<li><strong><code>enable_partition_pruning</code></strong> 預設 on、確保啟用</li>
<li><strong>PG 12+</strong>：planner 對 partition table 的 list 處理優化、planning time 上限拉高、但仍要控</li>
</ol>
<h3 id="case-5detach-後磁碟空間沒回收">Case 5：DETACH 後磁碟空間沒回收</h3>
<p><strong>徵兆</strong>：DETACH PARTITION 後 <code>pg_database_size</code> 沒下降、預期釋放 50GB；磁碟仍滿。</p>
<p><strong>根因</strong>：DETACH 只是把 partition 從 parent table <em>分離</em>、partition 自己仍是獨立表存在；要真釋放需要 <code>DROP TABLE detached_partition</code>。SRE 以為 DETACH = 刪掉。</p>
<p><strong>修法</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- 完整流程
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events</span><span class="w"> </span><span class="n">DETACH</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="n">events_2024_01</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="c1">-- events_2024_01 仍存在、佔磁碟
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="c1">-- 確認沒 query 在用後
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="k">DROP</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events_2024_01</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="c1">-- 才釋放磁碟</span></span></span></code></pre></div><h3 id="routinearchive-workflow">Routine：archive workflow</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- 月底跑：
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">-- 1. detach 13 個月前的 partition
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events</span><span class="w"> </span><span class="n">DETACH</span><span class="w"> </span><span class="n">PARTITION</span><span class="w"> </span><span class="n">events_2025_04</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="c1">-- 2. dump 到 cold storage
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="err">\</span><span class="k">COPY</span><span class="w"> </span><span class="n">events_2025_04</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="s1">&#39;/cold/events_2025_04.csv&#39;</span><span class="w"> </span><span class="p">(</span><span class="n">FORMAT</span><span class="w"> </span><span class="n">CSV</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w"></span><span class="c1">-- 3. drop 釋放磁碟
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="c1"></span><span class="k">DROP</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">events_2025_04</span><span class="p">;</span></span></span></code></pre></div><h2 id="容量規劃">容量規劃</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>估算</th>
          <th>警戒</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單 partition size</td>
          <td>跟單表 vacuum 上限對齊（10-100GB sweet spot）</td>
          <td>&gt; 200GB 時考慮 sub-partition 或細化 granularity</td>
      </tr>
      <tr>
          <td>Partition 數量</td>
          <td>對應 retention × granularity</td>
          <td>&gt; 200 partition 時 planning time 開始浮現</td>
      </tr>
      <tr>
          <td>Partition key cardinality</td>
          <td>LIST：&lt; 100 / HASH：自定 modulus / RANGE：時間 + 維度</td>
          <td>太多獨立 partition value 用 HASH</td>
      </tr>
      <tr>
          <td>Cross-partition query 比例</td>
          <td>EXPLAIN 看 partition scan 數</td>
          <td>&gt; 30% query 掃 &gt; 50% partition 表示 key 選錯</td>
      </tr>
      <tr>
          <td>Maintenance window</td>
          <td>DROP / DETACH / ATTACH 各 partition 各自管</td>
          <td>hot partition 維護仍在 maintenance window</td>
      </tr>
  </tbody>
</table>
<p>實務 default：</p>
<ul>
<li>時間序列（events / log）：monthly RANGE partition、retention 12-24 個月</li>
<li>Multi-tenant（orders / records）：tenant_id LIST partition + 大 tenant 各自獨立 partition</li>
<li>均勻散落（user / metric）：8-16 個 HASH partition、單 partition 50-100GB</li>
</ul>
<h2 id="整合--下一步">整合 / 下一步</h2>
<h3 id="跟-autovacuum-tuning-整合">跟 <a href="/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/" data-link-title="PostgreSQL autovacuum tuning：為什麼你的 autovacuum 永遠追不上 bloat" data-link-desc="MVCC 怎麼產生 dead tuple、autovacuum cost-based throttle 為什麼預設保守、per-table tuning 怎麼設、5 個 production 踩雷（cost_limit 太低 / 長 transaction blocks vacuum / anti-wraparound 在 peak / partition vacuum 滿 worker / index bloat 沒處理）、跟 partitioning &#43; monitoring 整合">autovacuum tuning</a> 整合</h3>
<p>partitioning 是 autovacuum 問題的長期解：</p>
<ol>
<li>Hot partition autovacuum 緊（scale_factor 0.05、cost_limit 5000）</li>
<li>Cold partition <code>autovacuum_enabled = false</code></li>
<li>但 partition 數爆會把 <code>autovacuum_max_workers</code> 跑滿、需要拉</li>
</ol>
<h3 id="跟-index-設計整合">跟 index 設計整合</h3>
<p>partition table 的 index 處理：</p>
<ol>
<li>PG 11+ 全域 index：<code>CREATE INDEX ON partitioned_table (...)</code> 自動在每 partition 建 local index</li>
<li><strong>不存在跨 partition unique</strong> — 只能 partition-local</li>
<li><strong>partition-wise index scan</strong>：PG 11+ 跟 partition-wise join 一起、index lookup 平行</li>
</ol>
<h3 id="跟-backup--pitr">跟 backup / PITR</h3>
<p>partition 不是 backup 替代品 — 但能加速 <em>partial restore</em>：</p>
<ol>
<li>只 restore 特定時段的 partition、不用 restore 整個表</li>
<li>對應 <a href="/blog/backend/01-database/vendors/postgresql/pitr-wal-archiving/" data-link-title="PostgreSQL PITR &#43; WAL archiving：從 base backup 到 point-in-time recovery 的完整鏈" data-link-desc="Base backup &#43; WAL archive 構成 PITR 的雙軌資料、archive_command &#43; restore_command 配置、用 pgBackRest / WAL-G 替代手寫腳本、5 個 production 踩雷（archive 靜默失敗 / archive lag / 錯誤 target time / base backup 過期未清 / timeline 分歧 recovery 模糊）、跟 Patroni &#43; monitoring 整合">PITR + WAL archiving</a> 的 partial recovery scenario</li>
</ol>
<h3 id="下一步議題">下一步議題</h3>
<ul>
<li><strong>Sub-partitioning</strong>：partition 內再 partition（時間 + tenant）、適合 multi-tenant + 時間序列</li>
<li><strong>pg_partman extension</strong>：自動建月 partition、不用 cron</li>
<li><strong>Foreign key to partitioned table</strong> (PG 12+)：跨 partition FK enforce、但 cascade 限制多</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>上游 vendor 頁：<a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL</a></li>
<li>上游 chapter：<a href="/blog/backend/01-database/schema-design/" data-link-title="1.2 Schema Design 與資料建模" data-link-desc="整理 table、index、key、partition、denormalization 與命名規則">Schema Design</a> — partition 是 schema 決策</li>
<li>平行 deep article：<a href="/blog/backend/01-database/vendors/postgresql/patroni-ha/" data-link-title="PostgreSQL Patroni HA：從 leader 失聯到 client 重連的 5 段 failover lifecycle" data-link-desc="Patroni 把 PostgreSQL HA 拆成 detection / election / promotion / reconfiguration / recovery 五段 lifecycle、每段都有獨立配置跟 failure mode；DCS quorum &#43; watchdog 防 split-brain、async/sync replication 取捨、5 個 production 踩雷、跟 PgBouncer / HAProxy / cert-manager 整合">Patroni HA</a> / <a href="/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/" data-link-title="PostgreSQL autovacuum tuning：為什麼你的 autovacuum 永遠追不上 bloat" data-link-desc="MVCC 怎麼產生 dead tuple、autovacuum cost-based throttle 為什麼預設保守、per-table tuning 怎麼設、5 個 production 踩雷（cost_limit 太低 / 長 transaction blocks vacuum / anti-wraparound 在 peak / partition vacuum 滿 worker / index bloat 沒處理）、跟 partitioning &#43; monitoring 整合">autovacuum tuning</a> / <a href="/blog/backend/01-database/vendors/postgresql/timescaledb-deep-dive/" data-link-title="TimescaleDB Deep Dive：Hypertable / Continuous Aggregate / Compression 把 PG 變 Time-Series DB" data-link-desc="TimescaleDB 是 PG extension（不是 fork）、用 *hypertable* 自動 partition by time、加 *continuous aggregate* 做 incremental materialized view、加 *compression* 對舊 chunk 壓 90%&#43;、把 PG 變成 InfluxDB / Prometheus 級 time-series DB。本文走 hypertable 機制、continuous aggregate 跟普通 MV 差異、compression policy、retention policy、5 production 踩雷（chunk size 不對 / CAGG refresh 落後 / compression 後 update 限制 / hypertable 不能加 FK / TimescaleDB 跟 PG 主版本對齊）、跟 PG 原生 partitioning 對比">TimescaleDB Deep Dive</a>（hypertable 是 partition 自動化）</li>
<li>後續路由：<a href="/blog/backend/01-database/vendors/postgresql/partition-redesign/" data-link-title="PostgreSQL Partition Redesign：當 monthly partition 越跑越慢" data-link-desc="PostgreSQL partition redesign 是 Type F「topology re-layout」第 2 個 dogfood — 從 monthly partition 改 daily / 從 range 改 list / 從單軸改 sub-partition；6 維 audit 皆 Low &#43; topology 軸 High；涵蓋 partition 不平衡偵測、ATTACH/DETACH 線上重劃、5 個 production 踩雷、跟 partition_pruning &#43; autovacuum 整合">Partition Redesign</a>（重排 partition strategy 的 migration playbook）</li>
<li>Methodology：<a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">Vendor 深度技術文章的寫作方法論</a></li>
</ul>
]]></content:encoded></item><item><title>9.C33 Maersk + Bosch：傳統產業在 Azure AKS 上的微服務治理</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/</guid><description>&lt;p>這個案例的核心責任是補強 Azure compute / K8s 維度缺口。Maersk（全球最大貨櫃航運公司、每天處理百萬級貨櫃移動）跟 Bosch（德國工業集團、智慧建築 IoT）是 &lt;em>傳統產業上雲&lt;/em> 的代表 — 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 雲原生 EKS&lt;/a> 形成對比、傳統產業的 K8s 採用動機跟雲原生公司不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Maersk + Bosch 在 Azure AKS 的關鍵敘述（引自 &lt;a href="https://azure.microsoft.com/en-us/products/kubernetes-service/">AKS Customer Stories&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Maersk&lt;/th>
 &lt;th>Bosch Software Innovations&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>行業&lt;/td>
 &lt;td>全球海運&lt;/td>
 &lt;td>工業 IoT（Connected Building Solution）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要 workload&lt;/td>
 &lt;td>貨櫃追蹤、港口物流、行程規劃&lt;/td>
 &lt;td>樓宇感測、能源管理、設備運維&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AKS 用途&lt;/td>
 &lt;td>deployment + 運維 + 管理 Kubernetes API&lt;/td>
 &lt;td>microservices 監控、不同 release cycle&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工程訴求&lt;/td>
 &lt;td>「focus on things that makes the most business impact」&lt;/td>
 &lt;td>「simplify management of microservices released on different cycles」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務組合&lt;/td>
 &lt;td>AKS + Azure 管理工具&lt;/td>
 &lt;td>AKS + monitoring capabilities&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>其他常見 AKS 大客戶：Siemens Healthineers（醫療設備）、Finastra（金融軟體）、Hafslund（能源）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Maersk 跟 Bosch 案例揭露三個傳統產業 K8s 治理的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>傳統產業上 K8s 的動機是「治理一致性」、不是「成長彈性」&lt;/strong>：
&lt;ul>
&lt;li>雲原生公司（Riot、Netflix）上 K8s 是為了 &lt;em>快速擴容&lt;/em> 跟 &lt;em>跨 region 部署&lt;/em>&lt;/li>
&lt;li>傳統產業上 K8s 是為了 &lt;em>統一 50+ 個應用團隊的部署流程&lt;/em>、降低 ops 複雜度&lt;/li>
&lt;li>訴求不同、配置不同 — 傳統產業可能用 &lt;em>較大 node、較少 cluster&lt;/em>、不是 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot 246 cluster&lt;/a> 那種多 cluster 策略&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>微服務 release cycle 多元化是傳統產業上 K8s 的核心需求&lt;/strong>：Bosch Connected Building 有「樓宇感測 daily release、能源計費 weekly release、設備運維 monthly release」、每個 release cycle 不同。K8s + GitOps（Argo CD、Flux）讓不同 cycle 共存於同一 cluster。對應 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組&lt;/a> 的 release governance。&lt;/li>
&lt;li>&lt;strong>「focus on business impact」是 managed K8s 的真正價值&lt;/strong>：Maersk 不是科技公司、是航運公司。工程資源從 &lt;em>維持 K8s 運維&lt;/em> 釋放到 &lt;em>貨櫃追蹤演算法、港口物流優化&lt;/em>、是商業 ROI 的關鍵。對應 &lt;a href="https://tarrragon.github.io/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 &amp;#43; AWS Media Services 撐 30 channels live &amp;#43; 5M MAU、工程工時下降 90%">9.C29 Lemino 90% 工程工時下降&lt;/a> 的同類訴求、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 的人力成本工程化。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：Azure 官方對 Maersk / Bosch 的描述偏行銷、缺具體 throughput / latency 數字。讀此類案例要對 &lt;em>策略&lt;/em> 學習、不要套用數字。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 Azure compute / K8s 維度缺口。Maersk（全球最大貨櫃航運公司、每天處理百萬級貨櫃移動）跟 Bosch（德國工業集團、智慧建築 IoT）是 <em>傳統產業上雲</em> 的代表 — 跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 雲原生 EKS</a> 形成對比、傳統產業的 K8s 採用動機跟雲原生公司不同。</p>
<h2 id="觀察">觀察</h2>
<p>Maersk + Bosch 在 Azure AKS 的關鍵敘述（引自 <a href="https://azure.microsoft.com/en-us/products/kubernetes-service/">AKS Customer Stories</a>）：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Maersk</th>
          <th>Bosch Software Innovations</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>行業</td>
          <td>全球海運</td>
          <td>工業 IoT（Connected Building Solution）</td>
      </tr>
      <tr>
          <td>主要 workload</td>
          <td>貨櫃追蹤、港口物流、行程規劃</td>
          <td>樓宇感測、能源管理、設備運維</td>
      </tr>
      <tr>
          <td>AKS 用途</td>
          <td>deployment + 運維 + 管理 Kubernetes API</td>
          <td>microservices 監控、不同 release cycle</td>
      </tr>
      <tr>
          <td>工程訴求</td>
          <td>「focus on things that makes the most business impact」</td>
          <td>「simplify management of microservices released on different cycles」</td>
      </tr>
      <tr>
          <td>服務組合</td>
          <td>AKS + Azure 管理工具</td>
          <td>AKS + monitoring capabilities</td>
      </tr>
  </tbody>
</table>
<p>其他常見 AKS 大客戶：Siemens Healthineers（醫療設備）、Finastra（金融軟體）、Hafslund（能源）。</p>
<h2 id="判讀">判讀</h2>
<p>Maersk 跟 Bosch 案例揭露三個傳統產業 K8s 治理的工程重點。</p>
<ol>
<li><strong>傳統產業上 K8s 的動機是「治理一致性」、不是「成長彈性」</strong>：
<ul>
<li>雲原生公司（Riot、Netflix）上 K8s 是為了 <em>快速擴容</em> 跟 <em>跨 region 部署</em></li>
<li>傳統產業上 K8s 是為了 <em>統一 50+ 個應用團隊的部署流程</em>、降低 ops 複雜度</li>
<li>訴求不同、配置不同 — 傳統產業可能用 <em>較大 node、較少 cluster</em>、不是 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot 246 cluster</a> 那種多 cluster 策略</li>
</ul>
</li>
<li><strong>微服務 release cycle 多元化是傳統產業上 K8s 的核心需求</strong>：Bosch Connected Building 有「樓宇感測 daily release、能源計費 weekly release、設備運維 monthly release」、每個 release cycle 不同。K8s + GitOps（Argo CD、Flux）讓不同 cycle 共存於同一 cluster。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 release governance。</li>
<li><strong>「focus on business impact」是 managed K8s 的真正價值</strong>：Maersk 不是科技公司、是航運公司。工程資源從 <em>維持 K8s 運維</em> 釋放到 <em>貨櫃追蹤演算法、港口物流優化</em>、是商業 ROI 的關鍵。對應 <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 90% 工程工時下降</a> 的同類訴求、跟 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 的人力成本工程化。</li>
</ol>
<p>需要警惕：Azure 官方對 Maersk / Bosch 的描述偏行銷、缺具體 throughput / latency 數字。讀此類案例要對 <em>策略</em> 學習、不要套用數字。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>傳統產業 K8s 採用先做「單一 cluster 多 namespace」、再考慮多 cluster</strong>：管理 1 個大 cluster 比管理 246 個小 cluster 容易。除非有 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 的隔離需求</a>、否則 single-cluster-multi-namespace 是 sane default。</li>
<li><strong>不同 release cycle 用 GitOps + namespace 隔離</strong>：每個團隊 own 自己的 namespace、配合 Argo CD / Flux 各自 release。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a>。</li>
<li><strong>AKS / EKS / GKE 的差異對傳統產業不關鍵</strong>：選哪家通常取決於企業已用哪家 cloud、不是 K8s feature 本身。重點是 <em>managed K8s ops 比自管划算</em>、不是哪家 managed 最好。</li>
<li><strong>監控訊號設計按業務 cycle</strong>：每天 release 的服務跟每月 release 的服務 monitoring 策略不同、alert 敏感度不同。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a>。</li>
</ol>
<p>跨平台等效：AWS EKS、GCP GKE、自管 Kubernetes + Rancher 都可實作對等架構。Azure 在 enterprise 整合（Active Directory、Azure DevOps）有優勢、特別適合 Microsoft 生態企業。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照雲原生 K8s 策略 → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a></li>
<li>對照其他 managed 服務釋放工程資源 → <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> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a></li>
<li>想設計 K8s 治理 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <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></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://azure.microsoft.com/en-us/products/kubernetes-service/">Azure Kubernetes Service customer stories</a></li>
<li><a href="https://customers.microsoft.com/en-us/story/maersk-travel-transportation-azure">Maersk Azure case</a></li>
<li><a href="https://azure.microsoft.com/en-us/blog/product/azure-kubernetes-service-aks/">Bosch Software Innovations</a></li>
<li><a href="https://azure.microsoft.com/en-us/solutions/kubernetes-on-azure">Kubernetes on Azure - Enterprise Expertise</a></li>
</ul>
]]></content:encoded></item><item><title>Reactive 監聽器的效能 audit：跨 listener 類型盤點觸發頻率</title><link>https://tarrragon.github.io/blog/report/reactive-listener-frequency-management/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/reactive-listener-frequency-management/</guid><description>&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;p>&lt;strong>監聽器的「觸發頻率」是效能的第一道防線、跨多種 listener 類型一起盤點。&lt;/strong> 本篇是 audit 視角（「我有效能問題、reactive 監聽器是不是嫌疑」）— 設計新 observer 的細節由 &lt;a href="../mutation-observer-scope/">#29 MutationObserver 範圍與觸發頻率&lt;/a> 處理。Audit 時把所有 reactive 監聽器列一張表、看哪些觸發頻率異常。&lt;/p>
&lt;blockquote>
&lt;p>本篇焦點：&lt;strong>跨 listener 類型的效能盤點&lt;/strong>。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>MutationObserver 的設計細節&lt;/strong>（root / option / debounce / self-mutation）由 &lt;a href="../mutation-observer-scope/">#29&lt;/a> 處理&lt;/li>
&lt;li>&lt;strong>Selector 範圍的設計&lt;/strong>由 &lt;a href="../dom-selector-precision/">#14&lt;/a> 處理&lt;/li>
&lt;li>&lt;strong>Runtime 計算成本&lt;/strong>（regex / textContent / forEach）由 &lt;a href="../runtime-iteration-and-regex-cost/">#34&lt;/a> 處理&lt;/li>
&lt;/ul>&lt;/blockquote>
&lt;hr>
&lt;h2 id="為什麼觸發頻率主導效能">為什麼觸發頻率主導效能&lt;/h2>
&lt;h3 id="商業邏輯">商業邏輯&lt;/h3>
&lt;p>Reactive 監聽器有三個獨立成本：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>成本來源&lt;/th>
 &lt;th>單次量級&lt;/th>
 &lt;th>累積方式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>觸發頻率&lt;/td>
 &lt;td>看範圍與 option&lt;/td>
 &lt;td>倍數疊加&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Callback 內部運算&lt;/td>
 &lt;td>看實作&lt;/td>
 &lt;td>每次完整跑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Callback 引發的副作用&lt;/td>
 &lt;td>看 DOM 變動&lt;/td>
 &lt;td>可能反向觸發&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>把單次 callback 從 5ms 優化到 2ms 是 2.5x；把觸發次數從 100 次/秒降到 10 次/秒是 10x。&lt;strong>觸發頻率優化的天花板更高&lt;/strong> — audit 時優先看頻率。&lt;/p>
&lt;h3 id="三類觸發頻率風險速覽">三類觸發頻率風險（速覽）&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>表現&lt;/th>
 &lt;th>詳細處理&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>範圍過寬（observer subtree）&lt;/td>
 &lt;td>無關變動也觸發&lt;/td>
 &lt;td>&lt;a href="../mutation-observer-scope/">#29 root 與 option 設計&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Option 全勾&lt;/td>
 &lt;td>多種變動類型同時觸發&lt;/td>
 &lt;td>&lt;a href="../mutation-observer-scope/">#29 三維度收斂&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自激迴圈&lt;/td>
 &lt;td>callback 自己改 DOM 觸發自己&lt;/td>
 &lt;td>&lt;a href="../mutation-observer-scope/">#29 self-mutation 處理&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>本篇不展開設計細節（避免跟 #29 重複）、只談「audit 時怎麼識別這些 risk」。&lt;/p>
&lt;hr>
&lt;h2 id="跨-observer-類型的盤點">跨 observer 類型的盤點&lt;/h2>
&lt;p>效能 audit 時、列出&lt;strong>所有&lt;/strong> reactive 監聽器、不只 MutationObserver。各類型觸發來源不同、需要分別評估。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>觸發來源&lt;/th>
 &lt;th>過頻訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>MutationObserver&lt;/td>
 &lt;td>DOM 變動&lt;/td>
 &lt;td>一次操作觸發 10+ 次&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ResizeObserver&lt;/td>
 &lt;td>元素尺寸變動&lt;/td>
 &lt;td>持續觸發（自激）/ resize 視窗時連發&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>IntersectionObserver&lt;/td>
 &lt;td>可視性變動&lt;/td>
 &lt;td>scroll 時連發&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Event listener (input / scroll / resize)&lt;/td>
 &lt;td>使用者互動&lt;/td>
 &lt;td>高頻事件未 debounce&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>setInterval&lt;/code> / &lt;code>requestAnimationFrame&lt;/code> 迴圈&lt;/td>
 &lt;td>時間&lt;/td>
 &lt;td>持續跑、不只在需要時&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="盤點工具">盤點工具&lt;/h3>
&lt;p>DevTools Performance 面板錄一段使用者操作、看 callback 觸發次數：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">// 在 callback 內加 console.count
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">mutations&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"> 3&lt;/span>&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;mutation observer fired&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="c1">// ... 處理
&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">&lt;/span>&lt;span class="p">}).&lt;/span>&lt;span class="nx">observe&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">new&lt;/span> &lt;span class="nx">ResizeObserver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">entries&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="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;resize observer fired&amp;#39;&lt;/span>&lt;span class="p">);&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>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}).&lt;/span>&lt;span class="nx">observe&lt;/span>&lt;span class="p">(...);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>跑一次「使用者打字 + 等結果」的完整操作、看 console 各 listener 觸發幾次。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>觸發次數&lt;/th>
 &lt;th>評估&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1-3 次&lt;/td>
 &lt;td>正常&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5-10 次&lt;/td>
 &lt;td>可能過頻、值得查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>10+ 次&lt;/td>
 &lt;td>範圍 / option 太寬、需要收斂&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>持續觸發（不停）&lt;/td>
 &lt;td>自激迴圈、需要立刻處理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="resizeobserver-寫變數造成自激">ResizeObserver 寫變數造成自激&lt;/h2>
&lt;p>ResizeObserver 的特殊風險是「寫 CSS 變數可能影響被觀察元素自己的尺寸」 — 這個 case 跟 &lt;a href="../mutation-observer-scope/">#29&lt;/a> 處理的 MutationObserver self-mutation 機制不同、值得獨立展開。&lt;/p></description><content:encoded><![CDATA[<h2 id="核心原則">核心原則</h2>
<p><strong>監聽器的「觸發頻率」是效能的第一道防線、跨多種 listener 類型一起盤點。</strong> 本篇是 audit 視角（「我有效能問題、reactive 監聽器是不是嫌疑」）— 設計新 observer 的細節由 <a href="../mutation-observer-scope/">#29 MutationObserver 範圍與觸發頻率</a> 處理。Audit 時把所有 reactive 監聽器列一張表、看哪些觸發頻率異常。</p>
<blockquote>
<p>本篇焦點：<strong>跨 listener 類型的效能盤點</strong>。</p>
<ul>
<li><strong>MutationObserver 的設計細節</strong>（root / option / debounce / self-mutation）由 <a href="../mutation-observer-scope/">#29</a> 處理</li>
<li><strong>Selector 範圍的設計</strong>由 <a href="../dom-selector-precision/">#14</a> 處理</li>
<li><strong>Runtime 計算成本</strong>（regex / textContent / forEach）由 <a href="../runtime-iteration-and-regex-cost/">#34</a> 處理</li>
</ul></blockquote>
<hr>
<h2 id="為什麼觸發頻率主導效能">為什麼觸發頻率主導效能</h2>
<h3 id="商業邏輯">商業邏輯</h3>
<p>Reactive 監聽器有三個獨立成本：</p>
<table>
  <thead>
      <tr>
          <th>成本來源</th>
          <th>單次量級</th>
          <th>累積方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>觸發頻率</td>
          <td>看範圍與 option</td>
          <td>倍數疊加</td>
      </tr>
      <tr>
          <td>Callback 內部運算</td>
          <td>看實作</td>
          <td>每次完整跑</td>
      </tr>
      <tr>
          <td>Callback 引發的副作用</td>
          <td>看 DOM 變動</td>
          <td>可能反向觸發</td>
      </tr>
  </tbody>
</table>
<p>把單次 callback 從 5ms 優化到 2ms 是 2.5x；把觸發次數從 100 次/秒降到 10 次/秒是 10x。<strong>觸發頻率優化的天花板更高</strong> — audit 時優先看頻率。</p>
<h3 id="三類觸發頻率風險速覽">三類觸發頻率風險（速覽）</h3>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>表現</th>
          <th>詳細處理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>範圍過寬（observer subtree）</td>
          <td>無關變動也觸發</td>
          <td><a href="../mutation-observer-scope/">#29 root 與 option 設計</a></td>
      </tr>
      <tr>
          <td>Option 全勾</td>
          <td>多種變動類型同時觸發</td>
          <td><a href="../mutation-observer-scope/">#29 三維度收斂</a></td>
      </tr>
      <tr>
          <td>自激迴圈</td>
          <td>callback 自己改 DOM 觸發自己</td>
          <td><a href="../mutation-observer-scope/">#29 self-mutation 處理</a></td>
      </tr>
  </tbody>
</table>
<p>本篇不展開設計細節（避免跟 #29 重複）、只談「audit 時怎麼識別這些 risk」。</p>
<hr>
<h2 id="跨-observer-類型的盤點">跨 observer 類型的盤點</h2>
<p>效能 audit 時、列出<strong>所有</strong> reactive 監聽器、不只 MutationObserver。各類型觸發來源不同、需要分別評估。</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>觸發來源</th>
          <th>過頻訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MutationObserver</td>
          <td>DOM 變動</td>
          <td>一次操作觸發 10+ 次</td>
      </tr>
      <tr>
          <td>ResizeObserver</td>
          <td>元素尺寸變動</td>
          <td>持續觸發（自激）/ resize 視窗時連發</td>
      </tr>
      <tr>
          <td>IntersectionObserver</td>
          <td>可視性變動</td>
          <td>scroll 時連發</td>
      </tr>
      <tr>
          <td>Event listener (input / scroll / resize)</td>
          <td>使用者互動</td>
          <td>高頻事件未 debounce</td>
      </tr>
      <tr>
          <td><code>setInterval</code> / <code>requestAnimationFrame</code> 迴圈</td>
          <td>時間</td>
          <td>持續跑、不只在需要時</td>
      </tr>
  </tbody>
</table>
<h3 id="盤點工具">盤點工具</h3>
<p>DevTools Performance 面板錄一段使用者操作、看 callback 觸發次數：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 在 callback 內加 console.count
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">mutations</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">count</span><span class="p">(</span><span class="s1">&#39;mutation observer fired&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="c1">// ... 處理
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"></span><span class="p">}).</span><span class="nx">observe</span><span class="p">(...);</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">new</span> <span class="nx">ResizeObserver</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">entries</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">count</span><span class="p">(</span><span class="s1">&#39;resize observer fired&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="c1">// ... 處理
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="p">}).</span><span class="nx">observe</span><span class="p">(...);</span></span></span></code></pre></div><p>跑一次「使用者打字 + 等結果」的完整操作、看 console 各 listener 觸發幾次。</p>
<table>
  <thead>
      <tr>
          <th>觸發次數</th>
          <th>評估</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1-3 次</td>
          <td>正常</td>
      </tr>
      <tr>
          <td>5-10 次</td>
          <td>可能過頻、值得查</td>
      </tr>
      <tr>
          <td>10+ 次</td>
          <td>範圍 / option 太寬、需要收斂</td>
      </tr>
      <tr>
          <td>持續觸發（不停）</td>
          <td>自激迴圈、需要立刻處理</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="resizeobserver-寫變數造成自激">ResizeObserver 寫變數造成自激</h2>
<p>ResizeObserver 的特殊風險是「寫 CSS 變數可能影響被觀察元素自己的尺寸」 — 這個 case 跟 <a href="../mutation-observer-scope/">#29</a> 處理的 MutationObserver self-mutation 機制不同、值得獨立展開。</p>
<h3 id="機制">機制</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">function</span> <span class="nx">syncScopeHeight</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">setProperty</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s1">&#39;--search-scope-h&#39;</span><span class="p">,</span> <span class="nx">scopeEl</span><span class="p">.</span><span class="nx">offsetHeight</span> <span class="o">+</span> <span class="s1">&#39;px&#39;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">new</span> <span class="nx">ResizeObserver</span><span class="p">(</span><span class="nx">syncScopeHeight</span><span class="p">).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">scopeEl</span><span class="p">);</span></span></span></code></pre></div><p>如果 <code>--search-scope-h</code> 在 CSS 中被用來計算 <code>scopeEl</code> 自己的 padding / margin / height — 寫入觸發 layout、layout 觸發 resize、resize 觸發 callback、callback 又寫入。</p>
<h3 id="症狀">症狀</h3>
<ul>
<li>CPU 持續被佔</li>
<li>Performance 面板看到 ResizeObserver callback 連發（&gt;60/秒）</li>
<li>元素尺寸持續微調</li>
</ul>
<h3 id="解法">解法</h3>
<p><strong>結構分離</strong>：寫的變數不該影響被觀察元素自己。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">new</span> <span class="nx">ResizeObserver</span><span class="p">(</span><span class="nx">syncScopeHeight</span><span class="p">).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">scopeEl</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// scopeEl 高度寫到 --search-scope-h
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// CSS 中 --search-scope-h 用來計算 drawer 的 margin-top
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// drawer 不是 scopeEl、不會反向觸發
</span></span></span></code></pre></div><p>設計時讓「觀察的元素」跟「受變數影響的元素」結構上分離 — 不會循環。</p>
<h3 id="跟-mutationobserver-self-mutation-的差異">跟 MutationObserver self-mutation 的差異</h3>
<table>
  <thead>
      <tr>
          <th>觀察類型</th>
          <th>self-mutation 機制</th>
          <th>處理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MutationObserver</td>
          <td>callback 改 DOM 結構 / attribute</td>
          <td>disconnect + observe 配對</td>
      </tr>
      <tr>
          <td>ResizeObserver</td>
          <td>callback 改變數 → 反向影響尺寸</td>
          <td>結構分離（觀察 A、影響 B）</td>
      </tr>
      <tr>
          <td>IntersectionObserver</td>
          <td>callback 改可視性 → 反向觸發</td>
          <td>罕見、設計時避免</td>
      </tr>
  </tbody>
</table>
<p>ResizeObserver 沒有 disconnect 配對的等價技巧（disconnect 後再 observe 仍會立即重觸發） — 必須靠結構分離。</p>
<hr>
<h2 id="盤點的標準格式">盤點的標準格式</h2>
<p>每個 reactive 監聽器寫成一段註解、audit 時讀這份「設定卡」即可：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="cm"> * 監聽：.pagefind-ui 的子節點變動
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="cm"> * 類型：MutationObserver
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="cm"> * 範圍：subtree（深層也看）
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="cm"> * Option：childList only
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="cm"> * Callback 是否改 DOM：是（toggle class）
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="cm"> * 自激風險：否（class change 不觸發 childList）
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="cm"> * Debounce：80ms
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="cm"> * 預期觸發頻率：使用者打字一次 &lt; 5 次
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="nx">schedule</span><span class="p">).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">ui</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span></span></span></code></pre></div><p>audit 時、看註解就知道：</p>
<ul>
<li>這個 observer 在做什麼</li>
<li>預期觸發頻率多少</li>
<li>實測超過預期 → 範圍太寬或 option 過勾</li>
</ul>
<hr>
<h2 id="設計取捨頻率管理策略選擇">設計取捨：頻率管理策略選擇</h2>
<p>當盤點發現某個 observer 觸發過頻、四種應對：</p>
<h3 id="a縮-observer-範圍--option這個專案的預設">A：縮 observer 範圍 / option（這個專案的預設）</h3>
<ul>
<li><strong>機制</strong>：subtree → 直接子；移除沒用的 option flag</li>
<li><strong>選 A 的理由</strong>：成本最低、改一行；觸發頻率倍數降低</li>
<li><strong>適合</strong>：絕大多數過頻 case</li>
<li><strong>代價</strong>：需要重新確認哪些變動類型真的需要監聽</li>
<li><strong>詳細</strong>：<a href="../mutation-observer-scope/">#29 三維度收斂</a></li>
</ul>
<h3 id="b加-debounce--throttle">B：加 debounce / throttle</h3>
<ul>
<li><strong>機制</strong>：高頻觸發合併成低頻 apply</li>
<li><strong>跟 A 的取捨</strong>：B 不解問題的根（觸發仍發生）、A 解根；但 B 對「無法縮範圍」的 case（如 input event）必要</li>
<li><strong>B 比 A 好的情境</strong>：使用者輸入事件、scroll 事件 — 本身高頻、無法縮範圍</li>
</ul>
<h3 id="cdisconnect--reconnect-配對">C：Disconnect / reconnect 配對</h3>
<ul>
<li><strong>機制</strong>：callback 改 DOM 前 disconnect、改完 reconnect</li>
<li><strong>跟 A/B 的取捨</strong>：C 處理 self-mutation、A/B 不處理；C 比 A/B 複雜</li>
<li><strong>C 比 A/B 好的情境</strong>：MutationObserver callback 必須改 DOM（沒有結構分離選項）</li>
<li><strong>詳細</strong>：<a href="../mutation-observer-scope/">#29 self-mutation 處理</a></li>
</ul>
<h3 id="dresizeobserver-結構分離">D：ResizeObserver 結構分離</h3>
<ul>
<li><strong>機制</strong>：觀察 A、影響 B（B ≠ A）</li>
<li><strong>跟 C 的取捨</strong>：ResizeObserver 沒 disconnect 等價技巧、必須用 D</li>
<li><strong>D 是 ResizeObserver 自激的唯一解</strong></li>
</ul>
<hr>
<h2 id="不該套用頻率管理的情境">不該套用「頻率管理」的情境</h2>
<p>不是所有 reactive 監聽器都需要管：</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>為什麼可以放任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開發階段、不上 production</td>
          <td>效能不影響真實使用者</td>
      </tr>
      <tr>
          <td>Callback 極輕（單次 &lt; 0.1ms）</td>
          <td>觸發 100 次也才 10ms</td>
      </tr>
      <tr>
          <td>觸發頻率本來就極低（一次 setup 一次 callback）</td>
          <td>沒有頻率問題</td>
      </tr>
  </tbody>
</table>
<p><strong>核心判準</strong>：實測有效能問題嗎？沒有就不必預先優化。Audit 是「找已存在的問題」、不是「預防所有可能」。</p>
<hr>
<h2 id="跟其他原則的關係">跟其他原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>篇</th>
          <th>關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../mutation-observer-scope/">#29 MutationObserver 範圍與觸發頻率</a></td>
          <td>互補 — #29 是設計指引（怎麼寫 observer）、本篇是 audit 視角（怎麼找問題）</td>
      </tr>
      <tr>
          <td><a href="../dom-selector-precision/">#14 Selector 精準度</a></td>
          <td>跟 observer 範圍同源 — selector 起點就是 observer root 的選擇基礎</td>
      </tr>
      <tr>
          <td><a href="../runtime-iteration-and-regex-cost/">#34 Runtime 計算成本</a></td>
          <td>互補 — 本篇看「觸發次數」、#34 看「單次 callback 成本」</td>
      </tr>
      <tr>
          <td><a href="../minimum-necessary-scope-is-sanity-defense/">#43 最小必要範圍</a></td>
          <td>「縮監聽範圍」是「最小必要範圍」原則的應用</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該檢查的位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者操作後瀏覽器卡頓</td>
          <td>該操作觸發了哪些 observer、各自觸發次數</td>
      </tr>
      <tr>
          <td>CPU 持續 100%</td>
          <td>observer 自激迴圈（特別是 ResizeObserver）</td>
      </tr>
      <tr>
          <td><code>setTimeout(0)</code> 也來不及處理</td>
          <td>observer / event 觸發頻率超過 schedule 處理速度</td>
      </tr>
      <tr>
          <td>Callback 內加 console.count 數字爆炸</td>
          <td>observer 範圍過寬 — 收斂方式由 <a href="../mutation-observer-scope/">#29</a> 處理</td>
      </tr>
      <tr>
          <td>ResizeObserver 在某 callback 後持續觸發</td>
          <td>寫的變數反向影響觀察元素 — 結構分離</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：reactive 監聽器的效能 audit = 列所有 listener + 量觸發次數 + 比對預期。發現問題後、設計修正方式由 <a href="../mutation-observer-scope/">#29</a> 等設計指引篇展開 — 本篇只負責「找問題」這一步。</p>
]]></content:encoded></item><item><title>9.C34 GCP：130,000-node GKE cluster 的工程極限</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/</guid><description>&lt;p>這個案例的核心責任是揭示「現代 AI workload 對 Kubernetes 規模極限的拉扯」。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster&lt;/a> 走「多小 cluster 隔離」相反 — GCP 內部驗證的是「單一巨大 cluster 集中管理」、為前沿 LLM 訓練的萬卡叢集需求設計。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>GCP 130K-node GKE cluster 實驗（引自 &lt;a href="https://cloud.google.com/blog/products/containers-kubernetes/how-we-built-a-130000-node-gke-cluster">How we built a 130,000-node GKE cluster&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>實驗節點數&lt;/td>
 &lt;td>130,000（vs 官方支援 65,000）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pod 創建峰值&lt;/td>
 &lt;td>1,000 Pods / 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Phase 1 deploy 時間&lt;/td>
 &lt;td>130,000 Pods in 3 分 40 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Phase 2 batch 創建&lt;/td>
 &lt;td>65,000 Pods in 81 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Preemption 峰值&lt;/td>
 &lt;td>39,000 Pods preempted in 93 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pod startup p99&lt;/td>
 &lt;td>~10 秒（inference workload）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>API server LIST p99&lt;/td>
 &lt;td>「well below defined thresholds」&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database objects&lt;/td>
 &lt;td>100 萬 +&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lease 更新 QPS&lt;/td>
 &lt;td>13,000&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客戶當前範圍&lt;/td>
 &lt;td>20-65K node range&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>預期 cluster size 穩定&lt;/td>
 &lt;td>100K node mark&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>工作負載類型：AI / ML 平台、三個 priority class：&lt;/p>
&lt;ul>
&lt;li>Low：preemptible batch（data prep）&lt;/li>
&lt;li>Medium：core model training（tolerant to queuing）&lt;/li>
&lt;li>High：latency-sensitive inference&lt;/li>
&lt;/ul>
&lt;p>關鍵 control plane 設計：&lt;/p>
&lt;ul>
&lt;li>Consistent Reads from Cache（KEP-2340）— 強一致 read 從 in-memory cache、不打 storage&lt;/li>
&lt;li>Snapshottable API Server Cache（KEP-4988）— B-tree snapshot 處理 LIST 請求&lt;/li>
&lt;li>Spanner-based key-value store 作為 K8s storage backend（撐 13K QPS lease 更新）&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>130K-node 案例揭露三個 hyperscale K8s 設計的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>單一 control plane 的極限取決於 storage backend、不是 nodes&lt;/strong>：130K node 不是「機器跑不動」、是「API server 跟 etcd 撐不撐住」。GCP 用 Spanner 替換 etcd、配上 cache-first read 設計、把 storage 從瓶頸變成「showed no signs of not being able to support higher scales」。對應 &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> 的「真實 bottleneck 在哪一層」。&lt;/li>
&lt;li>&lt;strong>AI workload 顛覆了 K8s 容量規劃&lt;/strong>：傳統 web workload 的 K8s 多在 1K-10K node、節點生命週期長。AI workload 短時間爆量創建跟銷毀 Pods（13 萬個 in 3 分 40 秒）、preempt 跟 schedule 頻繁、對 control plane 是完全不同壓力模式。對應 &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> — workload 形狀完全不同、容量規劃也完全不同。&lt;/li>
&lt;li>&lt;strong>「power constraint &amp;gt; chip supply」是新瓶頸&lt;/strong>：單顆 NVIDIA GB200 GPU 吃 2700W、萬卡叢集 = 27MW 用電量。未來 mega cluster 必須跨多個 data center（一個 DC 電力撐不住）、需要 &lt;em>robust multi-cluster solutions&lt;/em>。這層瓶頸跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界&lt;/a> 對接 — 電力成本變成主要 cost driver。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是揭示「現代 AI workload 對 Kubernetes 規模極限的拉扯」。跟 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a> 走「多小 cluster 隔離」相反 — GCP 內部驗證的是「單一巨大 cluster 集中管理」、為前沿 LLM 訓練的萬卡叢集需求設計。</p>
<h2 id="觀察">觀察</h2>
<p>GCP 130K-node GKE cluster 實驗（引自 <a href="https://cloud.google.com/blog/products/containers-kubernetes/how-we-built-a-130000-node-gke-cluster">How we built a 130,000-node GKE cluster</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>實驗節點數</td>
          <td>130,000（vs 官方支援 65,000）</td>
      </tr>
      <tr>
          <td>Pod 創建峰值</td>
          <td>1,000 Pods / 秒</td>
      </tr>
      <tr>
          <td>Phase 1 deploy 時間</td>
          <td>130,000 Pods in 3 分 40 秒</td>
      </tr>
      <tr>
          <td>Phase 2 batch 創建</td>
          <td>65,000 Pods in 81 秒</td>
      </tr>
      <tr>
          <td>Preemption 峰值</td>
          <td>39,000 Pods preempted in 93 秒</td>
      </tr>
      <tr>
          <td>Pod startup p99</td>
          <td>~10 秒（inference workload）</td>
      </tr>
      <tr>
          <td>API server LIST p99</td>
          <td>「well below defined thresholds」</td>
      </tr>
      <tr>
          <td>Database objects</td>
          <td>100 萬 +</td>
      </tr>
      <tr>
          <td>Lease 更新 QPS</td>
          <td>13,000</td>
      </tr>
      <tr>
          <td>客戶當前範圍</td>
          <td>20-65K node range</td>
      </tr>
      <tr>
          <td>預期 cluster size 穩定</td>
          <td>100K node mark</td>
      </tr>
  </tbody>
</table>
<p>工作負載類型：AI / ML 平台、三個 priority class：</p>
<ul>
<li>Low：preemptible batch（data prep）</li>
<li>Medium：core model training（tolerant to queuing）</li>
<li>High：latency-sensitive inference</li>
</ul>
<p>關鍵 control plane 設計：</p>
<ul>
<li>Consistent Reads from Cache（KEP-2340）— 強一致 read 從 in-memory cache、不打 storage</li>
<li>Snapshottable API Server Cache（KEP-4988）— B-tree snapshot 處理 LIST 請求</li>
<li>Spanner-based key-value store 作為 K8s storage backend（撐 13K QPS lease 更新）</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>130K-node 案例揭露三個 hyperscale K8s 設計的工程重點。</p>
<ol>
<li><strong>單一 control plane 的極限取決於 storage backend、不是 nodes</strong>：130K node 不是「機器跑不動」、是「API server 跟 etcd 撐不撐住」。GCP 用 Spanner 替換 etcd、配上 cache-first read 設計、把 storage 從瓶頸變成「showed no signs of not being able to support higher scales」。對應 <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> 的「真實 bottleneck 在哪一層」。</li>
<li><strong>AI workload 顛覆了 K8s 容量規劃</strong>：傳統 web workload 的 K8s 多在 1K-10K node、節點生命週期長。AI workload 短時間爆量創建跟銷毀 Pods（13 萬個 in 3 分 40 秒）、preempt 跟 schedule 頻繁、對 control plane 是完全不同壓力模式。對應 <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> — workload 形狀完全不同、容量規劃也完全不同。</li>
<li><strong>「power constraint &gt; chip supply」是新瓶頸</strong>：單顆 NVIDIA GB200 GPU 吃 2700W、萬卡叢集 = 27MW 用電量。未來 mega cluster 必須跨多個 data center（一個 DC 電力撐不住）、需要 <em>robust multi-cluster solutions</em>。這層瓶頸跟 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界</a> 對接 — 電力成本變成主要 cost driver。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>130K-node 是 <em>Google 內部實驗</em>、不是 <em>客戶能用的 production</em> 配置。目前 GKE 官方支援 65K node、客戶用到 100K+ 還很遠。</li>
<li>AI workload 跟 web workload 完全不同、把 AI 經驗套用到 web service 容量規劃是錯誤類比。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>K8s control plane 跟 data plane 分開規劃容量</strong>：data plane（worker nodes）擴容容易、control plane（API server、etcd / storage）擴容難。瓶頸通常在 control plane、不是 worker。</li>
<li><strong>storage backend 是 K8s 規模極限的關鍵</strong>：etcd 撐 5K-10K node 後開始吃力、要用 PostgreSQL / Spanner / 自家 KV 替換、才能擴到萬級節點。一般客戶用不到、但要知道「為什麼到某個規模 etcd 不夠」。</li>
<li><strong>AI workload 用 specialized scheduler</strong>（Kueue、Volcano）：默認 K8s scheduler 為 web workload 設計、AI 的 gang scheduling、fair-sharing、preemption 都不太適合。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 scheduler 選型。</li>
<li><strong>power-aware capacity planning 是未來方向</strong>：傳統按 CPU / RAM 規劃容量、未來要加上 <em>power budget</em>。data center 用電量是硬上限、不是錢的問題。</li>
<li><strong>multi-cluster 是萬卡訓練的必然</strong>：單一 cluster 撐不住、要 MultiKueue 等跨 cluster 排程方案。對應 <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games multi-cluster</a> 但目的完全不同。</li>
</ol>
<p>跨平台等效：AWS EKS 官方支援單 cluster 多至 100K pod / cluster、Azure AKS 支援 5K node / cluster。GCP 用 Spanner 替換 etcd 是最深的工程投資、目前其他兩家還沒到這個規模。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他大規模 K8s → <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games 246 cluster</a>（多 cluster 策略）</li>
<li>對照 AI workload → <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 Pokemon GO 50x surge</a>（非 AI 但同 GCP K8s）</li>
<li>想理解 control plane vs data plane → <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</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></li>
<li>想設計 K8s 容量上限 → <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> + <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/containers-kubernetes/how-we-built-a-130000-node-gke-cluster">How we built a 130,000-node GKE cluster</a></li>
<li><a href="https://cloud.google.com/blog/products/containers-kubernetes/gke-and-kubernetes-at-kubecon-2025">GKE and Kubernetes at KubeCon 2025</a></li>
<li><a href="https://cloud.google.com/blog/products/containers-kubernetes/whats-new-in-gke-at-next26">What&rsquo;s new in GKE at Next 26</a></li>
</ul>
]]></content:encoded></item><item><title>Runtime 計算成本：每筆迭代與正則</title><link>https://tarrragon.github.io/blog/report/runtime-iteration-and-regex-cost/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/runtime-iteration-and-regex-cost/</guid><description>&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;p>&lt;strong>每筆迭代的成本 = 單次計算 × 迭代次數。&lt;/strong> 兩個變數都會放大效能問題；單次計算便宜時、迭代次數變多仍可能爆掉 frame budget。盤點時兩維度一起看、不只看單筆。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼迭代次數值得獨立看待">為什麼迭代次數值得獨立看待&lt;/h2>
&lt;h3 id="商業邏輯">商業邏輯&lt;/h3>
&lt;p>開發階段測試的資料量通常少（10 筆結果）— 單次迭代 + 10 次 = 不痛。&lt;/p>
&lt;p>上線後資料量放大（200 筆結果）— 同樣的單次計算 × 200 = 痛。&lt;/p>
&lt;p>&lt;strong>單次計算的最佳化收益是固定倍數、迭代次數的成長是線性放大&lt;/strong> — 後者更值得關注。&lt;/p>
&lt;h3 id="三類迭代成本">三類迭代成本&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>對 DOM 集合迭代&lt;/td>
 &lt;td>&lt;code>forEach&lt;/code> over &lt;code>querySelectorAll&lt;/code> 結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>對資料陣列迭代&lt;/td>
 &lt;td>&lt;code>map&lt;/code> / &lt;code>filter&lt;/code> over 大量物件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>對 DOM 樹遞迴&lt;/td>
 &lt;td>&lt;code>.contains()&lt;/code> 或 ancestor walk&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每類有不同的優化策略、共通是「先量規模再決定動哪」。&lt;/p>
&lt;hr>
&lt;h2 id="搜尋頁的具體風險點">搜尋頁的具體風險點&lt;/h2>
&lt;h3 id="風險-1scope-filter-對每筆-result-跑-regex">風險 1：scope filter 對每筆 result 跑 regex&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：&lt;code>assets/search.js&lt;/code> 的 &lt;code>apply()&lt;/code>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="nx">items&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">forEach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">el&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">2&lt;/span>&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">titleEl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">el&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.pagefind-ui__result-title&amp;#39;&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="kd">var&lt;/span> &lt;span class="nx">excerptEl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">el&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.pagefind-ui__result-excerpt&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="kd">var&lt;/span> &lt;span class="nx">title&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">titleEl&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">titleEl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">textContent&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&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="kd">var&lt;/span> &lt;span class="nx">excerpt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">excerptEl&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">excerptEl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">textContent&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&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="kd">var&lt;/span> &lt;span class="nx">show&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">scope&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">re&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">title&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nx">re&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">excerpt&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="c1">// ...
&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">&lt;/span>&lt;span class="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每筆 result 做的事：&lt;/p>
&lt;ol>
&lt;li>兩次 &lt;code>querySelector&lt;/code>（DOM 查詢）&lt;/li>
&lt;li>兩次 &lt;code>textContent&lt;/code> 讀取（DOM 屬性讀取）&lt;/li>
&lt;li>一次 &lt;code>re.test&lt;/code>（正則比對）&lt;/li>
&lt;li>一次 &lt;code>classList.toggle&lt;/code>（class 操作）&lt;/li>
&lt;/ol>
&lt;p>單筆 ~0.1ms 等級、看 DOM 大小。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>結果 10 筆 → 1ms、無感&lt;/li>
&lt;li>結果 100 筆 → 10ms、接近 frame budget（16.67ms）&lt;/li>
&lt;li>結果 500 筆 → 50ms、明顯卡頓&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>症狀&lt;/strong>：使用者打字時 input lag、scroll jank。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：DevTools Performance 面板錄一次 apply、看 forEach 那段佔多少。&amp;gt; 5ms 開始考慮優化。&lt;/p>
&lt;h3 id="風險-2textcontent-讀取的隱藏成本">風險 2：textContent 讀取的隱藏成本&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：上述 &lt;code>titleEl.textContent&lt;/code>。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：&lt;code>textContent&lt;/code> 看似簡單、實際在某些瀏覽器中要 traverse 整個子樹拼字串。對於有 highlight &lt;code>&amp;lt;mark&amp;gt;&lt;/code> 標籤的結果、textContent 要組合多個 text node。&lt;/p>
&lt;p>&lt;strong>症狀&lt;/strong>：textContent 比預期慢、特別在 result 內結構複雜時。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：用 &lt;code>console.time&lt;/code> 量一次 textContent 讀取、看單次幾 ms。&lt;/p>
&lt;h3 id="風險-3每次-apply-都重新-queryselector">風險 3：每次 apply 都重新 querySelector&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：&lt;code>apply()&lt;/code> 每次跑都 &lt;code>document.querySelectorAll('.pagefind-ui__result')&lt;/code>。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：querySelector 是 fresh 查詢、不快取。每次 apply 都重新掃 DOM 找到結果集合。&lt;/p>
&lt;p>&lt;strong>症狀&lt;/strong>：apply 觸發頻繁時、querySelector 是固定開銷。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：把結果集合 cache 一份、observer 觸發時更新 cache、apply 用 cache 不重查 DOM。&lt;/p>
&lt;h3 id="風險-4regex-編譯成本">風險 4：Regex 編譯成本&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">re&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">RegExp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">escapeRegex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">query&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="s1">&amp;#39;i&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每次 apply 編譯一次 regex。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：Regex 編譯成本比想像中重 — 對複雜 pattern 可達數 ms。&lt;/p>
&lt;p>&lt;strong>症狀&lt;/strong>：query 字串長、apply 觸發頻繁時、regex 編譯佔 frame budget。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：把 regex cache 起來、query 變動才重編譯。&lt;/p></description><content:encoded><![CDATA[<h2 id="核心原則">核心原則</h2>
<p><strong>每筆迭代的成本 = 單次計算 × 迭代次數。</strong> 兩個變數都會放大效能問題；單次計算便宜時、迭代次數變多仍可能爆掉 frame budget。盤點時兩維度一起看、不只看單筆。</p>
<hr>
<h2 id="為什麼迭代次數值得獨立看待">為什麼迭代次數值得獨立看待</h2>
<h3 id="商業邏輯">商業邏輯</h3>
<p>開發階段測試的資料量通常少（10 筆結果）— 單次迭代 + 10 次 = 不痛。</p>
<p>上線後資料量放大（200 筆結果）— 同樣的單次計算 × 200 = 痛。</p>
<p><strong>單次計算的最佳化收益是固定倍數、迭代次數的成長是線性放大</strong> — 後者更值得關注。</p>
<h3 id="三類迭代成本">三類迭代成本</h3>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>對 DOM 集合迭代</td>
          <td><code>forEach</code> over <code>querySelectorAll</code> 結果</td>
      </tr>
      <tr>
          <td>對資料陣列迭代</td>
          <td><code>map</code> / <code>filter</code> over 大量物件</td>
      </tr>
      <tr>
          <td>對 DOM 樹遞迴</td>
          <td><code>.contains()</code> 或 ancestor walk</td>
      </tr>
  </tbody>
</table>
<p>每類有不同的優化策略、共通是「先量規模再決定動哪」。</p>
<hr>
<h2 id="搜尋頁的具體風險點">搜尋頁的具體風險點</h2>
<h3 id="風險-1scope-filter-對每筆-result-跑-regex">風險 1：scope filter 對每筆 result 跑 regex</h3>
<p><strong>位置</strong>：<code>assets/search.js</code> 的 <code>apply()</code>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">items</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kd">var</span> <span class="nx">titleEl</span>   <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__result-title&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="kd">var</span> <span class="nx">excerptEl</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__result-excerpt&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="kd">var</span> <span class="nx">title</span>   <span class="o">=</span> <span class="nx">titleEl</span>   <span class="o">?</span> <span class="nx">titleEl</span><span class="p">.</span><span class="nx">textContent</span>   <span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="kd">var</span> <span class="nx">excerpt</span> <span class="o">=</span> <span class="nx">excerptEl</span> <span class="o">?</span> <span class="nx">excerptEl</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="kd">var</span> <span class="nx">show</span> <span class="o">=</span> <span class="nx">scope</span> <span class="o">===</span> <span class="s1">&#39;title&#39;</span> <span class="o">?</span> <span class="nx">re</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">title</span><span class="p">)</span> <span class="o">:</span> <span class="nx">re</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">excerpt</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><p>每筆 result 做的事：</p>
<ol>
<li>兩次 <code>querySelector</code>（DOM 查詢）</li>
<li>兩次 <code>textContent</code> 讀取（DOM 屬性讀取）</li>
<li>一次 <code>re.test</code>（正則比對）</li>
<li>一次 <code>classList.toggle</code>（class 操作）</li>
</ol>
<p>單筆 ~0.1ms 等級、看 DOM 大小。</p>
<p><strong>判讀</strong>：</p>
<ul>
<li>結果 10 筆 → 1ms、無感</li>
<li>結果 100 筆 → 10ms、接近 frame budget（16.67ms）</li>
<li>結果 500 筆 → 50ms、明顯卡頓</li>
</ul>
<p><strong>症狀</strong>：使用者打字時 input lag、scroll jank。</p>
<p><strong>第一個該查的</strong>：DevTools Performance 面板錄一次 apply、看 forEach 那段佔多少。&gt; 5ms 開始考慮優化。</p>
<h3 id="風險-2textcontent-讀取的隱藏成本">風險 2：textContent 讀取的隱藏成本</h3>
<p><strong>位置</strong>：上述 <code>titleEl.textContent</code>。</p>
<p><strong>判讀</strong>：<code>textContent</code> 看似簡單、實際在某些瀏覽器中要 traverse 整個子樹拼字串。對於有 highlight <code>&lt;mark&gt;</code> 標籤的結果、textContent 要組合多個 text node。</p>
<p><strong>症狀</strong>：textContent 比預期慢、特別在 result 內結構複雜時。</p>
<p><strong>第一個該查的</strong>：用 <code>console.time</code> 量一次 textContent 讀取、看單次幾 ms。</p>
<h3 id="風險-3每次-apply-都重新-queryselector">風險 3：每次 apply 都重新 querySelector</h3>
<p><strong>位置</strong>：<code>apply()</code> 每次跑都 <code>document.querySelectorAll('.pagefind-ui__result')</code>。</p>
<p><strong>判讀</strong>：querySelector 是 fresh 查詢、不快取。每次 apply 都重新掃 DOM 找到結果集合。</p>
<p><strong>症狀</strong>：apply 觸發頻繁時、querySelector 是固定開銷。</p>
<p><strong>第一個該查的</strong>：把結果集合 cache 一份、observer 觸發時更新 cache、apply 用 cache 不重查 DOM。</p>
<h3 id="風險-4regex-編譯成本">風險 4：Regex 編譯成本</h3>
<p><strong>位置</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">var</span> <span class="nx">re</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="nx">escapeRegex</span><span class="p">(</span><span class="nx">query</span><span class="p">),</span> <span class="s1">&#39;i&#39;</span><span class="p">);</span></span></span></code></pre></div><p>每次 apply 編譯一次 regex。</p>
<p><strong>判讀</strong>：Regex 編譯成本比想像中重 — 對複雜 pattern 可達數 ms。</p>
<p><strong>症狀</strong>：query 字串長、apply 觸發頻繁時、regex 編譯佔 frame budget。</p>
<p><strong>第一個該查的</strong>：把 regex cache 起來、query 變動才重編譯。</p>
<hr>
<h2 id="內在屬性比較四種優化方向">內在屬性比較：四種優化方向</h2>
<table>
  <thead>
      <tr>
          <th>方向</th>
          <th>縮減幅度</th>
          <th>複雜度</th>
          <th>適用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>縮迭代次數（IntersectionObserver 只處理可視區）</td>
          <td>大</td>
          <td>中</td>
          <td>結果數量大、多數不在可視範圍</td>
      </tr>
      <tr>
          <td>縮單次計算（cache textContent / regex）</td>
          <td>中</td>
          <td>低</td>
          <td>重複計算同樣的東西</td>
      </tr>
      <tr>
          <td>分批處理（requestIdleCallback / chunk）</td>
          <td>大 — 攤開時間</td>
          <td>中</td>
          <td>一次處理量大但可延後</td>
      </tr>
      <tr>
          <td>Web Worker</td>
          <td>最大 — 獨立 thread</td>
          <td>高</td>
          <td>純計算密集、跟 DOM 無關</td>
      </tr>
  </tbody>
</table>
<p>對 scope filter 的場景：<strong>IntersectionObserver 只處理可視區</strong> + <strong>regex cache</strong> 是性價比最高的兩項。</p>
<hr>
<h2 id="規模放大的盤點">規模放大的盤點</h2>
<p>對每個迭代的 callback、預先估算「規模放大時會怎樣」：</p>
<table>
  <thead>
      <tr>
          <th>當前規模</th>
          <th>10x 規模</th>
          <th>100x 規模</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10 筆 result × 0.1ms = 1ms</td>
          <td>100 筆 = 10ms（接近 16ms 上限）</td>
          <td>1000 筆 = 100ms（明顯卡）</td>
      </tr>
  </tbody>
</table>
<p>10x / 100x 的數字是「未來內容增長 1 個 / 2 個數量級」的預警。當前 fine 但 10x 後不 fine、值得提前考慮優化機制。</p>
<hr>
<h2 id="設計取捨per-item-迭代成本的優化策略">設計取捨：per-item 迭代成本的優化策略</h2>
<p>四種做法、各自機會成本不同。預設先做 A（縮迭代次數）、A 不夠才考慮 B/C/D。</p>
<h3 id="a縮迭代次數intersectionobserver--分頁--過濾這個專案的預設">A：縮迭代次數（IntersectionObserver / 分頁 / 過濾）（這個專案的預設）</h3>
<ul>
<li><strong>機制</strong>：用 IntersectionObserver 只處理可視區、用過濾條件預先排除大量項目</li>
<li><strong>選 A 的理由</strong>：縮減幅度大（線性放大反向操作）、callback 內部不變</li>
<li><strong>適合</strong>：結果數量大、但實際需要處理的部分少（多數在可視區外）</li>
<li><strong>代價</strong>：增加 observer setup、需要設計「該處理什麼項目」的判斷</li>
</ul>
<h3 id="b縮單次計算cache-textcontent--regex--dom-query">B：縮單次計算（cache textContent / regex / DOM query）</h3>
<ul>
<li><strong>機制</strong>：把重複計算的結果 cache、避免每次重做</li>
<li><strong>跟 A 的取捨</strong>：B 縮減幅度中等（看 cache 命中率）、A 縮減幅度大；兩者解不同問題、可並用</li>
<li><strong>B 比 A 好的情境</strong>：迭代次數無法縮（必須處理所有項目）、但每項計算重複（regex 編譯、textContent 重讀）</li>
</ul>
<h3 id="c分批處理requestidlecallback--chunk">C：分批處理（requestIdleCallback / chunk）</h3>
<ul>
<li><strong>機制</strong>：把一次處理拆成多次、攤開到多個 frame</li>
<li><strong>跟 A/B 的取捨</strong>：C 攤開時間、A/B 縮減總時間；C 在「總時間無法縮、但可以延後」時合理</li>
<li><strong>C 比 A 好的情境</strong>：處理量大但可延後（initial render 時的非關鍵 enhancement）</li>
</ul>
<h3 id="dweb-worker">D：Web Worker</h3>
<ul>
<li><strong>機制</strong>：把計算搬到獨立 thread</li>
<li><strong>跟 A/B/C 的取捨</strong>：D 完全不阻 main thread、但 setup 成本高（postMessage 序列化）</li>
<li><strong>D 才合理的情境</strong>：純計算密集、跟 DOM 無關（搜尋 indexing、複雜資料處理）— 對 DOM 操作沒意義（Web Worker 不能直接動 DOM）</li>
</ul>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該檢查的位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>forEach over 大集合佔用 frame budget</td>
          <td>用 IntersectionObserver 只處理可視區</td>
      </tr>
      <tr>
          <td>每次 apply 重做相同的查詢 / 編譯</td>
          <td>Cache 結果、變動觸發時更新 cache</td>
      </tr>
      <tr>
          <td>Async 處理可接受時還在同步跑</td>
          <td>改 requestIdleCallback / 分批 setTimeout</td>
      </tr>
      <tr>
          <td>資料量比測試時大 N 倍後才發現問題</td>
          <td>開發時做規模 10x / 100x 預估</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：「每筆都做」的計算成本 = 每筆 × 筆數。優化時兩維度都看、不要只盯單次。</p>
]]></content:encoded></item><item><title>9.C35 Snap：GCP + KeyDB 在 multi-cloud 架構下的低延遲快取</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/</guid><description>&lt;p>這個案例的核心責任是補強 GCP cache 維度、並揭示 multi-cloud 架構的隱性 latency 議題。Snap（Snapchat 母公司、日活 4 億 +）2011 年從零起就在 GCP 上、是雲原生最早期客戶之一、但近年走 multi-cloud（GCP + AWS）。這個架構引出「跨 cloud cache latency 怎麼處理」的工程議題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Snap 在 GCP 的關鍵敘述（引自 &lt;a href="https://cloud.google.com/blog/products/application-modernization/snap-deploys-keydb-on-google-cloud-to-reduce-cross-cloud-latency">Snap deploys KeyDB on Google Cloud&lt;/a>、&lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/snap-inc-uses-google-cloud-tpu-for-deep-learning-recommendation-models">Snap TPU recommendation&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>內容&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>用戶基礎&lt;/td>
 &lt;td>4 億 + DAU、年增 18% YoY&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>開始在 GCP 時間&lt;/td>
 &lt;td>2011 年（產品早期）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Multi-cloud cache 方案&lt;/td>
 &lt;td>GCP 上部署 KeyDB cluster 減少 cross-cloud latency&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ML training&lt;/td>
 &lt;td>TPU（vs GPU 吞吐高 67%、成本低 52%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>安全框架&lt;/td>
 &lt;td>BeyondCorp Enterprise（Zero Trust）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>關鍵架構決策：在 &lt;em>GCP&lt;/em> 上部署 KeyDB（Redis fork、multi-threaded）作為 cache layer、減少 cross-cloud latency。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Snap 案例揭露三個 multi-cloud 容量設計的工程重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>跨 cloud latency 是隱性容量瓶頸&lt;/strong>：當 application 在 AWS、cache 在 GCP（或反之）、每個 cache lookup 都吃跨 cloud 網路 latency（通常 5-30ms、視 region pair 而定）。對 &lt;a href="https://tarrragon.github.io/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 提供配對引擎所需的次毫秒延遲快取層">Snap 這類「每次互動查多個 cache」&lt;/a> 的服務、5ms × 10 cache lookup = 50ms 額外 latency、用戶感受明顯。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget&lt;/a> 的 latency budget 反推。&lt;/li>
&lt;li>&lt;strong>KeyDB 是 Redis 的 multi-threaded 替代&lt;/strong>：Redis 7+ 之前是 single-threaded、單實例吞吐受限。KeyDB（Snap 等大型用戶採用）改成 multi-threaded、單實例 throughput 提升 5-10x、適合超高吞吐 cache 需求。對應 &lt;a href="https://tarrragon.github.io/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&lt;/a> 的 cache layer 設計、但 Snap 規模更大要走專業 fork。&lt;/li>
&lt;li>&lt;strong>TPU vs GPU 是 ML training 的容量成本決策&lt;/strong>：Snap 算過 GPU 的「throughput -67% + cost +52%」就是 TPU 的反向 — TPU 的 throughput 高 67%、cost 低 52% — 對 ML-heavy 公司是巨大決策。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency&lt;/a> 的雲端硬體選型、跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &amp;#43; 1.5 億商品、用 GCP Vertex AI Search &amp;#43; BigQuery 提供近即時搜尋與分析">9.C31 Mercado Libre Vertex AI&lt;/a> 的 ML 容量規劃同類。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是補強 GCP cache 維度、並揭示 multi-cloud 架構的隱性 latency 議題。Snap（Snapchat 母公司、日活 4 億 +）2011 年從零起就在 GCP 上、是雲原生最早期客戶之一、但近年走 multi-cloud（GCP + AWS）。這個架構引出「跨 cloud cache latency 怎麼處理」的工程議題。</p>
<h2 id="觀察">觀察</h2>
<p>Snap 在 GCP 的關鍵敘述（引自 <a href="https://cloud.google.com/blog/products/application-modernization/snap-deploys-keydb-on-google-cloud-to-reduce-cross-cloud-latency">Snap deploys KeyDB on Google Cloud</a>、<a href="https://cloud.google.com/blog/products/ai-machine-learning/snap-inc-uses-google-cloud-tpu-for-deep-learning-recommendation-models">Snap TPU recommendation</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>用戶基礎</td>
          <td>4 億 + DAU、年增 18% YoY</td>
      </tr>
      <tr>
          <td>開始在 GCP 時間</td>
          <td>2011 年（產品早期）</td>
      </tr>
      <tr>
          <td>Multi-cloud cache 方案</td>
          <td>GCP 上部署 KeyDB cluster 減少 cross-cloud latency</td>
      </tr>
      <tr>
          <td>ML training</td>
          <td>TPU（vs GPU 吞吐高 67%、成本低 52%）</td>
      </tr>
      <tr>
          <td>安全框架</td>
          <td>BeyondCorp Enterprise（Zero Trust）</td>
      </tr>
  </tbody>
</table>
<p>關鍵架構決策：在 <em>GCP</em> 上部署 KeyDB（Redis fork、multi-threaded）作為 cache layer、減少 cross-cloud latency。</p>
<h2 id="判讀">判讀</h2>
<p>Snap 案例揭露三個 multi-cloud 容量設計的工程重點。</p>
<ol>
<li><strong>跨 cloud latency 是隱性容量瓶頸</strong>：當 application 在 AWS、cache 在 GCP（或反之）、每個 cache lookup 都吃跨 cloud 網路 latency（通常 5-30ms、視 region pair 而定）。對 <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 提供配對引擎所需的次毫秒延遲快取層">Snap 這類「每次互動查多個 cache」</a> 的服務、5ms × 10 cache lookup = 50ms 額外 latency、用戶感受明顯。對應 <a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a> 的 latency budget 反推。</li>
<li><strong>KeyDB 是 Redis 的 multi-threaded 替代</strong>：Redis 7+ 之前是 single-threaded、單實例吞吐受限。KeyDB（Snap 等大型用戶採用）改成 multi-threaded、單實例 throughput 提升 5-10x、適合超高吞吐 cache 需求。對應 <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 layer 設計、但 Snap 規模更大要走專業 fork。</li>
<li><strong>TPU vs GPU 是 ML training 的容量成本決策</strong>：Snap 算過 GPU 的「throughput -67% + cost +52%」就是 TPU 的反向 — TPU 的 throughput 高 67%、cost 低 52% — 對 ML-heavy 公司是巨大決策。對應 <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界與 efficiency</a> 的雲端硬體選型、跟 <a href="/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &#43; 1.5 億商品、用 GCP Vertex AI Search &#43; BigQuery 提供近即時搜尋與分析">9.C31 Mercado Libre Vertex AI</a> 的 ML 容量規劃同類。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>KeyDB 是 <em>fork-based</em> 軟體、有 vendor lock-in 風險（Snap 大規模採用後、KeyDB 公司被收購、未來 fork 走向不確定）</li>
<li>TPU 是 <em>Google 專屬硬體</em>、不能在其他 cloud 用、是 vendor lock-in 來源</li>
<li>「年增 18%」是用戶數、不是流量。流量成長通常超過用戶成長（per-user engagement 上升）</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>Multi-cloud 架構優先把 cache 跟 application 放同一 cloud</strong>：跨 cloud 的不該是 cache lookup（高頻、低 latency 容忍）、應該是 batch sync（低頻、高 latency 容忍）。對應 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 的部署策略。</li>
<li><strong>Redis 規模化遇到 single-threaded 限制時的選項</strong>：
<ul>
<li>拆 cluster（多個 Redis instance）— 應用層分散 key</li>
<li>換 KeyDB / Dragonfly（multi-threaded fork）</li>
<li>換 Redis 7+ I/O thread（保留 protocol）</li>
<li>換 Memcached（multi-threaded、但功能少）</li>
</ul>
</li>
<li><strong>ML training infrastructure 選型按 throughput / cost 而非品牌</strong>：GPU vs TPU vs Trainium 不是「哪家好」、是「在 <em>本 workload</em> 上哪個划算」。要實測 benchmark、不是看 vendor marketing。</li>
<li><strong>跨 cloud 部署的「資料引力」</strong>：data 在哪、application 通常會被 data 吸過去。Snap 把 cache 放 GCP 是因為 production data 在 GCP — 想搬 cache 到 AWS 同時要搬 data、成本高。</li>
</ol>
<p>跨平台等效：AWS ElastiCache + Cassandra / DynamoDB Global Tables、Azure Cache for Redis + Cosmos DB 都可實作 multi-region cache 但 single-cloud 內。multi-cloud cache 通常要自管（自管 KeyDB / Dragonfly / Redis Cluster）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他 cache 案例 → <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> / <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 ML feature store</a></li>
<li>想設計 multi-cloud cache → <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</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></li>
<li>想做 ML training 容量規劃 → <a href="/blog/backend/09-performance-capacity/cost-engineering/" data-link-title="9.7 成本邊界與 efficiency" data-link-desc="cost per request、cost curve、降級成本、over-provisioning trade-off">9.7 成本邊界</a> + <a href="/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &#43; 1.5 億商品、用 GCP Vertex AI Search &#43; BigQuery 提供近即時搜尋與分析">9.C31 Mercado Libre</a></li>
<li>想理解 cross-cloud latency → <a href="/blog/backend/09-performance-capacity/slo-performance-budget/" data-link-title="9.12 SLO 與 Performance Budget" data-link-desc="performance budget 跟 SLO / error budget 的對接">9.12 SLO 與 Performance Budget</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/application-modernization/snap-deploys-keydb-on-google-cloud-to-reduce-cross-cloud-latency">Snap deploys KeyDB on Google Cloud to reduce cross-cloud latency</a></li>
<li><a href="https://cloud.google.com/blog/products/ai-machine-learning/snap-inc-uses-google-cloud-tpu-for-deep-learning-recommendation-models">Snap Inc. uses Google Cloud TPU for deep learning recommendation models</a></li>
<li><a href="https://cloud.google.com/blog/products/gcp/snap-maintains-uptime-with-mcs-from-google-cloud/">Snap maintains uptime with MCS from Google Cloud</a></li>
<li><a href="https://cloud.google.com/blog/products/identity-security/why-snap-chose-beyondcorp-enterprise-to-build-a-durable-zero-trust-framework">Why Snap chose BeyondCorp Enterprise</a></li>
</ul>
]]></content:encoded></item><item><title>Layout reflow / repaint 的可量化評估</title><link>https://tarrragon.github.io/blog/report/layout-reflow-measurement/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/layout-reflow-measurement/</guid><description>&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;p>&lt;strong>Reflow 與 repaint 的成本差兩個數量級、用 Performance 面板可以量化判斷哪個發生。&lt;/strong> 開發時不需要「全部避開 reflow」、要做的是「知道哪些操作觸發 reflow、規模放大時哪些值得優化」。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼要量化不憑感覺">為什麼要量化、不憑感覺&lt;/h2>
&lt;h3 id="商業邏輯">商業邏輯&lt;/h3>
&lt;p>瀏覽器渲染管線分階段：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>階段&lt;/th>
 &lt;th>觸發條件&lt;/th>
 &lt;th>相對成本&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Style recalc&lt;/td>
 &lt;td>CSS 規則變動、class toggle&lt;/td>
 &lt;td>低&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Layout (reflow)&lt;/td>
 &lt;td>影響元素尺寸 / 位置的 CSS 改變&lt;/td>
 &lt;td>高（要重算所有受影響元素）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Paint (repaint)&lt;/td>
 &lt;td>顏色 / 背景變動但位置不變&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Composite&lt;/td>
 &lt;td>transform / opacity 等 GPU 加速屬性&lt;/td>
 &lt;td>最低&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>不同操作落在不同階段。「改 width」觸發 reflow、「改 transform」只到 composite。差距 ~10-100x。&lt;/p>
&lt;p>但這不代表要「永遠用 transform」 — 多數場景 reflow 成本可以接受、過度避免反而讓 layout 變脆。&lt;/p>
&lt;h3 id="量化的工具">量化的工具&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>看什麼&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Chrome DevTools Performance&lt;/td>
 &lt;td>整段操作的 reflow / paint / composite 時間&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Performance API（&lt;code>performance.measure&lt;/code>）&lt;/td>
 &lt;td>程式化量自家函式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Layout shift (Web Vitals CLS)&lt;/td>
 &lt;td>視覺上的 layout 跳動&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>優先用 DevTools Performance 量、有具體數字後再決定是否優化。&lt;/p>
&lt;hr>
&lt;h2 id="搜尋頁的具體風險點">搜尋頁的具體風險點&lt;/h2>
&lt;h3 id="風險-1filter-slot-跨-viewport-切換">風險 1：Filter slot 跨 viewport 切換&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：matchMedia callback 內 &lt;code>slot.appendChild(filter)&lt;/code> / &lt;code>drawer.insertBefore(filter, ...)&lt;/code>。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>整個 filter 子樹移動 = layout 重算（filter 的新位置、原位置元素重排）&lt;/li>
&lt;li>同時 main 區域與 sidebar 區域的尺寸都重算&lt;/li>
&lt;li>一次性發生、不持續觸發&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>症狀&lt;/strong>：使用者拖動視窗寬度跨過 1400px 時、瞬間卡頓 1-2 frame。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：DevTools Performance 錄下 resize 跨過 breakpoint 的瞬間、看 Layout 區塊有多大。&amp;lt; 16ms = OK；&amp;gt; 16ms 考慮 debounce matchMedia callback。&lt;/p>
&lt;h3 id="風險-2css-變數寫入">風險 2：CSS 變數寫入&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：&lt;code>document.body.style.setProperty('--search-scope-h', ...)&lt;/code>。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：寫 CSS 變數不一定觸發 reflow — 看哪些規則用了這個變數、那些規則影響哪些元素。&lt;/p>
&lt;ul>
&lt;li>&lt;code>--search-scope-h&lt;/code> 用於 drawer 的 &lt;code>margin-top&lt;/code> → drawer 位置變動 → reflow&lt;/li>
&lt;li>&lt;code>--search-scope-h&lt;/code> 用於 filter slot 的 &lt;code>padding-top&lt;/code> → filter slot 高度變動 → reflow&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>症狀&lt;/strong>：scope 大小變動時、drawer 與 filter slot 同時重排、可能看到輕微跳動。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：DevTools Performance 錄一次 scope 變大的事件、看 Layout 區塊。多數場景 &amp;lt; 5ms、可忽略。&lt;/p>
&lt;h3 id="風險-3absolute-定位的重算">風險 3：Absolute 定位的重算&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：&lt;code>.search-filter-slot { position: absolute; ... }&lt;/code>。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：Absolute 元素跟一般 flow 元素分離、自己不影響 sibling 的 layout、但仍受自身 position / size 變動影響。Filter 改 top 觸發自身 reflow、不影響 main。&lt;/p></description><content:encoded><![CDATA[<h2 id="核心原則">核心原則</h2>
<p><strong>Reflow 與 repaint 的成本差兩個數量級、用 Performance 面板可以量化判斷哪個發生。</strong> 開發時不需要「全部避開 reflow」、要做的是「知道哪些操作觸發 reflow、規模放大時哪些值得優化」。</p>
<hr>
<h2 id="為什麼要量化不憑感覺">為什麼要量化、不憑感覺</h2>
<h3 id="商業邏輯">商業邏輯</h3>
<p>瀏覽器渲染管線分階段：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>觸發條件</th>
          <th>相對成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Style recalc</td>
          <td>CSS 規則變動、class toggle</td>
          <td>低</td>
      </tr>
      <tr>
          <td>Layout (reflow)</td>
          <td>影響元素尺寸 / 位置的 CSS 改變</td>
          <td>高（要重算所有受影響元素）</td>
      </tr>
      <tr>
          <td>Paint (repaint)</td>
          <td>顏色 / 背景變動但位置不變</td>
          <td>中</td>
      </tr>
      <tr>
          <td>Composite</td>
          <td>transform / opacity 等 GPU 加速屬性</td>
          <td>最低</td>
      </tr>
  </tbody>
</table>
<p>不同操作落在不同階段。「改 width」觸發 reflow、「改 transform」只到 composite。差距 ~10-100x。</p>
<p>但這不代表要「永遠用 transform」 — 多數場景 reflow 成本可以接受、過度避免反而讓 layout 變脆。</p>
<h3 id="量化的工具">量化的工具</h3>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>看什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Chrome DevTools Performance</td>
          <td>整段操作的 reflow / paint / composite 時間</td>
      </tr>
      <tr>
          <td>Performance API（<code>performance.measure</code>）</td>
          <td>程式化量自家函式</td>
      </tr>
      <tr>
          <td>Layout shift (Web Vitals CLS)</td>
          <td>視覺上的 layout 跳動</td>
      </tr>
  </tbody>
</table>
<p>優先用 DevTools Performance 量、有具體數字後再決定是否優化。</p>
<hr>
<h2 id="搜尋頁的具體風險點">搜尋頁的具體風險點</h2>
<h3 id="風險-1filter-slot-跨-viewport-切換">風險 1：Filter slot 跨 viewport 切換</h3>
<p><strong>位置</strong>：matchMedia callback 內 <code>slot.appendChild(filter)</code> / <code>drawer.insertBefore(filter, ...)</code>。</p>
<p><strong>判讀</strong>：</p>
<ul>
<li>整個 filter 子樹移動 = layout 重算（filter 的新位置、原位置元素重排）</li>
<li>同時 main 區域與 sidebar 區域的尺寸都重算</li>
<li>一次性發生、不持續觸發</li>
</ul>
<p><strong>症狀</strong>：使用者拖動視窗寬度跨過 1400px 時、瞬間卡頓 1-2 frame。</p>
<p><strong>第一個該查的</strong>：DevTools Performance 錄下 resize 跨過 breakpoint 的瞬間、看 Layout 區塊有多大。&lt; 16ms = OK；&gt; 16ms 考慮 debounce matchMedia callback。</p>
<h3 id="風險-2css-變數寫入">風險 2：CSS 變數寫入</h3>
<p><strong>位置</strong>：<code>document.body.style.setProperty('--search-scope-h', ...)</code>。</p>
<p><strong>判讀</strong>：寫 CSS 變數不一定觸發 reflow — 看哪些規則用了這個變數、那些規則影響哪些元素。</p>
<ul>
<li><code>--search-scope-h</code> 用於 drawer 的 <code>margin-top</code> → drawer 位置變動 → reflow</li>
<li><code>--search-scope-h</code> 用於 filter slot 的 <code>padding-top</code> → filter slot 高度變動 → reflow</li>
</ul>
<p><strong>症狀</strong>：scope 大小變動時、drawer 與 filter slot 同時重排、可能看到輕微跳動。</p>
<p><strong>第一個該查的</strong>：DevTools Performance 錄一次 scope 變大的事件、看 Layout 區塊。多數場景 &lt; 5ms、可忽略。</p>
<h3 id="風險-3absolute-定位的重算">風險 3：Absolute 定位的重算</h3>
<p><strong>位置</strong>：<code>.search-filter-slot { position: absolute; ... }</code>。</p>
<p><strong>判讀</strong>：Absolute 元素跟一般 flow 元素分離、自己不影響 sibling 的 layout、但仍受自身 position / size 變動影響。Filter 改 top 觸發自身 reflow、不影響 main。</p>
<p><strong>症狀</strong>：filter slot 的 padding-top 變動（隨 scope-h）— 只影響 filter 自身高度。</p>
<p><strong>第一個該查的</strong>：DevTools Performance 看 filter padding 變動時的 layout 範圍 — 應該只到 filter 內部、不擴散到 main / footer。若擴散表示有意外的 stacking context 影響。</p>
<h3 id="風險-4js-連續操作-dom">風險 4：JS 連續操作 DOM</h3>
<p><strong>位置</strong>：<code>reorderFilters()</code> 用 <code>appendChild</code> 多次調整順序。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">desiredOrder</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">k</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">byKey</span><span class="p">[</span><span class="nx">k</span><span class="p">])</span> <span class="nx">filter</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">byKey</span><span class="p">[</span><span class="nx">k</span><span class="p">]);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p><strong>判讀</strong>：</p>
<ul>
<li>多次 <code>appendChild</code> 可能觸發多次 layout</li>
<li>但 browser 通常會合併同步 DOM 變動到一次 layout（natural batching）</li>
<li>真正會「強制 layout」的是 DOM 寫入後馬上讀 layout 屬性（如 offsetHeight）</li>
</ul>
<p><strong>症狀</strong>：rare — reorder 一次只在 setup 時跑、影響很短。</p>
<p><strong>第一個該查的</strong>：若有這類「寫後立刻讀」的 pattern、用 <code>requestAnimationFrame</code> 把讀延後到下一幀、避免 forced sync layout。</p>
<hr>
<h2 id="內在屬性比較四種-layout-變動類型">內在屬性比較：四種 layout 變動類型</h2>
<table>
  <thead>
      <tr>
          <th>變動類型</th>
          <th>成本</th>
          <th>可控性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Composite-only（transform / opacity）</td>
          <td>最低</td>
          <td>GPU 加速、&lt; 1ms</td>
      </tr>
      <tr>
          <td>Paint-only（顏色變動）</td>
          <td>低</td>
          <td>局部重繪</td>
      </tr>
      <tr>
          <td>Layout（尺寸 / 位置變動）</td>
          <td>中-高</td>
          <td>要算受影響的範圍</td>
      </tr>
      <tr>
          <td>Forced sync layout（DOM 寫後立刻讀）</td>
          <td>最高</td>
          <td>連續觸發是 perf killer</td>
      </tr>
  </tbody>
</table>
<p>選擇順序：<strong>有意識避免 forced sync layout</strong>、<strong>對動畫優先用 transform</strong>、<strong>一般 layout 變動量小不必特別避免</strong>。</p>
<hr>
<h2 id="預估成本的快速法則">預估成本的快速法則</h2>
<p>不要每個操作都用 DevTools 量、用快速法則先判斷：</p>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>預估等級</th>
          <th>何時要量</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改 class（class toggle）</td>
          <td>1ms 等級</td>
          <td>套用到大量元素時</td>
      </tr>
      <tr>
          <td>Append / remove 單一節點</td>
          <td>1-5ms</td>
          <td>大規模迭代時</td>
      </tr>
      <tr>
          <td>移動 DOM 子樹（reparent）</td>
          <td>5-20ms</td>
          <td>子樹大、頻繁觸發時</td>
      </tr>
      <tr>
          <td>改 CSS 變數（簡單 calc）</td>
          <td>1-5ms</td>
          <td>頻繁觸發時</td>
      </tr>
      <tr>
          <td>Forced sync layout</td>
          <td>5-50ms</td>
          <td>任何寫後立刻讀的 pattern 都該量</td>
      </tr>
  </tbody>
</table>
<p>預估超過 frame budget（16.67ms）才值得實際量、進一步優化。</p>
<hr>
<h2 id="設計取捨layout-操作的處理策略">設計取捨：layout 操作的處理策略</h2>
<p>四種做法、各自機會成本不同。這個專案選 A（量化評估再決定）當預設、其他做法在特定情境合理。</p>
<h3 id="a量化評估按規模決定優化與否這個專案的預設">A：量化評估、按規模決定優化與否（這個專案的預設）</h3>
<ul>
<li><strong>機制</strong>：用 DevTools Performance 量每個 layout 操作的實際成本、超過 frame budget（16.67ms）才優化</li>
<li><strong>選 A 的理由</strong>：避免過度優化（多數 reflow 成本可接受）、又不漏真正貴的（forced sync layout）</li>
<li><strong>適合</strong>：所有效能盤點情境</li>
<li><strong>代價</strong>：需要學會用 DevTools Performance、對效能 dispute 要量</li>
</ul>
<h3 id="b全部用-transform--opacity-避免-reflow">B：全部用 transform / opacity 避免 reflow</h3>
<ul>
<li><strong>機制</strong>：所有動畫 / 變動都用 transform 或 opacity（GPU composite）</li>
<li><strong>跟 A 的取捨</strong>：B 預先避免 reflow、A 量化按需處理；但 B 寫出複雜的 transform / absolute 組合、layout 邏輯難維護</li>
<li><strong>B 比 A 好的情境</strong>：高頻動畫（每 frame 變動的旋轉 / 移動）— 確定觸發 layout 會卡</li>
</ul>
<h3 id="c完全避免-layout-操作">C：完全避免 layout 操作</h3>
<ul>
<li><strong>機制</strong>：把所有可能觸發 reflow 的操作都繞開</li>
<li><strong>跟 A/B 的取捨</strong>：C 過度反應、A/B 適度；C 寫法極受限、layout 表達力下降</li>
<li><strong>C 才合理的情境</strong>：純動畫場景（沒有 layout 需求）— 對一般 UI 不適用</li>
</ul>
<h3 id="d不量靠經驗判斷">D：不量、靠經驗判斷</h3>
<ul>
<li><strong>機制</strong>：依「我覺得這應該快」做決定</li>
<li><strong>成本特別高的原因</strong>：瀏覽器 / 設備 / 場景差異大、直覺不可靠；可能漏掉 forced sync layout 等真正貴的 pattern</li>
<li><strong>D 是反模式</strong>：效能 dispute 必須有數字 — 直覺判斷會漏掉 forced sync layout 等真正貴的 pattern、跨設備差異大</li>
</ul>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該檢查的位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者操作後輕微跳動或卡頓</td>
          <td>DevTools Performance 看 Layout 區塊</td>
      </tr>
      <tr>
          <td>動畫不順</td>
          <td>確認動畫屬性是 transform / opacity 而非 width / left</td>
      </tr>
      <tr>
          <td>Layout shift 警告</td>
          <td>找出觸發 layout 的元素、量穩定性</td>
      </tr>
      <tr>
          <td>Console 出現「Forced reflow」warning</td>
          <td>找寫後立刻讀的 DOM pattern</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：Reflow 是 layout 系統的正常運作、不是要消滅的敵人。盤點時量化看哪些值得優化、哪些可以接受。</p>
]]></content:encoded></item><item><title>9.C36 Coinbase：MongoDB 撐 Ruby 單體 + 1.5M reads/sec identity 服務</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/</guid><description>&lt;p>這個案例的核心責任是說明「document database 在大規模 OLTP 場景如何撐住」。Coinbase 從 Ruby on Rails 單體 + MongoDB 起家、八年後仍保留 MongoDB 作為主資料層、並把 connection pooling、ML 預測擴容、cache + freshness token 都疊在 document model 上。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365&lt;/a> 對照 — Microsoft 365 走「遷出 MongoDB、保留 document API」、Coinbase 走「保留 MongoDB、補周邊工具」。兩條路徑都揭露 MongoDB 在 production 主角位置會遇到什麼壓力。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Coinbase MongoDB 平台的關鍵數字（引自 &lt;a href="https://www.coinbase.com/blog/scaling-connections-with-ruby-and-mongodb">Coinbase Engineering Blog&lt;/a> 與 &lt;a href="https://www.mongodb.com/solutions/customer-case-studies/coinbase">MongoDB customer case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Users 服務尖峰讀取&lt;/td>
 &lt;td>1.5M reads / sec&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Deploy 時 MongoDB 連線尖峰&lt;/td>
 &lt;td>~60K connections / minute（單 cluster）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>mongobetween 後連線降幅&lt;/td>
 &lt;td>30K → ~2K（一個量級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MongoDB cluster 數量&lt;/td>
 &lt;td>many clusters（多服務 federated）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>加密貨幣 surge 擴容時間&lt;/td>
 &lt;td>70 分鐘 → 25 分鐘（-64%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ML 預測擴容領先窗&lt;/td>
 &lt;td>60 分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cache 命中後跳過 DB&lt;/td>
 &lt;td>是（Memcached query-cache）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：MongoDB Atlas（主資料層）、DynamoDB（部分 workload 的 federated store）、Memcached（query result cache）、自研 mongobetween proxy（連線多工）、Ruby on Rails 單體 + 多個 Fragment APIs、ML 預測模型驅動 cluster auto-scaling。&lt;/p>
&lt;p>關鍵負載形狀：「加密貨幣價格突發 + 用戶交易需求湧入」雙峰疊加。價格 alert 觸發 read 爆量（users / portfolio 查詢）、下單觸發 write 爆量（order book / wallet 寫入）。兩種峰值不像 &lt;a href="https://tarrragon.github.io/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 流量 &amp;#43;50% 不影響延遲">9.C4 DraftKings&lt;/a> 的 Super Bowl 事件型可預測、是隨外部市場波動的 &lt;em>low-latency-sustained 中夾雜 surge&lt;/em>。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Coinbase MongoDB 的工程選擇揭露三個 document database 在 production 主角位置的設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>MongoDB + Ruby 連線爆炸需要外部 connection pool&lt;/strong>：CRuby 因為 GVL 必須每 CPU core 起一個 process、blue-green 部署期間 instance 數量 ×2、連線數隨之 ×2、單一 cluster 看到 60K 連線/分鐘。原生 MongoDB driver 沒有跨 process 的 connection pool — 跟 PostgreSQL 走 pgbouncer 是同樣需求、所以 Coinbase 自建 &lt;a href="https://github.com/coinbase/mongobetween">mongobetween&lt;/a> 做多工。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取&lt;/a> 的 connection storm 問題、document database 不會自動解決、要主動補工具。&lt;/li>
&lt;li>&lt;strong>document model 撐 1.5M reads/sec 靠 cache + freshness token&lt;/strong>：直接打 MongoDB 不可能撐 1.5M reads/sec — Coinbase 在 users 服務前面加 Memcached query cache、單 document query 先查 cache。但 cache + write 會有一致性問題、所以引入 OCC version 跟 &lt;em>freshness token&lt;/em>：write 成功後給 client 一個 token、client 之後 read 帶 token、server 保證返回的資料版本 ≥ token、必要時 bypass cache 直接打 DB。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a> 的 read-after-write 設計。&lt;/li>
&lt;li>&lt;strong>加密貨幣 surge 用 ML 預測、不靠 reactive scaling&lt;/strong>：cluster 擴容要 70 分鐘、傳統 CPU / queue 觸發的 reactive scaling 在 surge 開始時才動、來不及。Coinbase 訓練 ML 模型分析價格資料、提前 60 分鐘預測流量、預先擴容。把擴容時間從 70 分鐘壓到 25 分鐘是 trigger 提前、不是擴容本身變快。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 的 predictive scaling。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「document database 在大規模 OLTP 場景如何撐住」。Coinbase 從 Ruby on Rails 單體 + MongoDB 起家、八年後仍保留 MongoDB 作為主資料層、並把 connection pooling、ML 預測擴容、cache + freshness token 都疊在 document model 上。跟 <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a> 對照 — Microsoft 365 走「遷出 MongoDB、保留 document API」、Coinbase 走「保留 MongoDB、補周邊工具」。兩條路徑都揭露 MongoDB 在 production 主角位置會遇到什麼壓力。</p>
<h2 id="觀察">觀察</h2>
<p>Coinbase MongoDB 平台的關鍵數字（引自 <a href="https://www.coinbase.com/blog/scaling-connections-with-ruby-and-mongodb">Coinbase Engineering Blog</a> 與 <a href="https://www.mongodb.com/solutions/customer-case-studies/coinbase">MongoDB customer case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Users 服務尖峰讀取</td>
          <td>1.5M reads / sec</td>
      </tr>
      <tr>
          <td>Deploy 時 MongoDB 連線尖峰</td>
          <td>~60K connections / minute（單 cluster）</td>
      </tr>
      <tr>
          <td>mongobetween 後連線降幅</td>
          <td>30K → ~2K（一個量級）</td>
      </tr>
      <tr>
          <td>MongoDB cluster 數量</td>
          <td>many clusters（多服務 federated）</td>
      </tr>
      <tr>
          <td>加密貨幣 surge 擴容時間</td>
          <td>70 分鐘 → 25 分鐘（-64%）</td>
      </tr>
      <tr>
          <td>ML 預測擴容領先窗</td>
          <td>60 分鐘</td>
      </tr>
      <tr>
          <td>Cache 命中後跳過 DB</td>
          <td>是（Memcached query-cache）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：MongoDB Atlas（主資料層）、DynamoDB（部分 workload 的 federated store）、Memcached（query result cache）、自研 mongobetween proxy（連線多工）、Ruby on Rails 單體 + 多個 Fragment APIs、ML 預測模型驅動 cluster auto-scaling。</p>
<p>關鍵負載形狀：「加密貨幣價格突發 + 用戶交易需求湧入」雙峰疊加。價格 alert 觸發 read 爆量（users / portfolio 查詢）、下單觸發 write 爆量（order book / wallet 寫入）。兩種峰值不像 <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</a> 的 Super Bowl 事件型可預測、是隨外部市場波動的 <em>low-latency-sustained 中夾雜 surge</em>。</p>
<h2 id="判讀">判讀</h2>
<p>Coinbase MongoDB 的工程選擇揭露三個 document database 在 production 主角位置的設計重點。</p>
<ol>
<li><strong>MongoDB + Ruby 連線爆炸需要外部 connection pool</strong>：CRuby 因為 GVL 必須每 CPU core 起一個 process、blue-green 部署期間 instance 數量 ×2、連線數隨之 ×2、單一 cluster 看到 60K 連線/分鐘。原生 MongoDB driver 沒有跨 process 的 connection pool — 跟 PostgreSQL 走 pgbouncer 是同樣需求、所以 Coinbase 自建 <a href="https://github.com/coinbase/mongobetween">mongobetween</a> 做多工。對應 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> 的 connection storm 問題、document database 不會自動解決、要主動補工具。</li>
<li><strong>document model 撐 1.5M reads/sec 靠 cache + freshness token</strong>：直接打 MongoDB 不可能撐 1.5M reads/sec — Coinbase 在 users 服務前面加 Memcached query cache、單 document query 先查 cache。但 cache + write 會有一致性問題、所以引入 OCC version 跟 <em>freshness token</em>：write 成功後給 client 一個 token、client 之後 read 帶 token、server 保證返回的資料版本 ≥ token、必要時 bypass cache 直接打 DB。對應 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a> 的 read-after-write 設計。</li>
<li><strong>加密貨幣 surge 用 ML 預測、不靠 reactive scaling</strong>：cluster 擴容要 70 分鐘、傳統 CPU / queue 觸發的 reactive scaling 在 surge 開始時才動、來不及。Coinbase 訓練 ML 模型分析價格資料、提前 60 分鐘預測流量、預先擴容。把擴容時間從 70 分鐘壓到 25 分鐘是 trigger 提前、不是擴容本身變快。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的 predictive scaling。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「1.5M reads/sec」是 users 服務 <em>加上 cache</em> 的數字、不是 MongoDB cluster 純讀取數字。讀案例時要區分「應用層觀察到」跟「DB 層實際承擔」。</li>
<li>mongobetween 是 Coinbase 特殊環境（Ruby + GVL + blue-green）的產物。Go / Java / Node.js 應用因為原生支援連線多工、通常不需要這層 proxy。</li>
<li>ML 預測有 false positive / false negative — 預測錯時要嘛浪費容量、要嘛 surge 真來時擋不住。Coinbase 沒揭露準確率、所以仍保留 reactive scaling 作為 safety net。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>document database 撐大規模 OLTP 要主動補 connection pool</strong>：MongoDB 原生 connection 模式對「process 數多 + deploy 重」的環境會爆。應用層或 sidecar proxy 做多工是基線設計。對應 <a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">01.10 KV / Document DB 容量規劃</a>。</li>
<li><strong>freshness token 是 read-after-write 一致性的可重用模式</strong>：比 strong consistency（性能差）跟 eventually consistent（read 不到剛寫的）更精細的中間路徑。token 機制可以推廣到任何「主要 eventually consistent、少數 read 要求最新」的場景。</li>
<li><strong>predictive scaling 適用於「外部訊號可預測流量」的服務</strong>：加密貨幣價格、賽事行程、票務開賣時間都是外部訊號。比 reactive scaling 早一個擴容週期出手。對應 <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> 的 AI 預測式擴容。</li>
<li><strong>federated DB（MongoDB + DynamoDB）按 workload 分流</strong>：document-shaped 用 MongoDB、access pattern 固定的 KV 用 DynamoDB。不是「全用 MongoDB」也不是「全遷 DynamoDB」、是按 workload 形狀分。對應 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> 的多 DB 整合反例（Netflix 走整合方向、Coinbase 走 federated）。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>AWS：MongoDB Atlas + ElastiCache + DynamoDB（Coinbase 配置）</li>
<li>GCP：MongoDB Atlas on GCP + Memorystore + Firestore（document API）</li>
<li>Azure：Cosmos DB MongoDB API + Cache for Redis、不需要 Atlas</li>
<li>mongobetween 風格的 proxy：PostgreSQL 走 pgbouncer / pgcat、MongoDB 走 mongobetween / mongoproxy</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 MongoDB 大規模 production → <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> + <a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">01.10 KV / Document DB 容量規劃</a></li>
<li>想做 read-after-write 一致性設計 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想做 predictive scaling → <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> + <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a></li>
<li>想對照 MongoDB 遷出 / 保留決策 → <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a>（遷到 Cosmos DB MongoDB API）</li>
<li>想理解 connection storm 問題 → <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a></li>
<li>想深入 connection / proxy 治理與 cache 層 → <a href="/blog/backend/01-database/vendors/mongodb/connection-management-and-cache-layer/" data-link-title="MongoDB Connection Management and Cache Layer：driver × 部署模型 × cache × predictive scaling" data-link-desc="MongoDB 大規模 OLTP 撞牆不是單一 driver 議題、是 driver × 部署模型 × cache × scaling trigger 三層協作；含 Coinbase mongobetween / freshness token / ML 預測擴容三件套 &#43; 適用範圍紀律">MongoDB connection 管理與 cache 層</a></li>
<li>想做 replica set 讀寫分離設計 → <a href="/blog/backend/01-database/vendors/mongodb/replica-set-read-preference/" data-link-title="MongoDB Replica Set Read Preference：DB 層 causal session vs cache 層 freshness token" data-link-desc="MongoDB read preference 五擇一 &#43; read concern &#43; causal consistency session 機制；DB 層機制解 cluster 內 read-your-own-write、cache 層 freshness token 解跨層 read-after-write、大規模 OLTP 必須兩層合用">MongoDB replica set read preference</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.coinbase.com/blog/scaling-connections-with-ruby-and-mongodb">Coinbase：Scaling connections with Ruby and MongoDB</a></li>
<li><a href="https://www.coinbase.com/blog/scaling-identity-how-coinbase-serves-1.5M-reads-second">Coinbase：Scaling Identity - How Coinbase Serves 1.5M Reads/Second</a></li>
<li><a href="https://www.coinbase.com/blog/how-we-do-mongodb-migrations-at-coinbase">Coinbase：How We Do MongoDB Migrations at Coinbase</a></li>
<li><a href="https://www.mongodb.com/solutions/customer-case-studies/coinbase">MongoDB customer case study：Coinbase Decreases Scaling Time</a></li>
<li><a href="https://github.com/coinbase/mongobetween">mongobetween GitHub repository</a></li>
</ul>
]]></content:encoded></item><item><title>資源載入時序：lazy chunk 與 critical path</title><link>https://tarrragon.github.io/blog/report/lazy-loading-and-critical-path/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/lazy-loading-and-critical-path/</guid><description>&lt;h2 id="核心原則">核心原則&lt;/h2>
&lt;p>&lt;strong>資源載入時序的設計選擇是「首次渲染速度」與「首次互動延遲」的權衡 — 不是越早載越好。&lt;/strong> 把不影響首次渲染的資源延後（lazy load）、首屏更快；但延後的資源在使用者真正需要時可能還沒到、互動延遲。盤點時兩者一起看。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼載入時序需要設計">為什麼載入時序需要設計&lt;/h2>
&lt;h3 id="商業邏輯">商業邏輯&lt;/h3>
&lt;p>每個資源都有兩個時點：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>時點&lt;/th>
 &lt;th>含義&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>開始下載&lt;/td>
 &lt;td>在 critical path（首屏）還是 lazy（首次互動才下載）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用&lt;/td>
 &lt;td>下載完 + parse + 執行完&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>把資源放 critical path = 阻塞首屏渲染；放 lazy = 首屏更快但首次互動可能等。&lt;/p>
&lt;p>對搜尋頁：使用者打開 &lt;code>/search/&lt;/code> 但可能不立刻搜尋 — pagefind index lazy load 是合理選擇。但若打開後立刻打字、index 還沒載完、第一次搜尋有明顯延遲。&lt;/p>
&lt;h3 id="critical-path-vs-lazy-的標準">Critical path vs lazy 的標準&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>資源類型&lt;/th>
 &lt;th>通常的選擇&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>視覺主體 CSS（首屏看到的）&lt;/td>
 &lt;td>Critical path&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>互動 JS（事件處理）&lt;/td>
 &lt;td>DOMContentLoaded 後即可&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>大型功能模組（搜尋 index）&lt;/td>
 &lt;td>Lazy、使用者觸發才載&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>圖片 / 影片&lt;/td>
 &lt;td>Lazy 視可見性&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>選擇原則：&lt;strong>「首屏渲染需要嗎？」是 → critical；「使用者一定會用嗎？」否 → lazy&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="搜尋頁的具體風險點">搜尋頁的具體風險點&lt;/h2>
&lt;h3 id="風險-1pagefind-index-下載延遲">風險 1：Pagefind index 下載延遲&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：PagefindUI 在 mount 時開始下載 entry chunk、之後才能搜尋。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>entry chunk（&lt;code>pagefind-entry.json&lt;/code>）~ 10KB&lt;/li>
&lt;li>下載 + parse 約 100-500ms（看網路）&lt;/li>
&lt;li>使用者打開搜尋頁立刻打字時、第一個字可能還沒搜尋&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>症狀&lt;/strong>：使用者打開 /search/ 立刻打字、第一個字沒回應、過 200-500ms 才開始搜尋。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：DevTools Network 看 entry chunk 下載時間。&amp;gt; 500ms 考慮 preload 機制。&lt;/p>
&lt;h3 id="風險-2個別-search-chunk-的-lazy-load">風險 2：個別 search chunk 的 lazy load&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：使用者搜尋特定 term 時、pagefind 動態下載對應 chunk。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：每個搜尋 term 對應一個 chunk（依 term 前綴分）。第一次搜尋某個 prefix 要下載對應 chunk、之後同 prefix 搜尋走 cache。&lt;/p>
&lt;p>&lt;strong>症狀&lt;/strong>：搜尋特定字時稍有延遲（200-500ms）、之後就快了。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：Pagefind 內建 cache 機制、多數情境表現可接受。若極慢可考慮 service worker preload chunk。&lt;/p>
&lt;h3 id="風險-3pagefind-ui-script-下載">風險 3：Pagefind UI script 下載&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：&lt;code>&amp;lt;script src=&amp;quot;/blog/pagefind/pagefind-ui.js&amp;quot;&amp;gt;&lt;/code>。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>~ 50KB minified、需在使用者打字前載完&lt;/li>
&lt;li>有 &lt;code>defer&lt;/code> 不阻塞 HTML parsing、但仍占 critical path 寬度&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>症狀&lt;/strong>：搜尋頁初次載入比一般頁慢。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：確認 &lt;code>&amp;lt;script&amp;gt;&lt;/code> 有 &lt;code>defer&lt;/code> attribute、使用者開啟搜尋頁後背景下載、不阻塞 HTML 渲染。&lt;/p>
&lt;h3 id="風險-4assetssearchcss-與-pagefind-uicss-載入順序">風險 4：assets/search.css 與 pagefind-ui.css 載入順序&lt;/h3>
&lt;p>&lt;strong>位置&lt;/strong>：兩個 stylesheet 都在 &lt;code>&amp;lt;head&amp;gt;&lt;/code> 載入。&lt;/p>
&lt;p>&lt;strong>判讀&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>pagefind-ui.css 5-10KB、search.css（拆檔後）3-5KB&lt;/li>
&lt;li>兩者都阻塞首屏渲染（CSS render-blocking）&lt;/li>
&lt;li>加總 &amp;lt; 20KB、影響輕微&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>症狀&lt;/strong>：rare、僅在極慢網路下感受到。&lt;/p>
&lt;p>&lt;strong>第一個該查的&lt;/strong>：DevTools Network 看 CSS 下載時間。考慮：&lt;/p>
&lt;ul>
&lt;li>把 critical CSS inline（首屏需要的部分）、其他 lazy&lt;/li>
&lt;li>用 Hugo &lt;code>resources.Get | minify | fingerprint&lt;/code> 確保最小化&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="內在屬性比較四種載入策略">內在屬性比較：四種載入策略&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>策略&lt;/th>
 &lt;th>首屏速度&lt;/th>
 &lt;th>首次互動延遲&lt;/th>
 &lt;th>適用情境&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>全 critical path&lt;/td>
 &lt;td>慢&lt;/td>
 &lt;td>0（即可用）&lt;/td>
 &lt;td>小型站、所有資源都重要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lazy load 大型模組&lt;/td>
 &lt;td>快&lt;/td>
 &lt;td>中 — 使用者觸發才下載&lt;/td>
 &lt;td>搜尋、富互動模組&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Critical path + lazy mix&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>一般情境（pagefind 走這條）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Service Worker preload&lt;/td>
 &lt;td>中 — 首次載完後永久快&lt;/td>
 &lt;td>0 — 從 cache 取&lt;/td>
 &lt;td>高頻使用者、PWA&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>對搜尋頁的場景：&lt;strong>Lazy load 大型模組&lt;/strong>是 pagefind 預設行為、合理；考慮再進一步可以 preload entry chunk 在 idle 時。&lt;/p></description><content:encoded><![CDATA[<h2 id="核心原則">核心原則</h2>
<p><strong>資源載入時序的設計選擇是「首次渲染速度」與「首次互動延遲」的權衡 — 不是越早載越好。</strong> 把不影響首次渲染的資源延後（lazy load）、首屏更快；但延後的資源在使用者真正需要時可能還沒到、互動延遲。盤點時兩者一起看。</p>
<hr>
<h2 id="為什麼載入時序需要設計">為什麼載入時序需要設計</h2>
<h3 id="商業邏輯">商業邏輯</h3>
<p>每個資源都有兩個時點：</p>
<table>
  <thead>
      <tr>
          <th>時點</th>
          <th>含義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>開始下載</td>
          <td>在 critical path（首屏）還是 lazy（首次互動才下載）</td>
      </tr>
      <tr>
          <td>可用</td>
          <td>下載完 + parse + 執行完</td>
      </tr>
  </tbody>
</table>
<p>把資源放 critical path = 阻塞首屏渲染；放 lazy = 首屏更快但首次互動可能等。</p>
<p>對搜尋頁：使用者打開 <code>/search/</code> 但可能不立刻搜尋 — pagefind index lazy load 是合理選擇。但若打開後立刻打字、index 還沒載完、第一次搜尋有明顯延遲。</p>
<h3 id="critical-path-vs-lazy-的標準">Critical path vs lazy 的標準</h3>
<table>
  <thead>
      <tr>
          <th>資源類型</th>
          <th>通常的選擇</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>視覺主體 CSS（首屏看到的）</td>
          <td>Critical path</td>
      </tr>
      <tr>
          <td>互動 JS（事件處理）</td>
          <td>DOMContentLoaded 後即可</td>
      </tr>
      <tr>
          <td>大型功能模組（搜尋 index）</td>
          <td>Lazy、使用者觸發才載</td>
      </tr>
      <tr>
          <td>圖片 / 影片</td>
          <td>Lazy 視可見性</td>
      </tr>
  </tbody>
</table>
<p>選擇原則：<strong>「首屏渲染需要嗎？」是 → critical；「使用者一定會用嗎？」否 → lazy</strong>。</p>
<hr>
<h2 id="搜尋頁的具體風險點">搜尋頁的具體風險點</h2>
<h3 id="風險-1pagefind-index-下載延遲">風險 1：Pagefind index 下載延遲</h3>
<p><strong>位置</strong>：PagefindUI 在 mount 時開始下載 entry chunk、之後才能搜尋。</p>
<p><strong>判讀</strong>：</p>
<ul>
<li>entry chunk（<code>pagefind-entry.json</code>）~ 10KB</li>
<li>下載 + parse 約 100-500ms（看網路）</li>
<li>使用者打開搜尋頁立刻打字時、第一個字可能還沒搜尋</li>
</ul>
<p><strong>症狀</strong>：使用者打開 /search/ 立刻打字、第一個字沒回應、過 200-500ms 才開始搜尋。</p>
<p><strong>第一個該查的</strong>：DevTools Network 看 entry chunk 下載時間。&gt; 500ms 考慮 preload 機制。</p>
<h3 id="風險-2個別-search-chunk-的-lazy-load">風險 2：個別 search chunk 的 lazy load</h3>
<p><strong>位置</strong>：使用者搜尋特定 term 時、pagefind 動態下載對應 chunk。</p>
<p><strong>判讀</strong>：每個搜尋 term 對應一個 chunk（依 term 前綴分）。第一次搜尋某個 prefix 要下載對應 chunk、之後同 prefix 搜尋走 cache。</p>
<p><strong>症狀</strong>：搜尋特定字時稍有延遲（200-500ms）、之後就快了。</p>
<p><strong>第一個該查的</strong>：Pagefind 內建 cache 機制、多數情境表現可接受。若極慢可考慮 service worker preload chunk。</p>
<h3 id="風險-3pagefind-ui-script-下載">風險 3：Pagefind UI script 下載</h3>
<p><strong>位置</strong>：<code>&lt;script src=&quot;/blog/pagefind/pagefind-ui.js&quot;&gt;</code>。</p>
<p><strong>判讀</strong>：</p>
<ul>
<li>~ 50KB minified、需在使用者打字前載完</li>
<li>有 <code>defer</code> 不阻塞 HTML parsing、但仍占 critical path 寬度</li>
</ul>
<p><strong>症狀</strong>：搜尋頁初次載入比一般頁慢。</p>
<p><strong>第一個該查的</strong>：確認 <code>&lt;script&gt;</code> 有 <code>defer</code> attribute、使用者開啟搜尋頁後背景下載、不阻塞 HTML 渲染。</p>
<h3 id="風險-4assetssearchcss-與-pagefind-uicss-載入順序">風險 4：assets/search.css 與 pagefind-ui.css 載入順序</h3>
<p><strong>位置</strong>：兩個 stylesheet 都在 <code>&lt;head&gt;</code> 載入。</p>
<p><strong>判讀</strong>：</p>
<ul>
<li>pagefind-ui.css 5-10KB、search.css（拆檔後）3-5KB</li>
<li>兩者都阻塞首屏渲染（CSS render-blocking）</li>
<li>加總 &lt; 20KB、影響輕微</li>
</ul>
<p><strong>症狀</strong>：rare、僅在極慢網路下感受到。</p>
<p><strong>第一個該查的</strong>：DevTools Network 看 CSS 下載時間。考慮：</p>
<ul>
<li>把 critical CSS inline（首屏需要的部分）、其他 lazy</li>
<li>用 Hugo <code>resources.Get | minify | fingerprint</code> 確保最小化</li>
</ul>
<hr>
<h2 id="內在屬性比較四種載入策略">內在屬性比較：四種載入策略</h2>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>首屏速度</th>
          <th>首次互動延遲</th>
          <th>適用情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>全 critical path</td>
          <td>慢</td>
          <td>0（即可用）</td>
          <td>小型站、所有資源都重要</td>
      </tr>
      <tr>
          <td>Lazy load 大型模組</td>
          <td>快</td>
          <td>中 — 使用者觸發才下載</td>
          <td>搜尋、富互動模組</td>
      </tr>
      <tr>
          <td>Critical path + lazy mix</td>
          <td>中</td>
          <td>低</td>
          <td>一般情境（pagefind 走這條）</td>
      </tr>
      <tr>
          <td>Service Worker preload</td>
          <td>中 — 首次載完後永久快</td>
          <td>0 — 從 cache 取</td>
          <td>高頻使用者、PWA</td>
      </tr>
  </tbody>
</table>
<p>對搜尋頁的場景：<strong>Lazy load 大型模組</strong>是 pagefind 預設行為、合理；考慮再進一步可以 preload entry chunk 在 idle 時。</p>
<hr>
<h2 id="preload-的取捨">Preload 的取捨</h2>
<p>預先載入下一步可能需要的資源 — 加快互動、但浪費頻寬（若使用者最終沒用）。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/blog/pagefind/pagefind-entry.json&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;fetch&#34;</span> <span class="na">crossorigin</span><span class="p">&gt;</span></span></span></code></pre></div><p>放 head、瀏覽器在 critical path 完成後 idle 時開始下載。</p>
<p><strong>值得做的條件</strong>：</p>
<ul>
<li>使用者進入此頁的明確意圖會觸發該資源（搜尋頁進入 = 會搜尋）</li>
<li>資源不大（entry chunk &lt; 10KB OK）</li>
</ul>
<p><strong>不值得</strong>：</p>
<ul>
<li>使用者可能只看不用（首頁載 search index 通常不值得）</li>
<li>資源很大（不要 preload 整個 search index）</li>
</ul>
<hr>
<h2 id="設計取捨資源載入時序的策略">設計取捨：資源載入時序的策略</h2>
<p>四種做法、各自機會成本不同。預設按資源性質選 — 影響首屏 → A、使用者必用大型模組 → B、進入此頁必觸發 → C。</p>
<h3 id="acritical-path首屏阻塞">A：Critical path（首屏阻塞）</h3>
<ul>
<li><strong>機制</strong>：CSS <code>&lt;link&gt;</code> 在 head、JS 用 <code>defer</code> 在 head 或 body 末</li>
<li><strong>選 A 的理由</strong>：首屏渲染就需要、不能延後</li>
<li><strong>適合</strong>：視覺主體 CSS（首屏可見）、互動處理 JS（DOMContentLoaded 後即用）</li>
<li><strong>代價</strong>：阻塞首屏渲染、加總大小要控制（&lt; 50KB 為佳）</li>
</ul>
<h3 id="blazy-load使用者觸發才載">B：Lazy load（使用者觸發才載）</h3>
<ul>
<li><strong>機制</strong>：用動態 import / IntersectionObserver / 按 click 載入</li>
<li><strong>跟 A 的取捨</strong>：B 首屏快、A 首次互動快；B 在使用者必用時造成互動延遲</li>
<li><strong>B 比 A 好的情境</strong>：大型功能模組（搜尋 index、富文字編輯器）、使用者可能不用</li>
</ul>
<h3 id="cpreload打賭使用者會用">C：Preload（打賭使用者會用）</h3>
<ul>
<li><strong>機制</strong>：<code>&lt;link rel=&quot;preload&quot;&gt;</code> 在 idle 時下載、需要時從 cache 取</li>
<li><strong>跟 A/B 的取捨</strong>：C 不阻塞首屏（idle 下載）、需要時無延遲；但賭錯（使用者不用）就浪費頻寬</li>
<li><strong>C 比 A/B 好的情境</strong>：進入此頁的明確意圖會觸發該資源（搜尋頁進入 = 必搜尋）+ 資源不大（&lt; 10KB）</li>
</ul>
<h3 id="dservice-worker-預先-cache">D：Service Worker 預先 cache</h3>
<ul>
<li><strong>機制</strong>：第一次造訪時 cache 進 SW、之後從 cache 取</li>
<li><strong>跟 C 的取捨</strong>：D 第一次造訪後永久快、C 每次都要重新 preload；D 適合 PWA 等「重複造訪」場景</li>
<li><strong>D 比 C 好的情境</strong>：高頻使用者、PWA 應用、需要 offline 支援</li>
</ul>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該檢查的位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者打開頁面立刻互動有明顯延遲</td>
          <td>該互動依賴的資源是否 lazy、是否值得 preload</td>
      </tr>
      <tr>
          <td>首屏渲染慢、CSS / JS 阻塞</td>
          <td>DevTools Network 找 critical path 中可拆 lazy 的資源</td>
      </tr>
      <tr>
          <td>Lazy 資源永遠不被觸發</td>
          <td>該資源預設或許不必 lazy（不會 lazy 也不會貴）</td>
      </tr>
      <tr>
          <td>慢網路 / 行動裝置使用者抱怨</td>
          <td>用 DevTools Network throttling 模擬、量首屏與首次互動</td>
      </tr>
  </tbody>
</table>
<p><strong>核心原則</strong>：載入時序是設計決定、不是預設。每個資源「critical / lazy / preload」三選一明確選、不要全部丟 critical path。</p>
]]></content:encoded></item><item><title>9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/</guid><description>&lt;p>這個案例的核心責任是說明「從自管 MongoDB 遷到 Atlas managed」這條路徑的工程與成本對照。Forbes 自 2011 年起用 MongoDB 重寫 CMS、2020 年把 production 遷到 Atlas on Google Cloud、保留同一個 document model、轉移 DBA 責任跟跨雲彈性。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato&lt;/a> 的「跨 DB 種類遷移」對照 — Forbes 是 &lt;em>同 DB、換託管模式&lt;/em>、不需要重寫 schema 跟 access pattern。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Forbes 遷移到 MongoDB Atlas on Google Cloud 的關鍵數字（引自 &lt;a href="https://cloud.google.com/blog/products/databases/forbes-migrates-to-mongodb-atlas-on-google-cloud">Google Cloud Blog&lt;/a> 與 &lt;a href="https://www.mongodb.com/solutions/customer-case-studies/forbes">MongoDB customer case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>單月不重複訪客&lt;/td>
 &lt;td>120M（2020 年 5 月）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build 時間&lt;/td>
 &lt;td>25 分鐘 → 9 分鐘（-64%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Release 頻率提升&lt;/td>
 &lt;td>2x – 10x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>微服務數量&lt;/td>
 &lt;td>50+（GKE 上）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移耗時&lt;/td>
 &lt;td>6 個月&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DB 總體擁有成本降幅&lt;/td>
 &lt;td>-25%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>電子報訂閱量&lt;/td>
 &lt;td>+92%（2020 全年）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Atlas 可用 region&lt;/td>
 &lt;td>70+（跨 AWS / GCP / Azure）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CMS MongoDB 起用年&lt;/td>
 &lt;td>2011（首版 CMS 兩個月內交付）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：MongoDB Atlas（managed document DB）、Google Cloud Platform（基礎設施）、Google Kubernetes Engine（50+ 微服務編排）、Google App Engine（部分 serverless 應用）、自建中介 abstraction layer（API 隔離 schema 變動）。&lt;/p>
&lt;p>關鍵負載形狀：「文章 publish 後突然爆量」是新聞媒體常態 — 熱門報導、人物專訪、財經事件都會在分鐘內把單篇文章拉到百萬讀者。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13 Hotstar IPL&lt;/a> 的「賽事時段預期峰值」不同、Forbes 的爆量是事件驅動、難以精確預測、需要 Atlas auto-scaling 撐住臨時讀爆。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Forbes 的遷移選擇揭露三個「自管 → managed」路徑的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>同 DB 換託管模式比換 DB 種類風險低、但 ROI 也較窄&lt;/strong>：Forbes 6 個月完成遷移、保留同 document model、schema 不動、application 改動只在 connection string 跟運維邊界。這跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato 從 TiDB 遷到 DynamoDB&lt;/a> 對照、後者要重新設計 access pattern、ROI 大但風險高。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組&lt;/a> 的 schema migration playbook：「換 DB」跟「換託管」是兩個不同議題、不要混為一談。&lt;/li>
&lt;li>&lt;strong>跨雲彈性的價值在規避未來鎖定、不是當下省成本&lt;/strong>：Atlas 提供 AWS / GCP / Azure 跨雲部署。Forbes 選 GCP 是當下決策、但 Atlas 的跨雲能力讓未來雲商選型不再綁定特定 vendor。這跟 DynamoDB（AWS only）、Cosmos DB（Azure only）、Spanner（GCP only）的單雲鎖定形成對照。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 vendor lock-in 評估。&lt;/li>
&lt;li>&lt;strong>Build 時間 25 → 9 分鐘 = 開發者效率改善、不是 DB 性能改善&lt;/strong>：Build 時間下降主因是 ephemeral test environment 用 Atlas API spin-up、不是 MongoDB query 變快。CMS 系統的 production read latency Atlas 跟自管 MongoDB 差距通常在 ±20% 內、真正贏的是「開發 / 部署 cycle 變短」。讀案例時要區分「開發者體驗 metric」跟「production 性能 metric」、兩者改善的杠桿完全不同。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「從自管 MongoDB 遷到 Atlas managed」這條路徑的工程與成本對照。Forbes 自 2011 年起用 MongoDB 重寫 CMS、2020 年把 production 遷到 Atlas on Google Cloud、保留同一個 document model、轉移 DBA 責任跟跨雲彈性。跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> 的「跨 DB 種類遷移」對照 — Forbes 是 <em>同 DB、換託管模式</em>、不需要重寫 schema 跟 access pattern。</p>
<h2 id="觀察">觀察</h2>
<p>Forbes 遷移到 MongoDB Atlas on Google Cloud 的關鍵數字（引自 <a href="https://cloud.google.com/blog/products/databases/forbes-migrates-to-mongodb-atlas-on-google-cloud">Google Cloud Blog</a> 與 <a href="https://www.mongodb.com/solutions/customer-case-studies/forbes">MongoDB customer case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單月不重複訪客</td>
          <td>120M（2020 年 5 月）</td>
      </tr>
      <tr>
          <td>Build 時間</td>
          <td>25 分鐘 → 9 分鐘（-64%）</td>
      </tr>
      <tr>
          <td>Release 頻率提升</td>
          <td>2x – 10x</td>
      </tr>
      <tr>
          <td>微服務數量</td>
          <td>50+（GKE 上）</td>
      </tr>
      <tr>
          <td>遷移耗時</td>
          <td>6 個月</td>
      </tr>
      <tr>
          <td>DB 總體擁有成本降幅</td>
          <td>-25%</td>
      </tr>
      <tr>
          <td>電子報訂閱量</td>
          <td>+92%（2020 全年）</td>
      </tr>
      <tr>
          <td>Atlas 可用 region</td>
          <td>70+（跨 AWS / GCP / Azure）</td>
      </tr>
      <tr>
          <td>CMS MongoDB 起用年</td>
          <td>2011（首版 CMS 兩個月內交付）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：MongoDB Atlas（managed document DB）、Google Cloud Platform（基礎設施）、Google Kubernetes Engine（50+ 微服務編排）、Google App Engine（部分 serverless 應用）、自建中介 abstraction layer（API 隔離 schema 變動）。</p>
<p>關鍵負載形狀：「文章 publish 後突然爆量」是新聞媒體常態 — 熱門報導、人物專訪、財經事件都會在分鐘內把單篇文章拉到百萬讀者。這跟 <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</a> 的「賽事時段預期峰值」不同、Forbes 的爆量是事件驅動、難以精確預測、需要 Atlas auto-scaling 撐住臨時讀爆。</p>
<h2 id="判讀">判讀</h2>
<p>Forbes 的遷移選擇揭露三個「自管 → managed」路徑的判讀重點。</p>
<ol>
<li><strong>同 DB 換託管模式比換 DB 種類風險低、但 ROI 也較窄</strong>：Forbes 6 個月完成遷移、保留同 document model、schema 不動、application 改動只在 connection string 跟運維邊界。這跟 <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato 從 TiDB 遷到 DynamoDB</a> 對照、後者要重新設計 access pattern、ROI 大但風險高。對應 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 的 schema migration playbook：「換 DB」跟「換託管」是兩個不同議題、不要混為一談。</li>
<li><strong>跨雲彈性的價值在規避未來鎖定、不是當下省成本</strong>：Atlas 提供 AWS / GCP / Azure 跨雲部署。Forbes 選 GCP 是當下決策、但 Atlas 的跨雲能力讓未來雲商選型不再綁定特定 vendor。這跟 DynamoDB（AWS only）、Cosmos DB（Azure only）、Spanner（GCP only）的單雲鎖定形成對照。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 vendor lock-in 評估。</li>
<li><strong>Build 時間 25 → 9 分鐘 = 開發者效率改善、不是 DB 性能改善</strong>：Build 時間下降主因是 ephemeral test environment 用 Atlas API spin-up、不是 MongoDB query 變快。CMS 系統的 production read latency Atlas 跟自管 MongoDB 差距通常在 ±20% 內、真正贏的是「開發 / 部署 cycle 變短」。讀案例時要區分「開發者體驗 metric」跟「production 性能 metric」、兩者改善的杠桿完全不同。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「25% TCO 降幅」是 <em>特定流量規模下</em> 的數字。Atlas managed 服務在小流量時 cost-per-GB 比自管低（不用養 DBA），但流量增長到一定規模後 self-hosted 反而便宜。Forbes 在 120M MAU 規模下選 managed 是合理判斷、但這個結論不是普適的。</li>
<li>「Build 25 → 9 分鐘」混合了「MongoDB Atlas API」、「GKE optimization」、「GCP CI/CD」三個變因。把全部歸功於 MongoDB Atlas 會誇大效益。</li>
<li>中介 abstraction layer 是 Forbes 主動加的設計、不是 Atlas 自帶。沒有這層 abstraction、schema 變動仍會直接打穿到所有 microservice、跨雲彈性也用不起來。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>自管 → managed 的遷移要先做 schema 跟 access pattern 盤點</strong>：確認沒有自管時的特殊 hack（自訂 plugin、特殊 storage engine、客製 oplog 處理）— 這些在 managed 服務上通常不支援。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>。</li>
<li><strong>微服務 + abstraction layer 隔離 schema 變動</strong>：document database 的 schema flexibility 容易讓 production 出現 data inconsistency。中介 API 層把 schema 變動限制在 DB 邊界、microservice 看到的是穩定 API。對應 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor 的 schema governance 段</a>。</li>
<li><strong>跨雲 managed 服務比單雲服務更適合長期不確定的雲商策略</strong>：Atlas（跨 AWS / GCP / Azure）vs DynamoDB / Cosmos DB / Spanner（單雲）的取捨。當雲商選擇尚未底定、跨雲服務的選項保留價值高。對應 <a href="/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB vendor page</a> 跟 <a href="/blog/backend/01-database/vendors/cosmosdb/" data-link-title="Azure Cosmos DB" data-link-desc="全球分散式 multi-model DB、5 個 consistency levels、Microsoft 自家 dogfood 證據">Cosmos DB vendor page</a> 對比。</li>
<li><strong>遷移時間表跟團隊規模耦合</strong>：Forbes 6 個月完成、團隊規模未揭露但顯然是中型團隊 + 多個 squad 並行。1-2 人團隊做同類遷移通常要 12+ 個月。對應 <a href="/blog/backend/01-database/large-scale-db-migration/" data-link-title="1.12 大規模 DB 遷移實戰" data-link-desc="跨 DB 遷移的 dual-write、[shadow read](/backend/knowledge-cards/shadow-read/)、cutover、rollback 流程 — 從實戰案例提煉的工程做法">01.12 大規模 DB 遷移實戰</a> 的時間估計。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>自管 MongoDB → MongoDB Atlas（同 DB、換託管）：Forbes、SEGA HARDlight 路徑</li>
<li>自管 MongoDB → DocumentDB（AWS 自研、API 部分相容）：較多應用層改動、跨雲彈性失去</li>
<li>自管 MongoDB → Cosmos DB MongoDB API（Azure）：<a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a> 路徑、有 RU 模型差異</li>
<li>自管 PostgreSQL → Aurora / Cloud SQL：對等遷移、但 RDB 跟 document DB 的 schema 治理議題不同</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 MongoDB 遷移到 Atlas → <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> + <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想評估跨雲 vs 單雲 DB 取捨 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/01-database/vendors/dynamodb/" data-link-title="DynamoDB" data-link-desc="AWS managed key-value、partition-based scaling、9000 萬 RPS sustained 實戰證據">DynamoDB vendor page</a> 對比段</li>
<li>想做 microservice + abstraction layer 設計 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a></li>
<li>想對照同類遷移 → <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a>（遷到 Cosmos DB MongoDB API）/ <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a>（換 DB 種類）</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/databases/forbes-migrates-to-mongodb-atlas-on-google-cloud">Forbes migrates from self-managed MongoDB to MongoDB Atlas on Google Cloud</a></li>
<li><a href="https://www.mongodb.com/solutions/customer-case-studies/forbes">New CMS and developer environment cuts build times to just nine minutes for Forbes</a></li>
<li><a href="https://www.mongodb.com/resources/solutions/use-cases/forbes-cloud-migration-helps-worlds-biggest-media-brand-continue-standard-digital-innovation">Forbes：MongoDB Cloud Migration Helps World&rsquo;s Biggest Media Brand</a></li>
</ul>
]]></content:encoded></item><item><title>9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/</guid><description>&lt;p>這個案例的核心責任是說明「IoT / telematics 高頻 sensor 寫入」如何套在 document model 上、以及 MongoDB Atlas 在 mission-critical（生命安全）服務中的角色。Toyota Connected 把車輛 sensor、緊急通報（SOS / 撞擊偵測）、駕駛資料都寫進 20 個 MongoDB Atlas database、用 event-driven microservice 處理。跟 &lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5 Amazon Ads DynamoDB&lt;/a> 對照 — Amazon Ads 用 KV 撐極高吞吐、Toyota 用 document model 撐「形狀變化頻繁的 sensor signal」、兩條路徑反映不同的工作負載決策。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Toyota Connected 平台關鍵數字（引自 &lt;a href="https://aws.amazon.com/solutions/case-studies/toyota-connected/">AWS case study&lt;/a> 與 &lt;a href="https://www.mongodb.com/solutions/customer-case-studies/toyota-connected">MongoDB customer case study&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>服務涵蓋車輛數&lt;/td>
 &lt;td>9M+（Toyota / Lexus 北美 Safety Connect）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>每月平台 transaction&lt;/td>
 &lt;td>18 Billion&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量擴展能力&lt;/td>
 &lt;td>18x usual 流量&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>緊急訊號處理延遲&lt;/td>
 &lt;td>3 秒內到 safety agent&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可用性目標&lt;/td>
 &lt;td>99.99%（target、實測 99% 月達成）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>MongoDB Atlas DB 數&lt;/td>
 &lt;td>20&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>AWS 用量成長&lt;/td>
 &lt;td>3x（自 2018 啟動以來）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>自管成本降幅&lt;/td>
 &lt;td>70-80%（serverless 架構整體）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>車載 sensor 種類&lt;/td>
 &lt;td>數百個（occupant、seatbelt、fuel、air quality）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：MongoDB Atlas（document store，20 databases）、AWS Lambda（serverless 處理事件）、Amazon Kinesis Data Streams（即時資料攝取）、CloudAMQP（非同步訊息）、Redis（hot cache）、Kubernetes（microservice 編排）。&lt;/p>
&lt;p>關鍵負載形狀：「車輛 sensor 持續低頻 + 緊急事件高優先低延遲」雙模式並存。&lt;/p>
&lt;ul>
&lt;li>&lt;em>持續模式&lt;/em>：900 萬車輛、每車數百 sensor、定期上報遙測資料。這是「sustained-growth + 高 throughput」的形狀、document model 比 wide-column 更適合 — 因為不同車型 / 不同年份的 sensor schema 不一樣、document 自然演進、不需要每加 sensor 就 ALTER TABLE。&lt;/li>
&lt;li>&lt;em>緊急模式&lt;/em>：SOS 按鈕、自動撞擊通報、車輛安全異常。這是 &lt;em>life-critical low-latency&lt;/em> — 3 秒內 sensor 訊號要從車輛到 agent 螢幕、含網路傳輸、event routing、microservice 處理、agent UI rendering。這個 budget 倒推回 MongoDB 寫入要求是 sub-100ms。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Toyota Connected 的 MongoDB 選擇揭露三個 IoT / telematics 工程決策的判讀重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>document model 適合「sensor schema 隨產品演進」的場景&lt;/strong>：車載 sensor 種類隨車型、年份、地區規範變化。RDB 走「每加 sensor 加 column」會讓 schema migration 變成發行週期的卡點；document model 走「polymorphic document」、新 sensor 只是新欄位、舊文件不需要 backfill。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page&lt;/a> 的 document shape 教學段。但這個彈性的成本是：production 必須做 schema governance（validation、版本欄位、application 層相容處理），否則「schema 自由」會變「production data inconsistency」。&lt;/li>
&lt;li>&lt;strong>20 個 Atlas database 不是技術上限、是業務邊界切分&lt;/strong>：18 Billion transactions / 月 ÷ 30 天 ÷ 86400 秒 ≈ 7K transactions / sec。這個數字單一 MongoDB cluster 可以撐、不需要 20 個 DB。Toyota 切 20 個 DB 是按 &lt;em>microservice ownership&lt;/em> 跟 &lt;em>blast radius&lt;/em> — 每個 microservice 擁有自己的 DB、單一 DB 故障不會影響其他服務。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程&lt;/a>、把「總吞吐」拆成「per-DB 邊界」。&lt;/li>
&lt;li>&lt;strong>99.99% target vs 99% 實測差距揭露 telematics 的可用性挑戰&lt;/strong>：99.99% 是 4 分鐘 / 月停機、99% 是 7.2 小時 / 月停機。差兩個 9 不是 MongoDB 自身可用性問題、是 &lt;em>end-to-end&lt;/em> 鏈路問題 — 車輛無線網路、cellular tower、AWS network、event bus、microservice、Atlas cluster 任一環節掉都會打掉可用性。MongoDB Atlas 自身的 SLA 通常是 99.95%、達到 99.99% 必須 multi-region + 跨雲冗餘。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &amp;#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys 99.999%&lt;/a> 的多 region active-active 設計。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「IoT / telematics 高頻 sensor 寫入」如何套在 document model 上、以及 MongoDB Atlas 在 mission-critical（生命安全）服務中的角色。Toyota Connected 把車輛 sensor、緊急通報（SOS / 撞擊偵測）、駕駛資料都寫進 20 個 MongoDB Atlas database、用 event-driven microservice 處理。跟 <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> 對照 — Amazon Ads 用 KV 撐極高吞吐、Toyota 用 document model 撐「形狀變化頻繁的 sensor signal」、兩條路徑反映不同的工作負載決策。</p>
<h2 id="觀察">觀察</h2>
<p>Toyota Connected 平台關鍵數字（引自 <a href="https://aws.amazon.com/solutions/case-studies/toyota-connected/">AWS case study</a> 與 <a href="https://www.mongodb.com/solutions/customer-case-studies/toyota-connected">MongoDB customer case study</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>服務涵蓋車輛數</td>
          <td>9M+（Toyota / Lexus 北美 Safety Connect）</td>
      </tr>
      <tr>
          <td>每月平台 transaction</td>
          <td>18 Billion</td>
      </tr>
      <tr>
          <td>流量擴展能力</td>
          <td>18x usual 流量</td>
      </tr>
      <tr>
          <td>緊急訊號處理延遲</td>
          <td>3 秒內到 safety agent</td>
      </tr>
      <tr>
          <td>可用性目標</td>
          <td>99.99%（target、實測 99% 月達成）</td>
      </tr>
      <tr>
          <td>MongoDB Atlas DB 數</td>
          <td>20</td>
      </tr>
      <tr>
          <td>AWS 用量成長</td>
          <td>3x（自 2018 啟動以來）</td>
      </tr>
      <tr>
          <td>自管成本降幅</td>
          <td>70-80%（serverless 架構整體）</td>
      </tr>
      <tr>
          <td>車載 sensor 種類</td>
          <td>數百個（occupant、seatbelt、fuel、air quality）</td>
      </tr>
  </tbody>
</table>
<p>服務組合：MongoDB Atlas（document store，20 databases）、AWS Lambda（serverless 處理事件）、Amazon Kinesis Data Streams（即時資料攝取）、CloudAMQP（非同步訊息）、Redis（hot cache）、Kubernetes（microservice 編排）。</p>
<p>關鍵負載形狀：「車輛 sensor 持續低頻 + 緊急事件高優先低延遲」雙模式並存。</p>
<ul>
<li><em>持續模式</em>：900 萬車輛、每車數百 sensor、定期上報遙測資料。這是「sustained-growth + 高 throughput」的形狀、document model 比 wide-column 更適合 — 因為不同車型 / 不同年份的 sensor schema 不一樣、document 自然演進、不需要每加 sensor 就 ALTER TABLE。</li>
<li><em>緊急模式</em>：SOS 按鈕、自動撞擊通報、車輛安全異常。這是 <em>life-critical low-latency</em> — 3 秒內 sensor 訊號要從車輛到 agent 螢幕、含網路傳輸、event routing、microservice 處理、agent UI rendering。這個 budget 倒推回 MongoDB 寫入要求是 sub-100ms。</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>Toyota Connected 的 MongoDB 選擇揭露三個 IoT / telematics 工程決策的判讀重點。</p>
<ol>
<li><strong>document model 適合「sensor schema 隨產品演進」的場景</strong>：車載 sensor 種類隨車型、年份、地區規範變化。RDB 走「每加 sensor 加 column」會讓 schema migration 變成發行週期的卡點；document model 走「polymorphic document」、新 sensor 只是新欄位、舊文件不需要 backfill。對應 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> 的 document shape 教學段。但這個彈性的成本是：production 必須做 schema governance（validation、版本欄位、application 層相容處理），否則「schema 自由」會變「production data inconsistency」。</li>
<li><strong>20 個 Atlas database 不是技術上限、是業務邊界切分</strong>：18 Billion transactions / 月 ÷ 30 天 ÷ 86400 秒 ≈ 7K transactions / sec。這個數字單一 MongoDB cluster 可以撐、不需要 20 個 DB。Toyota 切 20 個 DB 是按 <em>microservice ownership</em> 跟 <em>blast radius</em> — 每個 microservice 擁有自己的 DB、單一 DB 故障不會影響其他服務。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>、把「總吞吐」拆成「per-DB 邊界」。</li>
<li><strong>99.99% target vs 99% 實測差距揭露 telematics 的可用性挑戰</strong>：99.99% 是 4 分鐘 / 月停機、99% 是 7.2 小時 / 月停機。差兩個 9 不是 MongoDB 自身可用性問題、是 <em>end-to-end</em> 鏈路問題 — 車輛無線網路、cellular tower、AWS network、event bus、microservice、Atlas cluster 任一環節掉都會打掉可用性。MongoDB Atlas 自身的 SLA 通常是 99.95%、達到 99.99% 必須 multi-region + 跨雲冗餘。對應 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys 99.999%</a> 的多 region active-active 設計。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>「18 Billion transactions / 月」是 <em>平台所有服務</em> 加總、不是 MongoDB 單一 cluster 數字。MongoDB 只承擔其中需要 document storage 的部分、其他走 Lambda 直接處理或寫到 Kinesis。</li>
<li>「3 秒延遲到 agent」包含車載、無線、雲端、UI、agent 操作多個環節。MongoDB 在這個延遲鏈裡通常分到 100-500ms 預算、不是整個 3 秒。</li>
<li>MongoDB 6.0+ 有 time series collection 對 IoT 寫入有專屬優化。Toyota 揭露的 20 個 DB 沒明確說有沒有用 time series collection — 對 IoT 案例這是重要區分、但 case study 沒揭露。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>IoT 高頻 sensor 寫入考慮 MongoDB time series collection（6.0+）</strong>：比 regular collection 寫入吞吐高 3-5x、storage 壓縮率更好。專為 timestamp + metadata + measurement 三段式資料優化。對應 <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> 的容量規劃要點段。</li>
<li><strong>mission-critical IoT 系統要做 multi-region 跟多供應商備援</strong>：99.99% 不能只靠 MongoDB Atlas 本身、要靠 region 冗餘 + 多條 cellular network + 多個 event bus 路徑。對應 <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a> 的 multi-region active-active。</li>
<li><strong>按 microservice ownership 切 MongoDB cluster、不要單一巨型 cluster</strong>：blast radius 邊界 = 業務邊界、不是「能不能撐」的問題。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a>。</li>
<li><strong>event-driven 處理 IoT 資料、不用 request-response</strong>：sensor 寫到 Kinesis / Kafka / event bus、microservice 從 stream 消費、寫進 MongoDB。這條 path 避免「sensor 寫不進去 DB 就 retry storm」的問題。對應 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a>。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>AWS：MongoDB Atlas + Kinesis + Lambda（Toyota 配置）</li>
<li>GCP：MongoDB Atlas on GCP + Pub/Sub + Cloud Functions、或 Firestore + Pub/Sub（document API native）</li>
<li>Azure：Cosmos DB MongoDB API + Event Hubs + Azure Functions</li>
<li>跨雲：MongoDB Atlas 是 IoT 平台保留跨雲彈性的少數選項</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想規劃 IoT / telematics 資料層 → <a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a> + <a href="/blog/backend/01-database/kv-document-capacity-planning/" data-link-title="1.10 KV / Document DB 容量規劃" data-link-desc="DynamoDB / Cosmos DB / Bigtable / MongoDB 等 KV / Document DB 的容量設計、partition key 取捨、capacity mode 選擇">01.10 KV / Document DB 容量規劃</a></li>
<li>想做 multi-region 高可用性 → <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys 99.999%</a></li>
<li>想對照不同 IoT 資料層選擇 → <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>（KV）/ <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</a>（高頻訊息）</li>
<li>想理解 event-driven IoT 架構 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a></li>
<li>想做 IoT 寫入吞吐的 shard key 選型 → <a href="/blog/backend/01-database/vendors/mongodb/shard-key-selection/" data-link-title="MongoDB Shard Key Selection：hashed vs ranged、單 cluster 切 shard vs 多 cluster 切 blast radius" data-link-desc="MongoDB sharded cluster shard key 選型（hashed / ranged / compound）、單 cluster 分 shard vs 多 cluster 分 blast radius 對照、跟 DynamoDB / Cosmos DB partition key 可逆性的跨 vendor 紀律">MongoDB shard key 選型</a></li>
<li>想規劃 telemetry schema design → <a href="/blog/backend/01-database/vendors/mongodb/schema-design-pattern/" data-link-title="MongoDB Schema Design Pattern：contract layer 在哪 vs embedded / reference" data-link-desc="MongoDB document schema 真正的 production 議題不是 embedded vs reference 二選一、是 schema contract 該放 DB 層 validator 還是 app 層 abstraction；含 Toyota polymorphic governance、Forbes abstraction layer、time-series collection 邊界">MongoDB schema design pattern</a></li>
<li>想處理 IoT 高 client 數的 connection storm → <a href="/blog/backend/01-database/vendors/mongodb/connection-management-and-cache-layer/" data-link-title="MongoDB Connection Management and Cache Layer：driver × 部署模型 × cache × predictive scaling" data-link-desc="MongoDB 大規模 OLTP 撞牆不是單一 driver 議題、是 driver × 部署模型 × cache × scaling trigger 三層協作；含 Coinbase mongobetween / freshness token / ML 預測擴容三件套 &#43; 適用範圍紀律">MongoDB connection 管理與 cache 層</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.mongodb.com/solutions/customer-case-studies/toyota-connected">Toyota Connected Aims For At Least 99.99% Availability With MongoDB Assistance</a></li>
<li><a href="https://aws.amazon.com/solutions/case-studies/toyota-connected/">Toyota Connected Reimagines Mobility on AWS</a></li>
<li><a href="https://digitalcxo.com/article/mongodb-aws-help-toyota-connected-move-past-legacy-database-challenges/">MongoDB, AWS Help Toyota Connected Move Past Legacy Database Challenges</a></li>
<li><a href="https://www.just-auto.com/news/toyota-connected-hails-efficiencies-from-migration-of-data-services-to-mongodb-atlas/">Toyota Connected hails efficiencies from migration of data services to MongoDB Atlas</a></li>
<li><a href="https://www.mongodb.com/company/blog/innovation/data-modeling-strategies-connected-vehicle-signal-data-in-mongodb">Data Modeling Strategies For Connected Vehicle Signal Data In MongoDB</a></li>
</ul>
]]></content:encoded></item><item><title>9.C39 DoorDash：Aurora Postgres 寫入瓶頸 → CockroachDB 多主寫入</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/</guid><description>&lt;p>這個案例的核心責任是說明「single-primary OLTP 撞到寫入天花板」如何用 distributed SQL 拆解。跟 &lt;a href="https://tarrragon.github.io/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 流量 &amp;#43;50% 不影響延遲">9.C4 DraftKings&lt;/a> 對比 — DraftKings 在 Aurora 上靠「業務切 200 個獨立 cluster」橫向擴展、DoorDash 是「保留 PostgreSQL wire 介面、但底層換成多主寫入的 CockroachDB」。兩條路徑都在解「Aurora 單主寫入容量上限」、走法不同。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>DoorDash 從 Aurora Postgres 遷到 CockroachDB 的關鍵敘述（引自 &lt;a href="https://www.cockroachlabs.com/blog/aurora-postgres-to-cockroachdb/">Why DoorDash migrated from Aurora Postgres to CockroachDB&lt;/a> / &lt;a href="https://thenewstack.io/how-doordash-migrated-from-aurora-postgres-to-cockroachdb/">The New Stack 報導&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>2020-04-17 高峰 QPS&lt;/td>
 &lt;td>&amp;gt; 1.636 million QPS&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件結果&lt;/td>
 &lt;td>multi-hour outage&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事件背景&lt;/td>
 &lt;td>疫情封鎖、外送需求暴增&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>遷移啟動&lt;/td>
 &lt;td>事件後幾週、先把 table 從主 cluster 拆出&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第一階段移轉量&lt;/td>
 &lt;td>一個月內把 dozens of tables 拆到獨立 Aurora cluster&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>第二階段&lt;/td>
 &lt;td>自動化工具把 Aurora Postgres → CockroachDB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>後續結果&lt;/td>
 &lt;td>跑更多 cluster、incident alert volume 反而下降&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：Aurora Postgres（遷移前主要 OLTP）、CockroachDB self-hosted、自製 table extraction tool、自製 lossless migration pipeline。&lt;/p>
&lt;p>關鍵負載形狀：DoorDash 是 &lt;em>規模化外送平台&lt;/em> — 訂單、Dasher 派遣、餐廳 menu、新業務（grocery / convenience）並存。寫入壓力來自訂單成立、status 變更、地圖位置更新等多種 hot write path。2020 疫情前流量已大、疫情後再翻倍、且高峰集中在週末晚餐 / 週日早午餐時段。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>DoorDash 的工程選擇揭露三個 OLTP 寫入容量設計重點。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Aurora 的「single-primary 寫入」是規模化的天花板&lt;/strong>：Aurora 把 storage 跟 compute 分離、read replica 容易擴、&lt;em>但寫入仍走唯一 primary&lt;/em>。1.636 M QPS 不是均勻分佈、是 hot table 集中寫爆。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取&lt;/a> 的寫入容量規劃。CockroachDB 改成 Raft per range、每個 node 都能服務寫入、容量隨節點線性擴。&lt;/li>
&lt;li>&lt;strong>Migration 工具自製是先決條件、不是 nice-to-have&lt;/strong>：DoorDash 沒「一次性遷整套」、而是先寫工具把 table 從主 cluster 拆到獨立 Aurora cluster（紓壓）、再寫第二套工具把 Aurora → CockroachDB（換引擎）。兩階段都要 &lt;em>lossless&lt;/em> + &lt;em>可回退&lt;/em>。對應 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook&lt;/a> 的「先建工具、再遷資料」原則。&lt;/li>
&lt;li>&lt;strong>Cluster 數量增加、alert volume 卻下降&lt;/strong>：直覺反過來、cluster 多 = 維運面變大、應該更多 alert。但每個 CockroachDB cluster 內建 Raft 自動容錯、單節點 fail 不會 page on-call、Aurora 時代的「primary failover alert」消失。對應 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組&lt;/a> 的「告警 surface 設計」與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06.x reliability&lt;/a> 的 graceful degradation。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：1.636 M QPS 是 &lt;em>主 cluster 峰值&lt;/em>、不是「DoorDash 全部寫入 QPS」。case 沒揭露遷移後單一 CockroachDB cluster 的峰值、只說「跑更多 cluster」。讀案例時不要把這個數字當成「CockroachDB 撐 1.6 M QPS」的證據、它是 &lt;em>Aurora 在那個時間點撞牆的痛點&lt;/em>。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「single-primary OLTP 撞到寫入天花板」如何用 distributed SQL 拆解。跟 <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</a> 對比 — DraftKings 在 Aurora 上靠「業務切 200 個獨立 cluster」橫向擴展、DoorDash 是「保留 PostgreSQL wire 介面、但底層換成多主寫入的 CockroachDB」。兩條路徑都在解「Aurora 單主寫入容量上限」、走法不同。</p>
<h2 id="觀察">觀察</h2>
<p>DoorDash 從 Aurora Postgres 遷到 CockroachDB 的關鍵敘述（引自 <a href="https://www.cockroachlabs.com/blog/aurora-postgres-to-cockroachdb/">Why DoorDash migrated from Aurora Postgres to CockroachDB</a> / <a href="https://thenewstack.io/how-doordash-migrated-from-aurora-postgres-to-cockroachdb/">The New Stack 報導</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2020-04-17 高峰 QPS</td>
          <td>&gt; 1.636 million QPS</td>
      </tr>
      <tr>
          <td>事件結果</td>
          <td>multi-hour outage</td>
      </tr>
      <tr>
          <td>事件背景</td>
          <td>疫情封鎖、外送需求暴增</td>
      </tr>
      <tr>
          <td>遷移啟動</td>
          <td>事件後幾週、先把 table 從主 cluster 拆出</td>
      </tr>
      <tr>
          <td>第一階段移轉量</td>
          <td>一個月內把 dozens of tables 拆到獨立 Aurora cluster</td>
      </tr>
      <tr>
          <td>第二階段</td>
          <td>自動化工具把 Aurora Postgres → CockroachDB</td>
      </tr>
      <tr>
          <td>後續結果</td>
          <td>跑更多 cluster、incident alert volume 反而下降</td>
      </tr>
  </tbody>
</table>
<p>服務組合：Aurora Postgres（遷移前主要 OLTP）、CockroachDB self-hosted、自製 table extraction tool、自製 lossless migration pipeline。</p>
<p>關鍵負載形狀：DoorDash 是 <em>規模化外送平台</em> — 訂單、Dasher 派遣、餐廳 menu、新業務（grocery / convenience）並存。寫入壓力來自訂單成立、status 變更、地圖位置更新等多種 hot write path。2020 疫情前流量已大、疫情後再翻倍、且高峰集中在週末晚餐 / 週日早午餐時段。</p>
<h2 id="判讀">判讀</h2>
<p>DoorDash 的工程選擇揭露三個 OLTP 寫入容量設計重點。</p>
<ol>
<li><strong>Aurora 的「single-primary 寫入」是規模化的天花板</strong>：Aurora 把 storage 跟 compute 分離、read replica 容易擴、<em>但寫入仍走唯一 primary</em>。1.636 M QPS 不是均勻分佈、是 hot table 集中寫爆。對應 <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a> 的寫入容量規劃。CockroachDB 改成 Raft per range、每個 node 都能服務寫入、容量隨節點線性擴。</li>
<li><strong>Migration 工具自製是先決條件、不是 nice-to-have</strong>：DoorDash 沒「一次性遷整套」、而是先寫工具把 table 從主 cluster 拆到獨立 Aurora cluster（紓壓）、再寫第二套工具把 Aurora → CockroachDB（換引擎）。兩階段都要 <em>lossless</em> + <em>可回退</em>。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的「先建工具、再遷資料」原則。</li>
<li><strong>Cluster 數量增加、alert volume 卻下降</strong>：直覺反過來、cluster 多 = 維運面變大、應該更多 alert。但每個 CockroachDB cluster 內建 Raft 自動容錯、單節點 fail 不會 page on-call、Aurora 時代的「primary failover alert」消失。對應 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 的「告警 surface 設計」與 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06.x reliability</a> 的 graceful degradation。</li>
</ol>
<p>需要警惕：1.636 M QPS 是 <em>主 cluster 峰值</em>、不是「DoorDash 全部寫入 QPS」。case 沒揭露遷移後單一 CockroachDB cluster 的峰值、只說「跑更多 cluster」。讀案例時不要把這個數字當成「CockroachDB 撐 1.6 M QPS」的證據、它是 <em>Aurora 在那個時間點撞牆的痛點</em>。</p>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>single-primary 撞牆前、先評估 multi-primary 選項</strong>：Aurora / RDS Postgres 是 single-primary 為主、寫入量持續成長最終會撞天花板。轉折點不是 IOPS、是 <em>primary CPU + WAL flush rate</em>。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的瓶頸辨識。</li>
<li><strong>遷 OLTP 引擎要走「兩階段紓壓」</strong>：先在原引擎內把 hot table 拆出（降低主 cluster 壓力、爭取時間）、再規劃換引擎（架構級改造）。直接「一次性換引擎」風險過高。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a>。</li>
<li><strong>PostgreSQL wire protocol 相容性是降低遷移成本的關鍵</strong>：DoorDash 保留 PostgreSQL driver / ORM、應用層改動小。CockroachDB 不是 PostgreSQL fork、是 <em>protocol-level 相容</em>、實際 SQL 行為（serializable default、retry semantics、partial index）仍要驗證。對應 <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a> 的 PostgreSQL 相容性 audit 段。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>AWS Aurora DSQL（2024）解同類「multi-primary 寫入」問題、但 AWS-only</li>
<li>Spanner（GCP）同類設計、GCP-only</li>
<li>TiDB（MySQL wire）解同類問題、亞洲生態深</li>
<li>自管 PostgreSQL + Citus（sharded extension）走 application 層 sharding、operation burden 較高</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想理解 single-primary 寫入天花板訊號 → <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> + <a href="/blog/backend/01-database/high-concurrency-access/" data-link-title="1.1 高併發下的 SQL 讀寫邊界" data-link-desc="說明高併發服務如何共用資料庫 client、控制 transaction、管理 connection pool、避免資料庫成為瓶頸">01.6 高併發資料存取</a></li>
<li>想規劃 PostgreSQL → CockroachDB migration → <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> + <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a></li>
<li>對照其他 OLTP 規模化案例 → <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>（按業務切 cluster）/ <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a>（DB 種類整合）</li>
<li>想對照其他 distributed SQL 案例 → <a href="/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/" data-link-title="9.C40 Netflix：380&#43; CockroachDB cluster 的 multi-active 拓樸艦隊" data-link-desc="Netflix 把 Cassandra 不夠用的 transactional workload 移到 CockroachDB、380&#43; cluster / 60&#43; 跨 region、含 Open Connect、studio cloud drive、gaming control plane">9.C40 Netflix CockroachDB fleet</a> / <a href="/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/" data-link-title="9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 &#43; 跨州單一邏輯 DB" data-link-desc="Hard Rock Digital 用 CockroachDB 跨 AWS Outposts &#43; US-East-1、Wire Act 強制資料留州、單一邏輯 DB 解多州 sportsbook、100 node 32 vCPU 撐 Super Bowl">9.C41 Hard Rock Digital</a></li>
<li>想理解全球一致性 OLTP 選型 → <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a></li>
<li>想拆 CockroachDB transaction retry 與 contention 模式 → <a href="/blog/backend/01-database/vendors/cockroachdb/transaction-retry-pattern/" data-link-title="CockroachDB Transaction Retry Pattern：serializable default 與 application contract 重塑" data-link-desc="CockroachDB default SERIALIZABLE、application 必須包 retry loop 處理 40001 serialization_failure。本文走 PG → CockroachDB application contract 重塑視角、SAVEPOINT cockroach_restart 語法、5 種失敗模式（retry storm / 非冪等 / cross-statement state / hot row / long-running transaction）。**整篇是跨 case 合成 frame**：DoorDash case 沒揭露 retry pattern、只揭露 PG wire protocol 相容 &#43; SQL 行為仍要 audit、本章 retry contract 重塑屬通用工程議題從 Cockroach Labs 官方 docs 合成">CockroachDB transaction retry pattern</a></li>
<li>想對比 Aurora DSQL / Spanner / CockroachDB 的選型 → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cockroachlabs.com/blog/aurora-postgres-to-cockroachdb/">Why DoorDash migrated from Aurora Postgres to CockroachDB</a></li>
<li><a href="https://thenewstack.io/how-doordash-migrated-from-aurora-postgres-to-cockroachdb/">How DoorDash Migrated from Aurora Postgres to CockroachDB（The New Stack）</a></li>
<li><a href="https://careersatdoordash.com/blog/how-we-scaled-new-verticals-fulfillment-backend-with-cockroachdb/">How We Scaled New Verticals Fulfillment Backend with CockroachDB（DoorDash Engineering Blog）</a></li>
<li><a href="https://www.infoq.com/news/2024/02/doordash-config-cockroachdb/">DoorDash Uses CockroachDB to Create Config Management Platform for Microservices（InfoQ）</a></li>
</ul>
]]></content:encoded></item><item><title>9.C40 Netflix：380+ CockroachDB cluster 的 multi-active 拓樸艦隊</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/</guid><description>&lt;p>這個案例的核心責任是說明「Cassandra 撐不住 transactional 一致性」如何用 distributed SQL 補位。Netflix &lt;em>用 CockroachDB 補 Cassandra 缺的那塊&lt;/em>、全面替換從來不是策略：需要 rich transaction + global secondary index + multi-active 寫入的場景。跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation&lt;/a> 對照 — Aurora 整合的是 OLTP single-region workload、CockroachDB 解的是「跨 region 強一致 + 跨 cluster 高彈性」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Netflix CockroachDB 艦隊的關鍵數字（引自 &lt;a href="https://www.cockroachlabs.com/customers/netflix/">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters&lt;/a> / &lt;a href="https://www.cockroachlabs.com/blog/netflix-at-cockroachdb/">The history of databases at Netflix&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>總 cluster 數&lt;/td>
 &lt;td>380+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production cluster&lt;/td>
 &lt;td>160+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Multi-region cluster&lt;/td>
 &lt;td>60+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>最大單區 cluster&lt;/td>
 &lt;td>60 nodes / 26.5 TB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Gaming 平台 cluster&lt;/td>
 &lt;td>48 nodes、跨 4 個 region&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>首個 prod cluster&lt;/td>
 &lt;td>2020 上線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production cluster&lt;/td>
 &lt;td>2022 已達 100、近年擴至 160+&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>部署拓樸常態&lt;/td>
 &lt;td>多數 single-region、3 個 AZ&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：CockroachDB self-managed（Netflix Database Platform Team 自運維）、跨 AWS region、與 Cassandra / EVCache / RDS 並存（polyglot persistence）。&lt;/p>
&lt;p>關鍵 workload：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Studio Cloud Drive&lt;/strong>：影視製作資產的 file-system 風格服務、需要強一致 metadata + 全球可寫&lt;/li>
&lt;li>&lt;strong>Open Connect 控制平面&lt;/strong>：Netflix 自有 CDN、控制全球網路設備、需要跨 region 一致 control state&lt;/li>
&lt;li>&lt;strong>Spinnaker（持續交付平台）&lt;/strong>：deployment workflow state 需要 transactional 一致&lt;/li>
&lt;li>&lt;strong>Maestro（ML / 資料 workflow orchestration）&lt;/strong>：scheduling 與 state machine 不容許 eventual consistency&lt;/li>
&lt;li>&lt;strong>Gaming control plane&lt;/strong>：metadata 跨 4 region、region failure 不能 downtime&lt;/li>
&lt;/ul>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Netflix CockroachDB 艦隊揭露三個「補 Cassandra 缺口」的 OLTP 工程選擇。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Cassandra 不是 transactional 引擎、補位需求是工程現實&lt;/strong>：Netflix 2014 全面採用 Cassandra 解 global replication、但 &lt;em>lightweight transaction&lt;/em> 跟 unreliable secondary index 在 studio / control plane 等場景出問題。2019 評估後選 CockroachDB 是因為它同時滿足 multi-active topology、global consistent secondary index、global transaction、open source、SQL — 五個條件 Cassandra 在 transactional 場景下湊不齊。對應 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組&lt;/a> 的 polyglot persistence 與 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary&lt;/a>。&lt;/li>
&lt;li>&lt;strong>380+ cluster ≠ 「一個巨型 DB」&lt;/strong>：Netflix 是 &lt;em>artery of small DBs&lt;/em> 模型 — 每個微服務 / 應用配自己的 cluster、cluster sizing 從幾個 node 到 60 nodes 不等。容量規劃變成「每個 cluster 各自規劃」、不是「全公司一個容量曲線」。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora&lt;/a> 的「微服務私有 store」哲學。&lt;/li>
&lt;li>&lt;strong>Multi-region 是「region failure 0 downtime」、不是「更快」&lt;/strong>：Netflix 60+ multi-region cluster 主要動機是 region-level survival、不是降 latency（跨 region quorum 反而會增 latency）。Gaming cluster 48-node 跨 4 region 就是為了「region failover 不停服」、不是讓玩家延遲變低。對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget&lt;/a> 的 latency vs availability 取捨。&lt;/li>
&lt;/ol>
&lt;p>需要警惕：&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「Cassandra 撐不住 transactional 一致性」如何用 distributed SQL 補位。Netflix <em>用 CockroachDB 補 Cassandra 缺的那塊</em>、全面替換從來不是策略：需要 rich transaction + global secondary index + multi-active 寫入的場景。跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora consolidation</a> 對照 — Aurora 整合的是 OLTP single-region workload、CockroachDB 解的是「跨 region 強一致 + 跨 cluster 高彈性」。</p>
<h2 id="觀察">觀察</h2>
<p>Netflix CockroachDB 艦隊的關鍵數字（引自 <a href="https://www.cockroachlabs.com/customers/netflix/">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters</a> / <a href="https://www.cockroachlabs.com/blog/netflix-at-cockroachdb/">The history of databases at Netflix</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>總 cluster 數</td>
          <td>380+</td>
      </tr>
      <tr>
          <td>Production cluster</td>
          <td>160+</td>
      </tr>
      <tr>
          <td>Multi-region cluster</td>
          <td>60+</td>
      </tr>
      <tr>
          <td>最大單區 cluster</td>
          <td>60 nodes / 26.5 TB</td>
      </tr>
      <tr>
          <td>Gaming 平台 cluster</td>
          <td>48 nodes、跨 4 個 region</td>
      </tr>
      <tr>
          <td>首個 prod cluster</td>
          <td>2020 上線</td>
      </tr>
      <tr>
          <td>Production cluster</td>
          <td>2022 已達 100、近年擴至 160+</td>
      </tr>
      <tr>
          <td>部署拓樸常態</td>
          <td>多數 single-region、3 個 AZ</td>
      </tr>
  </tbody>
</table>
<p>服務組合：CockroachDB self-managed（Netflix Database Platform Team 自運維）、跨 AWS region、與 Cassandra / EVCache / RDS 並存（polyglot persistence）。</p>
<p>關鍵 workload：</p>
<ul>
<li><strong>Studio Cloud Drive</strong>：影視製作資產的 file-system 風格服務、需要強一致 metadata + 全球可寫</li>
<li><strong>Open Connect 控制平面</strong>：Netflix 自有 CDN、控制全球網路設備、需要跨 region 一致 control state</li>
<li><strong>Spinnaker（持續交付平台）</strong>：deployment workflow state 需要 transactional 一致</li>
<li><strong>Maestro（ML / 資料 workflow orchestration）</strong>：scheduling 與 state machine 不容許 eventual consistency</li>
<li><strong>Gaming control plane</strong>：metadata 跨 4 region、region failure 不能 downtime</li>
</ul>
<h2 id="判讀">判讀</h2>
<p>Netflix CockroachDB 艦隊揭露三個「補 Cassandra 缺口」的 OLTP 工程選擇。</p>
<ol>
<li><strong>Cassandra 不是 transactional 引擎、補位需求是工程現實</strong>：Netflix 2014 全面採用 Cassandra 解 global replication、但 <em>lightweight transaction</em> 跟 unreliable secondary index 在 studio / control plane 等場景出問題。2019 評估後選 CockroachDB 是因為它同時滿足 multi-active topology、global consistent secondary index、global transaction、open source、SQL — 五個條件 Cassandra 在 transactional 場景下湊不齊。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 polyglot persistence 與 <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a>。</li>
<li><strong>380+ cluster ≠ 「一個巨型 DB」</strong>：Netflix 是 <em>artery of small DBs</em> 模型 — 每個微服務 / 應用配自己的 cluster、cluster sizing 從幾個 node 到 60 nodes 不等。容量規劃變成「每個 cluster 各自規劃」、不是「全公司一個容量曲線」。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 跟 <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> 的「微服務私有 store」哲學。</li>
<li><strong>Multi-region 是「region failure 0 downtime」、不是「更快」</strong>：Netflix 60+ multi-region cluster 主要動機是 region-level survival、不是降 latency（跨 region quorum 反而會增 latency）。Gaming cluster 48-node 跨 4 region 就是為了「region failover 不停服」、不是讓玩家延遲變低。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.12 SLO 與 Performance Budget</a> 的 latency vs availability 取捨。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>case study 沒揭露單一 cluster QPS / latency 具體數字、只揭露 <em>艦隊規模</em> 跟 <em>最大 cluster 容量</em>。讀案例時不要把「380 cluster」直接換算成「Netflix CockroachDB QPS 上限」。</li>
<li>Netflix 是 <em>self-managed</em>、不是 Cockroach Cloud — 需要專屬 Database Platform Team 養 380+ cluster。沒這量級團隊的組織直接 self-host 380 cluster 是 ops 自殺、Cockroach Cloud 才是合理路徑。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>不要試圖一個 DB 撐全部</strong>：Netflix 同時用 Cassandra（高吞吐 eventual）、CockroachDB（transactional + global）、Aurora（單區 ACID）、EVCache（cache）。每種 DB 對應不同 workload 類型、不混用。對應 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 的 polyglot persistence。</li>
<li><strong>每個 cluster 對應一個 application boundary</strong>：避免 multi-tenant 大 cluster、改用「per-app cluster」— 容量規劃顆粒對齊 application、爆掉時 blast radius 限縮在單一 app。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.5 瓶頸定位流程</a> 的 blast radius 設計。</li>
<li><strong>Multi-region 用於 survival、不是 latency 優化</strong>：跨 region quorum 物理上必然增 latency。把 multi-region 動機釐清成 <em>region failure 容忍</em>、不要混淆「跨 region = 更快」。對應 <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> 的 survival goal vs latency budget 取捨。</li>
<li><strong>Self-managed 規模化需要專屬平台團隊</strong>：Netflix 有 Database Platform Team 養 380+ cluster — 包含 backup、upgrade、incident response、capacity review。沒這量級團隊就走 managed service。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本權衡。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>Spanner（GCP）解同類「global transaction + secondary index」、GCP-only</li>
<li>DynamoDB Global Tables 走 eventual consistency、不是 Netflix 想要的 strong consistency</li>
<li>Yugabyte / TiDB 是 distributed SQL 對等候選、生態深度與 PostgreSQL wire 相容度有差</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>想理解 polyglot persistence 選型 → <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> + <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a></li>
<li>想規劃 multi-region survival goal → <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> + <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a></li>
<li>對照其他 distributed SQL 案例 → <a href="/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/" data-link-title="9.C39 DoorDash：Aurora Postgres 寫入瓶頸 → CockroachDB 多主寫入" data-link-desc="DoorDash 從 Aurora Postgres 遷到 CockroachDB、解 1.6 M QPS 單主寫入瓶頸、外送平台爆量壓力下重做 OLTP 拓樸">9.C39 DoorDash</a> / <a href="/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/" data-link-title="9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 &#43; 跨州單一邏輯 DB" data-link-desc="Hard Rock Digital 用 CockroachDB 跨 AWS Outposts &#43; US-East-1、Wire Act 強制資料留州、單一邏輯 DB 解多州 sportsbook、100 node 32 vCPU 撐 Super Bowl">9.C41 Hard Rock Digital</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想理解 transaction vs eventual consistency 邊界 → <a href="/blog/backend/01-database/transaction-boundary/" data-link-title="1.3 Transaction 與一致性邊界" data-link-desc="交易邊界、isolation level、retry 策略、distributed transaction（2PC、Saga）與跨 region 強一致取捨">01.5 transaction boundary</a></li>
<li>想深入 CockroachDB survival goal 與 region failure 取捨 → <a href="/blog/backend/01-database/vendors/cockroachdb/survival-goals/" data-link-title="CockroachDB Survival Goals：zone 級 vs region 級配置與業務 SLO 倒推流程" data-link-desc="CockroachDB 用 SURVIVE ZONE FAILURE / SURVIVE REGION FAILURE 兩種 survival goal 宣告式控制 Raft replica 分佈、決定 RTO / RPO。本文走 Hard Rock Digital bet placement RPO=0 倒推流程、Netflix Gaming 48-node 跨 4 region 「為求 survival 而非 latency」的反直覺判讀、配置語法、寫入 latency 暴漲跟 cost 暴漲兩條失敗模式、合規邊界對比">CockroachDB survival goals</a></li>
<li>想規劃跨 region schema 與資料本地化 → <a href="/blog/backend/01-database/vendors/cockroachdb/locality-aware-schema/" data-link-title="CockroachDB Locality-Aware Schema：跨州合規 &#43; 邏輯一個 cluster 的 region placement 策略" data-link-desc="Hard Rock Digital 跨 8 州 sportsbook、用 AWS Outposts &#43; region placement 把運算釘在州內、邏輯上仍是一個 CockroachDB cluster。本文走 REGIONAL BY ROW / REGIONAL BY TABLE / GLOBAL 三種 locality、Hard Rock 拓樸創新對比 Standard Chartered Aurora 7 cluster fleet、AWS Outposts 是合規工具不是 latency 工具的反直覺判讀">CockroachDB locality-aware schema</a></li>
<li>想對比 Aurora DSQL / Spanner / CockroachDB → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://assets.ctfassets.net/00voh0j35590/7qBPsA0FKKTuAK4JhK27uu/1b30b2015f32878874bd0873a2a54361/CockroachLabs-NETFLIX-Case-Study.pdf">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters（PDF）</a></li>
<li><a href="https://www.cockroachlabs.com/customers/netflix/">Now Streaming: Why Netflix Runs a Fleet of 380+ CockroachDB Clusters（cockroachlabs.com Netflix customer page）</a></li>
<li><a href="https://www.cockroachlabs.com/blog/netflix-at-cockroachdb/">The history of databases at Netflix: From Cassandra to CockroachDB</a></li>
<li><a href="https://www.cockroachlabs.com/blog/netflix-dbaas-roachfest24-recap/">A Netflix RoachFest24 Original: The Case for Multi-Region Clusters</a></li>
<li><a href="https://www.cockroachlabs.com/blog/persistence-as-a-service-at-netflix/">How Netflix engineers choose their tech stack</a></li>
</ul>
]]></content:encoded></item><item><title>9.C41 Hard Rock Digital：CockroachDB on AWS Outposts、Wire Act 合規 + 跨州單一邏輯 DB</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/</link><pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hard-rock-digital-cockroachdb-sports-betting/</guid><description>&lt;p>這個案例的核心責任是說明「合規強制資料留地理邊界 + 想要單一邏輯 DB」如何用 distributed SQL + 邊緣硬體解。跟 &lt;a href="https://tarrragon.github.io/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&lt;/a> 對比 — Standard Chartered 走「Aurora 多 region、each region 一個 cluster」、Hard Rock Digital 走「跨 AWS Outposts + AWS region 一個邏輯 cluster」。兩條都解受監管金融類業務、結構差異反映法規顆粒不同：銀行是國家層級、美國運動博彩是 &lt;em>州&lt;/em> 層級。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Hard Rock Digital sportsbook 部署的關鍵數字（引自 &lt;a href="https://www.cockroachlabs.com/customers/hard-rock-digital/">Hard Rock Digital customer page&lt;/a> / &lt;a href="https://www.cockroachlabs.com/blog/highly-available-sports-betting-app/">How Hard Rock Digital built a highly available and compliant sports betting app&lt;/a>）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>指標&lt;/th>
 &lt;th>數字&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>營運州數&lt;/td>
 &lt;td>8（AZ / IN / TN / FL / OH / IL / NJ / VA）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高峰節點數&lt;/td>
 &lt;td>~100 nodes、each 32 vCPU&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>淡季節點數&lt;/td>
 &lt;td>scales down ~33 nodes（約 1/3）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>基礎設施組合&lt;/td>
 &lt;td>AWS Regions + AWS Local Zones + AWS Outposts（按州合規要求布局）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料庫拓樸&lt;/td>
 &lt;td>跨所有 region 一個 logical database&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Survival goal&lt;/td>
 &lt;td>單一 Outpost 或 AWS AZ 失敗不丟資料&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>顯著測試失敗事件&lt;/td>
 &lt;td>node crash / EC2 instance fail / single state loss — 對使用者無感&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重大事件流量&lt;/td>
 &lt;td>Super Bowl / World Cup 等高峰、無效能退化紀錄&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Engineering 團隊&lt;/td>
 &lt;td>tech team ~50 人；若用 PostgreSQL 估計需多加 10-20 工程師&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務組合：CockroachDB self-managed、AWS US-East-1（共用 control plane）、AWS Outposts（部分州合規要求設備位於州內）、AWS Local Zones（特定都會區延遲補強）。&lt;/p>
&lt;p>關鍵 workload：bet placement、bet settlement、account management、cache loading、sports metadata import。&lt;/p>
&lt;p>關鍵負載形狀：sports betting 是 &lt;em>event-driven peak&lt;/em> — Super Bowl / World Cup 等賽事是已知時間點、流量在開賽前 30-60 分鐘飆升、賽中持續高水位、賽後 settlement 集中爆發。「100 → 33 → 100」的 scale up / down 反映賽季 vs 淡季的容量需求差。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Hard Rock Digital 的工程選擇揭露三個受監管 OLTP 的設計重點。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「合規強制資料留地理邊界 + 想要單一邏輯 DB」如何用 distributed SQL + 邊緣硬體解。跟 <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> 對比 — Standard Chartered 走「Aurora 多 region、each region 一個 cluster」、Hard Rock Digital 走「跨 AWS Outposts + AWS region 一個邏輯 cluster」。兩條都解受監管金融類業務、結構差異反映法規顆粒不同：銀行是國家層級、美國運動博彩是 <em>州</em> 層級。</p>
<h2 id="觀察">觀察</h2>
<p>Hard Rock Digital sportsbook 部署的關鍵數字（引自 <a href="https://www.cockroachlabs.com/customers/hard-rock-digital/">Hard Rock Digital customer page</a> / <a href="https://www.cockroachlabs.com/blog/highly-available-sports-betting-app/">How Hard Rock Digital built a highly available and compliant sports betting app</a>）：</p>
<table>
  <thead>
      <tr>
          <th>指標</th>
          <th>數字</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>營運州數</td>
          <td>8（AZ / IN / TN / FL / OH / IL / NJ / VA）</td>
      </tr>
      <tr>
          <td>高峰節點數</td>
          <td>~100 nodes、each 32 vCPU</td>
      </tr>
      <tr>
          <td>淡季節點數</td>
          <td>scales down ~33 nodes（約 1/3）</td>
      </tr>
      <tr>
          <td>基礎設施組合</td>
          <td>AWS Regions + AWS Local Zones + AWS Outposts（按州合規要求布局）</td>
      </tr>
      <tr>
          <td>資料庫拓樸</td>
          <td>跨所有 region 一個 logical database</td>
      </tr>
      <tr>
          <td>Survival goal</td>
          <td>單一 Outpost 或 AWS AZ 失敗不丟資料</td>
      </tr>
      <tr>
          <td>顯著測試失敗事件</td>
          <td>node crash / EC2 instance fail / single state loss — 對使用者無感</td>
      </tr>
      <tr>
          <td>重大事件流量</td>
          <td>Super Bowl / World Cup 等高峰、無效能退化紀錄</td>
      </tr>
      <tr>
          <td>Engineering 團隊</td>
          <td>tech team ~50 人；若用 PostgreSQL 估計需多加 10-20 工程師</td>
      </tr>
  </tbody>
</table>
<p>服務組合：CockroachDB self-managed、AWS US-East-1（共用 control plane）、AWS Outposts（部分州合規要求設備位於州內）、AWS Local Zones（特定都會區延遲補強）。</p>
<p>關鍵 workload：bet placement、bet settlement、account management、cache loading、sports metadata import。</p>
<p>關鍵負載形狀：sports betting 是 <em>event-driven peak</em> — Super Bowl / World Cup 等賽事是已知時間點、流量在開賽前 30-60 分鐘飆升、賽中持續高水位、賽後 settlement 集中爆發。「100 → 33 → 100」的 scale up / down 反映賽季 vs 淡季的容量需求差。</p>
<h2 id="判讀">判讀</h2>
<p>Hard Rock Digital 的工程選擇揭露三個受監管 OLTP 的設計重點。</p>
<ol>
<li><strong>法規顆粒決定基礎設施拓樸、不是反過來</strong>：美國 Wire Act 要求 <em>betting data 必須在下注州內處理</em>、所以每個營運州都要有州內運算資源。傳統路徑是「每州一個獨立 silo」— 但 silo 之間的玩家統一帳戶、跨州 reporting、欺詐偵測會撞牆。Hard Rock Digital 用 AWS Outposts 把運算放進州內、但邏輯上仍是 <em>一個</em> CockroachDB cluster — region placement 配置決定哪些 range 釘在哪個 Outpost、合規與單一邏輯 DB 同時成立。對應 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a> 的合規 boundary 設計與 <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> 的 region placement。</li>
<li><strong>Survival goal 「Outpost 或 AZ 失敗不丟」對應業務 SLO</strong>：sports betting 中 <em>bet placement</em> 不能 lose — 玩家下注後系統 crash 沒紀錄、對博彩牌照是合規事故。CockroachDB Raft 3-replica + 跨 AZ 配置讓 Outpost 失敗時其他 replica 還在、自動 failover。對應 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">06 reliability</a> 的 RPO=0 設計與 <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a> 的 Survival Goals。</li>
<li><strong>Scale up / down 是賽季常態、不是異常事件</strong>：100 → 33 → 100 的擺盪在 sportsbook 業務是 <em>年度循環</em> — NFL 季結束 / NBA 季初切換、流量結構性下降。CockroachDB 加減節點靠 range rebalance、不停服。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</a> 的 seasonality 與 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.11 高峰事件準備</a> 的 event-driven scaling。</li>
</ol>
<p>需要警惕：</p>
<ul>
<li>case study 沒揭露 QPS、p99 latency 具體數字。100 node × 32 vCPU 是硬體規模、不是 throughput。讀案例時要區分 <em>容量 sizing</em>（節點數）跟 <em>workload throughput</em>（每秒處理量）。</li>
<li>「省了 10-20 工程師」是 <em>估計差距</em>、不是已 hire 後解雇。對應的是「沒選 PostgreSQL 所以沒招那麼多 DBA」、是機會成本不是節省支出。</li>
<li>Wire Act 是 <em>美國聯邦法</em>、各州還有獨立法規（NJ DGE、NV NGC 等）。Hard Rock Digital 模型適合 <em>跨州</em> 合規、不是 <em>跨國</em> — 跨國牌照差異更大、不能直接套。</li>
</ul>
<h2 id="策略">策略</h2>
<p>可重用的工程做法：</p>
<ol>
<li><strong>合規 boundary 用 region placement 表達、不是 cluster fragmentation</strong>：當法規要求資料留某地理邊界、優先看 distributed SQL 的 region placement / pin-to-region 能力、不要直接開獨立 cluster。獨立 cluster 解了合規但破壞了業務邏輯（跨州統一帳戶、欺詐偵測、reporting）。對應 <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a> 的 multi-region table 與 <a href="/blog/backend/01-database/vendors/spanner/" data-link-title="Google Cloud Spanner" data-link-desc="全球分散式 strong-consistency OLTP、TrueTime API、線性擴展到 10 億 req/sec">Spanner vendor</a> 的 placement。</li>
<li><strong>邊緣硬體（AWS Outposts / Local Zones）是合規工具、不是 latency 工具</strong>：Outposts 主要為「資料留某地理邊界」而存在、latency 改善是副作用。決策時先看合規驅動力、latency 改善列為 bonus。對應 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 的 hybrid cloud 設計。</li>
<li><strong>賽季型擴縮容寫進 baseline 容量模型</strong>：Hard Rock Digital 100 ↔ 33 的擺盪不是「臨時 scale up」、是計畫內年度循環。容量規劃要直接把 NFL / NBA / 國際賽事曆塞進預測模型、不要當 surprise。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃模型</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 體育博彩 AI 預測</a>。</li>
<li><strong>distributed SQL 的 ops 槓桿：team 小、cluster 大</strong>：Hard Rock Digital 50 人 tech team 養全部運維、估省了 10-20 個 DBA。distributed SQL 把「DBA 養單區、跨區 sync 養運維」的工作量壓進 <em>系統內建</em> 的 Raft / placement、人月支出降。對應 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.7 成本邊界與 efficiency</a> 的人力成本工程化。</li>
</ol>
<p>跨平台等效：</p>
<ul>
<li>Spanner（GCP）也支援 region placement、但 GCP-only、無 Outposts 等效</li>
<li>Aurora DSQL（AWS 2024）支援跨 region 強一致、但 Outpost 部署現階段未完整覆蓋</li>
<li>自管 PostgreSQL + application 層 sharding：理論可行、operation burden 跟人力需求大幅上升、Hard Rock Digital 評估後選 CockroachDB 的主因之一</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>對照其他受監管金融 / 博彩 OLTP → <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>（銀行國家層級）/ <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</a>（fantasy sports）</li>
<li>對照 event-driven peak 設計 → <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> / <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</a></li>
<li>想規劃 multi-region OLTP survival goal → <a href="/blog/backend/01-database/global-distributed-oltp/" data-link-title="1.11 全球分散式 OLTP" data-link-desc="Spanner / Aurora DSQL / Cosmos DB multi-region write / CockroachDB / TiDB 的全球一致性取捨">1.11 全球分散式 OLTP</a> + <a href="/blog/backend/01-database/vendors/cockroachdb/" data-link-title="CockroachDB" data-link-desc="分散式 SQL、PostgreSQL 相容、跨區強一致、Spanner 的開源 / 跨雲替代">CockroachDB vendor</a></li>
<li>對照其他 distributed SQL 案例 → <a href="/blog/backend/09-performance-capacity/cases/doordash-cockroachdb-orders-platform/" data-link-title="9.C39 DoorDash：Aurora Postgres 寫入瓶頸 → CockroachDB 多主寫入" data-link-desc="DoorDash 從 Aurora Postgres 遷到 CockroachDB、解 1.6 M QPS 單主寫入瓶頸、外送平台爆量壓力下重做 OLTP 拓樸">9.C39 DoorDash</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-cockroachdb-multi-region-fleet/" data-link-title="9.C40 Netflix：380&#43; CockroachDB cluster 的 multi-active 拓樸艦隊" data-link-desc="Netflix 把 Cassandra 不夠用的 transactional workload 移到 CockroachDB、380&#43; cluster / 60&#43; 跨 region、含 Open Connect、studio cloud drive、gaming control plane">9.C40 Netflix</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a></li>
<li>想理解合規驅動的拓樸設計 → <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> + <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">01.4 database migration playbook</a></li>
<li>想拆 CockroachDB survival goal 與合規拓樸對齊 → <a href="/blog/backend/01-database/vendors/cockroachdb/survival-goals/" data-link-title="CockroachDB Survival Goals：zone 級 vs region 級配置與業務 SLO 倒推流程" data-link-desc="CockroachDB 用 SURVIVE ZONE FAILURE / SURVIVE REGION FAILURE 兩種 survival goal 宣告式控制 Raft replica 分佈、決定 RTO / RPO。本文走 Hard Rock Digital bet placement RPO=0 倒推流程、Netflix Gaming 48-node 跨 4 region 「為求 survival 而非 latency」的反直覺判讀、配置語法、寫入 latency 暴漲跟 cost 暴漲兩條失敗模式、合規邊界對比">CockroachDB survival goals</a></li>
<li>想做 region pinning 與在地化 schema → <a href="/blog/backend/01-database/vendors/cockroachdb/locality-aware-schema/" data-link-title="CockroachDB Locality-Aware Schema：跨州合規 &#43; 邏輯一個 cluster 的 region placement 策略" data-link-desc="Hard Rock Digital 跨 8 州 sportsbook、用 AWS Outposts &#43; region placement 把運算釘在州內、邏輯上仍是一個 CockroachDB cluster。本文走 REGIONAL BY ROW / REGIONAL BY TABLE / GLOBAL 三種 locality、Hard Rock 拓樸創新對比 Standard Chartered Aurora 7 cluster fleet、AWS Outposts 是合規工具不是 latency 工具的反直覺判讀">CockroachDB locality-aware schema</a></li>
<li>想對比 Aurora DSQL / Spanner / CockroachDB 給博彩 OLTP → <a href="/blog/backend/01-database/vendors/cockroachdb/aurora-dsql-spanner-decision-tree/" data-link-title="CockroachDB vs Aurora DSQL vs Spanner：撞牆訊號分型 &#43; 七問題決策樹" data-link-desc="Distributed SQL 三選一決策樹。先用撞牆訊號分型識別 driver path（DoorDash 單主寫入撞牆 / Netflix Cassandra 缺口 / Hard Rock 合規驅動）、再走七問題（跨雲 / 雲商生態 / 風險預算 / PG 相容 / 管理負擔 / team size / vendor sizing barrier）。PostgreSQL 相容性 audit checklist 4 項、Spanner 100 pu sizing barrier、Hard Rock 「省 10-20 工程師」機會成本警示、Netflix Database Platform Team 規模">Aurora DSQL / Spanner / CockroachDB 決策樹</a></li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cockroachlabs.com/customers/hard-rock-digital/">Hard Rock Digital: scaling a performant sports betting platform（cockroachlabs.com customer page）</a></li>
<li><a href="https://downloads.ctfassets.net/00voh0j35590/7dKNWhsW4RjpUlFgzHB8qw/752a22c833c879bca503bbffb2b584c7/CockroachLabs-Hard-Rock-Digital-Case-Study-v2.pdf">Hard Rock, anytime, anywhere: scaling a performant sports betting platform（PDF case study）</a></li>
<li><a href="https://www.cockroachlabs.com/blog/highly-available-sports-betting-app/">How Hard Rock Digital built a highly available and compliant sports betting app</a></li>
<li><a href="https://www.cockroachlabs.com/blog/real-money-gaming-reference-architecture/">Building a sports betting application to handle &lsquo;Big Game&rsquo; traffic</a></li>
<li><a href="https://www.cockroachlabs.com/solutions/verticals/gambling/">CockroachDB for Gambling solutions page</a></li>
</ul>
]]></content:encoded></item><item><title>Build-time 預處理 vs Runtime 計算的光譜：何時把成本前置</title><link>https://tarrragon.github.io/blog/report/build-time-vs-runtime-computation-spectrum/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/build-time-vs-runtime-computation-spectrum/</guid><description>&lt;h2 id="結論">結論&lt;/h2>
&lt;p>計算放哪裡有光譜、不是二元：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>位置&lt;/th>
 &lt;th>預付成本&lt;/th>
 &lt;th>Runtime 成本&lt;/th>
 &lt;th>儲存成本&lt;/th>
 &lt;th>Freshness&lt;/th>
 &lt;th>適合&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Pure build-time&lt;/strong>&lt;/td>
 &lt;td>高（pipeline + 一次計算全部）&lt;/td>
 &lt;td>~0&lt;/td>
 &lt;td>高（存 N 種預算結果）&lt;/td>
 &lt;td>差（每次 build 才 refresh）&lt;/td>
 &lt;td>高頻 query、少變動、closed-set&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Hybrid hot-path&lt;/strong>&lt;/td>
 &lt;td>中（預算 top X%）&lt;/td>
 &lt;td>低（hot 命中 0、cold runtime）&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>中（hot stale 風險）&lt;/td>
 &lt;td>長尾分布、可分 hot/cold&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Pure runtime&lt;/strong>&lt;/td>
 &lt;td>0&lt;/td>
 &lt;td>高（per query）&lt;/td>
 &lt;td>0&lt;/td>
 &lt;td>即時&lt;/td>
 &lt;td>低頻、高變動、open-ended query&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>光譜兩端都有合理場景、不是「build-time 永遠贏」。&lt;strong>選哪個位置依四軸：query 頻率 / dataset 大小 / freshness 需求 / build pipeline 複雜度&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="build-time-的三個成本維度">build-time 的三個成本維度&lt;/h2>
&lt;p>直覺反應：「能 precompute 就 precompute、runtime 0 最好」。但這個直覺漏了三個維度：&lt;/p>
&lt;h3 id="1-freshness-成本">1. Freshness 成本&lt;/h3>
&lt;p>Build-time 結果是 build 那一刻的 snapshot。dataset 改變後、結果直到下次 build 才 refresh。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>適合 build-time&lt;/strong>：靜態 / 慢變的內容（blog post、product catalog）&lt;/li>
&lt;li>&lt;strong>不適合 build-time&lt;/strong>：頻繁更新（user posts、live data、search index over user content）&lt;/li>
&lt;/ul>
&lt;h3 id="2-儲存成本">2. 儲存成本&lt;/h3>
&lt;p>Precompute N 種 query 的結果 = 存 N 份。當 query 是 open-ended（任意組合 filter / sort / search term）、N 是組合爆炸。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>適合 build-time&lt;/strong>：closed-set（fixed list of routes、pre-defined search terms）&lt;/li>
&lt;li>&lt;strong>不適合 build-time&lt;/strong>：open-ended（任意 user input）&lt;/li>
&lt;/ul>
&lt;h3 id="3-pipeline-複雜度">3. Pipeline 複雜度&lt;/h3>
&lt;p>Build-time 計算需要 build pipeline 配合 — 加一條規則 = 加一份 artifact、需要 CI 跑、版本管理、deployment 同步。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>適合 build-time&lt;/strong>：已有 build pipeline、加一條規則便宜&lt;/li>
&lt;li>&lt;strong>不適合 build-time&lt;/strong>：純 dynamic system、加 build step = 引入新 infrastructure&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="四軸判準">四軸判準&lt;/h2>
&lt;p>評估某個計算該放哪一端：&lt;/p>
&lt;h3 id="軸-1query-頻率">軸 1：Query 頻率&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>高頻&lt;/strong>（同一 query 每秒被 call N 次）→ build-time 划算（一次算、N 次受益）&lt;/li>
&lt;li>&lt;strong>低頻&lt;/strong>（query 多樣、每個 query 唯一）→ runtime 划算（precompute 全部 = 浪費）&lt;/li>
&lt;/ul>
&lt;h3 id="軸-2dataset-大小">軸 2：Dataset 大小&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>小 dataset&lt;/strong> → 兩端都可以、依其他軸&lt;/li>
&lt;li>&lt;strong>大 dataset&lt;/strong> → build-time 的儲存成本爆炸、傾向 runtime / hybrid&lt;/li>
&lt;li>&lt;strong>超大&lt;/strong> → 幾乎強制 runtime（即使 hot path 也 partial precompute）&lt;/li>
&lt;/ul>
&lt;h3 id="軸-3freshness-需求">軸 3：Freshness 需求&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>可接受 stale&lt;/strong>（小時 / 天） → build-time 可行&lt;/li>
&lt;li>&lt;strong>要近即時&lt;/strong>（分鐘級） → runtime 或 hybrid + invalidation&lt;/li>
&lt;li>&lt;strong>強即時&lt;/strong>（秒級） → 強制 runtime&lt;/li>
&lt;/ul>
&lt;h3 id="軸-4build-pipeline-複雜度">軸 4：Build pipeline 複雜度&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>既有 pipeline 成熟&lt;/strong> → 加 build-time step 便宜&lt;/li>
&lt;li>&lt;strong>沒 pipeline 或脆弱&lt;/strong> → runtime 更實際（不引入新 infrastructure）&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="三段光譜的實例對照">三段光譜的實例對照&lt;/h2>
&lt;h3 id="pure-build-time">Pure build-time&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>領域&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>靜態網站&lt;/td>
 &lt;td>Hugo / Jekyll generate HTML&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Search index&lt;/td>
 &lt;td>Pagefind build-time index、Algolia indexer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Image 變體&lt;/td>
 &lt;td>sharp / imagemin pre-generate sizes&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Route table&lt;/td>
 &lt;td>Compile-time routes（Next.js static export）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ML model&lt;/td>
 &lt;td>Train once、serve trained weights&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Sitemap / RSS&lt;/td>
 &lt;td>Build-time generate&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="hybrid-hot-path">Hybrid hot-path&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>領域&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Cache (Redis)&lt;/td>
 &lt;td>Hot keys precompute、cold keys runtime + write-back&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CDN&lt;/td>
 &lt;td>Hot routes cached、cold routes hit origin&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>LLM RAG&lt;/td>
 &lt;td>Hot embeddings precompute、cold runtime embed&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Search autocomplete&lt;/td>
 &lt;td>Top N suggestions precompute、tail runtime&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Image responsive&lt;/td>
 &lt;td>Hot sizes precompute、edge cases runtime resize&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="pure-runtime">Pure runtime&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>領域&lt;/th>
 &lt;th>例子&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Live search over user data&lt;/td>
 &lt;td>每 query 掃 DB&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>User-specific compute（dashboard、recommendation）&lt;/td>
 &lt;td>每 user 每次 reload 算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Real-time analytics&lt;/td>
 &lt;td>per-event 處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Open-ended NLP query&lt;/td>
 &lt;td>LLM call per query&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Crypto / hash signature&lt;/td>
 &lt;td>Per-request 算&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="兩極之間的決策當不確定該選哪端">兩極之間的決策：當不確定該選哪端&lt;/h2>
&lt;h3 id="步驟-1列query-frequency--dataset-size象限">步驟 1：列「query frequency × dataset size」象限&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>&lt;/th>
 &lt;th>小 dataset&lt;/th>
 &lt;th>大 dataset&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>高頻 query&lt;/strong>&lt;/td>
 &lt;td>Build-time&lt;/td>
 &lt;td>Hybrid（hot precompute）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>低頻 query&lt;/strong>&lt;/td>
 &lt;td>兩端都可&lt;/td>
 &lt;td>Runtime&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="步驟-2套-freshness-限制">步驟 2：套 freshness 限制&lt;/h3>
&lt;p>如果 freshness 需求高、把 build-time 列從候選移除（除非有 incremental build / invalidation 機制）。&lt;/p></description><content:encoded><![CDATA[<h2 id="結論">結論</h2>
<p>計算放哪裡有光譜、不是二元：</p>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>預付成本</th>
          <th>Runtime 成本</th>
          <th>儲存成本</th>
          <th>Freshness</th>
          <th>適合</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Pure build-time</strong></td>
          <td>高（pipeline + 一次計算全部）</td>
          <td>~0</td>
          <td>高（存 N 種預算結果）</td>
          <td>差（每次 build 才 refresh）</td>
          <td>高頻 query、少變動、closed-set</td>
      </tr>
      <tr>
          <td><strong>Hybrid hot-path</strong></td>
          <td>中（預算 top X%）</td>
          <td>低（hot 命中 0、cold runtime）</td>
          <td>中</td>
          <td>中（hot stale 風險）</td>
          <td>長尾分布、可分 hot/cold</td>
      </tr>
      <tr>
          <td><strong>Pure runtime</strong></td>
          <td>0</td>
          <td>高（per query）</td>
          <td>0</td>
          <td>即時</td>
          <td>低頻、高變動、open-ended query</td>
      </tr>
  </tbody>
</table>
<p>光譜兩端都有合理場景、不是「build-time 永遠贏」。<strong>選哪個位置依四軸：query 頻率 / dataset 大小 / freshness 需求 / build pipeline 複雜度</strong>。</p>
<hr>
<h2 id="build-time-的三個成本維度">build-time 的三個成本維度</h2>
<p>直覺反應：「能 precompute 就 precompute、runtime 0 最好」。但這個直覺漏了三個維度：</p>
<h3 id="1-freshness-成本">1. Freshness 成本</h3>
<p>Build-time 結果是 build 那一刻的 snapshot。dataset 改變後、結果直到下次 build 才 refresh。</p>
<ul>
<li><strong>適合 build-time</strong>：靜態 / 慢變的內容（blog post、product catalog）</li>
<li><strong>不適合 build-time</strong>：頻繁更新（user posts、live data、search index over user content）</li>
</ul>
<h3 id="2-儲存成本">2. 儲存成本</h3>
<p>Precompute N 種 query 的結果 = 存 N 份。當 query 是 open-ended（任意組合 filter / sort / search term）、N 是組合爆炸。</p>
<ul>
<li><strong>適合 build-time</strong>：closed-set（fixed list of routes、pre-defined search terms）</li>
<li><strong>不適合 build-time</strong>：open-ended（任意 user input）</li>
</ul>
<h3 id="3-pipeline-複雜度">3. Pipeline 複雜度</h3>
<p>Build-time 計算需要 build pipeline 配合 — 加一條規則 = 加一份 artifact、需要 CI 跑、版本管理、deployment 同步。</p>
<ul>
<li><strong>適合 build-time</strong>：已有 build pipeline、加一條規則便宜</li>
<li><strong>不適合 build-time</strong>：純 dynamic system、加 build step = 引入新 infrastructure</li>
</ul>
<hr>
<h2 id="四軸判準">四軸判準</h2>
<p>評估某個計算該放哪一端：</p>
<h3 id="軸-1query-頻率">軸 1：Query 頻率</h3>
<ul>
<li><strong>高頻</strong>（同一 query 每秒被 call N 次）→ build-time 划算（一次算、N 次受益）</li>
<li><strong>低頻</strong>（query 多樣、每個 query 唯一）→ runtime 划算（precompute 全部 = 浪費）</li>
</ul>
<h3 id="軸-2dataset-大小">軸 2：Dataset 大小</h3>
<ul>
<li><strong>小 dataset</strong> → 兩端都可以、依其他軸</li>
<li><strong>大 dataset</strong> → build-time 的儲存成本爆炸、傾向 runtime / hybrid</li>
<li><strong>超大</strong> → 幾乎強制 runtime（即使 hot path 也 partial precompute）</li>
</ul>
<h3 id="軸-3freshness-需求">軸 3：Freshness 需求</h3>
<ul>
<li><strong>可接受 stale</strong>（小時 / 天） → build-time 可行</li>
<li><strong>要近即時</strong>（分鐘級） → runtime 或 hybrid + invalidation</li>
<li><strong>強即時</strong>（秒級） → 強制 runtime</li>
</ul>
<h3 id="軸-4build-pipeline-複雜度">軸 4：Build pipeline 複雜度</h3>
<ul>
<li><strong>既有 pipeline 成熟</strong> → 加 build-time step 便宜</li>
<li><strong>沒 pipeline 或脆弱</strong> → runtime 更實際（不引入新 infrastructure）</li>
</ul>
<hr>
<h2 id="三段光譜的實例對照">三段光譜的實例對照</h2>
<h3 id="pure-build-time">Pure build-time</h3>
<table>
  <thead>
      <tr>
          <th>領域</th>
          <th>例子</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>靜態網站</td>
          <td>Hugo / Jekyll generate HTML</td>
      </tr>
      <tr>
          <td>Search index</td>
          <td>Pagefind build-time index、Algolia indexer</td>
      </tr>
      <tr>
          <td>Image 變體</td>
          <td>sharp / imagemin pre-generate sizes</td>
      </tr>
      <tr>
          <td>Route table</td>
          <td>Compile-time routes（Next.js static export）</td>
      </tr>
      <tr>
          <td>ML model</td>
          <td>Train once、serve trained weights</td>
      </tr>
      <tr>
          <td>Sitemap / RSS</td>
          <td>Build-time generate</td>
      </tr>
  </tbody>
</table>
<h3 id="hybrid-hot-path">Hybrid hot-path</h3>
<table>
  <thead>
      <tr>
          <th>領域</th>
          <th>例子</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cache (Redis)</td>
          <td>Hot keys precompute、cold keys runtime + write-back</td>
      </tr>
      <tr>
          <td>CDN</td>
          <td>Hot routes cached、cold routes hit origin</td>
      </tr>
      <tr>
          <td>LLM RAG</td>
          <td>Hot embeddings precompute、cold runtime embed</td>
      </tr>
      <tr>
          <td>Search autocomplete</td>
          <td>Top N suggestions precompute、tail runtime</td>
      </tr>
      <tr>
          <td>Image responsive</td>
          <td>Hot sizes precompute、edge cases runtime resize</td>
      </tr>
  </tbody>
</table>
<h3 id="pure-runtime">Pure runtime</h3>
<table>
  <thead>
      <tr>
          <th>領域</th>
          <th>例子</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Live search over user data</td>
          <td>每 query 掃 DB</td>
      </tr>
      <tr>
          <td>User-specific compute（dashboard、recommendation）</td>
          <td>每 user 每次 reload 算</td>
      </tr>
      <tr>
          <td>Real-time analytics</td>
          <td>per-event 處理</td>
      </tr>
      <tr>
          <td>Open-ended NLP query</td>
          <td>LLM call per query</td>
      </tr>
      <tr>
          <td>Crypto / hash signature</td>
          <td>Per-request 算</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="兩極之間的決策當不確定該選哪端">兩極之間的決策：當不確定該選哪端</h2>
<h3 id="步驟-1列query-frequency--dataset-size象限">步驟 1：列「query frequency × dataset size」象限</h3>
<table>
  <thead>
      <tr>
          <th></th>
          <th>小 dataset</th>
          <th>大 dataset</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>高頻 query</strong></td>
          <td>Build-time</td>
          <td>Hybrid（hot precompute）</td>
      </tr>
      <tr>
          <td><strong>低頻 query</strong></td>
          <td>兩端都可</td>
          <td>Runtime</td>
      </tr>
  </tbody>
</table>
<h3 id="步驟-2套-freshness-限制">步驟 2：套 freshness 限制</h3>
<p>如果 freshness 需求高、把 build-time 列從候選移除（除非有 incremental build / invalidation 機制）。</p>
<h3 id="步驟-3看-build-pipeline-cost">步驟 3：看 build pipeline cost</h3>
<p>如果 build-time 成本（新 step、新 artifact、新 deploy 流程）大於 runtime 成本（per query CPU）、選 runtime。</p>
<h3 id="步驟-4留-escape-hatch">步驟 4：留 escape hatch</h3>
<p>選了一端不代表永遠 — 設計 invalidation hook / runtime fallback、未來能重新平衡。</p>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「能 precompute 就 precompute」當預設</td>
          <td>freshness / 儲存爆炸</td>
      </tr>
      <tr>
          <td>「runtime 比較動態」當預設</td>
          <td>高頻 query 浪費 CPU</td>
      </tr>
      <tr>
          <td>Build-time 沒留 invalidation hook</td>
          <td>dataset 改了無法 refresh</td>
      </tr>
      <tr>
          <td>Hybrid 沒明示 hot 邊界</td>
          <td>運作不穩、cold path 突然爆量</td>
      </tr>
      <tr>
          <td>把 freshness 假設成「不變」</td>
          <td>真實 dataset 會變、blowup</td>
      </tr>
      <tr>
          <td>Pre-build 全部 + runtime 又再算一次</td>
          <td>雙倍成本、無增益</td>
      </tr>
      <tr>
          <td>「先 runtime、之後 optimize 成 build-time」當口號</td>
          <td>optimize 那次永遠不發生（<a href="../external-trigger-for-high-roi-work/">#72</a>）</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="何時是兩端都不對要重思-problem">何時是「兩端都不對、要重思 problem」</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build-time 結果 stale、runtime 又太慢</td>
          <td>Hybrid + invalidation 設計</td>
      </tr>
      <tr>
          <td>Hybrid hot 一直 miss、cold path 是常態</td>
          <td>重排 hot 邊界、可能整個翻成 pure runtime</td>
      </tr>
      <tr>
          <td>Open-ended query 試圖 build-time</td>
          <td>Reformulate problem、可能要分 query class</td>
      </tr>
      <tr>
          <td>加了 invalidation 後 build pipeline 太複雜</td>
          <td>改成 runtime + cache、別再強行 build-time</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../capability-gap-three-layer-escalation/">#86 Capability gap 三層階梯</a></td>
          <td>L3 structural rebuild 通常是「動 build-time 計算」、本卡是 L3 內部的具體取捨</td>
      </tr>
      <tr>
          <td><a href="../filter-source-composition-strategies/">#59 五策略選擇矩陣</a></td>
          <td>A 推進 query（runtime）vs C 預先建 index（build-time）vs B 自動續抓（hybrid）— 五策略的本卡映射</td>
      </tr>
      <tr>
          <td><a href="../main-strategy-plus-supplementary/">#75 主策略 + 補強疊加</a></td>
          <td>Hybrid hot-path 是 build-time + runtime 疊加的具體 case</td>
      </tr>
      <tr>
          <td><a href="../minimum-necessary-scope-is-sanity-defense/">#43 最小必要範圍</a></td>
          <td>Hybrid 的 hot 邊界 = 最小必要範圍、有證據再擴張</td>
      </tr>
      <tr>
          <td><a href="../search-engine-matching-mode-mismatch/">#73 search 匹配模式</a></td>
          <td>Build-time tokenize（B 策略）vs client-side fallback（C 策略）就是本卡兩極的具體 case</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「能 precompute 就 precompute」沒列軸</td>
          <td>套四軸（頻率 / 大小 / freshness / pipeline）</td>
      </tr>
      <tr>
          <td>Build-time artifact 越來越大</td>
          <td>檢查 query frequency 分布、可能該移到 hybrid</td>
      </tr>
      <tr>
          <td>Runtime 計算成本爆</td>
          <td>找 hot path、考慮 hybrid</td>
      </tr>
      <tr>
          <td>Freshness 抱怨</td>
          <td>Build-time 已不適用、改 hybrid + invalidation</td>
      </tr>
      <tr>
          <td>加了 build step 後 deploy 變慢</td>
          <td>Build pipeline 成本不可忽略、評估是否仍划算</td>
      </tr>
      <tr>
          <td>Hybrid 邊界從沒重新 review</td>
          <td>hot / cold 比例會漂移、定期重 baseline</td>
      </tr>
  </tbody>
</table>
<p><strong>核心</strong>：Build-time vs runtime 是光譜、不是二元 — 中間 hybrid 段是多數實務情境的最佳位置。<strong>「能 precompute 就 precompute」是便利驅動（<a href="../ease-of-writing-vs-intent-alignment/">#67</a>）的口號</strong>、實際要套四軸（頻率 / 大小 / freshness / pipeline）才知道該放哪。</p>
]]></content:encoded></item><item><title>Dataset 規模改變什麼可行：「需要 index」依 scale 而定</title><link>https://tarrragon.github.io/blog/report/dataset-scale-changes-feasibility/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/report/dataset-scale-changes-feasibility/</guid><description>&lt;h2 id="結論">結論&lt;/h2>
&lt;p>「需要 index、需要 cache、需要 hot path」這類設計動詞、預設是 &lt;strong>production scale 的語言&lt;/strong>。Dataset 小的時候、O(N) scan 甚至 O(N²) 都可能 trivial、不需要 index。&lt;/p>
&lt;p>具體 threshold：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Dataset 大小&lt;/th>
 &lt;th>可行操作&lt;/th>
 &lt;th>Index / cache 必要？&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>&amp;lt; 1 MB&lt;/strong>&lt;/td>
 &lt;td>O(N²) 全表 scan、JS regex、無腦比對&lt;/td>
 &lt;td>完全不需要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>1-10 MB&lt;/strong>&lt;/td>
 &lt;td>O(N) full scan per query、簡單 in-memory 處理&lt;/td>
 &lt;td>通常不需要、有 index 是 nice-to-have&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>10-100 MB&lt;/strong>&lt;/td>
 &lt;td>O(N) 仍可、但要避免 per-keystroke；考慮 lazy load + index&lt;/td>
 &lt;td>開始需要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>100 MB - 1 GB&lt;/strong>&lt;/td>
 &lt;td>必須 index / 分塊 / streaming&lt;/td>
 &lt;td>必要&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>&amp;gt; 1 GB&lt;/strong>&lt;/td>
 &lt;td>必須分散式 / DB / search engine&lt;/td>
 &lt;td>強制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>錯誤預設&lt;/strong>：「production scale 設計」直接套用到小 dataset = 過度工程。&lt;strong>修法&lt;/strong>：先量測實際 dataset 大小、再決定要不要 index。&lt;/p>
&lt;hr>
&lt;h2 id="為什麼預設-production-scale-是反模式">為什麼預設 production scale 是反模式&lt;/h2>
&lt;p>寫程式的習慣偏好「scalable solution」 — 但在 small dataset 情境下：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>過度工程成本&lt;/strong>：寫 / 維護 index、cache invalidation、tiered storage 都是 cost&lt;/li>
&lt;li>&lt;strong>實際收益 0&lt;/strong>：dataset 小、O(N) scan 已經 &amp;lt; 1ms、index 沒帶來感知差異&lt;/li>
&lt;li>&lt;strong>複雜度引入新 bug&lt;/strong>：cache invalidation 是出名難題、small dataset 直接 scan 反而對&lt;/li>
&lt;/ul>
&lt;p>實務上 80% 內部工具 / 個人專案 / 小型部落格的 dataset 是「&amp;lt; 10 MB」級別。為它寫 production-scale 設計 = 為不存在的問題付成本。&lt;/p>
&lt;hr>
&lt;h2 id="具體-threshold-跟可行操作">具體 threshold 跟可行操作&lt;/h2>
&lt;p>每段 dataset 大小都有「直覺以為需要、實際不需要」的對照：&lt;/p>
&lt;h3 id="-1-mb極小">&amp;lt; 1 MB（極小）&lt;/h3>
&lt;p>例：個人部落格內容 metadata、小型 config、單頁 SPA state。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>直覺以為需要&lt;/th>
 &lt;th>實際不需要、可用&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Search index&lt;/td>
 &lt;td>&lt;code>Array.filter(text.includes)&lt;/code> 就夠&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Pagination&lt;/td>
 &lt;td>全載入、CSS scroll&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Lazy load&lt;/td>
 &lt;td>一次全 fetch、&amp;lt; 100 KB 沒差&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Web Worker&lt;/td>
 &lt;td>主執行緒同步處理、&amp;lt; 1ms 不會卡&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cache&lt;/td>
 &lt;td>重算每次、&amp;lt; 1ms 沒差&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="1-10-mb小">1-10 MB（小）&lt;/h3>
&lt;p>例：本部落格 raw markdown（7.5 MB、135 篇）、中型 documentation site、小型 e-commerce catalog。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>直覺以為需要&lt;/th>
 &lt;th>實際不需要、可用&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Search index 全集&lt;/td>
 &lt;td>Title-only index（更小）+ runtime substring fallback、依場景&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>分頁 query&lt;/td>
 &lt;td>全 fetch + client filter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Server-side rendering each request&lt;/td>
 &lt;td>Static + client interaction&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Database indexing&lt;/td>
 &lt;td>In-memory map（如果 query pattern 簡單）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Background indexing&lt;/td>
 &lt;td>Build-time 一次處理&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>但有時候已經值得 index — 視 query 頻率、複雜度、UX 需求而定（看 &lt;a href="../build-time-vs-runtime-computation-spectrum/">#87&lt;/a> 四軸）。&lt;/p></description><content:encoded><![CDATA[<h2 id="結論">結論</h2>
<p>「需要 index、需要 cache、需要 hot path」這類設計動詞、預設是 <strong>production scale 的語言</strong>。Dataset 小的時候、O(N) scan 甚至 O(N²) 都可能 trivial、不需要 index。</p>
<p>具體 threshold：</p>
<table>
  <thead>
      <tr>
          <th>Dataset 大小</th>
          <th>可行操作</th>
          <th>Index / cache 必要？</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>&lt; 1 MB</strong></td>
          <td>O(N²) 全表 scan、JS regex、無腦比對</td>
          <td>完全不需要</td>
      </tr>
      <tr>
          <td><strong>1-10 MB</strong></td>
          <td>O(N) full scan per query、簡單 in-memory 處理</td>
          <td>通常不需要、有 index 是 nice-to-have</td>
      </tr>
      <tr>
          <td><strong>10-100 MB</strong></td>
          <td>O(N) 仍可、但要避免 per-keystroke；考慮 lazy load + index</td>
          <td>開始需要</td>
      </tr>
      <tr>
          <td><strong>100 MB - 1 GB</strong></td>
          <td>必須 index / 分塊 / streaming</td>
          <td>必要</td>
      </tr>
      <tr>
          <td><strong>&gt; 1 GB</strong></td>
          <td>必須分散式 / DB / search engine</td>
          <td>強制</td>
      </tr>
  </tbody>
</table>
<p><strong>錯誤預設</strong>：「production scale 設計」直接套用到小 dataset = 過度工程。<strong>修法</strong>：先量測實際 dataset 大小、再決定要不要 index。</p>
<hr>
<h2 id="為什麼預設-production-scale-是反模式">為什麼預設 production scale 是反模式</h2>
<p>寫程式的習慣偏好「scalable solution」 — 但在 small dataset 情境下：</p>
<ul>
<li><strong>過度工程成本</strong>：寫 / 維護 index、cache invalidation、tiered storage 都是 cost</li>
<li><strong>實際收益 0</strong>：dataset 小、O(N) scan 已經 &lt; 1ms、index 沒帶來感知差異</li>
<li><strong>複雜度引入新 bug</strong>：cache invalidation 是出名難題、small dataset 直接 scan 反而對</li>
</ul>
<p>實務上 80% 內部工具 / 個人專案 / 小型部落格的 dataset 是「&lt; 10 MB」級別。為它寫 production-scale 設計 = 為不存在的問題付成本。</p>
<hr>
<h2 id="具體-threshold-跟可行操作">具體 threshold 跟可行操作</h2>
<p>每段 dataset 大小都有「直覺以為需要、實際不需要」的對照：</p>
<h3 id="-1-mb極小">&lt; 1 MB（極小）</h3>
<p>例：個人部落格內容 metadata、小型 config、單頁 SPA state。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際不需要、可用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Search index</td>
          <td><code>Array.filter(text.includes)</code> 就夠</td>
      </tr>
      <tr>
          <td>Pagination</td>
          <td>全載入、CSS scroll</td>
      </tr>
      <tr>
          <td>Lazy load</td>
          <td>一次全 fetch、&lt; 100 KB 沒差</td>
      </tr>
      <tr>
          <td>Web Worker</td>
          <td>主執行緒同步處理、&lt; 1ms 不會卡</td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>重算每次、&lt; 1ms 沒差</td>
      </tr>
  </tbody>
</table>
<h3 id="1-10-mb小">1-10 MB（小）</h3>
<p>例：本部落格 raw markdown（7.5 MB、135 篇）、中型 documentation site、小型 e-commerce catalog。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際不需要、可用</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Search index 全集</td>
          <td>Title-only index（更小）+ runtime substring fallback、依場景</td>
      </tr>
      <tr>
          <td>分頁 query</td>
          <td>全 fetch + client filter</td>
      </tr>
      <tr>
          <td>Server-side rendering each request</td>
          <td>Static + client interaction</td>
      </tr>
      <tr>
          <td>Database indexing</td>
          <td>In-memory map（如果 query pattern 簡單）</td>
      </tr>
      <tr>
          <td>Background indexing</td>
          <td>Build-time 一次處理</td>
      </tr>
  </tbody>
</table>
<p>但有時候已經值得 index — 視 query 頻率、複雜度、UX 需求而定（看 <a href="../build-time-vs-runtime-computation-spectrum/">#87</a> 四軸）。</p>
<h3 id="10-100-mb中">10-100 MB（中）</h3>
<p>例：中型公司內部工具的 user list、開源 project 全 repo、中型 dataset analytics。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Index for everything</td>
          <td>主要 query patterns 加 index、cold path runtime 仍可</td>
      </tr>
      <tr>
          <td>Aggressive pagination</td>
          <td>分頁 + filter pushdown</td>
      </tr>
      <tr>
          <td>Server-side scan</td>
          <td>通常仍可、加 cache 即可</td>
      </tr>
      <tr>
          <td>分散式處理</td>
          <td>通常單機夠</td>
      </tr>
  </tbody>
</table>
<h3 id="100-mb---1-gb中-大">100 MB - 1 GB（中-大）</h3>
<p>例：中型 SaaS 的 user data、search engine over medium corpus、ML feature store。</p>
<table>
  <thead>
      <tr>
          <th>直覺以為需要</th>
          <th>實際</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Sharding</td>
          <td>通常單機 SSD / RAM 還夠、先垂直擴展</td>
      </tr>
      <tr>
          <td>Distributed system</td>
          <td>Single-node DB（PostgreSQL）+ index 多半夠</td>
      </tr>
      <tr>
          <td>Custom search engine</td>
          <td>Postgres FTS / SQLite FTS5 通常足夠</td>
      </tr>
      <tr>
          <td>Caching layer</td>
          <td>必要、但簡單 LRU 即可</td>
      </tr>
  </tbody>
</table>
<h3 id="-1-gb大">&gt; 1 GB（大）</h3>
<p>例：大型 SaaS、社交網路、search engine over web。</p>
<p>這個 scale 才真的需要 production-scale 設計：分散式、shard、index 嚴格設計、cache 多層、async pipeline。</p>
<hr>
<h2 id="我以後會長大的迷思">「我以後會長大」的迷思</h2>
<p>常見藉口：「現在 dataset 小、但以後會長大、所以該為以後設計」。</p>
<p>這個邏輯有兩個漏洞：</p>
<h3 id="漏洞-1未來成長不確定">漏洞 1：未來成長不確定</h3>
<p>多數內部工具 / 個人專案 / 中小企業 dataset <strong>不會</strong> 長到 production scale。為「以後可能爆炸」設計、多半是為不存在的未來付當下成本。</p>
<h3 id="漏洞-2成長前重-design-比現在-design-容易">漏洞 2：成長前重 design 比現在 design 容易</h3>
<p>當 dataset 真的長大、你會有：</p>
<ul>
<li>真實的 query pattern（vs 現在猜的）</li>
<li>真實的 hot spots（vs 現在猜的）</li>
<li>真實的 budget（vs 現在估的）</li>
</ul>
<p><strong>等需要時 redesign 比現在 over-design 划算</strong>。當前 simple design 也比較容易被 redesign（複雜結構難動）。</p>
<hr>
<h2 id="dataset-質的差別size-是-first-order-proxytype-是-second-order-multiplier">Dataset 質的差別：size 是 first-order proxy、type 是 second-order multiplier</h2>
<p>同樣 N MB dataset、不同 type 的處理成本差異大。size 只是 first approximation、要看 <strong>data type 跟 access pattern</strong>：</p>
<table>
  <thead>
      <tr>
          <th>Type</th>
          <th>1 MB 行為</th>
          <th>10 MB 行為</th>
          <th>100 MB 行為</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Plain text</strong></td>
          <td>regex 全掃 &lt; 1ms</td>
          <td>regex 全掃 &lt; 50ms</td>
          <td>需分塊或 index</td>
      </tr>
      <tr>
          <td><strong>JSON / structured</strong></td>
          <td>parse + filter &lt; 10ms</td>
          <td>parse 開始貴、可能要 stream parse</td>
          <td>強制 stream / index</td>
      </tr>
      <tr>
          <td><strong>Image / binary</strong></td>
          <td>load 即一個壓力、無法 in-memory regex</td>
          <td>不能放 client、需 lazy load</td>
          <td>必 server-side 處理</td>
      </tr>
      <tr>
          <td><strong>Time series</strong></td>
          <td>順序敏感、不能 random access</td>
          <td>同 + 必須有 time index</td>
          <td>同 + 必須分區</td>
      </tr>
      <tr>
          <td><strong>Graph</strong></td>
          <td>traversal 成本不只 size、看連通度</td>
          <td>連通度高 → 易爆炸</td>
          <td>必須圖 DB</td>
      </tr>
  </tbody>
</table>
<h3 id="同-size-不同-type-的可行性逆轉">同 size 不同 type 的可行性逆轉</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1 MB 純文字 search：trivial
</span></span><span class="line"><span class="ln">2</span><span class="cl">1 MB JSON deep filter：可能慢（parse + nested traversal）
</span></span><span class="line"><span class="ln">3</span><span class="cl">1 MB graph traversal：可能極慢（depth 深時呈指數）
</span></span><span class="line"><span class="ln">4</span><span class="cl">1 MB image：完全不能 client substring scan</span></span></code></pre></div><p><strong>判讀</strong>：先看 type、再看 size。size 算 capacity、type 決定每 byte 的成本。</p>
<h3 id="跨-type-共通的判準">跨 type 共通的判準</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「dataset 才 1 MB 應該 fast」</td>
          <td>先確認 type — JSON / 圖 / binary 行為差異大</td>
      </tr>
      <tr>
          <td>Text 規模套到 image 場景</td>
          <td>size 直覺失效、重新評估</td>
      </tr>
      <tr>
          <td>Graph dataset 用 size 推斷</td>
          <td>看 connectivity 不只看 size</td>
      </tr>
      <tr>
          <td>Time series 想用 random access</td>
          <td>type 不對、改 sequential 設計</td>
      </tr>
  </tbody>
</table>
<p>「Dataset size」不是萬用尺、不同 type 要套不同 threshold。</p>
<hr>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>預設「需要 index」沒量 dataset</td>
          <td>過度工程、抽象 leak</td>
      </tr>
      <tr>
          <td>「production-grade」當設計詞、不分場景套</td>
          <td>內部工具 over-engineered</td>
      </tr>
      <tr>
          <td>Big-O 思維直接套小 dataset</td>
          <td>漏掉 constant factor、實際 O(N²) 比 O(N log N) 還快（小 N）</td>
      </tr>
      <tr>
          <td>Cache 在 dataset &lt; 1 MB 場景</td>
          <td>Cache invalidation 引入 bug、收益 0</td>
      </tr>
      <tr>
          <td>分散式設計在 single-node 夠用場景</td>
          <td>維運複雜度爆炸</td>
      </tr>
      <tr>
          <td>「scale 萬一爆」當預設</td>
          <td>機率低事件主導設計</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="何時應該照-production-scale-設計">何時應該照 production-scale 設計</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>為什麼</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>已知會在 6 個月內 grow 10x+</td>
          <td>證據明確、提前 design 划算</td>
      </tr>
      <tr>
          <td>公開服務、流量不可控</td>
          <td>防爆炸、必要</td>
      </tr>
      <tr>
          <td>User-generated content（dataset size 由使用者決定）</td>
          <td>上限不可控</td>
      </tr>
      <tr>
          <td>Real-time / SLA 嚴格</td>
          <td>constant factor 也重要</td>
      </tr>
      <tr>
          <td>已經慢了、有實證</td>
          <td>Bottleneck 已浮現</td>
      </tr>
  </tbody>
</table>
<p>五類共通：<strong>有證據 dataset 會大 / 已大 / 不可控</strong>。其他情境用當前 dataset size 設計。</p>
<hr>
<h2 id="跟其他抽象層原則的關係">跟其他抽象層原則的關係</h2>
<table>
  <thead>
      <tr>
          <th>原則</th>
          <th>關係</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="../minimum-necessary-scope-is-sanity-defense/">#43 最小必要範圍</a></td>
          <td>本卡是 #43 在「規模假設」維度的展現 — 不該 over-claim 規模</td>
      </tr>
      <tr>
          <td><a href="../build-time-vs-runtime-computation-spectrum/">#87 Build-time vs Runtime</a></td>
          <td>#87 的「dataset 大小」軸跟本卡同骨</td>
      </tr>
      <tr>
          <td><a href="../capability-gap-three-layer-escalation/">#86 Capability gap 三層階梯</a></td>
          <td>小 dataset 內 L2 augmenting 通常足夠、不必跳 L3</td>
      </tr>
      <tr>
          <td><a href="../ease-of-writing-vs-intent-alignment/">#67 寫作便利度</a></td>
          <td>「寫 production-scale」比「量 dataset 再決定」容易、跟實際 ROI 反相關</td>
      </tr>
      <tr>
          <td><a href="../two-occurrence-threshold/">#42 2 次門檻</a></td>
          <td>真實 production 問題 ≥ 2 次再升級設計、不為「萬一」設計</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="判讀徵兆">判讀徵兆</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>寫到「需要 index」沒先量 dataset</td>
          <td>量、可能不需要</td>
      </tr>
      <tr>
          <td>寫到「production-scale」當形容詞</td>
          <td>確認真的是 production scale、否則拿掉</td>
      </tr>
      <tr>
          <td>Cache 設計、dataset 卻 &lt; 1 MB</td>
          <td>拿掉 cache、直接重算</td>
      </tr>
      <tr>
          <td>分散式、卻是個人 project</td>
          <td>退回 single-node</td>
      </tr>
      <tr>
          <td>「以後會長大」當理由</td>
          <td>等真的長大、現在用 simple</td>
      </tr>
      <tr>
          <td>Big-O 焦慮、卻沒量實際 latency</td>
          <td>量了再決、constant factor 可能主導</td>
      </tr>
      <tr>
          <td>「lazy load 是必要」沒看 bundle size</td>
          <td>確認 bundle 真的大、&lt; 200 KB 全載入即可</td>
      </tr>
  </tbody>
</table>
<p><strong>核心</strong>：「需要 index / cache / 分散式 / lazy load」這類動詞<strong>不是普世真理、是 production scale 的詞</strong>。預設套用到小 dataset = 過度工程。<strong>先量再決</strong>、跟「猜的 scale」對齊不如跟「實際 dataset」對齊。</p>
]]></content:encoded></item><item><title>效能與容量工具清單</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/</link><pubDate>Fri, 15 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/</guid><description>&lt;p>效能與容量工具清單的核心責任是把工具名稱放回 workload model、saturation discovery、capacity planning 與 production validation 的服務責任。工具頁先回答它降低哪一種風險，再討論 scenario scripting、distributed load、結果保存、CI 整合、成本與案例回寫。&lt;/p>
&lt;h2 id="讀法">讀法&lt;/h2>
&lt;p>效能工具要從問題節點進入。團隊如果缺 workload model，先讀 &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>；如果缺 saturation 邊界，先讀 &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>；如果缺 production 驗證，先讀 &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>。&lt;/p>
&lt;p>工具頁的任務是承接這些問題節點。k6、JMeter、Gatling、Locust 與 Vegeta 都能產生負載，但它們在腳本語言、protocol 覆蓋、分散式執行、CI integration、報表與團隊學習成本上不同；production replay、profiling 與 cost analysis 工具則承擔不同的證據責任。&lt;/p>
&lt;h2 id="教學順序同步">教學順序同步&lt;/h2>
&lt;p>效能與容量工具頁的教學順序是先建立 load test，再進入 replay / mirroring、profiling、optimization 與 FinOps。這個順序對齊 checkout E7：讀者先理解 workload model、saturation evidence 與 capacity gate，再比較 production traffic evidence、profile evidence、rightsizing 建議與成本 owner 如何形成改善閉環。&lt;/p>
&lt;h2 id="t1-工具頁">T1 工具頁&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>工具&lt;/th>
 &lt;th>類型&lt;/th>
 &lt;th>核心責任&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>Load test&lt;/td>
 &lt;td>用 scriptable scenario 建立 API / protocol 負載&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>Load test&lt;/td>
 &lt;td>用 GUI、plugin 與多 protocol sampler 承接企業測試資產&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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>&lt;/td>
 &lt;td>Load test&lt;/td>
 &lt;td>用 JVM DSL 與 injection profile 表達複雜 scenario&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&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;/td>
 &lt;td>Load test&lt;/td>
 &lt;td>用 Python user behavior 與 distributed worker 表達高自訂負載&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vegeta/" data-link-title="Vegeta" data-link-desc="用簡潔 CLI 與固定 rate HTTP attack 快速探測 latency、throughput 與 saturation 的效能工程工具">Vegeta&lt;/a>&lt;/td>
 &lt;td>HTTP probe&lt;/td>
 &lt;td>用固定 rate HTTP attack 快速探測 endpoint saturation&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay&lt;/a>&lt;/td>
 &lt;td>Traffic replay&lt;/td>
 &lt;td>捕捉 production HTTP traffic 並重播到 shadow target&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>Traffic mirror&lt;/td>
 &lt;td>用 proxy route policy mirror production traffic&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/" data-link-title="AWS VPC Traffic Mirroring" data-link-desc="用 VPC 網路層封包鏡像觀察 production traffic 的低侵入 production validation 方式">AWS VPC Traffic Mirroring&lt;/a>&lt;/td>
 &lt;td>Traffic mirror&lt;/td>
 &lt;td>用 VPC 網路層封包鏡像建立低侵入 production evidence&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler&lt;/a>&lt;/td>
 &lt;td>Profiling&lt;/td>
 &lt;td>用 SaaS APM 整合與 deploy marker 支援 profile diff&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope&lt;/a>&lt;/td>
 &lt;td>Profiling&lt;/td>
 &lt;td>用 Grafana / OSS profiling backend 建立可自管 profile diff&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca&lt;/a>&lt;/td>
 &lt;td>Profiling&lt;/td>
 &lt;td>用 eBPF 與平台視角建立 infrastructure-wide profile evidence&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/akamas/" data-link-title="Akamas" data-link-desc="用 AI-driven optimization 把效能、可靠性與雲端成本放進同一個容量調校閉環">Akamas&lt;/a>&lt;/td>
 &lt;td>Optimization&lt;/td>
 &lt;td>用 SLO constraint 與配置實驗建立 capacity / cost 調校閉環&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage&lt;/a>&lt;/td>
 &lt;td>FinOps&lt;/td>
 &lt;td>用 cost reports、Kubernetes cost 與 forecast 建立成本可見性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth&lt;/a>&lt;/td>
 &lt;td>FinOps&lt;/td>
 &lt;td>用 enterprise governance、policy 與 allocation 管理雲端成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/" data-link-title="AWS Cost Explorer" data-link-desc="用 AWS-native 成本與用量分析建立 account、service、tag 與 usage type 的成本判讀入口">AWS Cost Explorer&lt;/a>&lt;/td>
 &lt;td>AWS FinOps&lt;/td>
 &lt;td>用 AWS-native cost / usage report 建立成本分析 baseline&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這批工具頁已完成 load test、production traffic replay、continuous profiling 與 capacity / cost analysis 的主要分流。k6 承接 scriptable scenario，JMeter 承接企業測試資產，Gatling 承接 JVM simulation，Locust 承接 Python custom behavior，Vegeta 承接快速 HTTP probe；GoReplay、Service Mesh Mirroring 與 AWS VPC Traffic Mirroring 承接不同層級的 production traffic evidence；Datadog Continuous Profiler、Pyroscope 與 Parca 承接不同操作模型的 profile evidence；Akamas、Vantage、CloudHealth 與 AWS Cost Explorer 承接 cost visibility、optimization 與 FinOps governance。&lt;/p></description><content:encoded><![CDATA[<p>效能與容量工具清單的核心責任是把工具名稱放回 workload model、saturation discovery、capacity planning 與 production validation 的服務責任。工具頁先回答它降低哪一種風險，再討論 scenario scripting、distributed load、結果保存、CI 整合、成本與案例回寫。</p>
<h2 id="讀法">讀法</h2>
<p>效能工具要從問題節點進入。團隊如果缺 workload model，先讀 <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>；如果缺 saturation 邊界，先讀 <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>；如果缺 production 驗證，先讀 <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>。</p>
<p>工具頁的任務是承接這些問題節點。k6、JMeter、Gatling、Locust 與 Vegeta 都能產生負載，但它們在腳本語言、protocol 覆蓋、分散式執行、CI integration、報表與團隊學習成本上不同；production replay、profiling 與 cost analysis 工具則承擔不同的證據責任。</p>
<h2 id="教學順序同步">教學順序同步</h2>
<p>效能與容量工具頁的教學順序是先建立 load test，再進入 replay / mirroring、profiling、optimization 與 FinOps。這個順序對齊 checkout E7：讀者先理解 workload model、saturation evidence 與 capacity gate，再比較 production traffic evidence、profile evidence、rightsizing 建議與成本 owner 如何形成改善閉環。</p>
<h2 id="t1-工具頁">T1 工具頁</h2>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>類型</th>
          <th>核心責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <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>
          <td>Load test</td>
          <td>用 scriptable scenario 建立 API / protocol 負載</td>
      </tr>
      <tr>
          <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>
          <td>Load test</td>
          <td>用 GUI、plugin 與多 protocol sampler 承接企業測試資產</td>
      </tr>
      <tr>
          <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>
          <td>Load test</td>
          <td>用 JVM DSL 與 injection profile 表達複雜 scenario</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a></td>
          <td>Load test</td>
          <td>用 Python user behavior 與 distributed worker 表達高自訂負載</td>
      </tr>
      <tr>
          <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>
          <td>HTTP probe</td>
          <td>用固定 rate HTTP attack 快速探測 endpoint saturation</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/goreplay/" data-link-title="GoReplay" data-link-desc="用 production HTTP traffic capture 與 replay 驗證真實請求形狀的效能工程工具">GoReplay</a></td>
          <td>Traffic replay</td>
          <td>捕捉 production HTTP traffic 並重播到 shadow target</td>
      </tr>
      <tr>
          <td><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>
          <td>Traffic mirror</td>
          <td>用 proxy route policy mirror production traffic</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/aws-vpc-traffic-mirroring/" data-link-title="AWS VPC Traffic Mirroring" data-link-desc="用 VPC 網路層封包鏡像觀察 production traffic 的低侵入 production validation 方式">AWS VPC Traffic Mirroring</a></td>
          <td>Traffic mirror</td>
          <td>用 VPC 網路層封包鏡像建立低侵入 production evidence</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler</a></td>
          <td>Profiling</td>
          <td>用 SaaS APM 整合與 deploy marker 支援 profile diff</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope</a></td>
          <td>Profiling</td>
          <td>用 Grafana / OSS profiling backend 建立可自管 profile diff</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca</a></td>
          <td>Profiling</td>
          <td>用 eBPF 與平台視角建立 infrastructure-wide profile evidence</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/akamas/" data-link-title="Akamas" data-link-desc="用 AI-driven optimization 把效能、可靠性與雲端成本放進同一個容量調校閉環">Akamas</a></td>
          <td>Optimization</td>
          <td>用 SLO constraint 與配置實驗建立 capacity / cost 調校閉環</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/vantage/" data-link-title="Vantage" data-link-desc="用 cloud cost reports、Kubernetes cost allocation 與 forecast 建立工程可用的成本可見性">Vantage</a></td>
          <td>FinOps</td>
          <td>用 cost reports、Kubernetes cost 與 forecast 建立成本可見性</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/cloudhealth/" data-link-title="CloudHealth" data-link-desc="用 enterprise FinOps governance、policy 與多雲成本管理支援大型組織的容量成本治理">CloudHealth</a></td>
          <td>FinOps</td>
          <td>用 enterprise governance、policy 與 allocation 管理雲端成本</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/vendors/aws-cost-explorer/" data-link-title="AWS Cost Explorer" data-link-desc="用 AWS-native 成本與用量分析建立 account、service、tag 與 usage type 的成本判讀入口">AWS Cost Explorer</a></td>
          <td>AWS FinOps</td>
          <td>用 AWS-native cost / usage report 建立成本分析 baseline</td>
      </tr>
  </tbody>
</table>
<p>這批工具頁已完成 load test、production traffic replay、continuous profiling 與 capacity / cost analysis 的主要分流。k6 承接 scriptable scenario，JMeter 承接企業測試資產，Gatling 承接 JVM simulation，Locust 承接 Python custom behavior，Vegeta 承接快速 HTTP probe；GoReplay、Service Mesh Mirroring 與 AWS VPC Traffic Mirroring 承接不同層級的 production traffic evidence；Datadog Continuous Profiler、Pyroscope 與 Parca 承接不同操作模型的 profile evidence；Akamas、Vantage、CloudHealth 與 AWS Cost Explorer 承接 cost visibility、optimization 與 FinOps governance。</p>
<h2 id="內容覆蓋進度">內容覆蓋進度</h2>
<p>每個工具頁下會擴充兩類文章：deep article（工具自身的配置、故障、容量、走 <a href="/blog/posts/vendor-%E6%B7%B1%E5%BA%A6%E6%8A%80%E8%A1%93%E6%96%87%E7%AB%A0%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84%E5%90%8C-vendor-%E7%B3%BB%E5%88%97%E7%9A%84%E9%96%8B%E5%A0%B4%E8%BC%AA%E6%9B%BF%E9%A9%97%E8%AD%89/" data-link-title="Vendor 深度技術文章方法論的演化紀錄：同 vendor 系列的開場輪替驗證" data-link-desc="vendor overview 飽和後要寫單一功能深度文章、需要選題與結構依據時回來。這套方法論的驗證來源與 cadence variant 在高風險場景（同 vendor sub-tool 系列）的實證。">6-section 模板</a>）跟 migration playbook（跨工具遷移流程、走 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6-type 結構</a>）。「← X」代表從 X 遷入。</p>
<table>
  <thead>
      <tr>
          <th>Vendor</th>
          <th>Deep article</th>
          <th>Migration playbook</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="k6/">k6</a></td>
          <td>—</td>
          <td><a href="k6/migrate-from-jmeter/">← JMeter (Type E)</a></td>
      </tr>
      <tr>
          <td><a href="datadog-continuous-profiler/">Datadog Continuous Profiler</a></td>
          <td>—</td>
          <td><a href="datadog-continuous-profiler/migrate-from-pyroscope/">← Pyroscope (Type C)</a></td>
      </tr>
  </tbody>
</table>
<p>其他 T1 工具（JMeter / Gatling / Locust / Vegeta / GoReplay / Service Mesh Mirroring / AWS VPC Traffic Mirroring / Pyroscope / Parca / Akamas / Vantage / CloudHealth / AWS Cost Explorer）尚未開始。跟 <a href="/blog/backend/06-reliability/vendors/" data-link-title="可靠性 Vendor 清單" data-link-desc="規劃 CI、壓測、chaos engineering 與 SLO 工具的服務頁撰寫順序與判準">06 vendors</a> 共用部分工具（k6 / JMeter / Gatling / Locust），未來寫 deep article 時需明確區分「驗證流程的工具鏈」（06）跟「效能工程的工具鏈」（09）的角度。對應的 backlog 議題見上方「T1 工具頁」段每個工具頁要回答的核心責任、跟各工具 <code>_index.md</code> 的「預計實作話題」段。</p>
<h2 id="後續候選">後續候選</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>候選工具</th>
          <th>寫作重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Load test</td>
          <td>Artillery、wrk、hey、Grafana k6 Cloud、AWS Distributed Load Testing、BlazeMeter、LoadRunner</td>
          <td>managed runner、跨 region、報表與費用</td>
      </tr>
      <tr>
          <td>Production traffic replay</td>
          <td>shadow traffic pattern、Diffy 類 response diff、proxy mirror variants</td>
          <td>response diff、資料遮罩、side effect 邊界</td>
      </tr>
      <tr>
          <td>Profiling</td>
          <td>GCP Cloud Profiler、AWS CodeGuru Profiler、Azure Application Insights Profiler、New Relic Profiler、Dynatrace Profiling</td>
          <td>雲端整合、採樣成本、profile diff</td>
      </tr>
      <tr>
          <td>Capacity / cost analysis</td>
          <td>Kubecost / OpenCost、CloudZero、CAST AI、Infracost、Harness Cloud Cost Management</td>
          <td>workload-level 成本、rightsizing、IaC cost</td>
      </tr>
      <tr>
          <td>Benchmark / workload model</td>
          <td>YCSB、JMH、pgbench、sysbench</td>
          <td>component benchmark、DB workload、micro vs system boundary</td>
      </tr>
  </tbody>
</table>
<p>Load test 工具頁要保留 workload model 語言。JMeter 適合 protocol 覆蓋與 GUI 驅動團隊，Gatling 適合程式化 scenario 與 JVM 生態，Locust 適合 Python 團隊，Vegeta 適合簡單 HTTP 壓測與 CLI workflow。</p>
<p>Production replay 工具頁要保留安全與副作用邊界。Replay production traffic 會碰到 PII、credential、payment callback、idempotency 與下游配額，因此文章要先定義遮罩、隔離、rate limit 與 stop condition。</p>
<p>Profiling 工具頁要保留長期成本。Continuous profiling 能降低退化定位時間，但會增加採樣成本、儲存成本、敏感資訊治理、symbolization 與 baseline 維護責任。</p>
<p>Capacity / cost analysis 工具頁要保留 owner 與行動閉環。成本報表只有在 tag、label、cost center、service owner、release marker 與 action workflow 對齊後，才會變成容量規劃與成本改善的工程證據。</p>
<p>主流覆蓋檢查的重點是分開 scenario load、quick probe、managed runner、traffic replay、profiling、FinOps 與 component benchmark。k6 / Gatling / Locust 解 scenario；Vegeta / wrk / hey 解 quick HTTP probe；Grafana k6 Cloud / AWS Distributed Load Testing / BlazeMeter 解 managed runner；Pyroscope / Parca / Datadog / cloud profiler 解 profiling；Kubecost / CloudZero / CAST AI 解 workload cost。</p>
<h2 id="工具頁標準章節">工具頁標準章節</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>效能與容量工具頁要補的內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>工具定位</td>
          <td>它是 load test、replay、traffic mirror、profiler、optimizer 還是 FinOps 工具</td>
      </tr>
      <tr>
          <td>本章目標</td>
          <td>讀者能判斷它降低容量未知、production gap、瓶頸定位或成本歸因哪種風險</td>
      </tr>
      <tr>
          <td>最短判讀路徑</td>
          <td>用「缺 workload、缺 saturation、缺 production evidence、缺 cost owner」快速定位</td>
      </tr>
      <tr>
          <td>日常操作與決策形狀</td>
          <td>scenario、runner、threshold、sampling、dashboard、recommendation、owner</td>
      </tr>
      <tr>
          <td>核心取捨表</td>
          <td>同類工具與相鄰工具的機會成本，例如 k6 vs JMeter、Vantage vs Cost Explorer</td>
      </tr>
      <tr>
          <td>進階主題</td>
          <td>distributed runner、shadow traffic、continuous profiling、optimization guardrail</td>
      </tr>
      <tr>
          <td>排錯與失敗快速判讀</td>
          <td>runner bottleneck、side effect、sampling bias、tag gap、forecast drift</td>
      </tr>
      <tr>
          <td>何時改走其他服務</td>
          <td>驗證流程回 06、觀測資料回 04、部署控制回 05、事故處理回 08</td>
      </tr>
      <tr>
          <td>不在本頁內的主題</td>
          <td>完整工具 CLI 教學、供應商 pricing 細節、所有 dashboard 設定</td>
      </tr>
      <tr>
          <td>案例回寫與下一步路由</td>
          <td>回到 09 cases、6.13 regression gate、4.20 evidence package</td>
      </tr>
  </tbody>
</table>
<h2 id="跨-vendor-議題對照">跨 vendor 議題對照</h2>
<p>本模組 15 個 vendor 跨 5 個 sub-category（load test / production replay / continuous profiling / optimization / FinOps）、解不同效能與容量工程問題、不是同類選一。</p>
<table>
  <thead>
      <tr>
          <th>Sub-category</th>
          <th>典型 vendor</th>
          <th>輸出證據</th>
          <th>Production 風險</th>
          <th>操作成本</th>
          <th>Owner</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Load test</td>
          <td>k6 / JMeter / Gatling / Locust / Vegeta</td>
          <td>threshold pass/fail / p95 p99 / throughput</td>
          <td>低（測試環境）</td>
          <td>scenario 維護 / runner 規模 / 測試資料</td>
          <td>Engineering / QA</td>
      </tr>
      <tr>
          <td>Production replay</td>
          <td>GoReplay / Service Mesh Mirroring / AWS VPC</td>
          <td>response diff / shadow load</td>
          <td>高（PII / side effect / 配額）</td>
          <td>masking / isolation / rate limit</td>
          <td>SRE + Security</td>
      </tr>
      <tr>
          <td>Continuous profiling</td>
          <td>Datadog Profiler / Pyroscope / Parca</td>
          <td>flame graph diff / regression detection</td>
          <td>中（採樣 overhead）</td>
          <td>symbolization / storage / baseline 維護</td>
          <td>Engineering</td>
      </tr>
      <tr>
          <td>Optimization</td>
          <td>Akamas</td>
          <td>recommendation / SLO-constrained config</td>
          <td>中（autopilot rollout）</td>
          <td>objective model / approval workflow</td>
          <td>SRE + FinOps</td>
      </tr>
      <tr>
          <td>FinOps</td>
          <td>Vantage / CloudHealth / AWS Cost Explorer</td>
          <td>cost report / forecast / rightsizing</td>
          <td>無(reporting)</td>
          <td>tag governance / owner mapping / cadence</td>
          <td>FinOps + Eng lead</td>
      </tr>
  </tbody>
</table>
<p>對照表的用途有三：</p>
<ul>
<li>對齊 sub-category 跟問題節點：缺 saturation → load test；缺 production gap → replay；缺 瓶頸定位 → profiler；缺 capacity / cost 閉環 → optimizer + FinOps</li>
<li>評估 production 風險：load test 安全、replay / mirror 要明示 side effect 邊界、profiler 要看採樣 overhead、FinOps reporting 無風險</li>
<li>對齊 owner：load test 多 Engineering / QA、replay 多 SRE + Security、optimization + FinOps 跨團隊</li>
</ul>
<p>下面 5 段把對照表的 sub-category 展開、每段帶 vendor 選型判讀。</p>
<h3 id="load-testk6--jmeter--gatling--locust--vegeta">Load test（k6 / JMeter / Gatling / Locust / Vegeta）</h3>
<p>Load test 是 09 模組的主要 saturation 探測工具、跟 <a href="/blog/backend/06-reliability/vendors/" data-link-title="可靠性 Vendor 清單" data-link-desc="規劃 CI、壓測、chaos engineering 與 SLO 工具的服務頁撰寫順序與判準">06 reliability load test 章節</a> 同 vendor 但角度不同 — 06 看 CI gate / regression evidence、09 看 capacity planning / saturation discovery / peak event readiness。</p>
<p>選型判讀：CI-first JS → <a href="/blog/backend/09-performance-capacity/vendors/k6/" data-link-title="k6" data-link-desc="用 scriptable scenario 建立 API、protocol 與 CI 友善壓測的效能工程工具">k6</a>；JVM + 複雜 scenario → <a href="/blog/backend/09-performance-capacity/vendors/gatling/" data-link-title="Gatling" data-link-desc="用 JVM DSL、simulation 與 injection profile 表達複雜 scenario 的效能工程工具">Gatling</a>；既有 .jmx 資產 → <a href="/blog/backend/09-performance-capacity/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="用 GUI、plugin 與多 protocol sampler 承接企業壓測資產的效能工程工具">JMeter</a>；Python custom behavior → <a href="/blog/backend/09-performance-capacity/vendors/locust/" data-link-title="Locust" data-link-desc="用 Python user behavior 與 distributed worker 表達高自訂負載模型的效能工程工具">Locust</a>；快速 HTTP probe / fixed rate → <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>（單一 HTTP attack 模式、不適合多 step scenario）。</p>
<h3 id="production-replaygoreplay--service-mesh-mirroring--aws-vpc-traffic-mirroring">Production replay（GoReplay / Service Mesh Mirroring / AWS VPC Traffic Mirroring）</h3>
<p>Production replay 把實際流量重播到 shadow target、補 load test 的「人工 scenario 跟真實流量差距」缺口。<strong>GoReplay</strong> 應用層 HTTP traffic capture + replay；<strong>Service Mesh Mirroring</strong> 用 Envoy / Istio proxy mirror、適合 K8s 內部；<strong>AWS VPC Traffic Mirroring</strong> L4 封包鏡像、適合非 HTTP / 低侵入。</p>
<p>選型判讀：HTTP application 層 → GoReplay；K8s 內 service mesh → Service Mesh Mirroring；非 HTTP / 跨 VPC / 低侵入 → AWS VPC。共同議題：PII 遮罩、idempotency boundary、downstream 配額 — 不可省。</p>
<h3 id="continuous-profilingdatadog-continuous-profiler--pyroscope--parca">Continuous profiling（Datadog Continuous Profiler / Pyroscope / Parca）</h3>
<p>Continuous profiling 在 production 持續採樣、退化時可 profile diff 找瓶頸。<strong>Datadog Continuous Profiler</strong> SaaS APM 整合、deploy marker 自動關聯；<strong>Pyroscope</strong> OSS / Grafana 生態、可自管或 Grafana Cloud；<strong>Parca</strong> eBPF-based、infrastructure-wide profile（不需 application instrumentation）。</p>
<p>選型判讀：已用 Datadog APM → Datadog Profiler；Grafana 生態 / OSS → Pyroscope；不想 instrument application + eBPF 友善 → Parca。共同議題：採樣 overhead（CPU / memory）、symbolization、storage cost、敏感資訊。</p>
<h3 id="optimizationakamas">Optimization（Akamas）</h3>
<p>Optimization 把 workload + SLO + cost 放進同一閉環、產出 configuration recommendation。<strong>Akamas</strong> 是 09 模組唯一 optimizer vendor、適合已有可量測 workload 跟成本壓力的服務。</p>
<p>選型判讀：Kubernetes rightsizing + runtime tuning + cost target → Akamas；純 FinOps reporting 不夠（要主動建議）→ Akamas。Akamas 不替代 FinOps tool — Vantage / CloudHealth 看歷史成本、Akamas 提產出未來 recommendation。</p>
<h3 id="finopsvantage--cloudhealth--aws-cost-explorer">FinOps（Vantage / CloudHealth / AWS Cost Explorer）</h3>
<p>FinOps 提供 cost visibility + forecast + allocation。<strong>Vantage</strong> Kubernetes cost + forecast 友善的 startup-friendly 平台；<strong>CloudHealth</strong> enterprise FinOps governance + policy + chargeback；<strong>AWS Cost Explorer</strong> AWS-native cost analysis baseline（免費、限 AWS）。</p>
<p>選型判讀：純 AWS 啟動 → Cost Explorer；多雲 + startup / mid-size → Vantage；enterprise + 多 BU chargeback → CloudHealth；K8s workload cost → Kubecost / OpenCost（不在本表、後續候選）。共同議題：tag governance、cost center mapping、cadence。</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/#%e8%b2%ab%e7%a9%bf%e5%bc%8f%e6%a1%88%e4%be%8bcheckout-%e6%9c%8d%e5%8b%99%e6%bc%94%e9%80%b2" data-link-title="Backend 服務實務指南" data-link-desc="用跨語言教學路線整理資料庫、快取、訊息佇列、觀測、部署、可靠性、資安、事故與容量等後端服務能力">Checkout 服務演進</a></li>
<li>平行：<a href="/blog/backend/06-reliability/vendors/" data-link-title="可靠性 Vendor 清單" data-link-desc="規劃 CI、壓測、chaos engineering 與 SLO 工具的服務頁撰寫順序與判準">06 Reliability vendors</a> — 06 從驗證流程看工具，09 從容量量化與效能工程看工具</li>
</ul>
]]></content:encoded></item><item><title>模組九案例正文</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/</guid><description>&lt;p>這個資料夾的核心責任是把雲端服務商公開的高併發實戰案例轉成可回寫主章判讀的案例正文。資料來源以 &lt;a href="https://aws.amazon.com/solutions/case-studies/">AWS Customer Success Stories&lt;/a>、&lt;a href="https://cloud.google.com/customers">Google Cloud Customer Stories&lt;/a> 與 &lt;a href="https://customers.microsoft.com/">Azure Customer Case Studies&lt;/a> 為主，因為這層案例同時提供具體流量數字、實際使用的服務組合與工程決策路徑，比一般 engineering blog 更接近實戰判讀。&lt;/p>
&lt;p>跟模組七案例庫一樣、本資料夾不只服務 09 主章閱讀、也是 01-05 模組寫作時的證據來源。當寫 01 資料庫章節需要說明「Aurora 真實流量下能撐多少」、當寫 02 快取章節需要說明「ElastiCache 在持續成長服務的角色」時、可以直接回查本資料夾相應案例。&lt;/p>
&lt;h2 id="跟-06-案例庫的差異">跟 06 案例庫的差異&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/" data-link-title="可靠性服務案例庫" data-link-desc="按服務組織的 SRE 實踐案例庫，累積架構脈絡與工程文化">06 cases&lt;/a>&lt;/th>
 &lt;th>09 cases（本資料夾）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>來源&lt;/td>
 &lt;td>大企業工程部落格（Google SRE Book、Netflix Tech Blog、Shopify 等）&lt;/td>
 &lt;td>AWS / GCP / Azure 官方 customer case studies&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>證據型態&lt;/td>
 &lt;td>方法論敘事（SLO 政策、chaos hypothesis、failure mode）&lt;/td>
 &lt;td>具體流量、實例、延遲、成本數字（QPS、msg/sec、p95、cost ratio）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>讀法&lt;/td>
 &lt;td>失敗模式如何被驗證&lt;/td>
 &lt;td>容量量化實踐：什麼配置撐多少、加多少、成本曲線怎麼走&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>教學責任&lt;/td>
 &lt;td>把驗證流程制度化&lt;/td>
 &lt;td>把容量地圖具體化、把成本邊界量化&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>兩層案例互補。06 教讀者「怎麼預先驗證失敗會被擋住」、09 教讀者「實際配置在實際流量下會怎麼跑」。同一個服務可以同時出現在兩處、但讀法不同。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;p>每個案例標 tag 讓多個主章可以反查。tag 維度：&lt;strong>雲商&lt;/strong>（aws / gcp / azure）、&lt;strong>服務維度&lt;/strong>（db-oltp / db-kv / cache / mq-stream / compute / global-edge / latency / data-architecture）、&lt;strong>負載形狀&lt;/strong>（predictable-peak / event-peak / surge / flash-sale-spike / low-latency-sustained / sustained-growth）。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>雲商&lt;/th>
 &lt;th>服務維度&lt;/th>
 &lt;th>負載形狀&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>AWS Prime Day 2025 dogfood&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>multi&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gr8-tech-ai-predicted-betting-peak/" data-link-title="9.C2 GR8 Tech：AI 預測式自動擴容下的體育博彩高峰" data-link-desc="AI 預測 &amp;#43; EKS 自動擴容怎麼在 25ms p95 下承載 54000 TPS 體育博彩峰值流量">9.C2&lt;/a>&lt;/td>
 &lt;td>GR8 Tech 體育博彩 AI 預測式擴容&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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 &amp;#43; z1d 而不是自動擴容 — 延遲敏感型負載的容量取捨">9.C3&lt;/a>&lt;/td>
 &lt;td>Coinbase 超低延遲交易&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>latency&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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 流量 &amp;#43;50% 不影響延遲">9.C4&lt;/a>&lt;/td>
 &lt;td>DraftKings Aurora 100 萬 ops/min&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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 &amp;#43; 500 萬 writes/sec、99.999% 可用性的廣告事件量測">9.C5&lt;/a>&lt;/td>
 &lt;td>Amazon Ads DynamoDB 9000 萬 RPS&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>Tinder ElastiCache 配對引擎&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>cache&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/lyft-microservice-eight-x-peak/" data-link-title="9.C7 Lyft：100&amp;#43; 微服務在 8 倍峰值下的 Auto Scaling" data-link-desc="Lyft 用 AWS Auto Scaling 跨 100&amp;#43; 個微服務承載 8 倍峰值流量、跨 200&amp;#43; 城市">9.C7&lt;/a>&lt;/td>
 &lt;td>Lyft 100+ 微服務 8x 峰值&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>Niantic Pokémon GO 50x 突發&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>surge&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9&lt;/a>&lt;/td>
 &lt;td>Spotify Kafka → Pub/Sub 遷移&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>mq-stream&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10&lt;/a>&lt;/td>
 &lt;td>Cloud Spanner 10 億 req/sec&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11&lt;/a>&lt;/td>
 &lt;td>Minecraft Earth Cosmos DB 全球&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>surge&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12&lt;/a>&lt;/td>
 &lt;td>Riot Games 246 EKS clusters&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/hotstar-ipl-eighteen-million-concurrent/" data-link-title="9.C13 Disney&amp;#43; Hotstar：IPL 板球決賽 1860 萬人同時直播" data-link-desc="Hotstar 在 IPL 板球決賽創下 1860 萬同時觀看的全球直播紀錄、CDN 與全球邊緣容量極限">9.C13&lt;/a>&lt;/td>
 &lt;td>Hotstar IPL 1860 萬同時觀看&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>global-edge&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>Standard Chartered Aurora 4000 TPS&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/tixcraft-ticketing-flash-sale-spike/" data-link-title="9.C15 拓元 Tixcraft：售票搶購的瞬間爆量架構" data-link-desc="拓元用 DynamoDB 當寫入緩衝 &amp;#43; 傳統伺服器當慢速消費者、承受 100K&amp;#43; 同時選位 &amp;#43; 30 秒從 6 台擴到 800 台">9.C15&lt;/a>&lt;/td>
 &lt;td>拓元 Tixcraft 售票搶購&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>flash-sale-spike&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/seatgeek-virtual-waiting-room/" data-link-title="9.C16 SeatGeek：DynamoDB &amp;#43; Lambda 打造的虛擬等候室" data-link-desc="SeatGeek 用 DynamoDB 4 張表 &amp;#43; Lambda Bouncer 實作 flash-sale 限流排隊機制、取代第三方 waiting room 服務">9.C16&lt;/a>&lt;/td>
 &lt;td>SeatGeek Virtual Waiting Room&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>flash-sale-spike&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>BookMyShow 印度年售 2 億張票&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>flash-sale-spike&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>Zoom COVID 30x DAU 突發&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>surge&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &amp;#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &amp;#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19&lt;/a>&lt;/td>
 &lt;td>Capcom 遊戲後端 DynamoDB + EKS&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20&lt;/a>&lt;/td>
 &lt;td>Zomato TiDB → DynamoDB 4x 吞吐&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21&lt;/a>&lt;/td>
 &lt;td>ASOS Cosmos DB Black Friday&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&amp;#43; 商品 &amp;#43; 16,000&amp;#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22&lt;/a>&lt;/td>
 &lt;td>Wayfair GCP burst capacity&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &amp;#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23&lt;/a>&lt;/td>
 &lt;td>Netflix Aurora 統一 +75% 效能&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &amp;#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24&lt;/a>&lt;/td>
 &lt;td>Genesys 99.999% 跨 15 region&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>Tubi ML feature store sub-10ms p99&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>cache&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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&lt;/a>&lt;/td>
 &lt;td>PayPay 行動支付每日 3 億訊息&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&amp;#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&amp;#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">9.C27&lt;/a>&lt;/td>
 &lt;td>Disney+ 觀看歷史每日數十億動作&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/fanduel-dual-peak-betting-streaming/" data-link-title="9.C28 FanDuel：體育直播 &amp;#43; 投注的雙重峰值" data-link-desc="FanDuel 3.5M MAU、Super Bowl 期間擴容 5-10 倍、用 AWS Local Zones &amp;#43; Wavelength &amp;#43; Outposts 處理 20&amp;#43; 州的雙重峰值">9.C28&lt;/a>&lt;/td>
 &lt;td>FanDuel 直播 + 投注雙重峰值&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>event-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/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 &amp;#43; AWS Media Services 撐 30 channels live &amp;#43; 5M MAU、工程工時下降 90%">9.C29&lt;/a>&lt;/td>
 &lt;td>NTT DOCOMO Lemino 5M MAU / 3 個月&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-kv&lt;/td>
 &lt;td>predictable-peak&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30&lt;/a>&lt;/td>
 &lt;td>Microsoft 365 MongoDB → Cosmos DB&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &amp;#43; 1.5 億商品、用 GCP Vertex AI Search &amp;#43; BigQuery 提供近即時搜尋與分析">9.C31&lt;/a>&lt;/td>
 &lt;td>Mercado Libre LatAm Vertex + BigQuery&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>data-architecture&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/" data-link-title="9.C32 Clearent：Azure SQL Hyperscale 撐每年 5 億筆支付交易" data-link-desc="Clearent 在 Azure SQL Hyperscale 上處理每年 5 億筆支付交易、autoscale &amp;#43; 微服務架構">9.C32&lt;/a>&lt;/td>
 &lt;td>Clearent Azure SQL Hyperscale 5 億 txn/年&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>db-oltp&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/" data-link-title="9.C33 Maersk &amp;#43; Bosch：傳統產業在 Azure AKS 上的微服務治理" data-link-desc="全球海運 Maersk 跟 Bosch 智慧建築把 AKS 當微服務治理基礎、釋放工程資源做業務功能">9.C33&lt;/a>&lt;/td>
 &lt;td>Maersk + Bosch Azure AKS&lt;/td>
 &lt;td>azure&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &amp;#43; 1000 Pods/sec 創建吞吐">9.C34&lt;/a>&lt;/td>
 &lt;td>GCP 130K-node GKE cluster (AI)&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>compute&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/" data-link-title="9.C35 Snap：GCP &amp;#43; KeyDB 在 multi-cloud 架構下的低延遲快取" data-link-desc="Snap 用 GCP 上的 KeyDB cluster 減少跨 cloud cache 延遲、用 TPU 訓練廣告推薦模型">9.C35&lt;/a>&lt;/td>
 &lt;td>Snap GCP KeyDB cross-cloud cache&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>cache&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/" data-link-title="9.C36 Coinbase：MongoDB 撐 Ruby 單體 &amp;#43; 1.5M reads/sec identity 服務" data-link-desc="Coinbase 以 MongoDB 為主資料層、自建 mongobetween connection proxy、users 服務在加密貨幣 surge 時撐 1.5M reads/sec">9.C36&lt;/a>&lt;/td>
 &lt;td>Coinbase MongoDB 1.5M reads/sec&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-document&lt;/td>
 &lt;td>low-latency-sustained&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37&lt;/a>&lt;/td>
 &lt;td>Forbes 自管 MongoDB → Atlas on GCP&lt;/td>
 &lt;td>gcp&lt;/td>
 &lt;td>db-document&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/" data-link-title="9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction" data-link-desc="Toyota Connected 用 MongoDB Atlas 撐 Safety Connect 900 萬車、月 180 億 transaction、緊急訊號 3 秒內到 agent">9.C38&lt;/a>&lt;/td>
 &lt;td>Toyota Connected MongoDB 月 180 億 txn&lt;/td>
 &lt;td>aws&lt;/td>
 &lt;td>db-document&lt;/td>
 &lt;td>sustained-growth&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="主章寫作時的反查路由">主章寫作時的反查路由&lt;/h2>
&lt;p>當寫 01-05 模組的具體服務章節需要援引「真實流量下會發生什麼」、查下表找對應案例。&lt;/p></description><content:encoded><![CDATA[<p>這個資料夾的核心責任是把雲端服務商公開的高併發實戰案例轉成可回寫主章判讀的案例正文。資料來源以 <a href="https://aws.amazon.com/solutions/case-studies/">AWS Customer Success Stories</a>、<a href="https://cloud.google.com/customers">Google Cloud Customer Stories</a> 與 <a href="https://customers.microsoft.com/">Azure Customer Case Studies</a> 為主，因為這層案例同時提供具體流量數字、實際使用的服務組合與工程決策路徑，比一般 engineering blog 更接近實戰判讀。</p>
<p>跟模組七案例庫一樣、本資料夾不只服務 09 主章閱讀、也是 01-05 模組寫作時的證據來源。當寫 01 資料庫章節需要說明「Aurora 真實流量下能撐多少」、當寫 02 快取章節需要說明「ElastiCache 在持續成長服務的角色」時、可以直接回查本資料夾相應案例。</p>
<h2 id="跟-06-案例庫的差異">跟 06 案例庫的差異</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th><a href="/blog/backend/06-reliability/cases/" data-link-title="可靠性服務案例庫" data-link-desc="按服務組織的 SRE 實踐案例庫，累積架構脈絡與工程文化">06 cases</a></th>
          <th>09 cases（本資料夾）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>來源</td>
          <td>大企業工程部落格（Google SRE Book、Netflix Tech Blog、Shopify 等）</td>
          <td>AWS / GCP / Azure 官方 customer case studies</td>
      </tr>
      <tr>
          <td>證據型態</td>
          <td>方法論敘事（SLO 政策、chaos hypothesis、failure mode）</td>
          <td>具體流量、實例、延遲、成本數字（QPS、msg/sec、p95、cost ratio）</td>
      </tr>
      <tr>
          <td>讀法</td>
          <td>失敗模式如何被驗證</td>
          <td>容量量化實踐：什麼配置撐多少、加多少、成本曲線怎麼走</td>
      </tr>
      <tr>
          <td>教學責任</td>
          <td>把驗證流程制度化</td>
          <td>把容量地圖具體化、把成本邊界量化</td>
      </tr>
  </tbody>
</table>
<p>兩層案例互補。06 教讀者「怎麼預先驗證失敗會被擋住」、09 教讀者「實際配置在實際流量下會怎麼跑」。同一個服務可以同時出現在兩處、但讀法不同。</p>
<h2 id="案例列表">案例列表</h2>
<p>每個案例標 tag 讓多個主章可以反查。tag 維度：<strong>雲商</strong>（aws / gcp / azure）、<strong>服務維度</strong>（db-oltp / db-kv / cache / mq-stream / compute / global-edge / latency / data-architecture）、<strong>負載形狀</strong>（predictable-peak / event-peak / surge / flash-sale-spike / low-latency-sustained / sustained-growth）。</p>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>雲商</th>
          <th>服務維度</th>
          <th>負載形狀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><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</a></td>
          <td>AWS Prime Day 2025 dogfood</td>
          <td>aws</td>
          <td>multi</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>GR8 Tech 體育博彩 AI 預測式擴容</td>
          <td>aws</td>
          <td>compute</td>
          <td>event-peak</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Coinbase 超低延遲交易</td>
          <td>aws</td>
          <td>latency</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>DraftKings Aurora 100 萬 ops/min</td>
          <td>aws</td>
          <td>db-oltp</td>
          <td>event-peak</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</a></td>
          <td>Amazon Ads DynamoDB 9000 萬 RPS</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Tinder ElastiCache 配對引擎</td>
          <td>aws</td>
          <td>cache</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Lyft 100+ 微服務 8x 峰值</td>
          <td>aws</td>
          <td>compute</td>
          <td>event-peak</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Niantic Pokémon GO 50x 突發</td>
          <td>gcp</td>
          <td>compute</td>
          <td>surge</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9</a></td>
          <td>Spotify Kafka → Pub/Sub 遷移</td>
          <td>gcp</td>
          <td>mq-stream</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10</a></td>
          <td>Cloud Spanner 10 億 req/sec</td>
          <td>gcp</td>
          <td>db-oltp</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11</a></td>
          <td>Minecraft Earth Cosmos DB 全球</td>
          <td>azure</td>
          <td>db-kv</td>
          <td>surge</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12</a></td>
          <td>Riot Games 246 EKS clusters</td>
          <td>aws</td>
          <td>compute</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Hotstar IPL 1860 萬同時觀看</td>
          <td>aws</td>
          <td>global-edge</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Standard Chartered Aurora 4000 TPS</td>
          <td>aws</td>
          <td>db-oltp</td>
          <td>sustained-growth</td>
      </tr>
      <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</a></td>
          <td>拓元 Tixcraft 售票搶購</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>flash-sale-spike</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>SeatGeek Virtual Waiting Room</td>
          <td>aws</td>
          <td>compute</td>
          <td>flash-sale-spike</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>BookMyShow 印度年售 2 億張票</td>
          <td>aws</td>
          <td>data-architecture</td>
          <td>flash-sale-spike</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>Zoom COVID 30x DAU 突發</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>surge</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19</a></td>
          <td>Capcom 遊戲後端 DynamoDB + EKS</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20</a></td>
          <td>Zomato TiDB → DynamoDB 4x 吞吐</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21</a></td>
          <td>ASOS Cosmos DB Black Friday</td>
          <td>azure</td>
          <td>db-kv</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22</a></td>
          <td>Wayfair GCP burst capacity</td>
          <td>gcp</td>
          <td>data-architecture</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23</a></td>
          <td>Netflix Aurora 統一 +75% 效能</td>
          <td>aws</td>
          <td>db-oltp</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24</a></td>
          <td>Genesys 99.999% 跨 15 region</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>low-latency-sustained</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</a></td>
          <td>Tubi ML feature store sub-10ms p99</td>
          <td>aws</td>
          <td>cache</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>PayPay 行動支付每日 3 億訊息</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/disney-plus-content-metadata/" data-link-title="9.C27 Disney&#43;：DynamoDB 撐每日數十億動作的觀看歷史" data-link-desc="Disney&#43; 用 DynamoDB 撐每日數十億動作的觀看歷史、watchlist、播放進度等串流 metadata">9.C27</a></td>
          <td>Disney+ 觀看歷史每日數十億動作</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><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</a></td>
          <td>FanDuel 直播 + 投注雙重峰值</td>
          <td>aws</td>
          <td>compute</td>
          <td>event-peak</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</a></td>
          <td>NTT DOCOMO Lemino 5M MAU / 3 個月</td>
          <td>aws</td>
          <td>db-kv</td>
          <td>predictable-peak</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30</a></td>
          <td>Microsoft 365 MongoDB → Cosmos DB</td>
          <td>azure</td>
          <td>data-architecture</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/mercado-libre-latam-bigquery-vertex/" data-link-title="9.C31 Mercado Libre：LatAm 電商在 GCP 上用 Vertex AI 搜尋 1.5 億商品" data-link-desc="Mercado Libre 1 億客戶 &#43; 1.5 億商品、用 GCP Vertex AI Search &#43; BigQuery 提供近即時搜尋與分析">9.C31</a></td>
          <td>Mercado Libre LatAm Vertex + BigQuery</td>
          <td>gcp</td>
          <td>data-architecture</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/clearent-azure-sql-hyperscale-payments/" data-link-title="9.C32 Clearent：Azure SQL Hyperscale 撐每年 5 億筆支付交易" data-link-desc="Clearent 在 Azure SQL Hyperscale 上處理每年 5 億筆支付交易、autoscale &#43; 微服務架構">9.C32</a></td>
          <td>Clearent Azure SQL Hyperscale 5 億 txn/年</td>
          <td>azure</td>
          <td>db-oltp</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/maersk-bosch-azure-aks/" data-link-title="9.C33 Maersk &#43; Bosch：傳統產業在 Azure AKS 上的微服務治理" data-link-desc="全球海運 Maersk 跟 Bosch 智慧建築把 AKS 當微服務治理基礎、釋放工程資源做業務功能">9.C33</a></td>
          <td>Maersk + Bosch Azure AKS</td>
          <td>azure</td>
          <td>compute</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/gcp-130k-node-gke-cluster/" data-link-title="9.C34 GCP：130,000-node GKE cluster 的工程極限" data-link-desc="Google 用單一 GKE control plane 跑 13 萬個 node、AI workload &#43; 1000 Pods/sec 創建吞吐">9.C34</a></td>
          <td>GCP 130K-node GKE cluster (AI)</td>
          <td>gcp</td>
          <td>compute</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/snap-gcp-keydb-cross-cloud/" data-link-title="9.C35 Snap：GCP &#43; KeyDB 在 multi-cloud 架構下的低延遲快取" data-link-desc="Snap 用 GCP 上的 KeyDB cluster 減少跨 cloud cache 延遲、用 TPU 訓練廣告推薦模型">9.C35</a></td>
          <td>Snap GCP KeyDB cross-cloud cache</td>
          <td>gcp</td>
          <td>cache</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/" data-link-title="9.C36 Coinbase：MongoDB 撐 Ruby 單體 &#43; 1.5M reads/sec identity 服務" data-link-desc="Coinbase 以 MongoDB 為主資料層、自建 mongobetween connection proxy、users 服務在加密貨幣 surge 時撐 1.5M reads/sec">9.C36</a></td>
          <td>Coinbase MongoDB 1.5M reads/sec</td>
          <td>aws</td>
          <td>db-document</td>
          <td>low-latency-sustained</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37</a></td>
          <td>Forbes 自管 MongoDB → Atlas on GCP</td>
          <td>gcp</td>
          <td>db-document</td>
          <td>sustained-growth</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/" data-link-title="9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction" data-link-desc="Toyota Connected 用 MongoDB Atlas 撐 Safety Connect 900 萬車、月 180 億 transaction、緊急訊號 3 秒內到 agent">9.C38</a></td>
          <td>Toyota Connected MongoDB 月 180 億 txn</td>
          <td>aws</td>
          <td>db-document</td>
          <td>sustained-growth</td>
      </tr>
  </tbody>
</table>
<h2 id="主章寫作時的反查路由">主章寫作時的反查路由</h2>
<p>當寫 01-05 模組的具體服務章節需要援引「真實流量下會發生什麼」、查下表找對應案例。</p>
<h3 id="寫-01-資料庫模組-時">寫 <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">01 資料庫模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>OLTP 高 TPS 容量</td>
          <td><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</a> / <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> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a></td>
      </tr>
      <tr>
          <td>KV 極高吞吐</td>
          <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> / <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</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</a> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> / <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS</a></td>
      </tr>
      <tr>
          <td>全球一致性 OLTP</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a>（multi-region active-active）</td>
      </tr>
      <tr>
          <td>Transaction boundary</td>
          <td><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</a>（RAFT、強順序）</td>
      </tr>
      <tr>
          <td>Hot partition / 分片</td>
          <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> / <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</a> / <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>
      </tr>
      <tr>
          <td>DB 作為寫入緩衝</td>
          <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>（DynamoDB 緩衝 + 傳統 server 慢速消費）</td>
      </tr>
      <tr>
          <td>DB 種類整合 / consolidation</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix Aurora</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys DynamoDB 為預設</a></td>
      </tr>
      <tr>
          <td>Migration 與合規</td>
          <td><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> / <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a> / <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato TiDB → DynamoDB</a> / <a href="/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37 Forbes 自管 MongoDB → Atlas</a></td>
      </tr>
      <tr>
          <td>多事件 ticketing 資料層</td>
          <td><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</a> / <a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair</a></td>
      </tr>
      <tr>
          <td>Document database / MongoDB</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/coinbase-mongodb-document-platform/" data-link-title="9.C36 Coinbase：MongoDB 撐 Ruby 單體 &#43; 1.5M reads/sec identity 服務" data-link-desc="Coinbase 以 MongoDB 為主資料層、自建 mongobetween connection proxy、users 服務在加密貨幣 surge 時撐 1.5M reads/sec">9.C36 Coinbase</a>（1.5M reads/sec、connection proxy）/ <a href="/blog/backend/09-performance-capacity/cases/forbes-mongodb-atlas-multi-cloud-migration/" data-link-title="9.C37 Forbes：自管 MongoDB → Atlas on GCP、build 時間 25 → 9 分鐘" data-link-desc="Forbes 把自管 MongoDB 遷到 Atlas on Google Cloud、6 個月完成、build 25 → 9 分鐘、120M 不重複訪客單月承接">9.C37 Forbes</a>（自管 → Atlas）/ <a href="/blog/backend/09-performance-capacity/cases/toyota-connected-mongodb-telematics-iot/" data-link-title="9.C38 Toyota Connected：MongoDB Atlas 撐 900 萬車輛 telematics、月 180 億 transaction" data-link-desc="Toyota Connected 用 MongoDB Atlas 撐 Safety Connect 900 萬車、月 180 億 transaction、緊急訊號 3 秒內到 agent">9.C38 Toyota Connected</a>（IoT telematics）/ <a href="/blog/backend/09-performance-capacity/cases/microsoft-365-cosmos-db-analytics/" data-link-title="9.C30 Microsoft 365：從 MongoDB 遷移到 Cosmos DB 的分析平台" data-link-desc="Microsoft 365 把使用分析平台從 MongoDB 遷移到 Cosmos DB、planet-scale 全球分散式分析">9.C30 Microsoft 365</a>（遷到 Cosmos DB）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-02-快取模組-時">寫 <a href="/blog/backend/02-cache-redis/" data-link-title="模組二：快取與 Redis" data-link-desc="整理快取策略、Redis 資料型別與分散式狀態輔助能力">02 快取模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>高吞吐 cache layer</td>
          <td><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</a></td>
      </tr>
      <tr>
          <td>Cache as SoT</td>
          <td><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</a>（配對快取為主要服務面）</td>
      </tr>
      <tr>
          <td>ML feature store</td>
          <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>（sub-10ms p99）</td>
      </tr>
      <tr>
          <td>Sub-ms latency 需求</td>
          <td><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</a>（不只 cache、整體 sub-ms 設計）</td>
      </tr>
      <tr>
          <td>Cache stampede</td>
          <td><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 Pokémon GO surge</a>（50x 突發必觸 stampede 風險）</td>
      </tr>
      <tr>
          <td>Cache hierarchy / 多層 cache</td>
          <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>（L1 in-process + L2 cache + L3 store）</td>
      </tr>
      <tr>
          <td>Cache vs durable store 取捨</td>
          <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>（從 ScyllaDB 遷到 ElastiCache）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-03-訊息佇列模組-時">寫 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 訊息佇列模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>大規模事件交付</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a></td>
      </tr>
      <tr>
          <td>Broker 自管 vs managed</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a></td>
      </tr>
      <tr>
          <td>極端 message volume</td>
          <td><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 AWS Prime Day</a>（SQS 1.66 億 msg/sec）</td>
      </tr>
      <tr>
          <td>Queue 作為緩衝吸收洪峰</td>
          <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>（DynamoDB 模仿 queue 行為）</td>
      </tr>
      <tr>
          <td>Migration playbook</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a></td>
      </tr>
  </tbody>
</table>
<h3 id="寫-04-可觀測性模組-時">寫 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SLO 量測 baseline</td>
          <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>（99.999% availability）/ <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a>（99.999% 12 個月達成）</td>
      </tr>
      <tr>
          <td>Latency budget 反推</td>
          <td><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</a> / <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot</a> / <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>（ML p99 分解）</td>
      </tr>
      <tr>
          <td>Saturation 訊號</td>
          <td><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>（25ms p95 是業務 KPI）</td>
      </tr>
      <tr>
          <td>多地區 metric 治理</td>
          <td><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</a> / <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a>（15 主 region）</td>
      </tr>
      <tr>
          <td>SLO 演進 / surge 後校準</td>
          <td><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</a>（30x 後 baseline 永久上移）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-05-部署平台模組-時">寫 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>K8s multi-cluster</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a>（多遊戲共用 vs 多 cluster）</td>
      </tr>
      <tr>
          <td>Container vs VM</td>
          <td><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 Pokémon GO</a></td>
      </tr>
      <tr>
          <td>微服務切分</td>
          <td><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</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a>（微服務私有 store）</td>
      </tr>
      <tr>
          <td>Autoscaling 策略</td>
          <td><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</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</a> / <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>（30 分鐘擴 130 倍）</td>
      </tr>
      <tr>
          <td>Global edge / CDN</td>
          <td><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</a> / <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>（CloudFront 卸載靜態）</td>
      </tr>
      <tr>
          <td>限流 / Virtual Waiting Room</td>
          <td><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</a>（明確排隊）/ <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>
      </tr>
      <tr>
          <td>Hybrid cloud / burst</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair</a>（on-prem + GCP burst）</td>
      </tr>
      <tr>
          <td>Control plane vs Data plane</td>
          <td><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</a>（DynamoDB 撐 metadata、影音另走 edge）</td>
      </tr>
  </tbody>
</table>
<h3 id="寫-00-服務選型模組-時">寫 <a href="/blog/backend/00-service-selection/" data-link-title="模組零：後端服務選型" data-link-desc="從需求類型判斷資料庫、快取、訊息佇列、觀測與部署平台的選型方向">00 服務選型模組</a> 時</h3>
<table>
  <thead>
      <tr>
          <th>議題</th>
          <th>對應案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Traffic / data scale</td>
          <td>全部案例都可作對標、特別是 <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</a> / <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</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10</a></td>
      </tr>
      <tr>
          <td>合規 / 受監管</td>
          <td><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></td>
      </tr>
      <tr>
          <td>Vendor 戰略支援</td>
          <td><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 Pokémon GO</a>（Google CRE）</td>
      </tr>
      <tr>
          <td>成本曲線</td>
          <td><a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot Games</a>（$10M 年省）</td>
      </tr>
  </tbody>
</table>
<h2 id="按負載形狀的讀法引導">按負載形狀的讀法引導</h2>
<p>當讀者遇到具體容量問題卡住時、先判斷負載屬於哪一種形狀、再選對應案例。</p>
<ol>
<li><strong>可預期極端峰值</strong>（年度活動、預售、賽事決賽）→ <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</a> / <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</a> / <a href="/blog/backend/09-performance-capacity/cases/asos-cosmos-db-black-friday/" data-link-title="9.C21 ASOS：Cosmos DB 在 Black Friday 撐 1.67 億請求" data-link-desc="ASOS 在 2016 Black Friday 用 Azure Cosmos DB 撐 24 小時 1.67 億請求、3500 req/sec、48ms 平均延遲">9.C21 ASOS Black Friday</a> / <a href="/blog/backend/09-performance-capacity/cases/wayfair-gcp-burst-capacity/" data-link-title="9.C22 Wayfair：用 GCP 提供 Way Day / Black Friday 的 burst capacity" data-link-desc="Wayfair 22M&#43; 商品 &#43; 16,000&#43; 供應商、用 GCP 補充 on-prem data center 在峰值事件的 burst capacity">9.C22 Wayfair</a></li>
<li><strong>事件型不可預期峰值</strong>（賽事高潮、突發新聞、KOL 推廣）→ <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> / <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</a> / <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</a></li>
<li><strong>突發遠超預期的 surge</strong>（產品爆紅、病毒式擴散、結構性外部事件）→ <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 Pokémon GO</a>（產品爆紅、暫時）/ <a href="/blog/backend/09-performance-capacity/cases/minecraft-earth-cosmos-db-global/" data-link-title="9.C11 Minecraft Earth：Azure Cosmos DB 上的全球分散式 AR 遊戲" data-link-desc="Minecraft Earth 用 Cosmos DB 跨地區分散、測試到 100 萬 RU/s 仍維持承諾延遲">9.C11 Minecraft Earth</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</a>（COVID 結構性永久）</li>
<li><strong>flash-sale 瞬間爆量</strong>（售票開賣、報名活動、限量搶購）→ <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>（隱性緩衝）/ <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</a>（明確排隊）/ <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</a>（規模化平台資料層）</li>
<li><strong>持續成長 sustained</strong>（用戶月增、業務擴張）→ <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> / <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</a> / <a href="/blog/backend/09-performance-capacity/cases/spotify-kafka-to-pubsub-migration-gcp/" data-link-title="9.C9 Spotify：從自管 Kafka 遷移到 GCP Pub/Sub 的事件交付系統" data-link-desc="Spotify 把自管 Kafka 事件系統遷移到 Google Cloud Pub/Sub、避免自管 broker 的容量規劃成本">9.C9 Spotify</a> / <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> / <a href="/blog/backend/09-performance-capacity/cases/capcom-gaming-dynamodb-eks/" data-link-title="9.C19 Capcom：Resident Evil / Monster Hunter 在 DynamoDB &#43; EKS 上的遊戲後端" data-link-desc="Capcom 把 Resident Evil、Street Fighter、Monster Hunter 遊戲後端跑在 DynamoDB &#43; EKS、單一秒位數延遲、營運成本降 30%">9.C19 Capcom</a> / <a href="/blog/backend/09-performance-capacity/cases/zomato-tidb-to-dynamodb-migration/" data-link-title="9.C20 Zomato：從 TiDB 遷移到 DynamoDB、吞吐 4 倍、延遲降 90%、成本減 50%" data-link-desc="Zomato 帳單系統從 TiDB 遷移到 DynamoDB、吞吐 2K→8K RPM、延遲降 90%、成本減 50%">9.C20 Zomato</a> / <a href="/blog/backend/09-performance-capacity/cases/netflix-aurora-consolidation/" data-link-title="9.C23 Netflix：把關聯式 DB 統一到 Aurora、效能 &#43;75%、成本 -28%" data-link-desc="Netflix 把多套關聯式 DB 統一到 Aurora、效能提升 75%、成本下降 28%、串流數十億小時">9.C23 Netflix</a></li>
<li><strong>低延遲持續需求</strong>（金融交易、即時配對、廣告競價、ML inference）→ <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</a> / <a href="/blog/backend/09-performance-capacity/cases/spanner-planetary-scale-database-gcp/" data-link-title="9.C10 Cloud Spanner：每秒 10 億請求的全球一致性資料庫" data-link-desc="Google Cloud Spanner 內部峰值 10 億 req/sec、跨地區強一致 — 全球分散式 OLTP 容量參考">9.C10 Spanner</a> / <a href="/blog/backend/09-performance-capacity/cases/riot-games-eks-multi-cluster/" data-link-title="9.C12 Riot Games：246 個 EKS cluster 的多遊戲多地區治理" data-link-desc="Riot Games 從 Mesos 遷移到 EKS、用 246 個 cluster 跨遊戲跨地區治理、年省 1000 萬美金">9.C12 Riot</a> / <a href="/blog/backend/09-performance-capacity/cases/genesys-dynamodb-99999-availability/" data-link-title="9.C24 Genesys：用 DynamoDB 在 15 region 跑出 99.999% 可用性" data-link-desc="Genesys 客服平台用 DynamoDB 為預設資料層、跨 15 主 region &#43; 5 衛星 region、達成 12 個月 99.999% 可用性">9.C24 Genesys</a> / <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></li>
</ol>
<h3 id="surge-形狀的兩種次分類">surge 形狀的兩種次分類</h3>
<p>surge（突發遠超預期）內部還可分兩種、設計回應完全不同：</p>
<ul>
<li><strong>產品爆紅 surge</strong>（<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 Pokémon GO</a>）：流量隨熱度消退、是「暫時偏離 baseline 又回歸」。容量規劃焦點是「撐過熱度高峰、避免在最忙時掛」。</li>
<li><strong>結構性 surge</strong>（<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</a>）：baseline 永久上移、是「新常態」。容量規劃焦點是「30x 後 SLO baseline 重新校準、長期成本曲線重算」。</li>
</ul>
<h3 id="flash-sale-spike-形狀的特殊性">flash-sale-spike 形狀的特殊性</h3>
<p>售票搶購 / 報名活動 / 限量搶購跟其他「峰值」案例有本質差異：</p>
<ul>
<li><strong>時間點精確、可秒級預測</strong>：開賣時刻 = 公告時刻、跟 GR8 Tech 的「賽事高潮」不一樣（賽事高潮在何時 + 多大都未知）</li>
<li><strong>持續時間極短</strong>：5-30 分鐘賣完、跟 Prime Day（48 小時）/ Hotstar IPL（4 小時）量級差很多</li>
<li><strong>峰值倍數極端</strong>：t=0 前流量近 0、t=0 瞬間衝到 10K-100K 倍、平均流量沒意義、只有峰值</li>
<li><strong>後端不容易跟上</strong>：高流量湧入時、付款 / 簽證 / 庫存後端通常是 legacy 系統、無法等比擴容、必須靠 buffer / queue / waiting room 解耦</li>
</ul>
<p>這個負載形狀的兩個主要設計模式：<strong>隱性緩衝</strong>（<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>：用 DynamoDB / Kafka 吸收洪峰、後端慢消費）跟<strong>明確排隊</strong>（<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 服務">SeatGeek 模式</a>：Virtual Waiting Room + token-based queue）。實務常見組合使用 — 入口先排隊、進入後仍用 buffer。</p>
<h2 id="案例覆蓋矩陣">案例覆蓋矩陣</h2>
<p>下表顯示 38 個案例在 <em>服務維度 × 雲商</em> 的覆蓋情況、空格代表待補。</p>
<table>
  <thead>
      <tr>
          <th>服務維度</th>
          <th>AWS</th>
          <th>GCP</th>
          <th>Azure</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DB-OLTP</td>
          <td>C4, C14, C23</td>
          <td>C10</td>
          <td>C32</td>
      </tr>
      <tr>
          <td>DB-KV</td>
          <td>C5, C15, C18, C19, C20, C24, C26, C27, C29</td>
          <td>（待補）</td>
          <td>C11, C21</td>
      </tr>
      <tr>
          <td>DB-Document</td>
          <td>C36, C38</td>
          <td>C37</td>
          <td>（透過 C30 對照）</td>
      </tr>
      <tr>
          <td>Cache</td>
          <td>C6, C25</td>
          <td>C35</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>MQ-Stream</td>
          <td>C1 (SQS), C7 (Kinesis)</td>
          <td>C9</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>Compute / K8s</td>
          <td>C2, C7, C12, C16, C19, C28</td>
          <td>C8, C34</td>
          <td>C33</td>
      </tr>
      <tr>
          <td>Global Edge</td>
          <td>C13</td>
          <td>（待補）</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>Latency 敏感</td>
          <td>C3, C25, C36</td>
          <td>C10, C35</td>
          <td>（待補）</td>
      </tr>
      <tr>
          <td>Data Architecture</td>
          <td>C17</td>
          <td>C22, C31</td>
          <td>C30</td>
      </tr>
  </tbody>
</table>
<p>AWS 25 個 case、GCP 8 個 case（補了 130K-node GKE + Snap KeyDB + Forbes）、Azure 5 個 case。三家覆蓋更平衡。新增 DB-Document 維度後、MongoDB 作為主角的案例（C36 Coinbase / C37 Forbes / C38 Toyota Connected）跟原本 C30 Microsoft 365（MongoDB 遷出 → Cosmos DB）形成完整 document model 案例組。剩餘缺口：Azure cache / global edge / latency、GCP DB-KV / MQ-Stream 加深、GCP / Azure global edge。</p>
<h3 id="負載形狀--雲商-覆蓋">負載形狀 × 雲商 覆蓋</h3>
<table>
  <thead>
      <tr>
          <th>負載形狀</th>
          <th>AWS</th>
          <th>GCP</th>
          <th>Azure</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>predictable-peak</td>
          <td>C1, C13, C27, C29</td>
          <td>C22</td>
          <td>C21</td>
      </tr>
      <tr>
          <td>event-peak</td>
          <td>C2, C4, C7, C28</td>
          <td>-</td>
          <td>-</td>
      </tr>
      <tr>
          <td>surge</td>
          <td>C18</td>
          <td>C8</td>
          <td>C11</td>
      </tr>
      <tr>
          <td>flash-sale-spike</td>
          <td>C15, C16, C17</td>
          <td>-</td>
          <td>-</td>
      </tr>
      <tr>
          <td>low-latency-sustained</td>
          <td>C3, C12, C24, C25, C36</td>
          <td>C10, C34, C35</td>
          <td>-</td>
      </tr>
      <tr>
          <td>sustained-growth</td>
          <td>C5, C6, C14, C19, C20, C23, C26, C38</td>
          <td>C9, C31, C37</td>
          <td>C30, C32, C33</td>
      </tr>
  </tbody>
</table>
<p>flash-sale-spike 是 09 案例庫的核心 differentiator — 雲商案例庫對這個負載形狀的著墨遠勝一般 engineering blog。surge 維度補了 Zoom 之後、跟 Pokemon GO（暫時 surge）跟 Minecraft Earth（地理 surge）形成三種次分類對照。後續若有 GCP / Azure 同類售票案例可補。</p>
<h2 id="規劃中案例第二批">規劃中案例（第二批）</h2>
<p>待 09 主章寫作推進、第二批案例可從下列候選補齊。</p>
<table>
  <thead>
      <tr>
          <th>候選案例</th>
          <th>預期教學重點</th>
          <th>來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disney+ DynamoDB</td>
          <td>每日數十億動作、watch list metadata</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>PayPay 30 億訊息/日</td>
          <td>行動支付的持續高頻 message</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Capcom DynamoDB</td>
          <td>遊戲業數十億請求、single-digit ms</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Zomato 90% 延遲下降</td>
          <td>帳務處理、跨資料庫遷移效益</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Zoom COVID 30x 成長</td>
          <td>1000 萬 → 3 億 DAU、突發長期 sustained</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>FanFight 100 萬寫入/秒</td>
          <td>印度 fantasy sports 體育博彩</td>
          <td><a href="https://aws.amazon.com/dynamodb/customers/">DynamoDB customers</a></td>
      </tr>
      <tr>
          <td>Tubi ScyllaDB → ElastiCache</td>
          <td>ML feature store sub-10ms p99</td>
          <td><a href="https://aws.amazon.com/elasticache/customers/">ElastiCache customers</a></td>
      </tr>
      <tr>
          <td>FanDuel 直播 + 投注</td>
          <td>雙重峰值對齊</td>
          <td><a href="https://aws.amazon.com/solutions/case-studies/fanduel-case-study/">FanDuel case study</a></td>
      </tr>
      <tr>
          <td>Blockchain.com Spanner</td>
          <td>Crypto 高頻交易、強一致全球</td>
          <td><a href="https://cloud.google.com/blog/products/databases/using-cloud-spanner-to-handle-high-throughput-writes/">Spanner blog</a></td>
      </tr>
      <tr>
          <td>Walmart Cosmos DB</td>
          <td>全球零售 KV、跨地區一致性策略</td>
          <td><a href="https://azure.microsoft.com/en-us/blog/azure-cosmos-db-pushing-the-frontier-of-globally-distributed-databases/">Cosmos DB blog</a></td>
      </tr>
      <tr>
          <td>Microsoft 365 Cosmos</td>
          <td>MongoDB → Cosmos 遷移、planet-scale 分析</td>
          <td><a href="https://azure.microsoft.com/en-us/blog/microsoft-365-boosts-usage-analytics-with-azure-cosmos-db/">Cosmos DB Microsoft 365 blog</a></td>
      </tr>
  </tbody>
</table>
<h2 id="engineering-blog-補充候選">Engineering Blog 補充候選</h2>
<p>當 AWS / GCP / Azure 案例缺乏某些工程紀律的深度（例如 chaos hypothesis、cell-based architecture 細節），補引 engineering blog 作為交叉驗證。候選來源：Shopify BFCM、Netflix Tech Blog、Amazon Builders&rsquo; Library、Google SRE Book、LinkedIn Engineering、Stripe Engineering、Cloudflare Blog、Discord Engineering、Uber Engineering、Pinterest Engineering 等。這層不另開資料夾、補在主章「案例對照」段。</p>
<h2 id="案例正文格式">案例正文格式</h2>
<p>每篇案例使用統一結構、方便快速比對。</p>
<ol>
<li><strong>觀察</strong>：客觀數字與事件序列。流量規模、實例配置、延遲分布、成本變化都用引用源的原始數字、不四捨五入。</li>
<li><strong>判讀</strong>：把案例的工程決策翻成主章的問題節點。</li>
<li><strong>策略</strong>：可重用的工程做法、去掉雲端 vendor 特異性。EKS、Auto Scaling、DynamoDB on-demand 等翻成跨平台等效概念。</li>
<li><strong>下一步路由</strong>：往哪個主章或前置案例延伸閱讀。</li>
<li><strong>引用源</strong>：雲端服務商官方 case study URL + 相關 Architecture Blog 連結。</li>
</ol>
<h2 id="tripwire">Tripwire</h2>
<ul>
<li>同一服務維度的 case 超過 5 個時、暫停擴張、改補其他維度。</li>
<li>AWS 案例數字過於行銷、缺工程細節 → 補 AWS Architecture Blog 同主題文章作為交叉驗證。</li>
<li>案例只是「我們用了 X 服務」、沒有具體量化結果 → 不收進案例庫、作為候選參考即可。</li>
<li>同一公司多個案例（例如 Coinbase 還有遷移案例）→ 拆 sub-case 而不是合成單一檔。</li>
<li>GCP / Azure 覆蓋持續落後 AWS 超過 2 倍時 → 主動補 GCP / Azure 案例、不要讓案例庫變成 AWS-only。</li>
</ul>
]]></content:encoded></item><item><title>MySQL Cross-buffer Memory Contention</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/cross-buffer-memory-contention/</link><pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/cross-buffer-memory-contention/</guid><description>&lt;p>MySQL cross-buffer memory contention 的核心責任是把 MySQL memory tuning 從單一 buffer pool 參數擴展到整體記憶體競爭。InnoDB buffer pool、redo log buffer、sort buffer、join buffer、tmp table、thread stack、connection memory、OS page cache 與 container limit 會共同決定 latency 與 OOM 風險。&lt;/p>
&lt;p>本文的判讀錨點是：MySQL memory 問題常來自&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/per-connection-memory/" data-link-title="Per-Connection Memory" data-link-desc="說明每條連線或每個操作的記憶體用量如何隨並發數放大">「每連線 / 每操作」記憶體&lt;/a>乘上 concurrency，而非只來自全域 buffer pool。調大單一 buffer 前，要先看 workload 與同時執行的 query。&lt;/p>
&lt;h2 id="memory-surfaces">Memory Surfaces&lt;/h2>
&lt;p>Memory surfaces 的核心責任是列出會互相競爭的記憶體來源。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Surface&lt;/th>
 &lt;th>類型&lt;/th>
 &lt;th>風險&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>InnoDB buffer pool&lt;/td>
 &lt;td>global&lt;/td>
 &lt;td>太小造成 read I/O，太大壓縮 OS 空間&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Redo log buffer&lt;/td>
 &lt;td>global&lt;/td>
 &lt;td>大交易 / burst write 需要審查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Sort buffer&lt;/td>
 &lt;td>per session / operation&lt;/td>
 &lt;td>concurrent sort 放大 memory&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Join buffer&lt;/td>
 &lt;td>per session / join&lt;/td>
 &lt;td>missing index 時放大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Temp table&lt;/td>
 &lt;td>memory / disk&lt;/td>
 &lt;td>group / sort / derived table&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Connection overhead&lt;/td>
 &lt;td>per connection&lt;/td>
 &lt;td>connection storm / thread memory&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>OS page cache&lt;/td>
 &lt;td>system&lt;/td>
 &lt;td>file、backup、binlog、tmp&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Per-session buffer 是最容易誤調的項目。把 sort / join buffer 全域調大，會在高 concurrency 下造成 memory spike。&lt;/p>
&lt;h2 id="contention-signals">Contention Signals&lt;/h2>
&lt;p>Contention signals 的核心責任是把 memory pressure 從 symptom 轉成可排查訊號。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Signal&lt;/th>
 &lt;th>意義&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>OOM / container restart&lt;/td>
 &lt;td>total memory 超出限制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>swap activity&lt;/td>
 &lt;td>memory pressure 已影響 latency&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Created_tmp_disk_tables 增加&lt;/td>
 &lt;td>memory temp table 不足或 query 太大&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Sort_merge_passes 增加&lt;/td>
 &lt;td>sort memory / query shape 問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Buffer pool hit rate 下降&lt;/td>
 &lt;td>working set / query pattern 問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Threads_connected 高&lt;/td>
 &lt;td>per-connection memory 放大&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Signal 要和 query workload 對照。Temp table 與 sort 問題通常需要 query rewrite、index 或報表隔離，而非只調 memory。&lt;/p>
&lt;h2 id="tuning-order">Tuning Order&lt;/h2>
&lt;p>Tuning order 的核心責任是建立安全調整順序。&lt;/p></description><content:encoded><![CDATA[<p>MySQL cross-buffer memory contention 的核心責任是把 MySQL memory tuning 從單一 buffer pool 參數擴展到整體記憶體競爭。InnoDB buffer pool、redo log buffer、sort buffer、join buffer、tmp table、thread stack、connection memory、OS page cache 與 container limit 會共同決定 latency 與 OOM 風險。</p>
<p>本文的判讀錨點是：MySQL memory 問題常來自<a href="/blog/backend/knowledge-cards/per-connection-memory/" data-link-title="Per-Connection Memory" data-link-desc="說明每條連線或每個操作的記憶體用量如何隨並發數放大">「每連線 / 每操作」記憶體</a>乘上 concurrency，而非只來自全域 buffer pool。調大單一 buffer 前，要先看 workload 與同時執行的 query。</p>
<h2 id="memory-surfaces">Memory Surfaces</h2>
<p>Memory surfaces 的核心責任是列出會互相競爭的記憶體來源。</p>
<table>
  <thead>
      <tr>
          <th>Surface</th>
          <th>類型</th>
          <th>風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>InnoDB buffer pool</td>
          <td>global</td>
          <td>太小造成 read I/O，太大壓縮 OS 空間</td>
      </tr>
      <tr>
          <td>Redo log buffer</td>
          <td>global</td>
          <td>大交易 / burst write 需要審查</td>
      </tr>
      <tr>
          <td>Sort buffer</td>
          <td>per session / operation</td>
          <td>concurrent sort 放大 memory</td>
      </tr>
      <tr>
          <td>Join buffer</td>
          <td>per session / join</td>
          <td>missing index 時放大</td>
      </tr>
      <tr>
          <td>Temp table</td>
          <td>memory / disk</td>
          <td>group / sort / derived table</td>
      </tr>
      <tr>
          <td>Connection overhead</td>
          <td>per connection</td>
          <td>connection storm / thread memory</td>
      </tr>
      <tr>
          <td>OS page cache</td>
          <td>system</td>
          <td>file、backup、binlog、tmp</td>
      </tr>
  </tbody>
</table>
<p>Per-session buffer 是最容易誤調的項目。把 sort / join buffer 全域調大，會在高 concurrency 下造成 memory spike。</p>
<h2 id="contention-signals">Contention Signals</h2>
<p>Contention signals 的核心責任是把 memory pressure 從 symptom 轉成可排查訊號。</p>
<table>
  <thead>
      <tr>
          <th>Signal</th>
          <th>意義</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>OOM / container restart</td>
          <td>total memory 超出限制</td>
      </tr>
      <tr>
          <td>swap activity</td>
          <td>memory pressure 已影響 latency</td>
      </tr>
      <tr>
          <td>Created_tmp_disk_tables 增加</td>
          <td>memory temp table 不足或 query 太大</td>
      </tr>
      <tr>
          <td>Sort_merge_passes 增加</td>
          <td>sort memory / query shape 問題</td>
      </tr>
      <tr>
          <td>Buffer pool hit rate 下降</td>
          <td>working set / query pattern 問題</td>
      </tr>
      <tr>
          <td>Threads_connected 高</td>
          <td>per-connection memory 放大</td>
      </tr>
  </tbody>
</table>
<p>Signal 要和 query workload 對照。Temp table 與 sort 問題通常需要 query rewrite、index 或報表隔離，而非只調 memory。</p>
<h2 id="tuning-order">Tuning Order</h2>
<p>Tuning order 的核心責任是建立安全調整順序。</p>
<ol>
<li>先確認 host / container memory limit。</li>
<li>設定 InnoDB buffer pool baseline。</li>
<li>控制 max connections 與 application pool。</li>
<li>用 top query 找 sort / join / temp table 來源。</li>
<li>對特定 session / workload 調 buffer，而非全域放大。</li>
<li>將 analytics / reporting 移到 replica 或 OLAP。</li>
</ol>
<p>這個順序讓全域 memory 先穩定，再處理 query 層問題。若反過來先調大 per-session buffer，壓力會在尖峰流量時爆發。</p>
<h2 id="query-patterns">Query Patterns</h2>
<p>Query patterns 的核心責任是找出 memory heavy 查詢。</p>
<table>
  <thead>
      <tr>
          <th>Pattern</th>
          <th>Memory 風險</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Large sort</td>
          <td>sort buffer / temp table</td>
          <td>index order、limit、pagination</td>
      </tr>
      <tr>
          <td>Missing join index</td>
          <td>join buffer 放大</td>
          <td>補 index、改 join order</td>
      </tr>
      <tr>
          <td>Big GROUP BY</td>
          <td>tmp table / disk spill</td>
          <td>pre-aggregate、OLAP、covering index</td>
      </tr>
      <tr>
          <td>Large transaction</td>
          <td>undo / lock / memory</td>
          <td>batch、縮短 transaction</td>
      </tr>
      <tr>
          <td>Many idle sessions</td>
          <td>connection memory</td>
          <td>pooler、timeout、max connection</td>
      </tr>
  </tbody>
</table>
<p>Memory tuning 要服務 query design。若 query 本身無界，memory 只會把問題延後到更大資料量。</p>
<h2 id="runbook">Runbook</h2>
<p>Runbook 的核心責任是把 memory incident 分流。</p>
<table>
  <thead>
      <tr>
          <th>Step</th>
          <th>操作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Confirm pressure</td>
          <td>OS memory、swap、OOM、MySQL status</td>
      </tr>
      <tr>
          <td>Identify workload</td>
          <td>processlist、performance schema、top SQL</td>
      </tr>
      <tr>
          <td>Reduce concurrency</td>
          <td>限流、停報表、降 background job</td>
      </tr>
      <tr>
          <td>Protect OLTP</td>
          <td>kill heavy query、切 read replica</td>
      </tr>
      <tr>
          <td>Tune safely</td>
          <td>session-level buffer、index、query</td>
      </tr>
      <tr>
          <td>Retrospective</td>
          <td>pool size、query guard、dashboard</td>
      </tr>
  </tbody>
</table>
<p>OOM 後要保存 evidence：memory limit、MySQL variables、Threads_connected、top queries、tmp table counters、container restart time。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>Cross-buffer memory contention 完成後，InnoDB 基礎讀 <a href="../innodb-tuning/">InnoDB Tuning</a>；query 層讀 <a href="../query-optimization/">Query Optimization</a>；lock 與 transaction 壓力讀 <a href="../lock-contention/">Lock Contention</a>。</p>
]]></content:encoded></item><item><title>SQLite PRAGMA Tuning and Performance</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/sqlite/pragma-tuning-performance/</link><pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/sqlite/pragma-tuning-performance/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/sqlite/" data-link-title="SQLite" data-link-desc="embedded、單檔案、test / CLI / edge 場景的標準選擇、近年因 Cloudflare D1 / Turso 等服務復興">SQLite&lt;/a> overview 的 implementation-layer deep article。Overview 已說明 SQLite 的容量規劃要點；本文聚焦 &lt;em>PRAGMA 設定如何變成 durability、latency、檔案大小與 restore risk 的取捨&lt;/em>。&lt;/p>&lt;/blockquote>
&lt;p>SQLite PRAGMA tuning 的核心責任是把單檔資料庫的行為固定成可重複、可觀測、可回退的操作契約。SQLite 的許多重要行為由 connection-level 或 database-level PRAGMA 控制；這些設定看起來像小開關，實際上會影響 crash recovery、commit latency、reader / writer 衝突、檔案大小與測試一致性。&lt;/p>
&lt;p>本文的判讀錨點是：PRAGMA 是 durability / latency / maintenance 的顯性取捨，而非效能魔法。Production runbook 要記錄設定值、設定時機、驗證 query 與回退條件，避免不同 process、test runner 或 migration tool 用不同 SQLite 行為。&lt;/p>
&lt;h2 id="baseline-pragma">Baseline PRAGMA&lt;/h2>
&lt;p>SQLite baseline PRAGMA 的責任是讓 application 每次啟動都進入同一個資料庫模式。對 production-like local store、small backend 或 test fixture，建議把 journal、sync、foreign key、busy timeout 與 checkpoint 明確設定，而非依賴語言 binding 預設值。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">PRAGMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">journal_mode&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">WAL&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PRAGMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">synchronous&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">NORMAL&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PRAGMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">foreign_keys&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PRAGMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">busy_timeout&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">5000&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="n">PRAGMA&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">wal_autocheckpoint&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>設定&lt;/th>
 &lt;th>服務責任&lt;/th>
 &lt;th>驗證方式&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>journal_mode=WAL&lt;/code>&lt;/td>
 &lt;td>降低 reader / writer 衝突&lt;/td>
 &lt;td>回傳值為 &lt;code>wal&lt;/code>，觀察 &lt;code>-wal&lt;/code> file&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>synchronous=NORMAL&lt;/code>&lt;/td>
 &lt;td>平衡 fsync cost 與 crash durability&lt;/td>
 &lt;td>查 &lt;code>PRAGMA synchronous&lt;/code>，跑 restore drill&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>foreign_keys=ON&lt;/code>&lt;/td>
 &lt;td>啟用 FK enforcement&lt;/td>
 &lt;td>&lt;code>PRAGMA foreign_key_check&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>busy_timeout&lt;/code>&lt;/td>
 &lt;td>吸收短暫 writer queue&lt;/td>
 &lt;td>記錄 busy wait 與 timeout rate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>wal_autocheckpoint&lt;/code>&lt;/td>
 &lt;td>控制 WAL growth cadence&lt;/td>
 &lt;td>觀察 WAL size 與 checkpoint duration&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這張表的重點是把設定與 evidence 綁在一起。若某個 PRAGMA 缺少成功訊號與失敗訊號，就先維持保守預設；盲目追求「最快」通常會把風險推到 power loss、restore 或長尾 latency。&lt;/p>
&lt;h2 id="journal_mode-與-wal-boundary">&lt;code>journal_mode&lt;/code> 與 WAL boundary&lt;/h2>
&lt;p>&lt;code>journal_mode&lt;/code> 的核心責任是決定 transaction 如何保護原始資料。SQLite 預設 rollback journal 對簡單場景合理；WAL mode 則讓 reader 可以在 writer append WAL 時保有 snapshot，適合多 reader、短寫入、互動式 workload。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>模式&lt;/th>
 &lt;th>適合情境&lt;/th>
 &lt;th>注意事項&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>DELETE&lt;/code>&lt;/td>
 &lt;td>最簡單、低併發、短生命週期檔案&lt;/td>
 &lt;td>write / read 衝突較明顯&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>WAL&lt;/code>&lt;/td>
 &lt;td>read-heavy、local app、小型 API&lt;/td>
 &lt;td>需要治理 &lt;code>-wal&lt;/code>、&lt;code>-shm&lt;/code>、checkpoint&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>MEMORY&lt;/code>&lt;/td>
 &lt;td>暫存測試、可丟資料&lt;/td>
 &lt;td>crash 後 recovery 風險高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>OFF&lt;/code>&lt;/td>
 &lt;td>可重建資料、一次性 bulk load&lt;/td>
 &lt;td>production formal state 應避開&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>WAL mode 是多數 production-like SQLite 的 baseline，但它也引入 sidecar file 與 checkpoint 責任。完整判讀見 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/sqlite/wal-concurrency-locking/" data-link-title="SQLite WAL Concurrency and Locking" data-link-desc="SQLite WAL mode 如何降低 reader / writer 衝突、保留 single writer boundary，並用 SQLITE_BUSY、WAL growth、checkpoint 訊號判斷 production 上限">WAL concurrency / locking&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/sqlite/" data-link-title="SQLite" data-link-desc="embedded、單檔案、test / CLI / edge 場景的標準選擇、近年因 Cloudflare D1 / Turso 等服務復興">SQLite</a> overview 的 implementation-layer deep article。Overview 已說明 SQLite 的容量規劃要點；本文聚焦 <em>PRAGMA 設定如何變成 durability、latency、檔案大小與 restore risk 的取捨</em>。</p></blockquote>
<p>SQLite PRAGMA tuning 的核心責任是把單檔資料庫的行為固定成可重複、可觀測、可回退的操作契約。SQLite 的許多重要行為由 connection-level 或 database-level PRAGMA 控制；這些設定看起來像小開關，實際上會影響 crash recovery、commit latency、reader / writer 衝突、檔案大小與測試一致性。</p>
<p>本文的判讀錨點是：PRAGMA 是 durability / latency / maintenance 的顯性取捨，而非效能魔法。Production runbook 要記錄設定值、設定時機、驗證 query 與回退條件，避免不同 process、test runner 或 migration tool 用不同 SQLite 行為。</p>
<h2 id="baseline-pragma">Baseline PRAGMA</h2>
<p>SQLite baseline PRAGMA 的責任是讓 application 每次啟動都進入同一個資料庫模式。對 production-like local store、small backend 或 test fixture，建議把 journal、sync、foreign key、busy timeout 與 checkpoint 明確設定，而非依賴語言 binding 預設值。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">PRAGMA</span><span class="w"> </span><span class="n">journal_mode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">WAL</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="n">PRAGMA</span><span class="w"> </span><span class="n">synchronous</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">NORMAL</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="n">PRAGMA</span><span class="w"> </span><span class="n">foreign_keys</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">ON</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="n">PRAGMA</span><span class="w"> </span><span class="n">busy_timeout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5000</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="n">PRAGMA</span><span class="w"> </span><span class="n">wal_autocheckpoint</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>設定</th>
          <th>服務責任</th>
          <th>驗證方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>journal_mode=WAL</code></td>
          <td>降低 reader / writer 衝突</td>
          <td>回傳值為 <code>wal</code>，觀察 <code>-wal</code> file</td>
      </tr>
      <tr>
          <td><code>synchronous=NORMAL</code></td>
          <td>平衡 fsync cost 與 crash durability</td>
          <td>查 <code>PRAGMA synchronous</code>，跑 restore drill</td>
      </tr>
      <tr>
          <td><code>foreign_keys=ON</code></td>
          <td>啟用 FK enforcement</td>
          <td><code>PRAGMA foreign_key_check</code></td>
      </tr>
      <tr>
          <td><code>busy_timeout</code></td>
          <td>吸收短暫 writer queue</td>
          <td>記錄 busy wait 與 timeout rate</td>
      </tr>
      <tr>
          <td><code>wal_autocheckpoint</code></td>
          <td>控制 WAL growth cadence</td>
          <td>觀察 WAL size 與 checkpoint duration</td>
      </tr>
  </tbody>
</table>
<p>這張表的重點是把設定與 evidence 綁在一起。若某個 PRAGMA 缺少成功訊號與失敗訊號，就先維持保守預設；盲目追求「最快」通常會把風險推到 power loss、restore 或長尾 latency。</p>
<h2 id="journal_mode-與-wal-boundary"><code>journal_mode</code> 與 WAL boundary</h2>
<p><code>journal_mode</code> 的核心責任是決定 transaction 如何保護原始資料。SQLite 預設 rollback journal 對簡單場景合理；WAL mode 則讓 reader 可以在 writer append WAL 時保有 snapshot，適合多 reader、短寫入、互動式 workload。</p>
<table>
  <thead>
      <tr>
          <th>模式</th>
          <th>適合情境</th>
          <th>注意事項</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>DELETE</code></td>
          <td>最簡單、低併發、短生命週期檔案</td>
          <td>write / read 衝突較明顯</td>
      </tr>
      <tr>
          <td><code>WAL</code></td>
          <td>read-heavy、local app、小型 API</td>
          <td>需要治理 <code>-wal</code>、<code>-shm</code>、checkpoint</td>
      </tr>
      <tr>
          <td><code>MEMORY</code></td>
          <td>暫存測試、可丟資料</td>
          <td>crash 後 recovery 風險高</td>
      </tr>
      <tr>
          <td><code>OFF</code></td>
          <td>可重建資料、一次性 bulk load</td>
          <td>production formal state 應避開</td>
      </tr>
  </tbody>
</table>
<p>WAL mode 是多數 production-like SQLite 的 baseline，但它也引入 sidecar file 與 checkpoint 責任。完整判讀見 <a href="/blog/backend/01-database/vendors/sqlite/wal-concurrency-locking/" data-link-title="SQLite WAL Concurrency and Locking" data-link-desc="SQLite WAL mode 如何降低 reader / writer 衝突、保留 single writer boundary，並用 SQLITE_BUSY、WAL growth、checkpoint 訊號判斷 production 上限">WAL concurrency / locking</a>。</p>
<h2 id="synchronouscommit-latency-與資料損失窗口"><code>synchronous</code>：commit latency 與資料損失窗口</h2>
<p><code>synchronous</code> 的核心責任是控制 SQLite 在關鍵時刻要求 storage flush 的強度。官方 PRAGMA 文件說明 WAL mode 下 <code>NORMAL</code> 會把 sync 主要放在 checkpoint 路徑；這通常讓 commit 更快，但 crash durability 的語意要由 service owner 接受。</p>
<table>
  <thead>
      <tr>
          <th>設定</th>
          <th>服務語意</th>
          <th>適合情境</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>FULL</code></td>
          <td>更保守的 durability</td>
          <td>金錢、ledger、不可重建 local state</td>
      </tr>
      <tr>
          <td><code>NORMAL</code></td>
          <td>多數 WAL production-like baseline</td>
          <td>local app、小型服務、可接受極小 crash window</td>
      </tr>
      <tr>
          <td><code>OFF</code></td>
          <td>追求速度，放棄重要 durability</td>
          <td>scratch DB、可重建 cache、bulk import staging</td>
      </tr>
  </tbody>
</table>
<p><code>synchronous=OFF</code> 要被視為明確風險接受。若資料是 <a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">source of truth</a>，設定檔、runbook 與 review 都應避免把 staging 的快速設定帶進 production。</p>
<h2 id="cachemmap-與-memory-pressure">Cache、mmap 與 memory pressure</h2>
<p>SQLite memory tuning 的核心責任是降低 read path I/O，同時避免把 device / container memory 壓到不可控。<code>cache_size</code> 控制 SQLite page cache；<code>mmap_size</code> 讓讀取可透過 memory-mapped I/O 加速，但仍受平台、檔案大小與 memory budget 影響。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">PRAGMA</span><span class="w"> </span><span class="n">cache_size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">-</span><span class="mi">64000</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="n">PRAGMA</span><span class="w"> </span><span class="n">mmap_size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">268435456</span><span class="p">;</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>設定</th>
          <th>改善目標</th>
          <th>觀測訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>cache_size</code></td>
          <td>減少重複 page read</td>
          <td>query latency、disk read、memory usage</td>
      </tr>
      <tr>
          <td><code>mmap_size</code></td>
          <td>降低 read syscall cost</td>
          <td>p95 / p99 read latency、address space</td>
      </tr>
      <tr>
          <td><code>temp_store</code></td>
          <td>控制 temp table 位置</td>
          <td>sort / join query latency、memory pressure</td>
      </tr>
  </tbody>
</table>
<p>Memory 設定要和 workload size 一起看。Desktop app、mobile app、edge worker、container service 的 memory ceiling 不同；把 server 上的設定複製到 mobile 或 edge runtime 會讓風險轉移到 OOM 或 OS reclaim。</p>
<h2 id="vacuum-與檔案大小治理">Vacuum 與檔案大小治理</h2>
<p>Vacuum 設定的核心責任是控制 delete 後的空間回收。SQLite delete row 後，database file 不會自然縮小；<code>auto_vacuum</code> 要在 database 建立早期決定，後續切換通常需要 <code>VACUUM</code> 重整整個 database。</p>
<table>
  <thead>
      <tr>
          <th>設定 / 操作</th>
          <th>適合情境</th>
          <th>風險 / 成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>auto_vacuum=NONE</code></td>
          <td>資料量穩定、delete 少</td>
          <td>檔案可能長期保持高水位</td>
      </tr>
      <tr>
          <td><code>auto_vacuum=INCREMENTAL</code></td>
          <td>需要逐步回收空間</td>
          <td>需要排程 <code>incremental_vacuum</code></td>
      </tr>
      <tr>
          <td><code>VACUUM</code></td>
          <td>maintenance window、重整資料庫</td>
          <td>需要額外空間與 I/O，可能影響服務</td>
      </tr>
      <tr>
          <td><code>VACUUM INTO</code></td>
          <td>compact copy / backup</td>
          <td>產出新檔，適合 restore drill 或 export</td>
      </tr>
  </tbody>
</table>
<p>檔案大小治理要接到 backup 成本。Database file 長期膨脹會放大備份時間、restore 時間與 edge deploy artifact size；若服務有大量 delete / churn，vacuum policy 要被寫進 runbook。</p>
<h2 id="production-踩雷">Production 踩雷</h2>
<h3 id="case-1pragma-只在某個-connection-設定">Case 1：PRAGMA 只在某個 connection 設定</h3>
<p>Connection-level PRAGMA 的核心風險是不同程式路徑行為不一致。Application 啟動時設了 <code>foreign_keys=ON</code>，migration tool 或 test runner 沒設，就會出現 production / migration / test 三種語意。</p>
<p>修正方向是把 baseline PRAGMA 放進 shared DB open path，並在 startup health check 印出設定值。Migration CLI、background worker、test fixture 都要共用同一份 connection initialization。</p>
<h3 id="case-2synchronousoff-從測試環境流到正式資料">Case 2：<code>synchronous=OFF</code> 從測試環境流到正式資料</h3>
<p>快速測試設定外流的核心風險是資料損失只在 crash 後出現。平常 query 都正常，直到 power loss、container kill 或 host crash 後，資料庫出現落差。</p>
<p>修正方向是設定分層。Test / benchmark 可以用 faster profile；formal state profile 要用 <code>NORMAL</code> 或 <code>FULL</code>，並要求 restore drill。</p>
<h3 id="case-3wal-growth-被誤判成資料成長">Case 3：WAL growth 被誤判成資料成長</h3>
<p>WAL growth 的核心風險是 checkpoint 問題被當成容量問題。Disk alert 看到 <code>db-wal</code> 變大，若只擴 disk，長 reader 或 checkpoint starvation 仍會持續。</p>
<p>修正方向是把 WAL size、checkpoint return 與 long reader 一起看。先找 reader lifecycle，再調 checkpoint cadence。</p>
<h3 id="case-4vacuum-在高峰期執行">Case 4：Vacuum 在高峰期執行</h3>
<p>Vacuum 的核心風險是把 maintenance I/O 放到使用者路徑。檔案縮小是好事，但 full vacuum 會消耗 I/O 與時間，對 mobile / desktop / small backend 都可能造成卡頓。</p>
<p>修正方向是把 vacuum 當 maintenance job。大檔案用 <code>incremental_vacuum</code> 或低流量窗口；備份前的 compact copy 可考慮 <code>VACUUM INTO</code>。</p>
<h2 id="操作檢查清單">操作檢查清單</h2>
<p>SQLite PRAGMA runbook 至少要記錄：</p>
<ol>
<li>所有 connection 初始化時執行的 baseline PRAGMA。</li>
<li><code>journal_mode</code> 實際回傳值與 sidecar file 位置。</li>
<li><code>synchronous</code> profile 與資料風險接受者。</li>
<li><code>busy_timeout</code> 值、busy wait metric、timeout threshold。</li>
<li><code>wal_autocheckpoint</code>、manual checkpoint cadence 與 WAL size alert。</li>
<li><code>cache_size</code> / <code>mmap_size</code> 對 memory budget 的影響。</li>
<li><code>auto_vacuum</code> / <code>VACUUM</code> / <code>VACUUM INTO</code> 的 maintenance window。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>上游：<a href="/blog/backend/01-database/vendors/sqlite/" data-link-title="SQLite" data-link-desc="embedded、單檔案、test / CLI / edge 場景的標準選擇、近年因 Cloudflare D1 / Turso 等服務復興">SQLite overview</a></li>
<li>前置：<a href="/blog/backend/01-database/vendors/sqlite/wal-concurrency-locking/" data-link-title="SQLite WAL Concurrency and Locking" data-link-desc="SQLite WAL mode 如何降低 reader / writer 衝突、保留 single writer boundary，並用 SQLITE_BUSY、WAL growth、checkpoint 訊號判斷 production 上限">WAL concurrency / locking</a></li>
<li>操作：<a href="/blog/backend/01-database/vendors/sqlite/hands-on/" data-link-title="SQLite Hands-on 操作路線" data-link-desc="SQLite local file lab、backup / restore drill、WAL busy reproduction、migration fixture、D1 / Turso preview 的操作型章節設計">SQLite Hands-on</a></li>
<li>平行：<a href="/blog/backend/01-database/vendors/sqlite/observability-runbook/" data-link-title="SQLite Observability and Runbook" data-link-desc="SQLite production runbook、backup evidence、WAL growth、busy errors、disk usage、restore drill 與 incident route">Observability / runbook</a></li>
<li>官方：<a href="https://www.sqlite.org/pragma.html">SQLite PRAGMA</a>、<a href="https://www.sqlite.org/lang_vacuum.html">SQLite VACUUM</a>、<a href="https://www.sqlite.org/wal.html">SQLite WAL</a></li>
</ul>
]]></content:encoded></item><item><title>JMeter → k6：k6 不是 JMeter 的「script 版本」、是 VU model 取代 thread model</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/migrate-from-jmeter/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/k6/migrate-from-jmeter/</guid><description>&lt;p>k6 不是 JMeter 的 &lt;em>「script 版本」&lt;/em>。&lt;/p>
&lt;p>這個誤解是 JMeter → k6 migration 第一週最常見的事故來源。Migration 啟動會議常聽到「JMeter 的 thread group 翻成 k6 的 VU 就好了吧」、然後團隊把 &lt;code>.jmx&lt;/code> 內 100 thread → k6 &lt;code>vus: 100&lt;/code>、跑下去發現 RPS 差三倍、p95 延遲表完全不同形狀、以為 k6 壞了。&lt;/p>
&lt;p>實際上 k6 的 &lt;em>Virtual User (VU)&lt;/em> 跟 JMeter 的 &lt;em>Thread&lt;/em> 是 &lt;em>兩種不同的使用者行為建模方式&lt;/em>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>JMeter Thread&lt;/strong>：一個 OS thread = 一個 user、&lt;code>numThreads=100&lt;/code> 就 &lt;em>固定 100 個 concurrent 使用者一直跑&lt;/em>、ramp-up period 控制怎麼啟動、無 explicit arrival rate 概念&lt;/li>
&lt;li>&lt;strong>k6 VU&lt;/strong>：一個 goroutine-like execution context、預設 &lt;code>vus&lt;/code> 是 &lt;em>concurrent VU pool&lt;/em>、但 k6 更推薦用 &lt;code>arrival-rate executor&lt;/code> — 直接表達 &lt;em>每秒進來幾個 request&lt;/em>、VU 是 &lt;em>為了達到 arrival rate 動態起的 worker&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>差別在 &lt;em>測量視角&lt;/em>：JMeter 預設視角是 &lt;em>「我有 100 個使用者在用系統」&lt;/em>、k6 預設視角是 &lt;em>「我每秒有 N 個請求進來」&lt;/em>。兩種視角下 &lt;em>同一個系統的瓶頸結果完全不同&lt;/em>：100 concurrent user 模型在 server 慢時 throughput 會自動降（user 等回應）、100 RPS arrival rate 模型在 server 慢時 queue 會累積、暴露 &lt;em>真實 production behavior&lt;/em>（user 不會體諒、會繼續送請求）。&lt;/p>
&lt;p>這篇 migration playbook 不是 schema translation 文（&lt;code>.jmx&lt;/code> 翻成 &lt;code>.js&lt;/code> 只是表面）、是 &lt;em>paradigm shift&lt;/em> — 從 closed-system model（thread）到 open-system model（arrival rate）的視角轉換。&lt;/p>
&lt;h2 id="為什麼是-type-eschema--paradigm-同-high">為什麼是 Type E（schema + paradigm 同 High）&lt;/h2>
&lt;p>跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/#6-%e7%b6%ad-diff-dimension-audit" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit&lt;/a>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>評&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Schema&lt;/td>
 &lt;td>High&lt;/td>
 &lt;td>&lt;code>.jmx&lt;/code> XML vs JavaScript scenario、test plan 完全不同 file format / DSL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Operational&lt;/td>
 &lt;td>Medium&lt;/td>
 &lt;td>CLI / distributed run 接近、CI integration 差別大、distributed runner 模型不同&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Paradigm&lt;/td>
 &lt;td>High&lt;/td>
 &lt;td>thread group closed model → arrival rate open model、測試思維不同&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Components&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;td>都是 load test runner、no multi-tool decomposition&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>App change&lt;/td>
 &lt;td>N/A&lt;/td>
 &lt;td>是 test code、不是 production code&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Topology&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;td>都是 CLI / runner 跑、無 sharding&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Schema High + Paradigm High 兩軸 High。按優先序 Schema &amp;gt; Paradigm、預設選 Type A。但對 JMeter → k6 的讀者來說、&lt;em>paradigm shift 才是難關&lt;/em> — schema translation 是工作量、但搞錯 paradigm 會讓 migration 後的測試結果 &lt;em>跟 production 不對應&lt;/em>。所以選 &lt;strong>Type E paradigm shift&lt;/strong> 結構、schema translation 抽出 Phase 1-2 補充。&lt;/p></description><content:encoded><![CDATA[<p>k6 不是 JMeter 的 <em>「script 版本」</em>。</p>
<p>這個誤解是 JMeter → k6 migration 第一週最常見的事故來源。Migration 啟動會議常聽到「JMeter 的 thread group 翻成 k6 的 VU 就好了吧」、然後團隊把 <code>.jmx</code> 內 100 thread → k6 <code>vus: 100</code>、跑下去發現 RPS 差三倍、p95 延遲表完全不同形狀、以為 k6 壞了。</p>
<p>實際上 k6 的 <em>Virtual User (VU)</em> 跟 JMeter 的 <em>Thread</em> 是 <em>兩種不同的使用者行為建模方式</em>：</p>
<ul>
<li><strong>JMeter Thread</strong>：一個 OS thread = 一個 user、<code>numThreads=100</code> 就 <em>固定 100 個 concurrent 使用者一直跑</em>、ramp-up period 控制怎麼啟動、無 explicit arrival rate 概念</li>
<li><strong>k6 VU</strong>：一個 goroutine-like execution context、預設 <code>vus</code> 是 <em>concurrent VU pool</em>、但 k6 更推薦用 <code>arrival-rate executor</code> — 直接表達 <em>每秒進來幾個 request</em>、VU 是 <em>為了達到 arrival rate 動態起的 worker</em></li>
</ul>
<p>差別在 <em>測量視角</em>：JMeter 預設視角是 <em>「我有 100 個使用者在用系統」</em>、k6 預設視角是 <em>「我每秒有 N 個請求進來」</em>。兩種視角下 <em>同一個系統的瓶頸結果完全不同</em>：100 concurrent user 模型在 server 慢時 throughput 會自動降（user 等回應）、100 RPS arrival rate 模型在 server 慢時 queue 會累積、暴露 <em>真實 production behavior</em>（user 不會體諒、會繼續送請求）。</p>
<p>這篇 migration playbook 不是 schema translation 文（<code>.jmx</code> 翻成 <code>.js</code> 只是表面）、是 <em>paradigm shift</em> — 從 closed-system model（thread）到 open-system model（arrival rate）的視角轉換。</p>
<h2 id="為什麼是-type-eschema--paradigm-同-high">為什麼是 Type E（schema + paradigm 同 High）</h2>
<p>跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/#6-%e7%b6%ad-diff-dimension-audit" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit</a>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>評</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema</td>
          <td>High</td>
          <td><code>.jmx</code> XML vs JavaScript scenario、test plan 完全不同 file format / DSL</td>
      </tr>
      <tr>
          <td>Operational</td>
          <td>Medium</td>
          <td>CLI / distributed run 接近、CI integration 差別大、distributed runner 模型不同</td>
      </tr>
      <tr>
          <td>Paradigm</td>
          <td>High</td>
          <td>thread group closed model → arrival rate open model、測試思維不同</td>
      </tr>
      <tr>
          <td>Components</td>
          <td>Low</td>
          <td>都是 load test runner、no multi-tool decomposition</td>
      </tr>
      <tr>
          <td>App change</td>
          <td>N/A</td>
          <td>是 test code、不是 production code</td>
      </tr>
      <tr>
          <td>Topology</td>
          <td>Low</td>
          <td>都是 CLI / runner 跑、無 sharding</td>
      </tr>
  </tbody>
</table>
<p>Schema High + Paradigm High 兩軸 High。按優先序 Schema &gt; Paradigm、預設選 Type A。但對 JMeter → k6 的讀者來說、<em>paradigm shift 才是難關</em> — schema translation 是工作量、但搞錯 paradigm 會讓 migration 後的測試結果 <em>跟 production 不對應</em>。所以選 <strong>Type E paradigm shift</strong> 結構、schema translation 抽出 Phase 1-2 補充。</p>
<h2 id="driverdeveloper-ergonomic--ci-gate-friendly">Driver：developer ergonomic + CI gate friendly</h2>
<p>從 JMeter 遷出 k6 的核心拉力是 <em>developer ergonomic + CI 友善</em>：</p>
<ul>
<li><strong><code>.jmx</code> XML 在 git 內 diff 不可讀</strong>：兩個 <code>.jmx</code> PR 的 diff 是 XML attribute reorder noise、reviewer 看不出來實際邏輯改了什麼；JavaScript 是純文字 + AST、PR diff 直接可讀</li>
<li><strong>GUI 學習曲線</strong>：JMeter GUI 不是現代 IDE、不熟的工程師寫一個 scenario 要花半天找對的 sampler 跟 listener；JavaScript 用既有 IDE（VS Code / IntelliJ）、autocomplete + lint + format 全有</li>
<li><strong>CI integration 步驟差</strong>：JMeter 在 CI 跑要 packaging plugin + non-GUI mode + result XML parser；k6 直接 <code>k6 run script.js</code>、result 是 JSON / Prometheus metrics、threshold pass/fail 直接 exit code</li>
<li><strong>單機 VU 容量</strong>：JMeter 單機通常 ~500-1000 thread（受 JVM 跟 OS thread limit）、k6 單機可跑 30K-50K VU（Go runtime + goroutine）、distributed runner 需求降低</li>
<li><strong>Workload model expressiveness</strong>：k6 <code>arrival-rate executor</code> + <code>ramping-vus</code> + <code>constant-vus</code> 三種 executor 直接對應 <em>open system / ramping / closed system</em> 三種測量視角、不像 JMeter 需要組合 Constant Throughput Timer + Synchronizing Timer + thread group 才達到</li>
</ul>
<p>這條 driver 在 <em>QA 團隊 GUI 維護 .jmx asset</em> 的 org 沒拉力（GUI 反而是優勢）、但對 <em>dev / SRE 寫 performance test 進 CI</em> 的 org 是強拉力。Audience 不同、migration value 完全不同。</p>
<h2 id="4-phase-partial-migration不收斂">4-phase partial migration（不收斂）</h2>
<p>Type E 的特徵是 <em>不收斂</em> — 多數 org 不會把 <code>.jmx</code> 全退役、會停在某個 phase 變成 hybrid：</p>
<h3 id="phase-1學會-k6-paradigm不寫實際-test">Phase 1：學會 k6 paradigm（不寫實際 test）</h3>
<p>寫一個 throwaway script 跑當前 production-like API、不為了 migrate、為了搞清楚 k6 paradigm：</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="nx">http</span> <span class="nx">from</span> <span class="s1">&#39;k6/http&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">check</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"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</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"> 5</span><span class="cl">  <span class="c1">// 不要用 vus: 100、用 arrival rate
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"></span>  <span class="nx">scenarios</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nx">open_model</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</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"> 9</span><span class="cl">      <span class="nx">rate</span><span class="o">:</span> <span class="mi">100</span><span class="p">,</span>           <span class="c1">// 每秒 100 request
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span>      <span class="nx">timeUnit</span><span class="o">:</span> <span class="s1">&#39;1s&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</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">12</span><span class="cl">      <span class="nx">preAllocatedVUs</span><span class="o">:</span> <span class="mi">200</span><span class="p">,</span> <span class="c1">// 預先準備 VU 數
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>      <span class="nx">maxVUs</span><span class="o">:</span> <span class="mi">500</span><span class="p">,</span>          <span class="c1">// 上限
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"></span>    <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="nx">thresholds</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</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="c1">// p95 &lt; 500ms
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"></span>    <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 class="c1">// 失敗率 &lt; 1%
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"></span>  <span class="p">},</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</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">23</span><span class="cl">  <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;https://api.example.com/orders&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="nx">check</span><span class="p">(</span><span class="nx">res</span><span class="p">,</span> <span class="p">{</span> <span class="s1">&#39;status 200&#39;</span><span class="o">:</span> <span class="p">(</span><span class="nx">r</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="mi">200</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>對比同一個 test 用 <code>.jmx</code> 寫的形狀、思考 <em>為什麼 arrival rate 跟 thread group 測出來不一樣</em>。這 phase 的目標是 <em>paradigm internalization</em>、不是產出 migration artifact。團隊每個寫 performance test 的人都要過這一關、不能跳。</p>
<p>完成標準：寫的人能講清楚「arrival rate 100 / 5 分鐘」跟「100 thread / 5 分鐘 ramp-up」的 production behavior 差異。</p>
<h3 id="phase-2高價值-critical-path-改-k6gui-留-jmeter">Phase 2：高價值 critical path 改 k6（GUI 留 JMeter）</h3>
<p>選 <em>最常跑 + 最重要</em> 的 1-3 條 scenario 改寫 k6、不全部一次轉。典型候選：</p>
<ul>
<li>Pre-release smoke test（核心 API 的 baseline check）</li>
<li>Nightly regression（per-commit performance gate）</li>
<li>Peak readiness rehearsal scenario（活動前 T-7 跑的 stress test）</li>
</ul>
<p>GUI / QA 團隊維護的 <code>.jmx</code> <em>不動</em> — 那些通常是 multi-protocol（JDBC / JMS / FTP）、不在 k6 適合 scope。</p>
<p>工作主要塊：</p>
<ul>
<li><code>.jmx</code> thread group → k6 scenario executor 的 <em>paradigm-correct</em> 翻譯（不是欄位翻譯）</li>
<li>HTTP request 跟 assertion 翻譯（payload / header / cookies）</li>
<li>CSV data source（JMeter CSV Data Set Config）→ k6 <code>SharedArray</code> from JSON</li>
<li>結果輸出 schema 改變（XML / JTL → JSON / Prometheus / k6 Cloud）</li>
<li>CI integration 重做（GitHub Actions / GitLab CI 直接 <code>k6 run</code>、不需要 packaging）</li>
</ul>
<p>完成標準：critical path 的 k6 baseline 跟 <code>.jmx</code> baseline 數據對比一致（p50 / p95 / throughput 在 10% 誤差內、行為不一致時知道是 paradigm 差還是 bug）。</p>
<h3 id="phase-3qa-團隊雙工具技能hybrid-穩定形態">Phase 3：QA 團隊雙工具技能（hybrid 穩定形態）</h3>
<p>很多 org 停在這個 phase：QA 團隊用 GUI 維護 multi-protocol .jmx（covering JDBC / JMS / LDAP / SOAP / FTP）、dev / SRE 用 k6 維護 HTTP / gRPC / WebSocket performance test in CI。Two-tool stack 不是 broken state、是 <em>not-converged-by-design</em>。</p>
<p>這個 phase 的工作主要塊：</p>
<ul>
<li>文件化：哪類 test 用 k6、哪類用 JMeter、決策樹寫在 team handbook</li>
<li>結果整合：兩個工具的 metrics 都進同一個 Grafana dashboard（k6 → Prometheus 直接、JMeter → InfluxDB / Prometheus exporter）</li>
<li>Release gate 用 k6 為主（CI 整合直接）、JMeter 用於 manual QA campaign / multi-protocol 場景</li>
</ul>
<p>多數 org 不進 Phase 4。</p>
<h3 id="phase-4jmeter-退役少見">Phase 4：JMeter 退役（少見）</h3>
<p>只有當 <em>所有 protocol 都換到 k6 extension</em> 或 <em>捨棄了 multi-protocol coverage</em> 時、才 fully 退役 JMeter。常見路徑：</p>
<ul>
<li>用 k6 xk6 extensions 補 protocol（xk6-sql for JDBC、xk6-kafka for Kafka、xk6-amqp for RabbitMQ、xk6-mqtt for MQTT）</li>
<li>評估每個 extension 的 maturity / community support — xk6 ecosystem 比 JMeter plugin 小很多</li>
<li>接受 part of legacy <code>.jmx</code> test 直接 deprecate（covered by integration test 而非 load test）</li>
</ul>
<p>完成標準：所有 protocol 都在 k6 + xk6 內可表達、<code>.jmx</code> 全部 archive。</p>
<h2 id="5-個-production-踩雷">5 個 production 踩雷</h2>
<h3 id="1-thread-group--vu-直接翻譯最常見phase-2-必踩">1. Thread group → VU 直接翻譯（最常見、Phase 2 必踩）</h3>
<p>把 <code>numThreads=100</code> 翻成 <code>vus: 100</code> 就完事 — 結果 RPS 跟 JMeter 不一致、p95 完全不同形狀。原因：JMeter 100 thread 是 <em>closed model</em>（thread 等回應才送下一個）、k6 <code>vus: 100</code> 預設也是 closed model、但 <em>iteration 結束就立刻送下一個</em>（無 think time）— 兩者的 <em>throughput 行為</em> 差異來自 think time / response time。</p>
<p>修法：</p>
<ul>
<li>不用 <code>vus: N</code>、用 <code>constant-arrival-rate</code> 或 <code>ramping-arrival-rate</code>、直接表達 <em>每秒幾個請求</em></li>
<li>如果一定要 closed model（pre-existing JMeter scenario 對比）、在 default function 內加 <code>sleep(thinkTime)</code> 模擬 JMeter Think Time</li>
</ul>
<h3 id="2-arrival-rate-vs-concurrent-vu-混淆">2. Arrival rate vs concurrent VU 混淆</h3>
<p><code>arrival-rate</code> executor 的 <code>rate: 100</code> 意思是 <em>每秒進來 100 request</em>、<code>preAllocatedVUs: 200</code> 是 <em>預先準備 200 個 VU worker pool</em>。如果 service 變慢（p95 從 100ms 飄到 500ms）、需要的 VU 數會從 100/sec * 0.1s = 10 暴增到 100/sec * 0.5s = 50、<code>preAllocatedVUs</code> 不夠就會 warning「ran out of VUs」、實際 arrival rate 達不到 spec。</p>
<p>修法：</p>
<ul>
<li><code>preAllocatedVUs</code> 設為 <code>maxVUs / 2</code></li>
<li><code>maxVUs</code> 設為 <code>rate * worst_case_response_time_seconds * 5</code>（5x safety margin）</li>
<li>Monitor <code>dropped_iterations</code> metric — 不該 &gt; 0、&gt; 0 表示 worker pool 不夠</li>
</ul>
<h3 id="3-protocol-gapk6-沒原生對應-jmeter-的部分">3. Protocol gap（k6 沒原生對應 JMeter 的部分）</h3>
<p>k6 原生支援 HTTP/1.1 / HTTP/2 / gRPC / WebSocket / SSE。<strong>沒有</strong>原生支援：</p>
<ul>
<li>JDBC（要 xk6-sql extension）</li>
<li>JMS（要 xk6-amqp / xk6-kafka extension）</li>
<li>LDAP（無 extension、要外接 LDAP client）</li>
<li>FTP（無 extension）</li>
<li>SMTP / IMAP / POP3（無 extension）</li>
<li>SOAP（HTTP module 內手寫 XML body、無 helper）</li>
</ul>
<p>如果 <code>.jmx</code> 用了這些 protocol、評估 xk6 extension 成熟度（GitHub stars、recent commit、issue volume）、不成熟就把這些 test 留在 JMeter。</p>
<h3 id="4-結果輸出-schema-改變result-post-processing-全部要重寫">4. 結果輸出 schema 改變（result post-processing 全部要重寫）</h3>
<p>JMeter 預設輸出 JTL XML（per-sample 一行）、有 listener 後處理。k6 預設輸出 stdout summary + optional JSON / CSV / Prometheus / k6 Cloud。如果有既有 <em>result analysis pipeline</em>（從 JTL 拉 data 進 BI tool、產 trend chart）、Phase 2 必須重寫。</p>
<p>修法：</p>
<ul>
<li>評估直接接 Prometheus + Grafana（k6 native）取代既有 BI dashboard</li>
<li>或寫 k6 JSON output → 自家 BI 的 transformation script</li>
</ul>
<h3 id="5-ci-integration-重做distributed-runner-模型不同">5. CI integration 重做（distributed runner 模型不同）</h3>
<p>JMeter 在 CI 跑要：JVM provision、plugin install、<code>.jmx</code> upload、non-GUI mode 跑、JTL 結果 parse、exit code 對應 threshold。k6 在 CI 跑：<code>k6 run script.js</code>、threshold pass / fail 直接 exit code、result 進 Prometheus / k6 Cloud。</p>
<p>看起來 k6 簡單、但有踩雷：</p>
<ul>
<li>Distributed run model 不同：JMeter 用 master-slave、k6 OSS 不內建 distributed、要 Grafana Cloud k6 或自建 k6-operator on Kubernetes</li>
<li>大規模負載（&gt; 50K VU）必須 distributed、Phase 2 評估時要先確認 distributed setup 不是 blocker</li>
<li>CI runner 資源：k6 是 native binary、CPU / memory 用量比 JMeter（JVM）低、但 runner spec 要按 max VU 估</li>
</ul>
<h2 id="protocol-gap-詳表">Protocol gap 詳表</h2>
<table>
  <thead>
      <tr>
          <th>Protocol</th>
          <th>JMeter sampler</th>
          <th>k6 對應</th>
          <th>成熟度 / 替代方案</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HTTP/1.1</td>
          <td>HTTP Request</td>
          <td><code>k6/http</code></td>
          <td>原生、成熟</td>
      </tr>
      <tr>
          <td>HTTP/2</td>
          <td>HTTP/2 sampler</td>
          <td><code>k6/http</code>（auto）</td>
          <td>原生、成熟</td>
      </tr>
      <tr>
          <td>gRPC</td>
          <td>（無原生、要 plugin）</td>
          <td><code>k6/net/grpc</code></td>
          <td>原生、成熟</td>
      </tr>
      <tr>
          <td>WebSocket</td>
          <td>WebSocket sampler（plugin）</td>
          <td><code>k6/ws</code></td>
          <td>原生、成熟</td>
      </tr>
      <tr>
          <td>SSE</td>
          <td>（無原生）</td>
          <td>xk6-sse</td>
          <td>extension、中等</td>
      </tr>
      <tr>
          <td>JDBC</td>
          <td>JDBC Request</td>
          <td>xk6-sql</td>
          <td>extension、不成熟、留 JMeter</td>
      </tr>
      <tr>
          <td>JMS</td>
          <td>JMS sampler</td>
          <td>xk6-amqp / xk6-kafka</td>
          <td>extension、protocol-specific</td>
      </tr>
      <tr>
          <td>LDAP</td>
          <td>LDAP Request</td>
          <td>（無）</td>
          <td>外接 / 留 JMeter</td>
      </tr>
      <tr>
          <td>FTP</td>
          <td>FTP Request</td>
          <td>（無）</td>
          <td>留 JMeter</td>
      </tr>
      <tr>
          <td>SMTP / IMAP</td>
          <td>Mail sampler</td>
          <td>（無）</td>
          <td>留 JMeter</td>
      </tr>
      <tr>
          <td>SOAP / XML-RPC</td>
          <td>SOAP / XML-RPC Request</td>
          <td><code>k6/http</code> 手寫 XML body</td>
          <td>工作量大、留 JMeter</td>
      </tr>
      <tr>
          <td>TCP socket</td>
          <td>TCP sampler</td>
          <td><code>k6/net/tcp</code></td>
          <td>原生但簡單、複雜 protocol 留 JMeter</td>
      </tr>
  </tbody>
</table>
<h2 id="容量與成本對照">容量與成本對照</h2>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>JMeter</th>
          <th>k6 OSS</th>
          <th>Grafana Cloud k6</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cost</td>
          <td>Free (Apache)</td>
          <td>Free (Apache 2.0)</td>
          <td>$49+ / mo (Pro)</td>
      </tr>
      <tr>
          <td>單機 VU 容量</td>
          <td>~500-1000 thread</td>
          <td>30K-50K VU</td>
          <td>unlimited（cloud runner）</td>
      </tr>
      <tr>
          <td>Distributed</td>
          <td>master-slave 內建</td>
          <td>不內建、需 k6-operator</td>
          <td>cloud-native</td>
      </tr>
      <tr>
          <td>Result store</td>
          <td>JTL XML（local）</td>
          <td>stdout / JSON / Prom</td>
          <td>cloud retained</td>
      </tr>
      <tr>
          <td>CI integration</td>
          <td>需 packaging</td>
          <td>native CLI</td>
          <td>native + cloud</td>
      </tr>
      <tr>
          <td>Multi-protocol coverage</td>
          <td>廣</td>
          <td>窄（HTTP/gRPC/WS）+ xk6</td>
          <td>同 OSS</td>
      </tr>
  </tbody>
</table>
<p>對 dev-driven CI gate use case：k6 OSS 已經夠用、Grafana Cloud k6 在 <em>跨 region runner + result retention + dashboard 整合</em> 時才有 ROI。對既有 multi-protocol .jmx asset：考慮 Phase 3 hybrid stable state、不要強推 Phase 4。</p>
<h2 id="何時不要切">何時不要切</h2>
<ul>
<li><strong>multi-protocol coverage 是核心需求</strong>：JDBC + JMS + LDAP + FTP 必要、xk6 extension 不夠成熟、留 JMeter</li>
<li><strong>QA 團隊維護 GUI .jmx</strong>：QA 不寫 code、<code>.jmx</code> GUI 是團隊資產、貿然轉 k6 等於 throwaway QA team</li>
<li><strong>既有 multi-year .jmx asset 大量</strong>：500+ scenario 全部翻譯成本 &gt; k6 ergonomic 收益、考慮 Phase 3 stable hybrid</li>
<li><strong>Distributed run 需求極大（&gt; 100K VU）但 ops budget 緊</strong>：k6-operator on Kubernetes 不便宜、Grafana Cloud k6 對應 tier 也不便宜、JMeter master-slave 仍是 cost-effective 選項</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>平行 batch：<a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/migrate-from-pyroscope/" data-link-title="Pyroscope → Datadog Continuous Profiler：profiling deployment lifecycle 各階段 operational ownership 轉手" data-link-desc="Pyroscope → Datadog Continuous Profiler 是 Type C operational hybrid migration — pprof data model 接近、profile lifecycle 五階段（install / instrument / ingest / query / cost）的 ops ownership 從 self-host 轉到 SaaS。本文走 6 維 audit（Operational High 其他 Low）、4-phase migration（operational audit &#43; agent parallel &#43; tag reconcile &#43; cutover）、5 production 踩雷（agent 重複 overhead / tag schema 不一致 / trace_id correlation 斷 / cost 突增 / retention 政策變動）、何時保留 Pyroscope（資料主權 / 內網 / OSS-first / cost sensitive）">Pyroscope → Datadog Profiler</a>（Type C operational hybrid）</li>
<li>同 batch Type E：<a href="/blog/backend/08-incident-response/vendors/pagerduty/migrate-to-incident-io/" data-link-title="PagerDuty → incident.io：「On-call」是個 retconned word、同名不同 contract" data-link-desc="PagerDuty → incident.io 不是 schema translation — 兩家的「on-call」字面相同、contract 不同（alert routing vs IR coordination &#43; Slack-native &#43; retrospective）。本文走 Type E paradigm shift、6 維 audit 顯示 paradigm / schema / operational 三軸 High、用 4-phase partial migration（不收斂、Phase 1-2 多數 org 停留）、5 個 production 踩雷（雙系統 state drift / severity 翻譯失真 / schedule layer 漏 / Slack channel 過載 / retrospective 斷層）、跟 PagerDuty Process Automation / AIOps 沒對應的 capability gap">PagerDuty → incident.io</a>（IR paradigm shift）</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> / <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/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>（CI gate integration）</li>
<li>vendor 對照：<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/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/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">Migration Playbook Methodology</a>（Type E paradigm shift 結構說明）</li>
</ul>
]]></content:encoded></item><item><title>Pyroscope → Datadog Continuous Profiler：profiling deployment lifecycle 各階段 operational ownership 轉手</title><link>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/migrate-from-pyroscope/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/migrate-from-pyroscope/</guid><description>&lt;p>Continuous profiling deployment 的 lifecycle 有五階段：&lt;strong>install&lt;/strong>（agent / SDK 部署） → &lt;strong>instrument&lt;/strong>（service / env / version tag 注入） → &lt;strong>ingest&lt;/strong>（profile sample 進 backend store） → &lt;strong>query&lt;/strong>（flame graph / diff / explore） → &lt;strong>cost&lt;/strong>（storage retention / billing）。Pyroscope 跟 Datadog Continuous Profiler 在這五階段的 &lt;em>ops ownership 分布完全不同&lt;/em>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>階段&lt;/th>
 &lt;th>Pyroscope（self-host）&lt;/th>
 &lt;th>Datadog Continuous Profiler&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Install&lt;/td>
 &lt;td>Grafana Alloy / Pyroscope agent / per-language SDK、自己部署&lt;/td>
 &lt;td>Datadog Agent（多半 APM 已部署）、SDK 加 flag&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Instrument&lt;/td>
 &lt;td>tag schema 自己設計&lt;/td>
 &lt;td>用 Datadog 既有 &lt;code>service&lt;/code> / &lt;code>env&lt;/code> / &lt;code>version&lt;/code> tag&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ingest&lt;/td>
 &lt;td>Pyroscope server（自管 storage / scaling）&lt;/td>
 &lt;td>Datadog SaaS（vendor 管）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query&lt;/td>
 &lt;td>Grafana datasource explore / flame graph panel&lt;/td>
 &lt;td>Datadog APM 介面、跟 trace / log / metrics deep link&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost&lt;/td>
 &lt;td>self-host TCO（storage + ops + on-call）&lt;/td>
 &lt;td>按 APM host 計費（profiling 是 add-on）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>從 Pyroscope 遷出 Datadog Continuous Profiler 的本質是 &lt;em>operational ownership 從 self-host 轉手到 SaaS&lt;/em> — pprof data model 跟 flame graph 視覺幾乎一樣、profile diff workflow 接近、&lt;em>差異 90% 在 ops 跟 ecosystem integration&lt;/em>。schema / paradigm 差距小、operational 差距大、就是 Type C operational hybrid 的 signature。&lt;/p>
&lt;h2 id="為什麼是-type-coperational-為主">為什麼是 Type C（operational 為主）&lt;/h2>
&lt;p>跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/#6-%e7%b6%ad-diff-dimension-audit" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit&lt;/a>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>評&lt;/th>
 &lt;th>說明&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Schema&lt;/td>
 &lt;td>Low-Medium&lt;/td>
 &lt;td>pprof 是 industry standard、profile types (CPU / heap / etc) 接近&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Operational&lt;/td>
 &lt;td>High&lt;/td>
 &lt;td>self-host backend storage / retention / scaling → SaaS 全託管&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Paradigm&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;td>都是 pprof-based continuous profiling、diff workflow 接近&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Components&lt;/td>
 &lt;td>Low-Medium&lt;/td>
 &lt;td>都需要 agent + backend、元件數量接近&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>App change&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;td>agent / SDK config 改、code instrumentation 接近&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Topology&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;td>都是 agent → backend 單向 ingest&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Operational = High（其他 Low） → &lt;strong>Type C operational hybrid&lt;/strong>。Type C 結構是 &lt;em>operational audit prefix + 4-phase drop-in cutover&lt;/em> — operational diff 集中在 ingest / cost / retention 三階段、其他階段是 schema-level drop-in。&lt;/p></description><content:encoded><![CDATA[<p>Continuous profiling deployment 的 lifecycle 有五階段：<strong>install</strong>（agent / SDK 部署） → <strong>instrument</strong>（service / env / version tag 注入） → <strong>ingest</strong>（profile sample 進 backend store） → <strong>query</strong>（flame graph / diff / explore） → <strong>cost</strong>（storage retention / billing）。Pyroscope 跟 Datadog Continuous Profiler 在這五階段的 <em>ops ownership 分布完全不同</em>：</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>Pyroscope（self-host）</th>
          <th>Datadog Continuous Profiler</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Install</td>
          <td>Grafana Alloy / Pyroscope agent / per-language SDK、自己部署</td>
          <td>Datadog Agent（多半 APM 已部署）、SDK 加 flag</td>
      </tr>
      <tr>
          <td>Instrument</td>
          <td>tag schema 自己設計</td>
          <td>用 Datadog 既有 <code>service</code> / <code>env</code> / <code>version</code> tag</td>
      </tr>
      <tr>
          <td>Ingest</td>
          <td>Pyroscope server（自管 storage / scaling）</td>
          <td>Datadog SaaS（vendor 管）</td>
      </tr>
      <tr>
          <td>Query</td>
          <td>Grafana datasource explore / flame graph panel</td>
          <td>Datadog APM 介面、跟 trace / log / metrics deep link</td>
      </tr>
      <tr>
          <td>Cost</td>
          <td>self-host TCO（storage + ops + on-call）</td>
          <td>按 APM host 計費（profiling 是 add-on）</td>
      </tr>
  </tbody>
</table>
<p>從 Pyroscope 遷出 Datadog Continuous Profiler 的本質是 <em>operational ownership 從 self-host 轉手到 SaaS</em> — pprof data model 跟 flame graph 視覺幾乎一樣、profile diff workflow 接近、<em>差異 90% 在 ops 跟 ecosystem integration</em>。schema / paradigm 差距小、operational 差距大、就是 Type C operational hybrid 的 signature。</p>
<h2 id="為什麼是-type-coperational-為主">為什麼是 Type C（operational 為主）</h2>
<p>跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/#6-%e7%b6%ad-diff-dimension-audit" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit</a>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>評</th>
          <th>說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema</td>
          <td>Low-Medium</td>
          <td>pprof 是 industry standard、profile types (CPU / heap / etc) 接近</td>
      </tr>
      <tr>
          <td>Operational</td>
          <td>High</td>
          <td>self-host backend storage / retention / scaling → SaaS 全託管</td>
      </tr>
      <tr>
          <td>Paradigm</td>
          <td>Low</td>
          <td>都是 pprof-based continuous profiling、diff workflow 接近</td>
      </tr>
      <tr>
          <td>Components</td>
          <td>Low-Medium</td>
          <td>都需要 agent + backend、元件數量接近</td>
      </tr>
      <tr>
          <td>App change</td>
          <td>Low</td>
          <td>agent / SDK config 改、code instrumentation 接近</td>
      </tr>
      <tr>
          <td>Topology</td>
          <td>Low</td>
          <td>都是 agent → backend 單向 ingest</td>
      </tr>
  </tbody>
</table>
<p>Operational = High（其他 Low） → <strong>Type C operational hybrid</strong>。Type C 結構是 <em>operational audit prefix + 4-phase drop-in cutover</em> — operational diff 集中在 ingest / cost / retention 三階段、其他階段是 schema-level drop-in。</p>
<h2 id="drivertco--datadog-ecosystem-內-deep-linking">Driver：TCO + Datadog ecosystem 內 deep linking</h2>
<p>從 Pyroscope 遷出 Datadog Profiler 的核心 driver 有兩條：</p>
<p><strong>TCO（total cost of ownership）</strong>：self-host Pyroscope 看起來免費（Apache 2.0）、但實際 ops 成本：</p>
<ul>
<li>Storage：profile sample 大、retention 與 storage cost 需要自己估（每 service 每天可能 1-10 GB）</li>
<li>Scaling：profile ingestion 突增（deploy event / canary rollout 期間）要 storage / ingester 撐住</li>
<li>On-call：Pyroscope server 自己會壞、要 on-call 帶</li>
<li>Ops engineer time：規模成長後可能需要 0.5-1 個 FTE 維護 Grafana stack 內的 Pyroscope</li>
</ul>
<p>對 <em>已經有 Datadog APM 帳單</em> 的 org、profiling 會跟 APM / profiled host 進同一個商務談判與 usage report，不需要額外 ops headcount。這條 TCO 拉力對 50-500 人 eng 規模最強 — 小於 50 人 self-host 也撐得住、大於 500 人 self-host 的 economy of scale 可能開始 favored Pyroscope。</p>
<p><strong>Ecosystem deep linking</strong>：Datadog Profiler 跟 trace / log / metrics <em>在同一個介面</em>、profile span 直接連到 trace span、deploy marker 直接顯示在 flame graph timeline、cross-signal query 不用 wire。Pyroscope 要透過 Grafana datasource correlation 達到類似效果、但需要 Tempo / Loki 已部署 + 手動配 correlation rule、整合精度跟自動程度都不如 Datadog 內建。</p>
<p>這條 driver 對 <em>已是 Datadog-heavy org</em> 強、對 <em>Grafana-heavy org</em> 弱（後者 Pyroscope 才是自然選擇、Datadog Profiler 反而 ecosystem misfit）。</p>
<h2 id="type-c-migration4-phase">Type C migration（4-phase）</h2>
<h3 id="phase-1operational-audit">Phase 1：Operational audit</h3>
<p>確認 Datadog Continuous Profiler 能 cover Pyroscope 當前用途、且 ops ownership 轉移可接受：</p>
<ul>
<li><strong>Language coverage</strong>：當前 Pyroscope 用哪些 SDK？Datadog Profiler 支援 Go / Java / Python / Node / Ruby / .NET / PHP / Rust / C / C++，但每個語言的 profiler type 與啟用方式不同；Erlang 等較小眾語言仍要逐項驗證</li>
<li><strong>Profile type coverage</strong>：Pyroscope 抓的 profile type（CPU / heap / allocation / goroutine / lock / wall time）在 Datadog Profiler 同語言是否都支援？Java 跟 Go 兩家都全、其他語言可能 partial</li>
<li><strong>Retention requirement</strong>：Pyroscope retention 可自管；Datadog Profiler retention 依產品資料保留政策與合約設定，要確認是否滿足既有 long-term baseline / audit 查詢需求</li>
<li><strong>資料主權</strong>：profile data 包含 application function name / line number、有時帶 customer data hint（function 名字暗示 customer-specific 邏輯）— 是否能 send to SaaS？</li>
<li><strong>Cost forecast</strong>：Datadog public pricing 以 profiled host / APM tier 計費，估算時要用實際 host 數、container density、APM plan 與 commit discount 跟 Pyroscope self-host TCO 比</li>
</ul>
<p>完成標準：寫出「Datadog 能 cover、不能 cover、不確定」三欄、不確定欄全部問過 Datadog SE / 用 trial 跑過 production-like load。</p>
<h3 id="phase-2agent-parallel-runprofile-雙寫">Phase 2：Agent parallel run（profile 雙寫）</h3>
<p>Datadog Agent 多半已部署（如果在用 Datadog APM）。Phase 2 在現有 Datadog Agent 開 profiling flag、<em>不關 Pyroscope agent</em>、跑 2-4 週 parallel：</p>
<ul>
<li>設定 <code>DD_PROFILING_ENABLED=true</code>（per service env var）</li>
<li>每個 service SDK init 加對應 profiling enable call（Go: <code>profiler.Start()</code>、Python: <code>import ddtrace.profiling.auto</code>、Java: agent flag 即可）</li>
<li>Pyroscope SDK / Alloy 繼續跑、profile 雙寫到兩家</li>
<li>對比同一個 service / 同一個時間段在 Pyroscope flame graph 跟 Datadog Profiler flame graph、確認 hot path 一致</li>
</ul>
<p>Parallel run 期間的 overhead：兩邊 agent 同時跑 profiling、CPU overhead 大致 2-4%（單一 profiler 通常 1-2%、雙寫 double）、production-acceptable but not free。Phase 2 不要超過 4 週、避免長期 double overhead。</p>
<p>完成標準：每個 production service 在 Datadog Profiler 都有 4 週連續 profile data、跟 Pyroscope flame graph 對比一致。</p>
<h3 id="phase-3tag-schema-reconcile--trace-correlation">Phase 3：Tag schema reconcile + trace correlation</h3>
<p>Pyroscope tag schema（自己設計）跟 Datadog standard tag（<code>service</code> / <code>env</code> / <code>version</code> / <code>host</code>）對齊：</p>
<ul>
<li>Pyroscope tag <code>app=checkout-api</code> → Datadog <code>service:checkout-api</code></li>
<li>Pyroscope tag <code>env=prod-us</code> → Datadog <code>env:prod</code> + <code>region:us-east-1</code></li>
<li>Pyroscope tag <code>git_sha=abc123</code> → Datadog <code>version:abc123</code>（透過 <code>DD_VERSION</code>）</li>
<li>Custom tag（team / business unit）→ Datadog custom tag（透過 SDK config 或 agent label）</li>
</ul>
<p>Trace correlation：Datadog Profiler 自動跟 APM trace 關聯（透過 <code>trace_id</code> injection into profile sample）— Phase 3 要驗證這個 correlation 可用（在 Datadog APM 點 trace span、應該能跳到對應時段 profile）。</p>
<p>Deploy marker：CI 在 deploy 時打 Datadog deployment marker（<code>datadog-ci deployment mark</code> 或 API call）、讓 Profiler diff view 知道 baseline / candidate 邊界。</p>
<p>完成標準：tag schema 1:1 對應、trace → profile deep link 可用、deploy marker 自動推送。</p>
<h3 id="phase-4pyroscope-agent-關掉--server-退役">Phase 4：Pyroscope agent 關掉 + server 退役</h3>
<p>逐步關 Pyroscope agent（per service rollout）：</p>
<ul>
<li>先關低重要性 service（dev / staging / non-critical prod）</li>
<li>觀察 1-2 週、確認沒事故再關下一批</li>
<li>最後關 critical service、留 Pyroscope server 跑 1-2 週空 ingest（rollback 緩衝）</li>
<li>取消 Pyroscope server（decommission storage、release K8s resource、關 on-call rotation）</li>
</ul>
<p>Pyroscope 歷史 profile data 保留策略：</p>
<ul>
<li>多數場景：直接 archive S3 / GCS、未來查得到但不維護 query UI</li>
<li>強合規場景：export Pyroscope flame graph data 為 pprof file 保存（pprof 是長期可讀格式）</li>
</ul>
<p>完成標準：所有 production service 只走 Datadog Profiler、Pyroscope server 取消、TCO 對比驗證符合預期。</p>
<h2 id="5-個-production-踩雷">5 個 production 踩雷</h2>
<h3 id="1-兩家-agent-同時跑造成-production-overhead">1. 兩家 agent 同時跑造成 production overhead</h3>
<p>Phase 2 parallel run 期間 CPU overhead 2-4%、預期內。但有些 service 設定錯誤（例如 sampling rate 預設都拉高）變成 6-10% overhead、p99 飄升、誤判為 Datadog Profiler 自己的問題。修法是 <em>parallel run 期間 Pyroscope sampling rate 降低 50%</em>（已經有歷史 baseline、不需要全採）、且 Phase 2 不要在 peak event 期間跑。</p>
<h3 id="2-tag-schema-不一致導致-historic-baseline-對不上">2. Tag schema 不一致導致 historic baseline 對不上</h3>
<p>Pyroscope tag <code>app=checkout-api</code> 跟 Datadog <code>service:checkout-api</code> 都指同一個 service、但 Datadog 內 <em>historic profile</em> 沒有 <code>app</code> tag、所以從 Pyroscope 視角看 baseline 跟 Datadog 視角看 baseline <em>是不同的時段切片</em>。Release regression 比較時用錯 baseline、會誤判 release 沒問題（實際 baseline 不對應）。修法是 Phase 3 明確記錄 <em>Datadog Profiler 的 baseline 起算時間是 Phase 2 開始日</em>、Pyroscope 歷史不直接搬入比較。</p>
<h3 id="3-trace_id-correlation-斷phase-3-最常見">3. Trace_id correlation 斷（Phase 3 最常見）</h3>
<p>Datadog Profiler 自動關聯 trace 的前提是 <em>同一個 Datadog Agent + APM SDK 注入 trace_id</em>。如果 service 用 OpenTelemetry SDK + Datadog Agent（OTel-first 配置）、trace_id 注入方式不同、profile 跟 trace 可能無法自動 correlate。修法是 <em>確認所有 service 用 Datadog SDK 或正確配 OTel-to-Datadog converter</em>、在 Datadog APM 介面 random 抽 10 個 trace 驗證 profile correlation 是否 wire 通。</p>
<h3 id="4-cost-突增phase-4-後常見">4. Cost 突增（Phase 4 後常見）</h3>
<p>關掉 Pyroscope agent 後、Datadog Profiler 變成 sole profile source、ingest volume 上升、Datadog bill 比預估高 30-50%。原因通常是：</p>
<ul>
<li>Profile sampling rate 不小心開太高（部分 service config 沒對齊）</li>
<li>Custom tag 太多（每個 unique tag combination 增加 indexing cost）</li>
<li>Profile event 量比預估高（service count × sampling rate × profile types）</li>
</ul>
<p>修法是 Phase 1 cost forecast 要保留 30% buffer、且 Phase 4 完成後立即跑 Datadog usage report 確認 actual 跟 forecast 對比。</p>
<h3 id="5-retention--baseline-政策變動造成歷史-query-斷層">5. Retention / baseline 政策變動造成歷史 query 斷層</h3>
<p>Pyroscope 自管 retention 可以設成配合內部 storage 與 compliance policy；Datadog Profiler 的 retention 依產品資料保留政策與合約設定。真正的風險不是固定「7 天 vs 90 天」，而是 <em>既有 baseline 查詢習慣是否還成立</em>：原 Pyroscope user 可能習慣查特定 release 前後的 flame graph、Datadog 端則要看 profile tag、deployment marker 與保留政策能否支援同樣查詢。修法是 Phase 1 明確列出「要查多久前、用什麼 tag 找、誰有權限看」三個問題，超出 profile retention 的長期 trend 改用 Datadog metrics-derived signal（cumulative CPU% / memory growth rate）或保留 Pyroscope archive。</p>
<h2 id="capability-對照">Capability 對照</h2>
<table>
  <thead>
      <tr>
          <th>能力</th>
          <th>Pyroscope（self-host）</th>
          <th>Datadog Continuous Profiler</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Language SDK 覆蓋</td>
          <td>Go / Java / Python / Node / Ruby / .NET / Rust / PHP</td>
          <td>Go / Java / Python / Node / Ruby / .NET / PHP / Rust / C / C++</td>
      </tr>
      <tr>
          <td>Profile type（CPU / heap / lock / etc）</td>
          <td>全（依語言 SDK 而定）</td>
          <td>全（依語言 SDK 而定）</td>
      </tr>
      <tr>
          <td>Flame graph diff workflow</td>
          <td>Grafana panel</td>
          <td>Datadog Profile Comparison</td>
      </tr>
      <tr>
          <td>Trace correlation</td>
          <td>手動配 Grafana correlation rule</td>
          <td>自動（trace_id injection）</td>
      </tr>
      <tr>
          <td>Deploy marker</td>
          <td>手動</td>
          <td>datadog-ci 自動</td>
      </tr>
      <tr>
          <td>Retention</td>
          <td>自管（無上限、cost 自負）</td>
          <td>依 Datadog retention policy / 合約設定</td>
      </tr>
      <tr>
          <td>資料主權</td>
          <td>完全自管</td>
          <td>SaaS（profile 出境）</td>
      </tr>
      <tr>
          <td>Ops ownership</td>
          <td>自管（storage / scaling / on-call）</td>
          <td>Vendor</td>
      </tr>
      <tr>
          <td>Cost model</td>
          <td>self-host TCO</td>
          <td>profiled host / APM tier / commit discount</td>
      </tr>
      <tr>
          <td>Cross-signal query</td>
          <td>Grafana cross-datasource</td>
          <td>Datadog native（trace / log / profile / metrics 同一 query bar）</td>
      </tr>
  </tbody>
</table>
<h2 id="何時不要切保留-pyroscope">何時不要切（保留 Pyroscope）</h2>
<ul>
<li><strong>資料主權 / compliance 不允許 profile data 出境</strong>：金融 / 醫療 / 政府 / 國防、保留 Pyroscope self-host</li>
<li><strong>內網 / air-gap 部署</strong>：物理上連不到 Datadog SaaS、保留 Pyroscope</li>
<li><strong>OSS-first / vendor neutrality policy</strong>：org 政策不允許 vendor lock-in profiling、保留 Pyroscope</li>
<li><strong>規模超大（&gt; 500 APM host）</strong>：Datadog Profiler add-on cost × host 數可能超過 Pyroscope self-host TCO、計算交叉點</li>
<li><strong>Long retention / 自訂 archive 強需求</strong>：若 profile data 必須照內部 retention policy 長期保存、保留 Pyroscope 或建立 export / archive 流程</li>
<li><strong>Datadog 不支援的語言或 profiler type</strong>：Erlang、特定 runtime 或特定 profile type 若 Datadog 無法覆蓋，保留 Pyroscope 為對應 service profiling</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>平行 batch：<a href="/blog/backend/09-performance-capacity/vendors/k6/migrate-from-jmeter/" data-link-title="JMeter → k6：k6 不是 JMeter 的「script 版本」、是 VU model 取代 thread model" data-link-desc="JMeter → k6 是 Type E paradigm shift、不是把 .jmx XML 翻成 JavaScript — VU (virtual user) model 跟 thread group model 是兩種對「使用者行為」不同的建模方式。本文走 6 維 audit（Schema High / Paradigm High / Operational Medium）、釐清反向定義、4-phase partial migration（多數 org 停 Phase 2-3 hybrid）、5 production 踩雷（thread group 翻譯失真 / arrival rate vs concurrent VU 混淆 / protocol gap / 結果 schema 改 / CI integration 重做）、protocol gap（JDBC / JMS / LDAP 在 k6 沒原生對應）、何時不要切">JMeter → k6</a>（Type E paradigm shift）</li>
<li>同 batch Type C：（待補、本篇是 batch 唯一 Type C）</li>
<li>上游：<a href="/blog/backend/09-performance-capacity/performance-observability/" data-link-title="9.8 效能可觀測性" data-link-desc="saturation metric、USE / RED method、cost dashboard">9.8 Performance Observability</a> / <a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 Continuous Profiling</a></li>
<li>下游：<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 Performance Improvement Loop</a>（profile diff 接入 release regression workflow）</li>
<li>vendor 對照：<a href="/blog/backend/09-performance-capacity/vendors/pyroscope/" data-link-title="Pyroscope" data-link-desc="用 Grafana 生態與開源 profiling backend 建立可自管 profile diff 與 flame graph 的工具">Pyroscope</a> / <a href="/blog/backend/09-performance-capacity/vendors/datadog-continuous-profiler/" data-link-title="Datadog Continuous Profiler" data-link-desc="用 SaaS APM 整合、deployment marker 與 profile diff 支援 release regression 定位的 profiling 工具">Datadog Continuous Profiler</a> / <a href="/blog/backend/09-performance-capacity/vendors/parca/" data-link-title="Parca" data-link-desc="用 eBPF 與開源 continuous profiling 平台建立 infrastructure-wide profile evidence 的工具">Parca</a></li>
<li>方法論：<a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">Migration Playbook Methodology</a>（Type C operational hybrid 結構說明）</li>
</ul>
]]></content:encoded></item><item><title>Frontend with Playwright — 框架無關的前端開發 + Playwright 驗證</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/</guid><description>&lt;h2 id="這個資料夾是什麼">這個資料夾是什麼&lt;/h2>
&lt;p>&lt;code>frontend-with-playwright&lt;/code> 是一套前端開發協議 skill，原生位置在 &lt;a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/frontend-with-playwright">&lt;code>.claude/skills/frontend-with-playwright/&lt;/code>&lt;/a> 供 Claude runtime 呼叫；這份是&lt;strong>同內容的文章版本&lt;/strong>，讓人類讀者也能直接在 blog 閱讀。&lt;/p>
&lt;p>原則框架無關 — 適用 vanilla HTML/CSS/JS、Vue、React、jQuery — 因為核心是「DOM / CSS / JS 三者的本質行為」加上「Playwright 用 live DOM 量測驗證」、不依賴特定框架的渲染機制。源頭是 &lt;a href="https://tarrragon.github.io/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。">&lt;code>content/report/&lt;/code>&lt;/a> 累積的 50+ 篇事後檢討、由本 skill 的六份 reference 萃取對應六個情境的協議步驟。&lt;/p>
&lt;h2 id="閱讀順序">閱讀順序&lt;/h2>
&lt;h3 id="場景-1第一次接觸">場景 1：第一次接觸&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>順序&lt;/th>
 &lt;th>檔案&lt;/th>
 &lt;th>目的&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/skill/" data-link-title="Frontend with Playwright — SKILL 入口" data-link-desc="框架無關的前端開發 &amp;#43; Playwright 驗證 SKILL 入口：三大支柱、六大原則速查、六份情境 reference 的觸發路由。">SKILL.md&lt;/a>&lt;/td>
 &lt;td>三大支柱 + 六大原則速查、觸發路由表&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2&lt;/td>
 &lt;td>依情境挑一份 reference（見下表）&lt;/td>
 &lt;td>把原則翻譯成可套用的協議步驟、模板與範例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3&lt;/td>
 &lt;td>該 reference 結尾的 self-check checklist&lt;/td>
 &lt;td>自評有沒有按協議走&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="場景-2已熟悉協議想直接解決當前任務">場景 2：已熟悉協議、想直接解決當前任務&lt;/h3>
&lt;p>直接依觸發情境跳對應 reference：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>觸發情境&lt;/th>
 &lt;th>reference&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>要寫 CSS 規則、需要先確認 DOM 結構 / selector 該怎麼寫&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定 selector 該多寬、命中其他元素&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不確定值該寫進 CSS 還是 JS、CSS layers / variable / class toggle 取捨&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>用 &lt;code>!important&lt;/code> / inline style 解 specificity&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要用 playwright 驗證 layout / 假設 / 互動&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Layout bug 第 2 次出現、想寫成測試&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>客製 UI 被 framework 還原、不知道該注入到哪&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要客製外部組件（pagefind / vendor library）&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>使用者反映卡頓、CPU 100%、scroll lag、resize jank&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要設計 MutationObserver / event listener 範圍&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>要驗收鍵盤 / screen reader / motor / 視覺 a11y&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &amp;gt; ARIA、視覺 / motor a11y。">accessibility-and-focus&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JS reparent 後 focus 跑掉、aria-live 沒朗讀&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &amp;gt; ARIA、視覺 / motor a11y。">accessibility-and-focus&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設計 filter / sort / count 操作、source 是分批 / streaming&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &amp;#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>「Load more 後畫面閃但內容沒變」的 silent 缺口&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &amp;#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition&lt;/a>（層錯位）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Backend / 演算法 / map-reduce 的 post-filter 漏項&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &amp;#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition&lt;/a>（跨領域同結構）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每份 reference 自包含：讀任一份不需要回頭讀其他 reference。&lt;/p></description><content:encoded><![CDATA[<h2 id="這個資料夾是什麼">這個資料夾是什麼</h2>
<p><code>frontend-with-playwright</code> 是一套前端開發協議 skill，原生位置在 <a href="https://github.com/tarrragon/blog/tree/main/.claude/skills/frontend-with-playwright"><code>.claude/skills/frontend-with-playwright/</code></a> 供 Claude runtime 呼叫；這份是<strong>同內容的文章版本</strong>，讓人類讀者也能直接在 blog 閱讀。</p>
<p>原則框架無關 — 適用 vanilla HTML/CSS/JS、Vue、React、jQuery — 因為核心是「DOM / CSS / JS 三者的本質行為」加上「Playwright 用 live DOM 量測驗證」、不依賴特定框架的渲染機制。源頭是 <a href="/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。"><code>content/report/</code></a> 累積的 50+ 篇事後檢討、由本 skill 的六份 reference 萃取對應六個情境的協議步驟。</p>
<h2 id="閱讀順序">閱讀順序</h2>
<h3 id="場景-1第一次接觸">場景 1：第一次接觸</h3>
<table>
  <thead>
      <tr>
          <th>順序</th>
          <th>檔案</th>
          <th>目的</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td><a href="/blog/skills/frontend-with-playwright/skill/" data-link-title="Frontend with Playwright — SKILL 入口" data-link-desc="框架無關的前端開發 &#43; Playwright 驗證 SKILL 入口：三大支柱、六大原則速查、六份情境 reference 的觸發路由。">SKILL.md</a></td>
          <td>三大支柱 + 六大原則速查、觸發路由表</td>
      </tr>
      <tr>
          <td>2</td>
          <td>依情境挑一份 reference（見下表）</td>
          <td>把原則翻譯成可套用的協議步驟、模板與範例</td>
      </tr>
      <tr>
          <td>3</td>
          <td>該 reference 結尾的 self-check checklist</td>
          <td>自評有沒有按協議走</td>
      </tr>
  </tbody>
</table>
<h3 id="場景-2已熟悉協議想直接解決當前任務">場景 2：已熟悉協議、想直接解決當前任務</h3>
<p>直接依觸發情境跳對應 reference：</p>
<table>
  <thead>
      <tr>
          <th>觸發情境</th>
          <th>reference</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>要寫 CSS 規則、需要先確認 DOM 結構 / selector 該怎麼寫</td>
          <td><a href="/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first</a></td>
      </tr>
      <tr>
          <td>不確定 selector 該多寬、命中其他元素</td>
          <td><a href="/blog/skills/frontend-with-playwright/dom-topology-first/" data-link-title="DOM Topology First — 寫 CSS 前先確認 DOM 結構" data-link-desc="frontend-with-playwright reference：寫 CSS 前用 playwright/DevTools 量真實 DOM、selector 三維度設計、起點四選一、idempotency 兩選一。">dom-topology-first</a></td>
      </tr>
      <tr>
          <td>不確定值該寫進 CSS 還是 JS、CSS layers / variable / class toggle 取捨</td>
          <td><a href="/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary</a></td>
      </tr>
      <tr>
          <td>用 <code>!important</code> / inline style 解 specificity</td>
          <td><a href="/blog/skills/frontend-with-playwright/css-js-boundary/" data-link-title="CSS / JS Boundary — CSS / JS 邊界與 specificity 處理" data-link-desc="frontend-with-playwright reference：CSS-only vs JS-assisted 判準、class toggle 取代 inline style、CSS layers 取代 specificity 戰、variable 單一定義位置、檔案拆分。">css-js-boundary</a></td>
      </tr>
      <tr>
          <td>要用 playwright 驗證 layout / 假設 / 互動</td>
          <td><a href="/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop</a></td>
      </tr>
      <tr>
          <td>Layout bug 第 2 次出現、想寫成測試</td>
          <td><a href="/blog/skills/frontend-with-playwright/playwright-in-loop/" data-link-title="Playwright in the Development Loop — 開發循環的三個位置" data-link-desc="frontend-with-playwright reference：Playwright 三個位置（假設 / 行為 / 互動驗證）的 evaluate 範例、寫成 layout test 的時機與模板、最低門檻 setup。">playwright-in-loop</a></td>
      </tr>
      <tr>
          <td>客製 UI 被 framework 還原、不知道該注入到哪</td>
          <td><a href="/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence</a></td>
      </tr>
      <tr>
          <td>要客製外部組件（pagefind / vendor library）</td>
          <td><a href="/blog/skills/frontend-with-playwright/framework-coexistence/" data-link-title="Framework Coexistence — 跟 framework-managed DOM 共處" data-link-desc="frontend-with-playwright reference：framework 邊界辨識、JS 操作四級安全度、客製 UI 注入到邊界外、外部組件四層合作（公共介面 → 邊界 → 邊界 DOM → 內部結構）。">framework-coexistence</a></td>
      </tr>
      <tr>
          <td>使用者反映卡頓、CPU 100%、scroll lag、resize jank</td>
          <td><a href="/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance</a></td>
      </tr>
      <tr>
          <td>要設計 MutationObserver / event listener 範圍</td>
          <td><a href="/blog/skills/frontend-with-playwright/reactive-performance/" data-link-title="Reactive Performance — Reactive 效能盤點與優化" data-link-desc="frontend-with-playwright reference：MutationObserver 三維度、polling → observer、iteration / regex 成本、layout reflow、resource 載入時序、reactive listener 盤點協議。">reactive-performance</a></td>
      </tr>
      <tr>
          <td>要驗收鍵盤 / screen reader / motor / 視覺 a11y</td>
          <td><a href="/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &gt; ARIA、視覺 / motor a11y。">accessibility-and-focus</a></td>
      </tr>
      <tr>
          <td>JS reparent 後 focus 跑掉、aria-live 沒朗讀</td>
          <td><a href="/blog/skills/frontend-with-playwright/accessibility-and-focus/" data-link-title="Accessibility and Focus — A11y 三道防線" data-link-desc="frontend-with-playwright reference：鍵盤可達性三要素、focus management on DOM move、aria-live 動態廣播、Native HTML &gt; ARIA、視覺 / motor a11y。">accessibility-and-focus</a></td>
      </tr>
      <tr>
          <td>設計 filter / sort / count 操作、source 是分批 / streaming</td>
          <td><a href="/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition</a></td>
      </tr>
      <tr>
          <td>「Load more 後畫面閃但內容沒變」的 silent 缺口</td>
          <td><a href="/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition</a>（層錯位）</td>
      </tr>
      <tr>
          <td>Backend / 演算法 / map-reduce 的 post-filter 漏項</td>
          <td><a href="/blog/skills/frontend-with-playwright/data-flow-and-filter-composition/" data-link-title="Data Flow and Filter Composition — Filter × Source 層錯位與五策略" data-link-desc="frontend-with-playwright reference：Filter / sort / count / transform stream 操作的層錯位識別 &#43; 五策略合成。原則跨前端 / 後端 / 演算法 / DB 通用、playwright 驗證模板。">data-flow-and-filter-composition</a>（跨領域同結構）</td>
      </tr>
  </tbody>
</table>
<p>每份 reference 自包含：讀任一份不需要回頭讀其他 reference。</p>
<h2 id="跟-requirement-protocol-的關係">跟 requirement-protocol 的關係</h2>
<p><a href="/blog/skills/requirement-protocol/" data-link-title="Requirement Protocol — 需求確認到實作的對話協議" data-link-desc="從需求確認到實作的對話協議：模糊指令澄清、可決定 vs 該確認、失敗 2 次轉折、覆寫成本告知、revert checkpoint、漸進驗證、工具切換時機。六大原則 &#43; 五份情境 reference。">requirement-protocol</a> 是上層的「對話協議」（澄清需求、失敗轉折、覆寫成本、工具切換時機）；本 skill 是下層的「前端執行協議」（DOM / CSS / JS / Playwright 的具體做法）。</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>該讀哪個 skill</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>不確定該怎麼跟使用者溝通、需求模糊、失敗該怎麼轉折</td>
          <td>requirement-protocol</td>
      </tr>
      <tr>
          <td>知道要做什麼、不確定前端該怎麼實作驗證</td>
          <td>frontend-with-playwright（本 skill）</td>
      </tr>
  </tbody>
</table>
<p>兩個 skill 的 <code>playwright</code> 段落互補：requirement-protocol 講「何時切」、本 skill 講「切了之後具體寫什麼 query」。</p>
<h2 id="與-blog-專案其他資料的關係">與 blog 專案其他資料的關係</h2>
<table>
  <thead>
      <tr>
          <th>位置</th>
          <th>角色</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>.claude/skills/frontend-with-playwright/</code></td>
          <td>實際 skill — Claude runtime 呼叫的檔案來源</td>
      </tr>
      <tr>
          <td><code>content/skills/frontend-with-playwright/</code>（本處）</td>
          <td>文章版本 — 人類讀者在 blog 閱讀</td>
      </tr>
      <tr>
          <td><a href="/blog/report/" data-link-title="Report — 開發過程的事後檢討" data-link-desc="blog 開發過程中、把實際遇到的版型 / 整合 / 框架共處等情境、整理成『應該怎麼做、沒這樣做會有什麼麻煩』的事後檢討。每篇皆為正向指引、幫助下一輪同類任務跳過反覆試錯。"><code>content/report/</code></a></td>
          <td>50+ 篇事後檢討、本 skill 的素材來源；reference 結尾連回對應篇</td>
      </tr>
      <tr>
          <td><a href="/blog/skills/requirement-protocol/" data-link-title="Requirement Protocol — 需求確認到實作的對話協議" data-link-desc="從需求確認到實作的對話協議：模糊指令澄清、可決定 vs 該確認、失敗 2 次轉折、覆寫成本告知、revert checkpoint、漸進驗證、工具切換時機。六大原則 &#43; 五份情境 reference。"><code>content/skills/requirement-protocol/</code></a></td>
          <td>上層對話協議 skill</td>
      </tr>
  </tbody>
</table>
<h2 id="last-updated">Last Updated</h2>
<p>2026-04-26 — v0.2.0 接入 #55-#68 系列：新增第 7 份 reference <code>data-flow-and-filter-composition</code>（Filter × Source 層錯位 + 五策略 + 跨前端 / 後端 / 演算法 / DB 範例）；強調原則跨領域通用、不只前端。</p>
<p>歷史版本：</p>
<ul>
<li>2026-04-26 — v0.1.0 初版：六份 references 對應「DOM topology / CSS-JS 邊界 / Playwright 三位置 / framework 共處 / Reactive 效能 / A11y」六個情境</li>
</ul>
]]></content:encoded></item><item><title>Reactive Performance — Reactive 效能盤點與優化</title><link>https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/skills/frontend-with-playwright/reactive-performance/</guid><description>&lt;p>前端 reactive 效能的盤點與優化：MutationObserver 三維度（root / options / debounce）、polling → observer、iteration / regex / reflow / lazy load 四個成本面。&lt;/p>
&lt;p>適用：使用者反映卡頓、CPU 100%、scroll lag、resize jank、首次互動延遲。
不適用：純後端效能、純伺服器渲染（SSR 的成本另一套）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>自包含聲明&lt;/strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四個效能風險面向、observer 設計準則、量測方法。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="何時參閱本文件">何時參閱本文件&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號&lt;/th>
 &lt;th>該做的第一件事&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>使用者打字時搜尋頁卡頓&lt;/td>
 &lt;td>量 input listener / observer 觸發頻率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Scroll 時掉幀&lt;/td>
 &lt;td>量 scroll listener 觸發頻率 + reflow 成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Resize 視窗時 layout 跳動&lt;/td>
 &lt;td>量 ResizeObserver 觸發 + 重新計算成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CPU 100%、即使頁面靜止&lt;/td>
 &lt;td>找 setInterval / setTimeout polling、換 observer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>結果規模大（&amp;gt; 500 筆）時慢&lt;/td>
 &lt;td>量 iteration cost、看是否每筆都跑 regex&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>首次互動延遲（搜尋頁 200ms+ 才能輸入）&lt;/td>
 &lt;td>量 critical path、看 lazy chunk 是否要 preload&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>即將寫 &lt;code>observer.observe(document.body, { subtree: true })&lt;/code>&lt;/td>
 &lt;td>停 — 範圍過寬、補上限制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="為什麼-reactive-效能要主動盤點">為什麼 reactive 效能要主動盤點&lt;/h2>
&lt;p>Reactive 系統的成本不是線性 — 一個觸發頻率失控的 listener 會放大整個系統的負擔：&lt;/p>
&lt;ul>
&lt;li>一個 observer 觸發 → callback 執行 → DOM 變動 → 再觸發 observer → 無限迴圈&lt;/li>
&lt;li>一個 input listener 沒 debounce → 每個鍵盤事件跑一次重 query → CPU 飆高&lt;/li>
&lt;li>一個 setInterval polling 50ms → 永遠不停、即使頁面背景&lt;/li>
&lt;/ul>
&lt;p>主動盤點 = 寫之前先估觸發頻率、寫之後用 &lt;code>console.count&lt;/code> 驗證。事後 debug 比事前設計貴 10 倍。&lt;/p>
&lt;hr>
&lt;h2 id="風險面向-1listener-觸發頻率">風險面向 1：Listener 觸發頻率&lt;/h2>
&lt;h3 id="mutationobserver-三維度">MutationObserver 三維度&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>預設&lt;/th>
 &lt;th>過寬訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Root&lt;/td>
 &lt;td>最窄（具體 element）&lt;/td>
 &lt;td>&lt;code>document.body&lt;/code> / &lt;code>document.documentElement&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Options&lt;/td>
 &lt;td>&lt;code>{ childList: true }&lt;/code>&lt;/td>
 &lt;td>&lt;code>{ subtree: true, attributes: true, characterData: true }&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Debounce&lt;/td>
 &lt;td>0ms 或微 microtask&lt;/td>
 &lt;td>沒寫 debounce、callback 執行 &amp;gt; 5ms&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="過寬範例">過寬範例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">// 監聽整個 page 任何變動
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cb&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">observe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">body&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">3&lt;/span>&lt;span class="cl"> &lt;span class="nx">childList&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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">subtree&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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">attributes&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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="nx">characterData&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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="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">// 一次 react state 變動 → 100+ 個 callback
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="對例">對例&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">querySelector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.pagefind-ui__results-area&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 class="kd">let&lt;/span> &lt;span class="nx">timer&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="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&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">clearTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">timer&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">timer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">callback&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// debounce 100ms
&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 class="nx">observe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">root&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">childList&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">true&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="c1">// 只監聽 results 直接子節點變動、debounce 100ms
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="量觸發頻率">量觸發頻率&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">let&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&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="k">new&lt;/span> &lt;span class="nx">MutationObserver&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="p">=&amp;gt;&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">count&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">4&lt;/span>&lt;span class="cl"> &lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;mutation&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">count&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="p">}).&lt;/span>&lt;span class="nx">observe&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>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1">// 預期：使用者打字 1 秒、觸發 10 次以下
&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">// 觀察：100+ 次 → 範圍過寬、加 debounce 或縮 root
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或用 &lt;code>console.count('decorate')&lt;/code> 計數、看每秒觸發幾次。&lt;/p></description><content:encoded><![CDATA[<p>前端 reactive 效能的盤點與優化：MutationObserver 三維度（root / options / debounce）、polling → observer、iteration / regex / reflow / lazy load 四個成本面。</p>
<p>適用：使用者反映卡頓、CPU 100%、scroll lag、resize jank、首次互動延遲。
不適用：純後端效能、純伺服器渲染（SSR 的成本另一套）。</p>
<blockquote>
<p><strong>自包含聲明</strong>：閱讀本文件不需要先讀其他 reference。本文件涵蓋四個效能風險面向、observer 設計準則、量測方法。</p></blockquote>
<hr>
<h2 id="何時參閱本文件">何時參閱本文件</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>該做的第一件事</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>使用者打字時搜尋頁卡頓</td>
          <td>量 input listener / observer 觸發頻率</td>
      </tr>
      <tr>
          <td>Scroll 時掉幀</td>
          <td>量 scroll listener 觸發頻率 + reflow 成本</td>
      </tr>
      <tr>
          <td>Resize 視窗時 layout 跳動</td>
          <td>量 ResizeObserver 觸發 + 重新計算成本</td>
      </tr>
      <tr>
          <td>CPU 100%、即使頁面靜止</td>
          <td>找 setInterval / setTimeout polling、換 observer</td>
      </tr>
      <tr>
          <td>結果規模大（&gt; 500 筆）時慢</td>
          <td>量 iteration cost、看是否每筆都跑 regex</td>
      </tr>
      <tr>
          <td>首次互動延遲（搜尋頁 200ms+ 才能輸入）</td>
          <td>量 critical path、看 lazy chunk 是否要 preload</td>
      </tr>
      <tr>
          <td>即將寫 <code>observer.observe(document.body, { subtree: true })</code></td>
          <td>停 — 範圍過寬、補上限制</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="為什麼-reactive-效能要主動盤點">為什麼 reactive 效能要主動盤點</h2>
<p>Reactive 系統的成本不是線性 — 一個觸發頻率失控的 listener 會放大整個系統的負擔：</p>
<ul>
<li>一個 observer 觸發 → callback 執行 → DOM 變動 → 再觸發 observer → 無限迴圈</li>
<li>一個 input listener 沒 debounce → 每個鍵盤事件跑一次重 query → CPU 飆高</li>
<li>一個 setInterval polling 50ms → 永遠不停、即使頁面背景</li>
</ul>
<p>主動盤點 = 寫之前先估觸發頻率、寫之後用 <code>console.count</code> 驗證。事後 debug 比事前設計貴 10 倍。</p>
<hr>
<h2 id="風險面向-1listener-觸發頻率">風險面向 1：Listener 觸發頻率</h2>
<h3 id="mutationobserver-三維度">MutationObserver 三維度</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>預設</th>
          <th>過寬訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Root</td>
          <td>最窄（具體 element）</td>
          <td><code>document.body</code> / <code>document.documentElement</code></td>
      </tr>
      <tr>
          <td>Options</td>
          <td><code>{ childList: true }</code></td>
          <td><code>{ subtree: true, attributes: true, characterData: true }</code></td>
      </tr>
      <tr>
          <td>Debounce</td>
          <td>0ms 或微 microtask</td>
          <td>沒寫 debounce、callback 執行 &gt; 5ms</td>
      </tr>
  </tbody>
</table>
<h3 id="過寬範例">過寬範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 監聽整個 page 任何變動
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="nx">cb</span><span class="p">).</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">attributes</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">characterData</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 一次 react state 變動 → 100+ 個 callback
</span></span></span></code></pre></div><h3 id="對例">對例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">root</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.pagefind-ui__results-area&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kd">let</span> <span class="nx">timer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">callback</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>  <span class="c1">// debounce 100ms
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="p">}).</span><span class="nx">observe</span><span class="p">(</span><span class="nx">root</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 只監聽 results 直接子節點變動、debounce 100ms
</span></span></span></code></pre></div><h3 id="量觸發頻率">量觸發頻率</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">let</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">count</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">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;mutation&#39;</span><span class="p">,</span> <span class="nx">count</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">}).</span><span class="nx">observe</span><span class="p">(...);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 預期：使用者打字 1 秒、觸發 10 次以下
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 觀察：100+ 次 → 範圍過寬、加 debounce 或縮 root
</span></span></span></code></pre></div><p>或用 <code>console.count('decorate')</code> 計數、看每秒觸發幾次。</p>
<hr>
<h2 id="風險面向-2polling-換-observer">風險面向 2：Polling 換 Observer</h2>
<h3 id="反例setinterval-polling">反例：setInterval polling</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">el</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">decorate</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">timer</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 class="mi">50</span><span class="p">);</span></span></span></code></pre></div><p>問題：CPU 50% busy waiting、即使元素永遠不出現、interval 永遠跑。</p>
<h3 id="對例mutationobserver--fast-path">對例：MutationObserver + fast-path</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">function</span> <span class="nx">waitForElement</span><span class="p">(</span><span class="nx">selector</span><span class="p">,</span> <span class="nx">root</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="kr">const</span> <span class="nx">existing</span> <span class="o">=</span> <span class="nx">root</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">existing</span><span class="p">)</span> <span class="k">return</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">existing</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="kr">const</span> <span class="nx">obs</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      <span class="kr">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nx">root</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nx">obs</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nx">resolve</span><span class="p">(</span><span class="nx">el</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">      <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="nx">obs</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">root</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</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></code></pre></div><p>Fast-path 先檢查（如果已經在 DOM 立即返回）、否則 observer 等元素出現。0 latency、0 idle CPU、元素出現立刻觸發。</p>
<hr>
<h2 id="風險面向-3iteration--regex-成本">風險面向 3：Iteration / Regex 成本</h2>
<h3 id="反例每筆結果跑重-regex">反例：每筆結果跑重 regex</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">pagefind</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kr">const</span> <span class="nx">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="o">/</span><span class="nx">complex</span><span class="o">|</span><span class="nx">regex</span><span class="o">|</span><span class="nx">here</span><span class="o">/</span><span class="nx">i</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">excerpt</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// 500 筆 × regex test = 500 次 regex 編譯與執行
</span></span></span></code></pre></div><h3 id="對例regex-compile-一次用-cached-version">對例：regex compile 一次、用 cached version</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">re</span> <span class="o">=</span> <span class="sr">/complex|regex|here/i</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kr">const</span> <span class="nx">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">r</span> <span class="p">=&gt;</span> <span class="nx">re</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">excerpt</span><span class="p">));</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1">// regex 只編譯一次、test 每次便宜
</span></span></span></code></pre></div><h3 id="量-iteration-成本">量 iteration 成本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">time</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kr">const</span> <span class="nx">filtered</span> <span class="o">=</span> <span class="nx">results</span><span class="p">.</span><span class="nx">filter</span><span class="p">(...);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">timeEnd</span><span class="p">(</span><span class="s1">&#39;filter&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1">// 觀察：&gt; 16ms → 影響 60fps、要優化
</span></span></span></code></pre></div><h3 id="大資料量的常用優化">大資料量的常用優化</h3>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>優化</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>每筆都跑 regex</td>
          <td>regex 編譯一次、test 重用</td>
      </tr>
      <tr>
          <td>每筆 query DOM</td>
          <td>DOM query 一次、緩存結果</td>
      </tr>
      <tr>
          <td>排序 N²</td>
          <td>用 <code>Array.sort()</code> (N log N)</td>
      </tr>
      <tr>
          <td>全量過濾後分頁</td>
          <td>分頁邊界提前 break、不跑完全部</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="風險面向-4layout-reflow-成本">風險面向 4：Layout Reflow 成本</h2>
<p>Reflow（重新計算 layout） &gt; Repaint（重繪） &gt; Composite（合成）— 三者成本遞減。</p>
<h3 id="reflow-觸發訊號">Reflow 觸發訊號</h3>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>改 width / height / top / margin</td>
          <td>Reflow（layout 變動）</td>
      </tr>
      <tr>
          <td>改 color / background</td>
          <td>Repaint（不影響 layout）</td>
      </tr>
      <tr>
          <td>改 transform / opacity</td>
          <td>Composite（GPU、最便宜）</td>
      </tr>
      <tr>
          <td>讀 <code>getBoundingClientRect()</code></td>
          <td>強制 sync reflow（如果 pending 變動）</td>
      </tr>
  </tbody>
</table>
<h3 id="反例read-write-read-write-觸發-layout-thrashing">反例：read-write-read-write 觸發 layout thrashing</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">elements</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="kr">const</span> <span class="nx">w</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetWidth</span><span class="p">;</span>       <span class="c1">// read
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">w</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>  <span class="c1">// write
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">h</span> <span class="o">=</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetHeight</span><span class="p">;</span>      <span class="c1">// read（強制 reflow）
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span>  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">h</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span> <span class="c1">// write
</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="c1">// 每次 read 觸發一次 reflow、N 個元素 = N 次 reflow
</span></span></span></code></pre></div><h3 id="對例批量-read批量-write">對例：批量 read、批量 write</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">sizes</span> <span class="o">=</span> <span class="nx">elements</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">el</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">el</span><span class="p">,</span> <span class="nx">w</span><span class="o">:</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetWidth</span><span class="p">,</span> <span class="nx">h</span><span class="o">:</span> <span class="nx">el</span><span class="p">.</span><span class="nx">offsetHeight</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}));</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">sizes</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(({</span> <span class="nx">el</span><span class="p">,</span> <span class="nx">w</span><span class="p">,</span> <span class="nx">h</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">w</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">el</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">h</span> <span class="o">*</span> <span class="mi">2</span><span class="si">}</span><span class="sb">px`</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1">// 1 次 reflow、性能提升 N 倍
</span></span></span></code></pre></div><h3 id="量-reflow-成本">量 reflow 成本</h3>
<p>Chrome DevTools Performance panel → 找 &ldquo;Layout&rdquo; 紫色塊。&gt; 16ms 要優化。</p>
<hr>
<h2 id="風險面向-5資源載入時序">風險面向 5：資源載入時序</h2>
<h3 id="critical-path-vs-lazy-chunk">Critical path vs lazy chunk</h3>
<table>
  <thead>
      <tr>
          <th>資源</th>
          <th>該不該 lazy</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>首屏需要的 CSS / JS</td>
          <td>否（critical path、preload）</td>
      </tr>
      <tr>
          <td>搜尋頁的 search index</td>
          <td>是（使用者進搜尋頁前不需要）</td>
      </tr>
      <tr>
          <td>Footer 圖片</td>
          <td>是（lazy load on scroll）</td>
      </tr>
      <tr>
          <td>跟首屏互動相關的 JS</td>
          <td>否（input listener 要立刻 ready）</td>
      </tr>
  </tbody>
</table>
<h3 id="範例搜尋頁的-lazy-chunk">範例：搜尋頁的 lazy chunk</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="ln">1</span><span class="cl"><span class="c">&lt;!-- 搜尋頁進來時、preload 第一個 chunk --&gt;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/_pagefind/pagefind-entry.json&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;fetch&#34;</span> <span class="na">crossorigin</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/_pagefind/pagefind.js&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;script&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;module&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;/_pagefind/pagefind.js&#39;</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">p</span> <span class="p">=&gt;</span> <span class="nx">p</span><span class="p">.</span><span class="nx">init</span><span class="p">());</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div><p>不 preload 的代價：使用者進搜尋頁 → 點 input → 等 200-500ms 才能搜尋。</p>
<h3 id="量-critical-path">量 critical path</h3>
<p>Chrome DevTools Network panel → 看每個資源的 timing。Slow 3G throttle 模擬真實使用者環境。</p>
<hr>
<h2 id="盤點-reactive-listener-的協議">盤點 reactive listener 的協議</h2>
<p>對複雜頁面（搜尋頁、dashboard）做一次性盤點：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// 1. 列出所有 observer / listener
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nx">mutationObservers</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">observers</span><span class="p">,</span>  <span class="c1">// 自家紀錄
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"></span>  <span class="nx">resizeObservers</span><span class="o">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">resizeObservers</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="nx">inputListeners</span><span class="o">:</span> <span class="s1">&#39;...&#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></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1">// 2. 加 console.count 在每個 callback
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">decorateCount</span> <span class="o">=</span> <span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">return</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">count</span><span class="p">(</span><span class="sb">`decorate </span><span class="si">${</span><span class="o">++</span><span class="nx">c</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span> <span class="p">};</span> <span class="p">})();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">// 3. 操作頁面 1 分鐘、看 console
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">// 4. 任何 callback 執行 &gt; 100 次/分鐘 → 評估是否需要 debounce / 縮範圍
</span></span></span></code></pre></div><p>定期盤點（每加新 observer 後）= 主動發現觸發頻率失控、不等使用者抱怨。</p>
<hr>
<h2 id="wrong-vs-right-對照">Wrong vs Right 對照</h2>
<h3 id="範例-1搜尋頁打字卡頓">範例 1：搜尋頁打字卡頓</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="c1">// 每個鍵盤事件都重 query 整個 results、重排版
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">expensiveQuery</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">renderResults</span><span class="p">(</span><span class="nx">results</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><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">let</span> <span class="nx">timer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">input</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;input&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="nx">expensiveQuery</span><span class="p">(</span><span class="nx">input</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="nx">renderResults</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="p">},</span> <span class="mi">200</span><span class="p">);</span>  <span class="c1">// debounce 200ms
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="p">});</span></span></span></code></pre></div><h3 id="範例-2等元素出現">範例 2：等元素出現</h3>
<p><strong>錯</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">timer</span> <span class="o">=</span> <span class="nx">setInterval</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">decorate</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">clearInterval</span><span class="p">(</span><span class="nx">timer</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">},</span> <span class="mi">100</span><span class="p">);</span></span></span></code></pre></div><p><strong>對</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">((</span><span class="nx">mutations</span><span class="p">,</span> <span class="nx">obs</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">&#39;.target&#39;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nx">obs</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="nx">decorate</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="p">}).</span><span class="nx">observe</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">subtree</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">// 注意：subtree 只在「等元素出現」場景可接受、決完後 disconnect
</span></span></span></code></pre></div><hr>
<h2 id="自檢清單dogfooding">自檢清單（dogfooding）</h2>
<p>寫 reactive code 或 perf debug 時：</p>
<ul>
<li><input disabled="" type="checkbox"> MutationObserver root 是不是最窄能達成目標的 element？</li>
<li><input disabled="" type="checkbox"> options 是不是只開必要的（<code>childList</code> 預設、<code>subtree</code> 要有理由、<code>attributes</code> 不是預設）？</li>
<li><input disabled="" type="checkbox"> 重 callback 有沒有 debounce / throttle？</li>
<li><input disabled="" type="checkbox"> setInterval / setTimeout polling 能不能換成 MutationObserver？</li>
<li><input disabled="" type="checkbox"> iteration / regex 在大資料量下測過嗎？&gt; 16ms 要優化</li>
<li><input disabled="" type="checkbox"> 改 layout 屬性有沒有 batch read-write、避免 layout thrashing？</li>
<li><input disabled="" type="checkbox"> Lazy chunk 是 critical path 還是真的 lazy？</li>
</ul>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<p>對應的事後檢討（在 <code>content/report/</code>）：</p>
<ul>
<li><a href="/blog/report/mutation-observer-scope/" data-link-title="MutationObserver 範圍與觸發頻率：監聽最少必要的變動" data-link-desc="MutationObserver 是非同步監聽工具、跟同步 selector 是不同議題。範圍寬會頻繁觸發、option 勾多會在不關心的變動上跑邏輯、apply 自己改 DOM 會觸發無限循環。本文是 observer 設計的完整指引。">mutation-observer-scope</a> — MutationObserver 範圍與觸發頻率</li>
<li><a href="/blog/report/mutationobserver-over-polling/" data-link-title="setTimeout 輪詢換 MutationObserver" data-link-desc="等元素出現的場景、用 MutationObserver 監聽 DOM 變化、看到目標就 disconnect — 沒延遲、CPU 不被輪詢吃。本文展開兩種等待機制的差異。">mutationobserver-over-polling</a> — setTimeout 輪詢換 MutationObserver</li>
<li><a href="/blog/report/reactive-listener-frequency-management/" data-link-title="Reactive 監聽器的效能 audit：跨 listener 類型盤點觸發頻率" data-link-desc="MutationObserver / ResizeObserver / event listener 各自的觸發頻率怎麼盤點。本文是效能 audit 視角 — 找問題用、跟 #29 (observer 設計指引) 互補不重複。">reactive-listener-frequency-management</a> — Reactive 監聽器的效能 audit</li>
<li><a href="/blog/report/runtime-iteration-and-regex-cost/" data-link-title="Runtime 計算成本：每筆迭代與正則" data-link-desc="Scope filter 對每筆結果跑 regex — 結果數量大時成為 frame budget 的主要消耗。本文盤點此類「每筆迭代 &#43; per-item 計算」的風險點與評估方法。">runtime-iteration-and-regex-cost</a> — Runtime 計算成本：每筆迭代與正則</li>
<li><a href="/blog/report/layout-reflow-measurement/" data-link-title="Layout reflow / repaint 的可量化評估" data-link-desc="Filter slot 切換、CSS 變數寫入、絕對定位重算 — 哪些操作觸發 reflow 而非僅 repaint、用什麼工具量、評估值落在哪個區間值得優化。">layout-reflow-measurement</a> — Layout reflow / repaint 的可量化評估</li>
<li><a href="/blog/report/lazy-loading-and-critical-path/" data-link-title="資源載入時序：lazy chunk 與 critical path" data-link-desc="Pagefind 的 index 採 chunked lazy load — 首次互動延遲與 critical path 之間的取捨怎麼盤點。預載 entry chunk 的時機與不預載的代價。">lazy-loading-and-critical-path</a> — 資源載入時序：lazy chunk 與 critical path</li>
</ul>
<hr>
<p><strong>Last Updated</strong>: 2026-04-26
<strong>Version</strong>: 0.1.0</p>
]]></content:encoded></item></channel></rss>