<?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>Capacity-Planning on Tarragon</title><link>https://tarrragon.github.io/blog/tags/capacity-planning/</link><description>Recent content in Capacity-Planning 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/capacity-planning/index.xml" rel="self" type="application/rss+xml"/><item><title>容器化資源設計</title><link>https://tarrragon.github.io/blog/devops/05-capacity-planning/container-resource-design/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/05-capacity-planning/container-resource-design/</guid><description>&lt;p>Container 的資源限制是容量規劃在容器化環境的落地。每個 container 設定 memory limit、CPU limit 和磁碟 I/O 控制，確保單一 container 不會吃光 host 資源影響其他服務。限制設太緊觸發 OOMKill 或 CPU throttle，設太鬆等於沒有限制。&lt;/p>
&lt;h2 id="memory-限制設計">Memory 限制設計&lt;/h2>
&lt;h3 id="觀察-baseline">觀察 baseline&lt;/h3>
&lt;p>在限制之前先觀察服務的真實記憶體使用。用 &lt;code>docker stats&lt;/code> 看 container 的 MEM USAGE，跑至少 24 小時涵蓋日常操作和定期 job（降採樣、清理）。&lt;/p>
&lt;p>Baseline 包含：&lt;/p>
&lt;ul>
&lt;li>應用程式本身的 heap + stack&lt;/li>
&lt;li>Runtime 開銷（Go 的 GC metadata、JVM 的 metaspace、Python 的 interpreter）&lt;/li>
&lt;li>內嵌資料庫的 page cache（如 SQLite 的 &lt;code>PRAGMA cache_size&lt;/code>）&lt;/li>
&lt;li>HTTP server 的連線 buffer&lt;/li>
&lt;/ul>
&lt;h3 id="設定-limit">設定 limit&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">Memory limit = baseline peak × 1.5（安全係數）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>安全係數 1.5 是經驗值 — 預留 burst 時的記憶體波動（如大 batch 的 JSON 反序列化、查詢結果集暫存）。安全係數太大浪費資源、太小在 burst 時 OOMKill。&lt;/p>
&lt;h3 id="oomkill-排查">OOMKill 排查&lt;/h3>
&lt;p>OOMKill 的症狀是 container 突然消失、沒有 application log。排查步驟：&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">docker inspect &amp;lt;container&amp;gt; &lt;span class="p">|&lt;/span> jq &lt;span class="s1">&amp;#39;.[0].State.OOMKilled&amp;#39;&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"># true = 被 OOM killer 終止&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">dmesg &lt;span class="p">|&lt;/span> grep -i oom
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="c1"># kernel log 中的 OOM 記錄、包含被殺的 process 和當時的記憶體使用&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>OOMKill 後的處理：提高 memory limit，或找出記憶體使用異常的原因（memory leak、unbounded cache、大結果集查詢）。&lt;/p>
&lt;h3 id="不同-runtime-的記憶體特性">不同 runtime 的記憶體特性&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Runtime&lt;/th>
 &lt;th>特性&lt;/th>
 &lt;th>注意事項&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Go&lt;/td>
 &lt;td>GC 自動管理、GOGC 控制觸發頻率&lt;/td>
 &lt;td>&lt;code>GOMEMLIMIT&lt;/code> 讓 Go runtime 感知 container 的 memory limit、避免 GC 不積極&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JVM&lt;/td>
 &lt;td>heap + metaspace + native memory&lt;/td>
 &lt;td>設 &lt;code>-Xmx&lt;/code> 小於 container limit（留空間給 native memory）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Python&lt;/td>
 &lt;td>無 GC 上限、依賴 OS&lt;/td>
 &lt;td>大 DataFrame / 大 dict 可能瞬間超限&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Node.js&lt;/td>
 &lt;td>V8 heap limit 預設 ~1.5GB&lt;/td>
 &lt;td>設 &lt;code>--max-old-space-size&lt;/code> 配合 container limit&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="cpu-限制設計">CPU 限制設計&lt;/h2>
&lt;h3 id="--cpus-vs---cpu-shares">&lt;code>--cpus&lt;/code> vs &lt;code>--cpu-shares&lt;/code>&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>&lt;code>--cpus=0.5&lt;/code>&lt;/td>
 &lt;td>Hard limit — 最多用 0.5 個 CPU core&lt;/td>
 &lt;td>嚴格隔離、多 container 共用一台主機&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>--cpu-shares=512&lt;/code>&lt;/td>
 &lt;td>Relative weight — 和其他 container 按比例分 CPU&lt;/td>
 &lt;td>彈性分配、host 閒置時可用更多&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="cpu-throttle-症狀">CPU throttle 症狀&lt;/h3>
&lt;p>CPU throttle 不會 crash（和 OOMKill 不同）。症狀是延遲上升 — request 處理時間從 10ms 變成 100ms，因為 container 的 CPU time 被 cgroup 暫停。&lt;/p></description><content:encoded><![CDATA[<p>Container 的資源限制是容量規劃在容器化環境的落地。每個 container 設定 memory limit、CPU limit 和磁碟 I/O 控制，確保單一 container 不會吃光 host 資源影響其他服務。限制設太緊觸發 OOMKill 或 CPU throttle，設太鬆等於沒有限制。</p>
<h2 id="memory-限制設計">Memory 限制設計</h2>
<h3 id="觀察-baseline">觀察 baseline</h3>
<p>在限制之前先觀察服務的真實記憶體使用。用 <code>docker stats</code> 看 container 的 MEM USAGE，跑至少 24 小時涵蓋日常操作和定期 job（降採樣、清理）。</p>
<p>Baseline 包含：</p>
<ul>
<li>應用程式本身的 heap + stack</li>
<li>Runtime 開銷（Go 的 GC metadata、JVM 的 metaspace、Python 的 interpreter）</li>
<li>內嵌資料庫的 page cache（如 SQLite 的 <code>PRAGMA cache_size</code>）</li>
<li>HTTP server 的連線 buffer</li>
</ul>
<h3 id="設定-limit">設定 limit</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">Memory limit = baseline peak × 1.5（安全係數）</span></span></code></pre></div><p>安全係數 1.5 是經驗值 — 預留 burst 時的記憶體波動（如大 batch 的 JSON 反序列化、查詢結果集暫存）。安全係數太大浪費資源、太小在 burst 時 OOMKill。</p>
<h3 id="oomkill-排查">OOMKill 排查</h3>
<p>OOMKill 的症狀是 container 突然消失、沒有 application log。排查步驟：</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">docker inspect &lt;container&gt; <span class="p">|</span> jq <span class="s1">&#39;.[0].State.OOMKilled&#39;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># true = 被 OOM killer 終止</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">dmesg <span class="p">|</span> grep -i oom
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># kernel log 中的 OOM 記錄、包含被殺的 process 和當時的記憶體使用</span></span></span></code></pre></div><p>OOMKill 後的處理：提高 memory limit，或找出記憶體使用異常的原因（memory leak、unbounded cache、大結果集查詢）。</p>
<h3 id="不同-runtime-的記憶體特性">不同 runtime 的記憶體特性</h3>
<table>
  <thead>
      <tr>
          <th>Runtime</th>
          <th>特性</th>
          <th>注意事項</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Go</td>
          <td>GC 自動管理、GOGC 控制觸發頻率</td>
          <td><code>GOMEMLIMIT</code> 讓 Go runtime 感知 container 的 memory limit、避免 GC 不積極</td>
      </tr>
      <tr>
          <td>JVM</td>
          <td>heap + metaspace + native memory</td>
          <td>設 <code>-Xmx</code> 小於 container limit（留空間給 native memory）</td>
      </tr>
      <tr>
          <td>Python</td>
          <td>無 GC 上限、依賴 OS</td>
          <td>大 DataFrame / 大 dict 可能瞬間超限</td>
      </tr>
      <tr>
          <td>Node.js</td>
          <td>V8 heap limit 預設 ~1.5GB</td>
          <td>設 <code>--max-old-space-size</code> 配合 container limit</td>
      </tr>
  </tbody>
</table>
<h2 id="cpu-限制設計">CPU 限制設計</h2>
<h3 id="--cpus-vs---cpu-shares"><code>--cpus</code> vs <code>--cpu-shares</code></h3>
<table>
  <thead>
      <tr>
          <th>設定</th>
          <th>行為</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>--cpus=0.5</code></td>
          <td>Hard limit — 最多用 0.5 個 CPU core</td>
          <td>嚴格隔離、多 container 共用一台主機</td>
      </tr>
      <tr>
          <td><code>--cpu-shares=512</code></td>
          <td>Relative weight — 和其他 container 按比例分 CPU</td>
          <td>彈性分配、host 閒置時可用更多</td>
      </tr>
  </tbody>
</table>
<h3 id="cpu-throttle-症狀">CPU throttle 症狀</h3>
<p>CPU throttle 不會 crash（和 OOMKill 不同）。症狀是延遲上升 — request 處理時間從 10ms 變成 100ms，因為 container 的 CPU time 被 cgroup 暫停。</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/fs/cgroup/cpu/cpu.stat
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># nr_throttled: 被限制的次數</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># throttled_time: 累計被暫停的時間（奈秒）</span></span></span></code></pre></div><p>I/O bound 的服務（如監控 collector — 主要時間花在 SQLite 寫入和 HTTP 收發）通常不需要嚴格 CPU 限制。CPU 只在查詢處理（JSON 反序列化、聚合計算）時短暫使用。</p>
<h2 id="磁碟-io-考量">磁碟 I/O 考量</h2>
<h3 id="overlay-filesystem-的寫入放大">Overlay filesystem 的寫入放大</h3>
<p>Docker 的 overlay2 storage driver 把 container 的寫入操作分層管理。每次寫入新檔案或修改檔案，overlay 在上層（upper layer）建立副本再修改（copy-on-write）。對 SQLite 這類頻繁 fsync 的嵌入式資料庫，overlay 層增加 20-40% 的寫入延遲。</p>
<h3 id="volume-mount-繞過-overlay">Volume mount 繞過 overlay</h3>
<p>把需要高 I/O 效能的目錄掛載為 host volume（<code>-v /host/path:/container/path</code>），寫入直接到 host 檔案系統、繞過 overlay。</p>
<p>適用 volume mount 的場景：</p>
<ul>
<li>嵌入式資料庫的資料目錄（SQLite、BoltDB）</li>
<li>需要持久化的 log 檔案</li>
<li>大量小檔案寫入（cache 目錄）</li>
</ul>
<p>不適用 volume mount 的場景（用 overlay 即可）：</p>
<ul>
<li>暫存檔（處理完就刪）</li>
<li>只讀的設定檔（<code>-v config:/config:ro</code>，overlay 讀取開銷小）</li>
</ul>
<h3 id="tmpfs-mount">tmpfs mount</h3>
<p>記憶體中的暫存目錄，不寫磁碟。適合不需要持久化的高頻寫入（如 SDK 的離線 buffer、session 暫存）：</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">docker run --tmpfs /tmp:size<span class="o">=</span>64m ...</span></span></code></pre></div><h2 id="health-check-設計">Health Check 設計</h2>
<p>Container 的 health check 告訴 orchestrator「這個 container 是否正常運作」。Process 活著但 HTTP 不回應的場景（deadlock、資源耗盡）只靠 process 監控抓不到。</p>
<h3 id="dockerfile-healthcheck">Dockerfile HEALTHCHECK</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">HEALTHCHECK</span> --interval<span class="o">=</span>30s --timeout<span class="o">=</span>5s --retries<span class="o">=</span><span class="m">3</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  <span class="k">CMD</span> wget -q --spider http://localhost:8080/health <span class="o">||</span> <span class="nb">exit</span> <span class="m">1</span></span></span></code></pre></div><h3 id="docker-compose-healthcheck">Docker Compose healthcheck</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">healthcheck</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="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;wget&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-q&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;--spider&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;http://localhost:8080/health&#34;</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="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">30s</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">  </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">  </span><span class="nt">start_period</span><span class="p">:</span><span class="w"> </span><span class="l">10s</span></span></span></code></pre></div><p><code>start_period</code> 是啟動寬限期 — container 啟動後前 10 秒的 health check 失敗不算。避免服務還在初始化時就被標記 unhealthy。</p>
<h3 id="kubernetes-probe-對應">Kubernetes probe 對應</h3>
<table>
  <thead>
      <tr>
          <th>Docker</th>
          <th>Kubernetes</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HEALTHCHECK</td>
          <td>livenessProbe</td>
          <td>container 是否活著（失敗 → 重啟）</td>
      </tr>
      <tr>
          <td>—</td>
          <td>readinessProbe</td>
          <td>container 是否準備好接流量（失敗 → 從 service 移除）</td>
      </tr>
      <tr>
          <td>—</td>
          <td>startupProbe</td>
          <td>container 是否完成啟動（失敗 → 重啟、比 liveness 寬容）</td>
      </tr>
  </tbody>
</table>
<p>Docker 的 HEALTHCHECK 只有一種、等同 Kubernetes 的 livenessProbe。Kubernetes 的 readinessProbe 和 startupProbe 在 Docker 單機環境沒有對應物 — 它們是多 pod 場景下的流量控制機制。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>監控 collector 的 container 部署實例 → <a href="/blog/monitoring/04-collector/container-deployment/" data-link-title="Container 部署設計" data-link-desc="Docker 部署 collector 的設計 — SQLite 在 overlay filesystem 的 I/O 考量、volume mount、graceful shutdown、資源限制">Container 部署設計</a></li>
<li>服務探活與自動恢復 → <a href="/blog/devops/04-service-health/" data-link-title="模組四：服務探活與自動恢復" data-link-desc="服務掛了怎麼自動發現和恢復 — health check 設計、liveness vs readiness、systemd watchdog、process supervisor">DevOps 服務探活</a></li>
<li>負載平衡設計 → <a href="/blog/devops/01-load-balancing/" data-link-title="模組一：負載平衡與反向代理" data-link-desc="流量進來怎麼分給多個服務實例 — nginx / HAProxy / DNS round-robin 的選型和健康檢查路由設計">DevOps 負載平衡</a></li>
</ul>
]]></content:encoded></item><item><title>模組五：容量規劃與壓力測試</title><link>https://tarrragon.github.io/blog/devops/05-capacity-planning/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/05-capacity-planning/</guid><description>&lt;p>回答「要準備多少資源才夠、多的時候怎麼加、少的時候怎麼省」。容量規劃的輸入是流量模型，輸出是資源規格和成本。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 流量模型建立（平均 / 峰值 / burst 的估算方法）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 壓力測試工具和方法（k6 / wrk / locust — 測什麼、怎麼測、結果怎麼讀）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 峰值估算（行銷活動的倍率、歷史峰值的安全係數）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 成本模型（資源規格 × 使用時間 × 計費模式 — reserved / on-demand / spot）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 規模拐點判斷（什麼訊號代表該擴容、什麼訊號代表可以縮容）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 容器化資源設計（memory / CPU / 磁碟限制、overlay fs、health check）&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">backend 效能容量&lt;/a>：Backend 的效能基準和容量估算&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/" data-link-title="模組七：突發流量應對" data-link-desc="行銷活動或新聞曝光帶來 10x-100x 流量時怎麼撐 — 突發分類、降級策略、queue 緩衝、規模分級應對">devops 模組七 突發流量&lt;/a>：突發流量的容量預備&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「要準備多少資源才夠、多的時候怎麼加、少的時候怎麼省」。容量規劃的輸入是流量模型，輸出是資源規格和成本。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input disabled="" type="checkbox"> 流量模型建立（平均 / 峰值 / burst 的估算方法）</li>
<li><input disabled="" type="checkbox"> 壓力測試工具和方法（k6 / wrk / locust — 測什麼、怎麼測、結果怎麼讀）</li>
<li><input disabled="" type="checkbox"> 峰值估算（行銷活動的倍率、歷史峰值的安全係數）</li>
<li><input disabled="" type="checkbox"> 成本模型（資源規格 × 使用時間 × 計費模式 — reserved / on-demand / spot）</li>
<li><input disabled="" type="checkbox"> 規模拐點判斷（什麼訊號代表該擴容、什麼訊號代表可以縮容）</li>
<li><input checked="" disabled="" type="checkbox"> 容器化資源設計（memory / CPU / 磁碟限制、overlay fs、health check）</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">backend 效能容量</a>：Backend 的效能基準和容量估算</li>
<li>→ <a href="/blog/devops/07-burst-traffic/" data-link-title="模組七：突發流量應對" data-link-desc="行銷活動或新聞曝光帶來 10x-100x 流量時怎麼撐 — 突發分類、降級策略、queue 緩衝、規模分級應對">devops 模組七 突發流量</a>：突發流量的容量預備</li>
</ul>
]]></content:encoded></item><item><title>Cosmos DB RU/s 成本模型 + 容量規劃：RU 思維、payload、index、provisioned vs autoscale vs serverless</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/cosmosdb/ru-cost-model-sizing/</link><pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/cosmosdb/ru-cost-model-sizing/</guid><description>&lt;p>Cosmos DB 用單一 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-unit/" data-link-title="Request Unit" data-link-desc="Cosmos DB 的容量抽象單位、1 RU = 1KB document strong-consistent read 的 CPU &amp;#43; memory &amp;#43; IOPS 綜合 cost、寫 ~5 RU、複雜 query 數百 RU">Request Unit&lt;/a>（RU）抽象 read / write / query / replace 的成本。這個抽象 &lt;em>簡化&lt;/em> 容量規劃（不用拆 RCU/WCU、不用估 CPU + IOPS）、但也引入 &lt;em>團隊知識遷移&lt;/em> 成本 — 從 MongoDB / PostgreSQL 自管團隊轉過來、工程師要重新學「query 為什麼吃 200 RU」「payload 從 1KB 變 10KB cost 怎麼變」「index 改一個欄位 write RU 漲 30%」這些 RU 思維問題。本文先講 RU 思維的學習曲線、再進操作流程（依負載形狀選容量模式）、再進失敗模式（autoscale reactive 限制等）。&lt;/p>
&lt;p>本文不是 Cosmos DB overview（請看 &lt;a href="https://tarrragon.github.io/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 頁&lt;/a>）— 而是 &lt;em>RU 成本模型 + sizing&lt;/em> 的深度展開。Case anchor 是 &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 ASOS&lt;/a>（24h 1.67 億 request、autoscale + RU budgeting）+ &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>（測試到 1M RU/s、RU 抽象單位定義）。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Cosmos DB 適用度前置判讀&lt;/strong>：本篇假設 workload 已通過 Cosmos DB 適用度四層 framing（API model 三型遷移路徑 / RU 思維轉換成本 / multi-model 差異化是否真用上 / 跨雲 hedging vs 單雲 lock-in）— 詳見 &lt;a href="../mongodb-api-vs-sql-api/#%e5%9b%9b%e5%b1%a4-framingvendor-selection-%e7%9a%84%e7%9c%9f%e5%af%a6%e6%b1%ba%e7%ad%96%e8%bb%b8">mongodb-api-vs-sql-api 開頭四層 framing&lt;/a>、本篇不重複展開。RU sizing + 容量模式選擇是 &lt;em>已選 Cosmos DB 後&lt;/em> 的成本決策；若 workload 不適用 Cosmos DB、RU sizing 無法救回 vendor 選錯的成本結構落差。&lt;/p>&lt;/blockquote>
&lt;h2 id="問題情境ru-思維的學習曲線">問題情境：RU 思維的學習曲線&lt;/h2>
&lt;p>典型觸發場景：團隊原本用 MongoDB 自管 / PostgreSQL、把容量規劃成「CPU + IOPS + working set RAM」三軸；遷到 Cosmos DB 後第一個問題是「我們的 query 要設多少 RU/s」 — 文件回答「估每個操作的 RU × 操作頻率」、但工程師沒有 RU 的直覺、不知道「200 RU 是貴還是便宜」。&lt;/p></description><content:encoded><![CDATA[<p>Cosmos DB 用單一 <a href="/blog/backend/knowledge-cards/request-unit/" data-link-title="Request Unit" data-link-desc="Cosmos DB 的容量抽象單位、1 RU = 1KB document strong-consistent read 的 CPU &#43; memory &#43; IOPS 綜合 cost、寫 ~5 RU、複雜 query 數百 RU">Request Unit</a>（RU）抽象 read / write / query / replace 的成本。這個抽象 <em>簡化</em> 容量規劃（不用拆 RCU/WCU、不用估 CPU + IOPS）、但也引入 <em>團隊知識遷移</em> 成本 — 從 MongoDB / PostgreSQL 自管團隊轉過來、工程師要重新學「query 為什麼吃 200 RU」「payload 從 1KB 變 10KB cost 怎麼變」「index 改一個欄位 write RU 漲 30%」這些 RU 思維問題。本文先講 RU 思維的學習曲線、再進操作流程（依負載形狀選容量模式）、再進失敗模式（autoscale reactive 限制等）。</p>
<p>本文不是 Cosmos DB overview（請看 <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 頁</a>）— 而是 <em>RU 成本模型 + sizing</em> 的深度展開。Case anchor 是 <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>（24h 1.67 億 request、autoscale + RU budgeting）+ <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>（測試到 1M RU/s、RU 抽象單位定義）。</p>
<blockquote>
<p><strong>Cosmos DB 適用度前置判讀</strong>：本篇假設 workload 已通過 Cosmos DB 適用度四層 framing（API model 三型遷移路徑 / RU 思維轉換成本 / multi-model 差異化是否真用上 / 跨雲 hedging vs 單雲 lock-in）— 詳見 <a href="../mongodb-api-vs-sql-api/#%e5%9b%9b%e5%b1%a4-framingvendor-selection-%e7%9a%84%e7%9c%9f%e5%af%a6%e6%b1%ba%e7%ad%96%e8%bb%b8">mongodb-api-vs-sql-api 開頭四層 framing</a>、本篇不重複展開。RU sizing + 容量模式選擇是 <em>已選 Cosmos DB 後</em> 的成本決策；若 workload 不適用 Cosmos DB、RU sizing 無法救回 vendor 選錯的成本結構落差。</p></blockquote>
<h2 id="問題情境ru-思維的學習曲線">問題情境：RU 思維的學習曲線</h2>
<p>典型觸發場景：團隊原本用 MongoDB 自管 / PostgreSQL、把容量規劃成「CPU + IOPS + working set RAM」三軸；遷到 Cosmos DB 後第一個問題是「我們的 query 要設多少 RU/s」 — 文件回答「估每個操作的 RU × 操作頻率」、但工程師沒有 RU 的直覺、不知道「200 RU 是貴還是便宜」。</p>
<p>讀者徵兆：</p>
<ul>
<li>「為什麼這個 query 吃 200 RU」</li>
<li>「payload 從 1KB 變 10KB、cost 怎麼變」</li>
<li>「Autoscale vs Provisioned 怎麼選」</li>
<li>「Serverless 跟 Provisioned 的 break-even 在哪」</li>
<li>「Index policy 改了一個欄位、write RU 漲 30%」</li>
</ul>
<p>真實壓力：Black Friday 流量 10x、autoscale 跟不上 throttle；dev 環境 24/7 跑、付 provisioned 月費卻只用 1 小時；team 估 RU 估到一半發現「不知道怎麼估」、回去問 PM「我們的 access pattern 是什麼」、PM 給不出答案。</p>
<h3 id="從-cpu--iops-思維轉到-ru-思維">從 CPU + IOPS 思維轉到 RU 思維</h3>
<p>9.C11 Minecraft Earth 案例的平台特性段揭露的 RU 對照：</p>
<ul>
<li>1 RU = 1 KB document 的 strong-consistent read 成本</li>
<li>寫成本約 5 RU</li>
<li>複雜 query 可達數百 RU</li>
</ul>
<p>這個對照看起來簡單、但 <em>容量規劃變成「估每個操作多少 RU × 操作頻率」</em>、跟傳統 RDB「估 CPU / IOPS / working set RAM」是完全不同的思維。具體差異：</p>
<ul>
<li>用 RU 思考、不是用 CPU 思考 — 不需要估「query 跑多久」、要估「query 吃多少 RU」</li>
<li>量單一 query 的 <code>x-ms-request-charge</code> header、不是看 slow query log — 監控位置從 server 端移到 SDK response</li>
<li>拆 query 為 RU budget、不是調 indexing strategy — Cosmos DB index policy 影響 RU、但 <em>改 index 不改 query 速度</em>、改的是 cost</li>
</ul>
<p>跨 vendor 的 capacity 抽象差距（本章合成 frame、跨 vendor case 比對）：</p>
<ul>
<li>MongoDB 用 CPU + IOPS + working set 三軸</li>
<li>DynamoDB 用 WCU / RCU 二軸 + on-demand vs provisioned 模式選擇 + adaptive capacity</li>
<li>Cosmos DB 用 RU 單軸 + 5 consistency level</li>
</ul>
<p><em>思維遷移成本可能高過 vendor 廣告的價格差距</em> — 工程師需要 4-6 週才會建立 RU 直覺、selection 評估時不能只看 monthly bill 就做 ROI 結論。對中型團隊、這個學習曲線可能直接決定遷移成功率。</p>
<p><strong>Scope warning</strong>：9.C11 揭露「100 萬 RU/s 壓測通過」 — <em>壓測通過數字、不是 production 持續跑</em>（case 自己警示）。引用 1M RU/s 時必須帶 scope：壓測 vs 持續、case 明示「實際營運要看 partition key 設計是否均勻」。把壓測數字當 production capacity 推算的後果是 sizing 嚴重低估 hot partition 風險。</p>
<h2 id="ru-的核心機制">RU 的核心機制</h2>
<h3 id="ru-基準">RU 基準</h3>
<p>1 RU = strong-consistent read of 1KB document、用 CPU + memory + IOPS 綜合抽象。每個操作的 RU charge 從 SDK response 的 <code>x-ms-request-charge</code> header 拿、不是事後估算。</p>
<p>操作 RU 對照（rule of thumb、實際以 <code>x-ms-request-charge</code> 為準）：</p>
<ul>
<li>Read 1KB（point read）：1 RU（eventual / session 更便宜、strong / bounded staleness 約 2x）</li>
<li>Write 1KB：5-10 RU（含 index 更新）</li>
<li>Replace 1KB：10-15 RU</li>
<li>Query：跟 query plan + result count + index hit 強相關、可從 5 RU 到 1000+ RU</li>
</ul>
<h3 id="payload-size-的影響">Payload size 的影響</h3>
<p>每多 1 KB payload、write RU 線性增加；read 同 partition 多個 doc 用 query / feed 比多次 point read 更便宜。常見誤區是「拆小 doc 比較便宜」 — 不一定、要看 read pattern：若每次 read 都拿 10 個小 doc、不如合成一個大 doc 一次 read。</p>
<h3 id="index-policy-的影響">Index policy 的影響</h3>
<p>預設 indexing 全欄位（auto-indexing）、降 query cost 但提 write cost；customize index policy（exclude path / include path）可降 write RU 30-50%。判讀時：write-heavy collection 通常該 exclude 不查的欄位、read-heavy collection 通常該 include 常用 query 欄位。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;indexingMode&#34;</span><span class="p">:</span> <span class="s2">&#34;consistent&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nt">&#34;includedPaths&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;/userId/?&#34;</span><span class="p">},</span> <span class="p">{</span><span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;/orderDate/?&#34;</span><span class="p">}],</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nt">&#34;excludedPaths&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;/*&#34;</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><h3 id="三種容量模式">三種容量模式</h3>
<ul>
<li><strong>Provisioned throughput</strong>：訂死 RU/s、不用也付、適合穩定流量</li>
<li><strong>Autoscale provisioned</strong>：訂 max、實際用多少算多少（10% min ceiling）、適合 unpredictable</li>
<li><strong>Serverless</strong>：完全按 request 計、小流量 / dev / 稀疏負載</li>
</ul>
<p>模式選擇不是「哪個便宜」、是「負載形狀適配哪個」— 下節展開。</p>
<h2 id="操作流程依負載形狀選容量模式">操作流程：依負載形狀選容量模式</h2>
<h3 id="量測單一-query-ru">量測單一 query RU</h3>
<p>SDK response header <code>x-ms-request-charge</code>、或 portal Query Stats。Phase 0 audit 一定要 <em>把 production query corpus 跑一遍量 RU</em>、不是估算 — 估算誤差通常 5-10x。</p>
<h3 id="量測-container-baseline-ru">量測 container baseline RU</h3>
<p><code>az cosmosdb sql container show-throughput</code>、portal Metrics &gt; Normalized RU Consumption。</p>
<h3 id="設定-autoscale">設定 autoscale</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">az cosmosdb sql container update <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --max-throughput <span class="m">40000</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --resource-group myrg --account-name mycosmos <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --database-name mydb --name mycontainer</span></span></code></pre></div><h3 id="依負載形狀對應容量模式">依負載形狀對應容量模式</h3>
<p>不同負載形狀的容量決策完全不同、不能用同一個模板：</p>
<p><strong>持續高峰（24h 整天高）</strong> — Provisioned + <a href="/blog/backend/knowledge-cards/scheduled-scaling/" data-link-title="Scheduled Scaling" data-link-desc="說明按已知時間表預先擴容的 autoscaler 模式">scheduled scaling</a></p>
<ul>
<li>Trigger 訊號：峰值 / 平均 &lt; 2x、預測性高</li>
<li>Case anchor：<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> — 24h 1.67 億 request、峰值 / 平均 = 1.81、整天高</li>
<li>為什麼選 provisioned：autoscale 的 reactive trigger 在持續高峰時仍會被拖累 p99、provisioned 鎖定 RU 反而平穩</li>
<li>Scheduled scaling 在 event 前 30-60 分鐘 pre-warm、避免事件開始 trigger autoscale</li>
</ul>
<p><strong>隨機 surge（不可預測 timing）</strong> — Autoscale + reactive safety net</p>
<ul>
<li>Trigger 訊號：不規則尖峰、預測訊號弱、流量曲線無規律</li>
<li>為什麼選 autoscale：成本不浪費（10% min ceiling）、reactive 雖然有延遲但比 over-provisioned 划算</li>
<li>Case anchor 屬本章合成 frame、case 庫未直接揭露純「隨機 surge」的 Cosmos DB 案例</li>
</ul>
<p><strong>預測性 surge（外部訊號可預測）</strong> — Pre-provision + scheduled scaling</p>
<ul>
<li>Trigger 訊號：賽事 / 上線 / 季節 peak、有外部訊號可學</li>
<li>Case anchor：<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 predictive scaling</a> 模型對 KV / document 同適用 — ML 預測 60 分鐘領先窗、改善的是 <em>trigger 提前</em>、不是擴容本身變快</li>
<li>Coinbase case 是 MongoDB 場景、模型可借鑑、但 Cosmos DB 沒有直接對應 ML 預測整合、需要自建</li>
</ul>
<p><strong>稀疏 / dev / 低流量</strong> — Serverless</p>
<ul>
<li>Trigger 訊號：&lt; 1000 RU/s 預期、長時間閒置（如 dev / test / 內部工具）</li>
<li>Serverless 是建 account 時選、<em>不能事後轉 provisioned</em>、要在 Phase 0 決定</li>
<li>屬本章合成 frame、case 庫未直接揭露 serverless 場景（多數案例都是 production 流量）</li>
</ul>
<p><strong>本章合成 frame 警示</strong>：上表是跨 4 個 case 合成（9.C21 ASOS 提供「持續高峰」明確 anchor、9.C36 Coinbase 提供「預測性 surge」模型）、其他兩格屬 outline knowledge — 引用時必須明示「對照表是本章合成、case 原文沒有此分類」。</p>
<h3 id="切換-provisioned--autoscale">切換 provisioned ↔ autoscale</h3>
<p>portal / CLI 支援、不需停機；但 Serverless 是建 account 時選、<em>不能轉 provisioned</em>。Phase 0 決定 mode 後若要切 serverless ↔ provisioned 等於重建 account + 資料遷移。</p>
<h3 id="驗證點">驗證點</h3>
<ul>
<li>autoscale min ceiling = 10% max；若 traffic 預測 baseline &gt; 25% peak、autoscale 不划算（baseline 已經超過 min ceiling、autoscale 的彈性沒用上）</li>
<li>p99 query RU &lt; provisioned / 100（給 burst 留 100x buffer 是 rule of thumb、實際視 query 分布）</li>
<li>每個 query pattern 的 <code>x-ms-request-charge</code> &lt; SLA budget</li>
</ul>
<h3 id="rollback-boundary">Rollback boundary</h3>
<p>throughput 可即時改、index policy 改完背景 rebuild（rebuild 期間 query 用舊 index、性能可能下降但不中斷）；mode（serverless ↔ provisioned）不可改。</p>
<h2 id="失敗模式">失敗模式</h2>
<h3 id="failure-1用-point-read-取代-query">Failure 1：用 point read 取代 query</h3>
<p>要拿同 partition 100 個 doc、做 100 次 point read（100 RU）vs 一次 query（可能 10-20 RU）— point read 雖然每次便宜、總成本反高。這個 anti-pattern 在 application code 很常見 — 「每次 read 一個 doc 比較簡單」是 application 角度、不是 RU 角度。</p>
<p>修：拉 access pattern audit、把 N+1 read pattern 改 batch query；用 query 拿同 partition 多 doc、用 cross-partition query 拿不同 partition（成本高、但比 N+1 point read 通常還便宜）。</p>
<h3 id="failure-2index-全開不審">Failure 2：Index 全開不審</h3>
<p>所有欄位 auto-index、write 大表時 RU 暴漲；徵兆是 <code>Total RU consumption</code> 寫入路徑佔 80%、read 只佔 20%、但 application 明明 read-heavy。原因是 index 維護成本太高。</p>
<p>修：customize index policy、exclude 不查的欄位（特別是 array / nested object 等高成本欄位）、include 常用 query 路徑。改完背景 rebuild、不中斷服務。</p>
<h3 id="failure-3autoscale-min-沒考慮">Failure 3：Autoscale min 沒考慮</h3>
<p>max 40000、min 4000（10% max ceiling）、實際 baseline 是 500、付 8x baseline 費；應該降 max 或改 serverless。autoscale 的 <em>min ceiling</em> 是常見的隱性成本來源 — 訂太高 max 就被 min 綁住、autoscale 反而比 provisioned 貴。</p>
<p>修：先量 baseline 跟 peak、算 peak / baseline ratio；ratio &gt; 10x 用 autoscale 划算、ratio &lt; 4x 用 provisioned 划算（autoscale min ceiling 吃掉彈性）。</p>
<h3 id="failure-4autoscale-撐不住預測性流量必須-scheduled-scaling-或-pre-provision">Failure 4：Autoscale 撐不住預測性流量、必須 scheduled scaling 或 pre-provision</h3>
<p>autoscale 的 min ceiling = 10% max、實際擴容仍是 <em>reactive</em>（看到 throttle 才往上推）。對預測性流量（季節 peak / 賽事 / 上線日）、autoscale 跟不上、必須 scheduled scaling 或 pre-provision。</p>
<p>9.C21 ASOS Black Friday 是「持續高峰」、整天高 — 用 provisioned + scheduled 比 autoscale 划算（autoscale 仍會被 reactive trigger 拖累 p99）。9.C36 Coinbase 模型雖然是 MongoDB case、可借鑑：cluster 擴容 70 分鐘、reactive 來不及、ML 預測 60 分鐘領先窗、改善的是 <em>trigger 提前</em>、不是擴容本身變快 — Cosmos DB autoscale 的 10% ceiling 同樣是 reactive 限制。</p>
<p>修：預測性 event 前 30-60 分鐘 pre-warm RU/s、事件結束後降回；用 scheduled scaling pipeline（Azure Function trigger + ARM template）自動化。</p>
<h3 id="failure-5provisioned-沒退場">Failure 5：Provisioned 沒退場</h3>
<p>dev / staging container 全開 provisioned、月費 $300+ × N 個 environment；應切 serverless 或共用 shared throughput（多個 container 共享一個 RU pool）。dev 環境的 cost waste 是長尾、月底帳單才發現。</p>
<p>修：dev / staging 改 serverless、production 才 provisioned；或用 <em>shared database throughput</em>、多個 container 共用 400-1000 RU pool。</p>
<h3 id="failure-6跨-partition-query-浪費">Failure 6：跨 partition query 浪費</h3>
<p>query 沒包含 partition key 條件、fan-out 全 partition、RU × partition 數；徵兆是 <code>RetrievedDocumentCount</code> 跟 <code>OutputDocumentCount</code> 比例 &gt; 10（拿了 10x doc 才篩出要的）。</p>
<p>修：query 強制帶 partition key 條件、改 access pattern 讓 query 自然帶 partition key；若必須跨 partition、用 <a href="https://learn.microsoft.com/azure/cosmos-db/change-feed">Change Feed</a> 把投影預先寫到另一個 container 用單一 partition 查。</p>
<h3 id="failure-7沒設-budget-alert">Failure 7：沒設 budget alert</h3>
<p>cost 失控直到月底帳單才發現。Cosmos DB 的成本可以在幾天內飆 10x（hot partition + index 全開 + autoscale max 設太高 互相加乘）、月底才看是災難。</p>
<p>修：Azure Cost Management 設 daily budget alert（超預算 1.5x trigger）、portal Insights &gt; Cost insights 每週 review。</p>
<h3 id="failure-8ttl-自動刪除把-ru-偷走">Failure 8：TTL 自動刪除把 RU 偷走</h3>
<p>Cosmos DB 容器層的 TTL（<a href="https://learn.microsoft.com/azure/cosmos-db/nosql/time-to-live">Time To Live</a>）會在 background 持續掃描過期文件、跑 delete 操作消耗 RU、但不會出現在 application driver 的 RU 統計、容易在 sizing 階段被忽略。屬通用工程議題、case 未直接量化 TTL 對 RU 的佔比。</p>
<p>徵兆：</p>
<ul>
<li>Provisioned RU 估算「query + write」流量明明很穩、實際 <code>NormalizedRUConsumption</code> 卻偏高、找不到對應 application call</li>
<li>高寫入率 container 開啟 TTL 後、<code>Total Request Units</code> 持續高於預期、portal Insights 「Background operations」段非零</li>
<li>TTL 設過短（例：分鐘級）、background delete 跟 application write 競爭同 partition、寫入 latency p99 變高</li>
</ul>
<p>修：</p>
<ul>
<li>估 RU 容量時把 TTL delete 當第三類流量（除了 user read / write 外）、用「過期 doc / 秒 × 平均 doc delete RU」估算</li>
<li>設定 TTL 不要過短、避免 delete 壓力跟 application write 撞 partition</li>
<li>對高 TTL volume 的 container 開啟 <a href="https://learn.microsoft.com/azure/cosmos-db/analytical-store-introduction">analytical store</a>、避免歷史資料保留在 transactional store 持續耗 RU</li>
<li>監控 <code>Background operations</code> 跟 <code>NormalizedRUConsumption</code> 的 ratio、把 TTL 對 RU 的影響可視化</li>
</ul>
<h2 id="容量與觀測">容量與觀測</h2>
<ul>
<li>必看 metric：<code>NormalizedRUConsumption</code>（peak）、<code>TotalRequestUnits</code>（cumulative）、<code>MetadataRequests</code>、<code>UserErrors</code>（for <code>429 throttle</code>）</li>
<li>成本分析：Azure Cost Management 按 container / region tag；portal Insights &gt; Cost insights</li>
<li>容量公式：peak RPS × avg RU per request × peak duration factor = required RU/s</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> 把 RU 當主要 capacity 軸（不只 storage / CPU）</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>：把 429 throttle 當 saturation 訊號</li>
<li>Alert：429 rate &gt; 0.1%、RU consumption &gt; 80% provisioned 持續 5 min、daily cost 超預算 1.5x</li>
</ul>
<h3 id="latency-budget-拆解vendor-sla-vs-end-to-end-實測">Latency budget 拆解：vendor SLA vs end-to-end 實測</h3>
<p><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> 觀察「48ms 平均響應」段揭露：48ms 包含 <em>網路 + DB + 應用層</em>、DB 本身可能只佔 5-10ms。引用時不能把 vendor 廣告的 5-10ms p99 當「使用者體驗」 — 詳細拆解見 <a href="../partition-key-design/">partition-key-design</a> 的 latency budget 段。</p>
<h3 id="跟其他-vendor-capacity-抽象的對照">跟其他 vendor capacity 抽象的對照</h3>
<table>
  <thead>
      <tr>
          <th>Vendor</th>
          <th>Capacity 抽象</th>
          <th>思維重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MongoDB</td>
          <td>CPU + IOPS + working set RAM</td>
          <td>估資源、調 indexing</td>
      </tr>
      <tr>
          <td>DynamoDB</td>
          <td>WCU / RCU + on-demand vs provisioned + adaptive</td>
          <td>mode 選擇 + PK 均勻度</td>
      </tr>
      <tr>
          <td>Cosmos DB</td>
          <td>RU + 5 consistency level</td>
          <td>RU 預算、每 query 量 charge</td>
      </tr>
      <tr>
          <td>Aurora</td>
          <td>instance class + replica count + storage IOPS</td>
          <td>provisioned</td>
      </tr>
      <tr>
          <td>Spanner</td>
          <td>processing unit（100 pu 起跳）</td>
          <td>node count</td>
      </tr>
      <tr>
          <td>CockroachDB</td>
          <td>range × replication factor × node count</td>
          <td>distributed</td>
      </tr>
  </tbody>
</table>
<p>對照表是本章合成 frame、case 庫沒有單一案例橫跨多 vendor。判讀時要明示「思維遷移成本是 selection 評估的隱性軸、不是只看 monthly bill」。</p>
<h2 id="邊界與整合">邊界與整合</h2>
<ul>
<li>Sibling deep articles：<a href="../partition-key-design/">partition-key-design</a>（partition skew 讓 RU 失效、hot partition 是 sizing 假設失敗的主因）、<a href="../consistency-levels-engineering/">consistency-levels-engineering</a>（Strong / Bounded 對 read RU 2x）、<a href="../multi-region-write-conflict/">multi-region-write-conflict</a>（multi-region RU × region 數）、<a href="../mongodb-api-vs-sql-api/">mongodb-api-vs-sql-api</a>（MongoDB API 翻譯層多 10-20% RU）</li>
<li>跟 1.x 章節：<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 選擇">1.10 KV / Document DB 容量規劃</a></li>
<li>跟 9.x 章節：<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>（429 throttle 當 saturation 訊號）</li>
<li>Knowledge cards：<a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast</a> / <a href="/blog/backend/knowledge-cards/hot-partition/" data-link-title="Hot Partition" data-link-desc="說明分散式 KV / OLTP 中、單一 partition 流量遠超其他的容量問題">Hot Partition</a></li>
<li>Anti-recommendation：流量 &lt; 1000 RU/s 不需 autoscale tuning、用 serverless 或 400 RU/s shared throughput；過度 sizing 比 under-sizing 更常見、特別是 dev / staging</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li><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 overview</a> — 本文是該頁尾 RU/s cost model backlog 的深度展開</li>
<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 case</a> — 持續高峰 + RU budgeting 主案例</li>
<li><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 case</a> — RU 抽象單位定義 + 1M RU/s 壓測（scope warning：壓測非持續）</li>
<li><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 predictive scaling case</a> — 預測性 surge 模型借鑑（跨 vendor）</li>
<li><a href="/blog/backend/knowledge-cards/peak-forecast/" data-link-title="Peak Forecast" data-link-desc="說明預期峰值流量的預測方法 — 容量規劃的第一個輸入">Peak Forecast 卡片</a> / <a href="/blog/backend/knowledge-cards/hot-partition/" data-link-title="Hot Partition" data-link-desc="說明分散式 KV / OLTP 中、單一 partition 流量遠超其他的容量問題">Hot Partition 卡片</a> — 概念基底</li>
<li>官方：<a href="https://learn.microsoft.com/azure/cosmos-db/request-units">Cosmos DB Request Units</a> / <a href="https://learn.microsoft.com/azure/cosmos-db/throughput-serverless">Provisioned throughput vs autoscale vs serverless</a></li>
</ul>
]]></content:encoded></item></channel></rss>