<?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>Devops on Tarragon</title><link>https://tarrragon.github.io/blog/tags/devops/</link><description>Recent content in Devops 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/devops/index.xml" rel="self" type="application/rss+xml"/><item><title>突發流量的分類</title><link>https://tarrragon.github.io/blog/devops/07-burst-traffic/burst-classification/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/07-burst-traffic/burst-classification/</guid><description>&lt;p>突發流量按可預測性分成兩類。可預期的突發（行銷活動、新聞發佈）可以事前準備容量；不可預期的突發（病毒傳播、error storm）只能靠架構設計吸收衝擊。&lt;/p>
&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;th>特徵&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>行銷活動（促銷、限時折扣）&lt;/td>
 &lt;td>5-50x&lt;/td>
 &lt;td>數小時～數天&lt;/td>
 &lt;td>流量集中在活動開始的前幾分鐘&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新聞曝光（媒體報導、社群爆紅）&lt;/td>
 &lt;td>10-100x&lt;/td>
 &lt;td>數小時&lt;/td>
 &lt;td>不可控的流量曲線、峰值在發佈後 1-2 小時&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>定時推播（每日報表、週報）&lt;/td>
 &lt;td>2-10x&lt;/td>
 &lt;td>分鐘級&lt;/td>
 &lt;td>短暫但可精確預測時間&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>新版本推送（app store 更新）&lt;/td>
 &lt;td>3-10x&lt;/td>
 &lt;td>數天（逐漸擴散）&lt;/td>
 &lt;td>流量緩慢上升、峰值在推送後 24-48 小時&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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;th>何時做&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>容量估算&lt;/td>
 &lt;td>歷史峰值 × 安全係數（1.5-2x）&lt;/td>
 &lt;td>活動前 1 週&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴容&lt;/td>
 &lt;td>加實例 / 加資源 / 預熱 cache&lt;/td>
 &lt;td>活動前 1 天&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>降級預案&lt;/td>
 &lt;td>設定動態取樣的觸發閾值&lt;/td>
 &lt;td>活動前 1 天&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>壓力測試&lt;/td>
 &lt;td>模擬預期流量打 staging&lt;/td>
 &lt;td>活動前 3 天&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>值班&lt;/td>
 &lt;td>安排值班人員監控 dashboard&lt;/td>
 &lt;td>活動期間&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;th>特徵&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>病毒傳播（社群分享爆量）&lt;/td>
 &lt;td>10-1000x&lt;/td>
 &lt;td>數小時&lt;/td>
 &lt;td>完全無法預測、可能超過任何預備容量&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DDoS 攻擊&lt;/td>
 &lt;td>100-10000x&lt;/td>
 &lt;td>不定&lt;/td>
 &lt;td>惡意流量、需要 WAF / CDN 擋在前面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error storm（app bug 觸發大量 error）&lt;/td>
 &lt;td>依 bug 影響範圍&lt;/td>
 &lt;td>直到 hotfix&lt;/td>
 &lt;td>每個受影響的使用者都在送 error 事件&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>外部依賴復原（積壓請求一次湧入）&lt;/td>
 &lt;td>2-5x&lt;/td>
 &lt;td>分鐘級&lt;/td>
 &lt;td>依賴恢復後積壓的 retry 一起到達&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>不可預期突發的應對核心是&lt;strong>降級&lt;/strong> — 系統在超載時自動犧牲非核心功能，保住核心功能。&lt;/p>
&lt;h3 id="監控系統的-error-storm">監控系統的 error storm&lt;/h3>
&lt;p>Error storm 是監控系統特有的突發場景：被監控的 app 出了 bug，每個受影響的使用者都在送 error 事件。如果有 10 萬使用者同時遇到同一個 bug，collector 瞬間收到 10 萬筆 error 事件。&lt;/p>
&lt;p>Error storm 的矛盾：error 事件是 debug 最需要的資料，但 storm 時的大量 error 可能打垮 collector。處理策略是&lt;strong>保留前 N 筆完整 error（含 stack trace）、後續的 error 只計數不存原始資料&lt;/strong>。第一筆 error 的 stack trace 足夠 debug，後續的 10 萬筆只是確認影響範圍。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>突發時的降級策略 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/degradation-strategy/" data-link-title="降級策略" data-link-desc="系統超載時犧牲什麼保住什麼 — 動態取樣、事件優先級、功能降級、聚合前移四種策略">降級策略&lt;/a>&lt;/li>
&lt;li>Queue 做 burst 緩衝 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/queue-buffering/" data-link-title="Queue 緩衝" data-link-desc="在 ingestion 和 processing 之間加 message queue 做 burst 緩衝 — Kafka / NATS / Redis Streams 的選型和引入條件">Queue 緩衝&lt;/a>&lt;/li>
&lt;li>不同規模的應對方案 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/scale-tier-response/" data-link-title="規模分級應對表" data-link-desc="自用級 → 中型 → 大型 → 商業網站級的四級應對方案 — 每級的觸發條件、架構組成和成本">規模分級應對表&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>突發流量按可預測性分成兩類。可預期的突發（行銷活動、新聞發佈）可以事前準備容量；不可預期的突發（病毒傳播、error storm）只能靠架構設計吸收衝擊。</p>
<h2 id="可預期突發">可預期突發</h2>
<p>事前知道流量會增加，有時間準備。</p>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>流量倍率</th>
          <th>持續時間</th>
          <th>特徵</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>行銷活動（促銷、限時折扣）</td>
          <td>5-50x</td>
          <td>數小時～數天</td>
          <td>流量集中在活動開始的前幾分鐘</td>
      </tr>
      <tr>
          <td>新聞曝光（媒體報導、社群爆紅）</td>
          <td>10-100x</td>
          <td>數小時</td>
          <td>不可控的流量曲線、峰值在發佈後 1-2 小時</td>
      </tr>
      <tr>
          <td>定時推播（每日報表、週報）</td>
          <td>2-10x</td>
          <td>分鐘級</td>
          <td>短暫但可精確預測時間</td>
      </tr>
      <tr>
          <td>新版本推送（app store 更新）</td>
          <td>3-10x</td>
          <td>數天（逐漸擴散）</td>
          <td>流量緩慢上升、峰值在推送後 24-48 小時</td>
      </tr>
  </tbody>
</table>
<p>可預期突發的應對核心是<strong>容量預備</strong> — 活動前擴容、活動後縮回。</p>
<h3 id="預備清單">預備清單</h3>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>做什麼</th>
          <th>何時做</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>容量估算</td>
          <td>歷史峰值 × 安全係數（1.5-2x）</td>
          <td>活動前 1 週</td>
      </tr>
      <tr>
          <td>擴容</td>
          <td>加實例 / 加資源 / 預熱 cache</td>
          <td>活動前 1 天</td>
      </tr>
      <tr>
          <td>降級預案</td>
          <td>設定動態取樣的觸發閾值</td>
          <td>活動前 1 天</td>
      </tr>
      <tr>
          <td>壓力測試</td>
          <td>模擬預期流量打 staging</td>
          <td>活動前 3 天</td>
      </tr>
      <tr>
          <td>值班</td>
          <td>安排值班人員監控 dashboard</td>
          <td>活動期間</td>
      </tr>
  </tbody>
</table>
<h2 id="不可預期突發">不可預期突發</h2>
<p>事前不知道流量會增加，只能靠架構設計吸收。</p>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>流量倍率</th>
          <th>持續時間</th>
          <th>特徵</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>病毒傳播（社群分享爆量）</td>
          <td>10-1000x</td>
          <td>數小時</td>
          <td>完全無法預測、可能超過任何預備容量</td>
      </tr>
      <tr>
          <td>DDoS 攻擊</td>
          <td>100-10000x</td>
          <td>不定</td>
          <td>惡意流量、需要 WAF / CDN 擋在前面</td>
      </tr>
      <tr>
          <td>Error storm（app bug 觸發大量 error）</td>
          <td>依 bug 影響範圍</td>
          <td>直到 hotfix</td>
          <td>每個受影響的使用者都在送 error 事件</td>
      </tr>
      <tr>
          <td>外部依賴復原（積壓請求一次湧入）</td>
          <td>2-5x</td>
          <td>分鐘級</td>
          <td>依賴恢復後積壓的 retry 一起到達</td>
      </tr>
  </tbody>
</table>
<p>不可預期突發的應對核心是<strong>降級</strong> — 系統在超載時自動犧牲非核心功能，保住核心功能。</p>
<h3 id="監控系統的-error-storm">監控系統的 error storm</h3>
<p>Error storm 是監控系統特有的突發場景：被監控的 app 出了 bug，每個受影響的使用者都在送 error 事件。如果有 10 萬使用者同時遇到同一個 bug，collector 瞬間收到 10 萬筆 error 事件。</p>
<p>Error storm 的矛盾：error 事件是 debug 最需要的資料，但 storm 時的大量 error 可能打垮 collector。處理策略是<strong>保留前 N 筆完整 error（含 stack trace）、後續的 error 只計數不存原始資料</strong>。第一筆 error 的 stack trace 足夠 debug，後續的 10 萬筆只是確認影響範圍。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>突發時的降級策略 → <a href="/blog/devops/07-burst-traffic/degradation-strategy/" data-link-title="降級策略" data-link-desc="系統超載時犧牲什麼保住什麼 — 動態取樣、事件優先級、功能降級、聚合前移四種策略">降級策略</a></li>
<li>Queue 做 burst 緩衝 → <a href="/blog/devops/07-burst-traffic/queue-buffering/" data-link-title="Queue 緩衝" data-link-desc="在 ingestion 和 processing 之間加 message queue 做 burst 緩衝 — Kafka / NATS / Redis Streams 的選型和引入條件">Queue 緩衝</a></li>
<li>不同規模的應對方案 → <a href="/blog/devops/07-burst-traffic/scale-tier-response/" data-link-title="規模分級應對表" data-link-desc="自用級 → 中型 → 大型 → 商業網站級的四級應對方案 — 每級的觸發條件、架構組成和成本">規模分級應對表</a></li>
</ul>
]]></content:encoded></item><item><title>背壓機制</title><link>https://tarrragon.github.io/blog/devops/03-traffic-management/backpressure/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/03-traffic-management/backpressure/</guid><description>&lt;p>背壓是一種被動的流量控制機制 — 當下游（處理端）的速度跟不上上游（請求端）時，下游透過訊號讓上游知道「慢一點」。背壓不拒絕請求，而是讓請求的發送者自己決定要等待、重試還是放棄。&lt;/p>
&lt;h2 id="背壓-vs-rate-limit">背壓 vs Rate Limit&lt;/h2>
&lt;p>背壓和 rate limit 都是流量控制，但觸發邏輯不同：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>背壓&lt;/th>
 &lt;th>Rate Limit&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>觸發條件&lt;/td>
 &lt;td>下游實際變慢了（buffer 滿）&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>HTTP 429 + Retry-After / TCP 窗口縮小 / channel 阻塞&lt;/td>
 &lt;td>HTTP 429 + 固定的 rate limit header&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>發送者行為&lt;/td>
 &lt;td>根據 Retry-After 動態調整&lt;/td>
 &lt;td>等待限速窗口重設&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>背壓在系統承載達到上限時才觸發，rate limit 在到達預設上限時就觸發（即使系統還有餘裕）。兩者互補：rate limit 防止單一來源打爆系統，背壓防止所有來源加起來打爆系統。&lt;/p>
&lt;h2 id="實作模式">實作模式&lt;/h2>
&lt;h3 id="有限-buffer--回壓訊號">有限 buffer + 回壓訊號&lt;/h3>
&lt;p>最常見的背壓實作是在處理管線中加一個有限容量的 buffer。Buffer 滿了代表下游處理不完，這時對新請求回傳「忙碌」訊號。&lt;/p>
&lt;p>在 Go 的 HTTP server 中，buffer 可以是一個有限容量的 channel：&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">ingestCh&lt;/span> &lt;span class="p">=&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="nx">Event&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 有限 buffer&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">handleIngest&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nx">event&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">parseEvent&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"> 5&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="nx">ingestCh&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="nx">event&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">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WriteHeader&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">StatusAccepted&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 202&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">default&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;5&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">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WriteHeader&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 class="c1">// 429&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &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="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Buffer 容量的選擇取決於下游的處理速度和可接受的記憶體用量。每個 event 約 1KB 時，10000 容量的 buffer 佔 ~10MB — 對多數服務來說可以接受。&lt;/p>
&lt;h3 id="http-429--retry-after">HTTP 429 + Retry-After&lt;/h3>
&lt;p>HTTP 429 Too Many Requests 是標準的回壓訊號。&lt;code>Retry-After&lt;/code> header 告訴 client 多少秒後重試。&lt;/p>
&lt;p>&lt;code>Retry-After&lt;/code> 的值可以是固定的（如 5 秒），也可以根據 buffer 的填充程度動態計算 — buffer 越滿、Retry-After 越長。&lt;/p>
&lt;h3 id="tcp-層的背壓">TCP 層的背壓&lt;/h3>
&lt;p>TCP 協議本身有背壓機制 — 接收端的 receive window 縮小時，發送端自動減速。但 HTTP 層的背壓比 TCP 層更精確，因為 HTTP 可以回傳語意化的狀態碼和 header，client 可以根據語意做出更智慧的回應（如優先重試 error 事件、放棄 event 事件）。&lt;/p>
&lt;h2 id="監控系統的應用">監控系統的應用&lt;/h2>
&lt;p>監控系統的 &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">collector&lt;/a> 是背壓的典型場景：多個 SDK 同時 flush 事件到 collector，collector 的寫入速度（SQLite / PostgreSQL）是瓶頸。&lt;/p>
&lt;p>背壓鏈路：SDK flush → collector HTTP endpoint → 寫入 channel（有限容量）→ 寫入 goroutine → storage。Channel 滿時回 429，SDK 的離線 buffer 機制接手 — 事件暫存本地，等 collector 恢復後補發。&lt;/p>
&lt;p>這個設計讓 collector 在高峰時不崩潰（有限 buffer 控制記憶體）、SDK 端不丟事件（離線 buffer 暫存）。代價是事件的到達有延遲（Retry-After 時間 + 補發時間）。&lt;/p></description><content:encoded><![CDATA[<p>背壓是一種被動的流量控制機制 — 當下游（處理端）的速度跟不上上游（請求端）時，下游透過訊號讓上游知道「慢一點」。背壓不拒絕請求，而是讓請求的發送者自己決定要等待、重試還是放棄。</p>
<h2 id="背壓-vs-rate-limit">背壓 vs Rate Limit</h2>
<p>背壓和 rate limit 都是流量控制，但觸發邏輯不同：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>背壓</th>
          <th>Rate Limit</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>觸發條件</td>
          <td>下游實際變慢了（buffer 滿）</td>
          <td>請求速率超過預設上限</td>
      </tr>
      <tr>
          <td>性質</td>
          <td>被動（根據實際負載）</td>
          <td>主動（根據預設規則）</td>
      </tr>
      <tr>
          <td>訊號</td>
          <td>HTTP 429 + Retry-After / TCP 窗口縮小 / channel 阻塞</td>
          <td>HTTP 429 + 固定的 rate limit header</td>
      </tr>
      <tr>
          <td>發送者行為</td>
          <td>根據 Retry-After 動態調整</td>
          <td>等待限速窗口重設</td>
      </tr>
  </tbody>
</table>
<p>背壓在系統承載達到上限時才觸發，rate limit 在到達預設上限時就觸發（即使系統還有餘裕）。兩者互補：rate limit 防止單一來源打爆系統，背壓防止所有來源加起來打爆系統。</p>
<h2 id="實作模式">實作模式</h2>
<h3 id="有限-buffer--回壓訊號">有限 buffer + 回壓訊號</h3>
<p>最常見的背壓實作是在處理管線中加一個有限容量的 buffer。Buffer 滿了代表下游處理不完，這時對新請求回傳「忙碌」訊號。</p>
<p>在 Go 的 HTTP server 中，buffer 可以是一個有限容量的 channel：</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">ingestCh</span> <span class="p">=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">Event</span><span class="p">,</span> <span class="mi">10000</span><span class="p">)</span> <span class="c1">// 有限 buffer</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">handleIngest</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"> 4</span><span class="cl">    <span class="nx">event</span> <span class="o">:=</span> <span class="nf">parseEvent</span><span class="p">(</span><span class="nx">r</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">select</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">case</span> <span class="nx">ingestCh</span> <span class="o">&lt;-</span> <span class="nx">event</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusAccepted</span><span class="p">)</span> <span class="c1">// 202</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">default</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;5&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nx">w</span><span class="p">.</span><span class="nf">WriteHeader</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusTooManyRequests</span><span class="p">)</span> <span class="c1">// 429</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></code></pre></div><p>Buffer 容量的選擇取決於下游的處理速度和可接受的記憶體用量。每個 event 約 1KB 時，10000 容量的 buffer 佔 ~10MB — 對多數服務來說可以接受。</p>
<h3 id="http-429--retry-after">HTTP 429 + Retry-After</h3>
<p>HTTP 429 Too Many Requests 是標準的回壓訊號。<code>Retry-After</code> header 告訴 client 多少秒後重試。</p>
<p><code>Retry-After</code> 的值可以是固定的（如 5 秒），也可以根據 buffer 的填充程度動態計算 — buffer 越滿、Retry-After 越長。</p>
<h3 id="tcp-層的背壓">TCP 層的背壓</h3>
<p>TCP 協議本身有背壓機制 — 接收端的 receive window 縮小時，發送端自動減速。但 HTTP 層的背壓比 TCP 層更精確，因為 HTTP 可以回傳語意化的狀態碼和 header，client 可以根據語意做出更智慧的回應（如優先重試 error 事件、放棄 event 事件）。</p>
<h2 id="監控系統的應用">監控系統的應用</h2>
<p>監控系統的 <a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">collector</a> 是背壓的典型場景：多個 SDK 同時 flush 事件到 collector，collector 的寫入速度（SQLite / PostgreSQL）是瓶頸。</p>
<p>背壓鏈路：SDK flush → collector HTTP endpoint → 寫入 channel（有限容量）→ 寫入 goroutine → storage。Channel 滿時回 429，SDK 的離線 buffer 機制接手 — 事件暫存本地，等 collector 恢復後補發。</p>
<p>這個設計讓 collector 在高峰時不崩潰（有限 buffer 控制記憶體）、SDK 端不丟事件（離線 buffer 暫存）。代價是事件的到達有延遲（Retry-After 時間 + 補發時間）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>主動的流量限制 → <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、優先級豁免">Rate Limiting</a></li>
<li>依賴服務失敗時的防護 → <a href="/blog/devops/03-traffic-management/circuit-breaker/" data-link-title="熔斷器" data-link-desc="依賴服務失敗時怎麼快速失敗而非拖慢自己 — 三狀態模型（closed → open → half-open）和熔斷判斷條件">熔斷器</a></li>
<li>突發流量時的組合策略 → <a href="/blog/devops/07-burst-traffic/" data-link-title="模組七：突發流量應對" data-link-desc="行銷活動或新聞曝光帶來 10x-100x 流量時怎麼撐 — 突發分類、降級策略、queue 緩衝、規模分級應對">模組七 突發流量</a></li>
</ul>
]]></content:encoded></item><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/01-load-balancing/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/01-load-balancing/</guid><description>&lt;p>回答「一個入口、多個後端實例，流量怎麼分」。反向代理是 DevOps 最基礎的元件。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 反向代理的職責（TLS 終止、路由、負載分散、健康檢查）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 負載分散演算法（round-robin / least-connections / IP hash / consistent hash）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> nginx 實務配置（upstream + health_check + 常見 gotcha）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 健康檢查路由設計（被動 vs 主動、check interval、unhealthy threshold）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 和模組二（水平擴展）的銜接：LB 是水平擴展的前提&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector 架構&lt;/a>：Collector 多實例部署時的 LB 設計&lt;/li>
&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 與服務生命週期合約">backend 部署平台&lt;/a>：PaaS / container 的 LB 內建 vs 自管&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">infra 模組三：網路地基&lt;/a>：ALB 掛在 public subnet、後端在 private subnet 的網路分層設計&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/05-core-services/loadbalancer-alb/" data-link-title="入口上 IaC — ALB、TLS 與健康檢查" data-link-desc="Application Load Balancer 的 listener、target group、健康檢查閾值設計，以及用 ACM 把 TLS 憑證的簽發、驗證與掛載整條鏈寫進版本控制">infra 模組五：入口上 IaC&lt;/a>：ALB 的 listener、target group、TLS 與健康檢查在 IaC 裡怎麼描述&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「一個入口、多個後端實例，流量怎麼分」。反向代理是 DevOps 最基礎的元件。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input disabled="" type="checkbox"> 反向代理的職責（TLS 終止、路由、負載分散、健康檢查）</li>
<li><input disabled="" type="checkbox"> 負載分散演算法（round-robin / least-connections / IP hash / consistent hash）</li>
<li><input disabled="" type="checkbox"> nginx 實務配置（upstream + health_check + 常見 gotcha）</li>
<li><input disabled="" type="checkbox"> 健康檢查路由設計（被動 vs 主動、check interval、unhealthy threshold）</li>
<li><input disabled="" type="checkbox"> 和模組二（水平擴展）的銜接：LB 是水平擴展的前提</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector 架構</a>：Collector 多實例部署時的 LB 設計</li>
<li>→ <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend 部署平台</a>：PaaS / container 的 LB 內建 vs 自管</li>
<li>→ <a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">infra 模組三：網路地基</a>：ALB 掛在 public subnet、後端在 private subnet 的網路分層設計</li>
<li>→ <a href="/blog/infra/05-core-services/loadbalancer-alb/" data-link-title="入口上 IaC — ALB、TLS 與健康檢查" data-link-desc="Application Load Balancer 的 listener、target group、健康檢查閾值設計，以及用 ACM 把 TLS 憑證的簽發、驗證與掛載整條鏈寫進版本控制">infra 模組五：入口上 IaC</a>：ALB 的 listener、target group、TLS 與健康檢查在 IaC 裡怎麼描述</li>
</ul>
]]></content:encoded></item><item><title>Rate Limiting</title><link>https://tarrragon.github.io/blog/devops/03-traffic-management/rate-limiting/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/03-traffic-management/rate-limiting/</guid><description>&lt;p>Rate limiting 是主動的流量控制 — 在系統還沒過載之前，就限制每個來源的請求速率。和背壓不同，rate limit 的觸發依據是預設的速率上限，而非實際的系統負載。&lt;/p>
&lt;h2 id="兩個粒度">兩個粒度&lt;/h2>
&lt;h3 id="per-client每來源限速">Per-client（每來源限速）&lt;/h3>
&lt;p>限制每個 client（by API key / IP / SDK instance）的請求速率。防止單一來源打爆系統。&lt;/p>
&lt;p>自用場景下 per-client 限速的價值不高（只有自己的 SDK），但開源工具被多人部署後，per-client 限速防止某個失控的 SDK 影響其他來源。&lt;/p>
&lt;h3 id="global全局限速">Global（全局限速）&lt;/h3>
&lt;p>限制系統的總吞吐量。不管多少個 client，collector 每秒最多處理 N 個事件。&lt;/p>
&lt;p>Global 限速是系統保護的最後一道線 — 即使每個 client 都在限速內，所有 client 加起來可能超過系統承載。Global 限速確保總量不超過系統能力。&lt;/p>
&lt;h2 id="演算法">演算法&lt;/h2>
&lt;h3 id="token-bucket">Token Bucket&lt;/h3>
&lt;p>桶裡有固定數量的 token，每個請求消耗一個 token，token 按固定速率補充。桶空了就拒絕。&lt;/p>
&lt;p>特點：允許短暫 burst（桶滿時一次消耗多個 token），但長期平均不超過補充速率。適合「允許偶爾的高峰但長期平均要在限制內」的場景。&lt;/p>
&lt;h3 id="sliding-window">Sliding Window&lt;/h3>
&lt;p>在固定的時間窗口（如 1 分鐘）內計數請求。超過上限就拒絕。窗口結束時計數重設。&lt;/p>
&lt;p>特點：嚴格的速率限制（窗口內不會超過 N 個），但窗口邊界有突增風險（上一個窗口末尾 + 下一個窗口開頭各 N 個 = 瞬間 2N）。滑動窗口（sliding window log / counter）解決邊界問題但記憶體較高。&lt;/p>
&lt;h3 id="選擇">選擇&lt;/h3>
&lt;p>自架監控系統推薦 token bucket — 允許 SDK 的 flush burst（一次送 100 個事件是正常行為），但限制長期平均速率。&lt;/p>
&lt;h2 id="http-429--retry-after">HTTP 429 + Retry-After&lt;/h2>
&lt;p>限速觸發時回 HTTP 429 Too Many Requests，帶 &lt;code>Retry-After&lt;/code> header 和 rate limit 相關 header：&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: 5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">X-RateLimit-Limit: 1000
&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: 1719302400&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SDK 收到 429 後觸發離線 buffer 暫存事件，&lt;code>Retry-After&lt;/code> 秒後重試。&lt;/p>
&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>Health check&lt;/td>
 &lt;td>不限&lt;/td>
 &lt;td>探活請求被限速等於 LB 誤判服務掛了&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error 事件&lt;/td>
 &lt;td>不限或較寬&lt;/td>
 &lt;td>Debug 價值最高、丟了就查不到&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Event 事件&lt;/td>
 &lt;td>限速&lt;/td>
 &lt;td>量大、行為分析可以接受取樣&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Metric 事件&lt;/td>
 &lt;td>限速&lt;/td>
 &lt;td>高頻取樣可以降頻&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>優先級的判斷依據是「這個事件丟了的代價」。Error 事件丟了影響 debug 能力，event 事件丟了影響行為分析精度 — 前者的代價更高。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>被動的流量控制 → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &amp;#43; 回壓訊號的設計、和 rate limit 的區別">背壓機制&lt;/a>&lt;/li>
&lt;li>依賴失敗時的快速失敗 → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/circuit-breaker/" data-link-title="熔斷器" data-link-desc="依賴服務失敗時怎麼快速失敗而非拖慢自己 — 三狀態模型（closed → open → half-open）和熔斷判斷條件">熔斷器&lt;/a>&lt;/li>
&lt;li>不同工作負載的資源隔離 → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/bulkhead/" data-link-title="Bulkhead 隔離" data-link-desc="不同工作負載的資源池隔離 — 一個功能過載不拖垮其他功能的隔艙設計">Bulkhead 隔離&lt;/a>&lt;/li>
&lt;li>Backend 的 rate limit 實作（middleware / Redis / 配額設計）→ &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/rate-limit-implementation/" data-link-title="Rate Limit 實作" data-link-desc="單機 middleware / Redis 分散式限速 / 配額設計 — 概念見 DevOps 流量管控，本章聚焦後端實作">Rate Limit 實作&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Rate limiting 是主動的流量控制 — 在系統還沒過載之前，就限制每個來源的請求速率。和背壓不同，rate limit 的觸發依據是預設的速率上限，而非實際的系統負載。</p>
<h2 id="兩個粒度">兩個粒度</h2>
<h3 id="per-client每來源限速">Per-client（每來源限速）</h3>
<p>限制每個 client（by API key / IP / SDK instance）的請求速率。防止單一來源打爆系統。</p>
<p>自用場景下 per-client 限速的價值不高（只有自己的 SDK），但開源工具被多人部署後，per-client 限速防止某個失控的 SDK 影響其他來源。</p>
<h3 id="global全局限速">Global（全局限速）</h3>
<p>限制系統的總吞吐量。不管多少個 client，collector 每秒最多處理 N 個事件。</p>
<p>Global 限速是系統保護的最後一道線 — 即使每個 client 都在限速內，所有 client 加起來可能超過系統承載。Global 限速確保總量不超過系統能力。</p>
<h2 id="演算法">演算法</h2>
<h3 id="token-bucket">Token Bucket</h3>
<p>桶裡有固定數量的 token，每個請求消耗一個 token，token 按固定速率補充。桶空了就拒絕。</p>
<p>特點：允許短暫 burst（桶滿時一次消耗多個 token），但長期平均不超過補充速率。適合「允許偶爾的高峰但長期平均要在限制內」的場景。</p>
<h3 id="sliding-window">Sliding Window</h3>
<p>在固定的時間窗口（如 1 分鐘）內計數請求。超過上限就拒絕。窗口結束時計數重設。</p>
<p>特點：嚴格的速率限制（窗口內不會超過 N 個），但窗口邊界有突增風險（上一個窗口末尾 + 下一個窗口開頭各 N 個 = 瞬間 2N）。滑動窗口（sliding window log / counter）解決邊界問題但記憶體較高。</p>
<h3 id="選擇">選擇</h3>
<p>自架監控系統推薦 token bucket — 允許 SDK 的 flush burst（一次送 100 個事件是正常行為），但限制長期平均速率。</p>
<h2 id="http-429--retry-after">HTTP 429 + Retry-After</h2>
<p>限速觸發時回 HTTP 429 Too Many Requests，帶 <code>Retry-After</code> header 和 rate limit 相關 header：</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: 5
</span></span><span class="line"><span class="ln">3</span><span class="cl">X-RateLimit-Limit: 1000
</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: 1719302400</span></span></code></pre></div><p>SDK 收到 429 後觸發離線 buffer 暫存事件，<code>Retry-After</code> 秒後重試。</p>
<h2 id="優先級豁免">優先級豁免</h2>
<p>某些請求不應被限速：</p>
<table>
  <thead>
      <tr>
          <th>請求類型</th>
          <th>限速？</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Health check</td>
          <td>不限</td>
          <td>探活請求被限速等於 LB 誤判服務掛了</td>
      </tr>
      <tr>
          <td>Error 事件</td>
          <td>不限或較寬</td>
          <td>Debug 價值最高、丟了就查不到</td>
      </tr>
      <tr>
          <td>Event 事件</td>
          <td>限速</td>
          <td>量大、行為分析可以接受取樣</td>
      </tr>
      <tr>
          <td>Metric 事件</td>
          <td>限速</td>
          <td>高頻取樣可以降頻</td>
      </tr>
  </tbody>
</table>
<p>優先級的判斷依據是「這個事件丟了的代價」。Error 事件丟了影響 debug 能力，event 事件丟了影響行為分析精度 — 前者的代價更高。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>被動的流量控制 → <a href="/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &#43; 回壓訊號的設計、和 rate limit 的區別">背壓機制</a></li>
<li>依賴失敗時的快速失敗 → <a href="/blog/devops/03-traffic-management/circuit-breaker/" data-link-title="熔斷器" data-link-desc="依賴服務失敗時怎麼快速失敗而非拖慢自己 — 三狀態模型（closed → open → half-open）和熔斷判斷條件">熔斷器</a></li>
<li>不同工作負載的資源隔離 → <a href="/blog/devops/03-traffic-management/bulkhead/" data-link-title="Bulkhead 隔離" data-link-desc="不同工作負載的資源池隔離 — 一個功能過載不拖垮其他功能的隔艙設計">Bulkhead 隔離</a></li>
<li>Backend 的 rate limit 實作（middleware / Redis / 配額設計）→ <a href="/blog/backend/09-performance-capacity/rate-limit-implementation/" data-link-title="Rate Limit 實作" data-link-desc="單機 middleware / Redis 分散式限速 / 配額設計 — 概念見 DevOps 流量管控，本章聚焦後端實作">Rate Limit 實作</a></li>
</ul>
]]></content:encoded></item><item><title>降級策略</title><link>https://tarrragon.github.io/blog/devops/07-burst-traffic/degradation-strategy/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/07-burst-traffic/degradation-strategy/</guid><description>&lt;p>降級策略的核心決策是「超載時犧牲什麼保住什麼」。犧牲的是精度、延遲或非核心功能；保住的是核心功能的可用性。沒有降級策略的系統在超載時整體崩潰 — 所有功能同時不可用。&lt;/p>
&lt;h2 id="動態取樣">動態取樣&lt;/h2>
&lt;p>流量超過閾值時自動降低取樣率。平時 100% 收集、超載時降到 10% — 仍有資料可分析，只是精度下降。&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>Collector 回 429 次數 &amp;gt; N / 分鐘&lt;/td>
 &lt;td>SDK 降低取樣率 50%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>連續 429 超過 M 分鐘&lt;/td>
 &lt;td>SDK 再降到 10%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>429 消失且 buffer 清空&lt;/td>
 &lt;td>SDK 恢復 100%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="取樣的公平性">取樣的公平性&lt;/h3>
&lt;p>動態取樣不應該只丟新事件保留舊事件（FIFO 丟棄）— 這會讓取樣偏向「burst 初期的事件」。更好的策略是隨機取樣（每個事件有 sampling_rate 的機率被保留），讓取樣後的資料仍然能代表整體分佈。&lt;/p>
&lt;p>取樣後的事件帶 &lt;code>_sampling_rate&lt;/code> 欄位，分析時用 &lt;code>1 / sampling_rate&lt;/code> 做加權還原。&lt;/p>
&lt;h2 id="事件優先級">事件優先級&lt;/h2>
&lt;p>不同事件類型的 debug 價值不同。超載時先丟價值低的，保留價值高的。&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>最高&lt;/td>
 &lt;td>error&lt;/td>
 &lt;td>debug 核心 — 丟了就查不到問題&lt;/td>
 &lt;td>全部保留&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高&lt;/td>
 &lt;td>lifecycle&lt;/td>
 &lt;td>session 邊界 — 影響 session 分析&lt;/td>
 &lt;td>全部保留&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>中&lt;/td>
 &lt;td>metric&lt;/td>
 &lt;td>趨勢可從取樣還原&lt;/td>
 &lt;td>降低取樣率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>低&lt;/td>
 &lt;td>event&lt;/td>
 &lt;td>行為分析可接受精度損失&lt;/td>
 &lt;td>降低取樣率或暫停&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>優先級的判斷原則：「這個事件丟了、要花多少時間從其他來源補回相同資訊」。Error 的 stack trace 丟了幾乎不可能從其他來源補回；event 的 click 計數可以從後續資料的趨勢推測。&lt;/p>
&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>Dashboard 即時刷新&lt;/td>
 &lt;td>每秒查詢&lt;/td>
 &lt;td>每 30 秒查詢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rule engine 評估&lt;/td>
 &lt;td>每筆事件即時評估&lt;/td>
 &lt;td>累積 10 筆批次評估&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>JSONL 匯出&lt;/td>
 &lt;td>隨時可匯出&lt;/td>
 &lt;td>暫停（避免 I/O 競爭）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>降採樣 job&lt;/td>
 &lt;td>每小時跑&lt;/td>
 &lt;td>延後到流量恢復後補跑&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>降級的觸發和恢復應該自動化 — 用 collector 的內部 metric（goroutine pool 使用率、寫入延遲）作為訊號。&lt;/p>
&lt;h2 id="聚合前移">聚合前移&lt;/h2>
&lt;p>讓 SDK 端做預聚合，減少送到 collector 的事件數量。&lt;/p>
&lt;p>平時：每次 click 送一筆 &lt;code>button.clicked&lt;/code> 事件 → 100 次 click = 100 筆事件。
聚合前移：SDK 累積 10 秒內的 click → 送一筆 &lt;code>button.clicked&lt;/code> 帶 &lt;code>count: 17&lt;/code> → 100 次 click = ~10 筆事件。&lt;/p>
&lt;p>聚合前移犧牲的是事件粒度（失去每次 click 的精確時間戳），換取的是 10x 的事件量減少。適用於高頻但單筆資訊量低的事件（click、scroll、mousemove）。&lt;/p>
&lt;p>聚合前移的觸發也可以是動態的 — collector 回 429 時 SDK 自動啟用聚合前移，流量恢復後關閉。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>突發流量的分類 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/burst-classification/" data-link-title="突發流量的分類" data-link-desc="可預期 vs 不可預期的突發流量 — 不同來源、持續時間和倍率決定不同的應對策略">突發流量的分類&lt;/a>&lt;/li>
&lt;li>Queue 做更大規模的緩衝 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/queue-buffering/" data-link-title="Queue 緩衝" data-link-desc="在 ingestion 和 processing 之間加 message queue 做 burst 緩衝 — Kafka / NATS / Redis Streams 的選型和引入條件">Queue 緩衝&lt;/a>&lt;/li>
&lt;li>不同規模的應對方案 → &lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/scale-tier-response/" data-link-title="規模分級應對表" data-link-desc="自用級 → 中型 → 大型 → 商業網站級的四級應對方案 — 每級的觸發條件、架構組成和成本">規模分級應對表&lt;/a>&lt;/li>
&lt;li>背壓和 rate limit 的基礎 → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">模組三 流量管控&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>降級策略的核心決策是「超載時犧牲什麼保住什麼」。犧牲的是精度、延遲或非核心功能；保住的是核心功能的可用性。沒有降級策略的系統在超載時整體崩潰 — 所有功能同時不可用。</p>
<h2 id="動態取樣">動態取樣</h2>
<p>流量超過閾值時自動降低取樣率。平時 100% 收集、超載時降到 10% — 仍有資料可分析，只是精度下降。</p>
<h3 id="觸發條件">觸發條件</h3>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Collector 回 429 次數 &gt; N / 分鐘</td>
          <td>SDK 降低取樣率 50%</td>
      </tr>
      <tr>
          <td>連續 429 超過 M 分鐘</td>
          <td>SDK 再降到 10%</td>
      </tr>
      <tr>
          <td>429 消失且 buffer 清空</td>
          <td>SDK 恢復 100%</td>
      </tr>
  </tbody>
</table>
<h3 id="取樣的公平性">取樣的公平性</h3>
<p>動態取樣不應該只丟新事件保留舊事件（FIFO 丟棄）— 這會讓取樣偏向「burst 初期的事件」。更好的策略是隨機取樣（每個事件有 sampling_rate 的機率被保留），讓取樣後的資料仍然能代表整體分佈。</p>
<p>取樣後的事件帶 <code>_sampling_rate</code> 欄位，分析時用 <code>1 / sampling_rate</code> 做加權還原。</p>
<h2 id="事件優先級">事件優先級</h2>
<p>不同事件類型的 debug 價值不同。超載時先丟價值低的，保留價值高的。</p>
<table>
  <thead>
      <tr>
          <th>優先級</th>
          <th>事件類型</th>
          <th>理由</th>
          <th>超載時處理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>最高</td>
          <td>error</td>
          <td>debug 核心 — 丟了就查不到問題</td>
          <td>全部保留</td>
      </tr>
      <tr>
          <td>高</td>
          <td>lifecycle</td>
          <td>session 邊界 — 影響 session 分析</td>
          <td>全部保留</td>
      </tr>
      <tr>
          <td>中</td>
          <td>metric</td>
          <td>趨勢可從取樣還原</td>
          <td>降低取樣率</td>
      </tr>
      <tr>
          <td>低</td>
          <td>event</td>
          <td>行為分析可接受精度損失</td>
          <td>降低取樣率或暫停</td>
      </tr>
  </tbody>
</table>
<p>優先級的判斷原則：「這個事件丟了、要花多少時間從其他來源補回相同資訊」。Error 的 stack trace 丟了幾乎不可能從其他來源補回；event 的 click 計數可以從後續資料的趨勢推測。</p>
<h2 id="功能降級">功能降級</h2>
<p>非核心功能暫時關閉或降低更新頻率，把資源留給核心功能。</p>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>正常模式</th>
          <th>降級模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Dashboard 即時刷新</td>
          <td>每秒查詢</td>
          <td>每 30 秒查詢</td>
      </tr>
      <tr>
          <td>Rule engine 評估</td>
          <td>每筆事件即時評估</td>
          <td>累積 10 筆批次評估</td>
      </tr>
      <tr>
          <td>JSONL 匯出</td>
          <td>隨時可匯出</td>
          <td>暫停（避免 I/O 競爭）</td>
      </tr>
      <tr>
          <td>降採樣 job</td>
          <td>每小時跑</td>
          <td>延後到流量恢復後補跑</td>
      </tr>
  </tbody>
</table>
<p>降級的觸發和恢復應該自動化 — 用 collector 的內部 metric（goroutine pool 使用率、寫入延遲）作為訊號。</p>
<h2 id="聚合前移">聚合前移</h2>
<p>讓 SDK 端做預聚合，減少送到 collector 的事件數量。</p>
<p>平時：每次 click 送一筆 <code>button.clicked</code> 事件 → 100 次 click = 100 筆事件。
聚合前移：SDK 累積 10 秒內的 click → 送一筆 <code>button.clicked</code> 帶 <code>count: 17</code> → 100 次 click = ~10 筆事件。</p>
<p>聚合前移犧牲的是事件粒度（失去每次 click 的精確時間戳），換取的是 10x 的事件量減少。適用於高頻但單筆資訊量低的事件（click、scroll、mousemove）。</p>
<p>聚合前移的觸發也可以是動態的 — collector 回 429 時 SDK 自動啟用聚合前移，流量恢復後關閉。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>突發流量的分類 → <a href="/blog/devops/07-burst-traffic/burst-classification/" data-link-title="突發流量的分類" data-link-desc="可預期 vs 不可預期的突發流量 — 不同來源、持續時間和倍率決定不同的應對策略">突發流量的分類</a></li>
<li>Queue 做更大規模的緩衝 → <a href="/blog/devops/07-burst-traffic/queue-buffering/" data-link-title="Queue 緩衝" data-link-desc="在 ingestion 和 processing 之間加 message queue 做 burst 緩衝 — Kafka / NATS / Redis Streams 的選型和引入條件">Queue 緩衝</a></li>
<li>不同規模的應對方案 → <a href="/blog/devops/07-burst-traffic/scale-tier-response/" data-link-title="規模分級應對表" data-link-desc="自用級 → 中型 → 大型 → 商業網站級的四級應對方案 — 每級的觸發條件、架構組成和成本">規模分級應對表</a></li>
<li>背壓和 rate limit 的基礎 → <a href="/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">模組三 流量管控</a></li>
</ul>
]]></content:encoded></item><item><title>模組二：水平擴展</title><link>https://tarrragon.github.io/blog/devops/02-horizontal-scaling/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/02-horizontal-scaling/</guid><description>&lt;p>回答「怎麼從一個實例變成多個實例」。水平擴展的前提是服務 stateless — 每個實例可以獨立處理任何請求。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Stateless 設計原則（狀態放 DB / cache / 外部儲存、不放 process memory）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Session 處理（sticky session / session store / JWT stateless）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Shared storage 的選型（NFS / S3 / DB — 不同 workload 的適合方案）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 擴展的觸發訊號和縮回條件&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 垂直擴展 vs 水平擴展的判斷（什麼時候加 CPU、什麼時候加實例）&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>← &lt;a href="https://tarrragon.github.io/blog/devops/01-load-balancing/" data-link-title="模組一：負載平衡與反向代理" data-link-desc="流量進來怎麼分給多個服務實例 — nginx / HAProxy / DNS round-robin 的選型和健康檢查路由設計">devops 模組一 負載平衡&lt;/a>：LB 是水平擴展的前提&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector&lt;/a>：Collector 的 stateless 設計讓多實例可行&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">backend 資料庫&lt;/a>：Shared storage 的 DB 選型&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「怎麼從一個實例變成多個實例」。水平擴展的前提是服務 stateless — 每個實例可以獨立處理任何請求。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input disabled="" type="checkbox"> Stateless 設計原則（狀態放 DB / cache / 外部儲存、不放 process memory）</li>
<li><input disabled="" type="checkbox"> Session 處理（sticky session / session store / JWT stateless）</li>
<li><input disabled="" type="checkbox"> Shared storage 的選型（NFS / S3 / DB — 不同 workload 的適合方案）</li>
<li><input disabled="" type="checkbox"> 擴展的觸發訊號和縮回條件</li>
<li><input disabled="" type="checkbox"> 垂直擴展 vs 水平擴展的判斷（什麼時候加 CPU、什麼時候加實例）</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>← <a href="/blog/devops/01-load-balancing/" data-link-title="模組一：負載平衡與反向代理" data-link-desc="流量進來怎麼分給多個服務實例 — nginx / HAProxy / DNS round-robin 的選型和健康檢查路由設計">devops 模組一 負載平衡</a>：LB 是水平擴展的前提</li>
<li>→ <a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector</a>：Collector 的 stateless 設計讓多實例可行</li>
<li>→ <a href="/blog/backend/01-database/" data-link-title="模組一：資料庫與持久化" data-link-desc="整理 SQL、transaction、migration 與 repository adapter 的後端實務">backend 資料庫</a>：Shared storage 的 DB 選型</li>
</ul>
]]></content:encoded></item><item><title>Queue 緩衝</title><link>https://tarrragon.github.io/blog/devops/07-burst-traffic/queue-buffering/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/07-burst-traffic/queue-buffering/</guid><description>&lt;p>Message queue 放在 ingestion（接收事件）和 processing（寫入 storage）之間，把兩者解耦。Ingestion 只負責驗證和寫入 queue，processing 按自己的速度從 queue 消費。Queue 做 burst 的時間緩衝 — 高峰時 queue 積壓、低峰時 worker 追上。&lt;/p>
&lt;h2 id="為什麼不直接寫-db">為什麼不直接寫 DB&lt;/h2>
&lt;p>直接寫 DB（SQLite / PostgreSQL）的問題是 ingestion 速度被 DB 寫入速度限制。DB 寫入慢（鎖定、WAL flush、索引更新）時，HTTP handler 的 goroutine 等在 &lt;code>Storage.Store()&lt;/code> 上 — goroutine 積壓 → 記憶體上升 → 最終 OOM 或 response timeout。&lt;/p>
&lt;p>Queue 的解決方式是把「接收」和「寫入」分開：接收端只做 JSON 驗證 + 寫入 queue（微秒級），處理端從 queue 讀取 + 寫入 DB（毫秒級）。接收端的吞吐量不再受 DB 限制。&lt;/p>
&lt;h3 id="取捨">取捨&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>直接寫 DB&lt;/th>
 &lt;th>經過 Queue&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>延遲&lt;/td>
 &lt;td>事件寫完 DB 即可查詢&lt;/td>
 &lt;td>事件要等 worker 消費後才可查詢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>吞吐&lt;/td>
 &lt;td>受 DB 寫入速度限制&lt;/td>
 &lt;td>受 queue 寫入速度限制（通常遠高於 DB）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>複雜度&lt;/td>
 &lt;td>一個元件&lt;/td>
 &lt;td>三個元件（collector + queue + worker）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>故障模式&lt;/td>
 &lt;td>DB 掛了事件丟失（除非有背壓）&lt;/td>
 &lt;td>Queue 做持久化，DB 掛了事件在 queue 等待&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>自用工具場景不需要 queue — 單 collector + SQLite 的直接寫入足夠。Queue 的引入條件是「直接寫 DB 的背壓開始頻繁觸發」。&lt;/p>
&lt;h2 id="候選類型">候選類型&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Queue&lt;/th>
 &lt;th>特點&lt;/th>
 &lt;th>適用場景&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Kafka&lt;/strong>&lt;/td>
 &lt;td>高吞吐、持久化、消費者群組&lt;/td>
 &lt;td>大規模（&amp;gt; 10 萬 events/sec）、多消費者&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>NATS JetStream&lt;/strong>&lt;/td>
 &lt;td>輕量、低延遲、Go 原生&lt;/td>
 &lt;td>中型（千 ~ 萬 events/sec）、Go 生態&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Redis Streams&lt;/strong>&lt;/td>
 &lt;td>用既有 Redis、XADD/XREAD API&lt;/td>
 &lt;td>中型、已有 Redis 基礎設施&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="選型判斷">選型判斷&lt;/h3>
&lt;p>已有 Redis → 先用 Redis Streams（零新增元件）。Go 為主的技術棧 → NATS JetStream（Go 原生 client、單 binary 部署）。需要跨消費者群組或日誌級持久化 → Kafka。&lt;/p>
&lt;h3 id="引入條件">引入條件&lt;/h3>
&lt;p>Queue 的引入是架構複雜度的顯著上升（一個元件變三個）。明確的觸發條件：&lt;/p>
&lt;ul>
&lt;li>背壓（429 回應）頻繁觸發（每天 &amp;gt; 100 次）且持續（不只是瞬間 burst）&lt;/li>
&lt;li>寫入延遲的 P95 超過 500ms（DB 成為瓶頸）&lt;/li>
&lt;li>需要多個 consumer（同一批事件要送到不同的下游 — analytics DB、alert engine、archive）&lt;/li>
&lt;/ul>
&lt;h2 id="監控系統的-queue-架構">監控系統的 Queue 架構&lt;/h2>





&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">SDK ──→ Collector (ingestion only)
&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"> ├─ 驗證 JSON Schema
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ├─ Redaction
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> └─ 寫入 Queue
&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"> ├── Worker A → PostgreSQL（主 storage）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ├── Worker B → 降採樣 → Summary tables
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> └── Worker C → Rule engine → Alert&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Collector 瘦身為 ingestion-only — 只做接收、驗證、redaction 和寫入 queue。Storage 寫入、降採樣、rule engine 都移到 worker 群。Collector 的吞吐瓶頸從 DB 寫入變成 queue 寫入（queue 的寫入吞吐通常是 DB 的 10-100 倍）。&lt;/p></description><content:encoded><![CDATA[<p>Message queue 放在 ingestion（接收事件）和 processing（寫入 storage）之間，把兩者解耦。Ingestion 只負責驗證和寫入 queue，processing 按自己的速度從 queue 消費。Queue 做 burst 的時間緩衝 — 高峰時 queue 積壓、低峰時 worker 追上。</p>
<h2 id="為什麼不直接寫-db">為什麼不直接寫 DB</h2>
<p>直接寫 DB（SQLite / PostgreSQL）的問題是 ingestion 速度被 DB 寫入速度限制。DB 寫入慢（鎖定、WAL flush、索引更新）時，HTTP handler 的 goroutine 等在 <code>Storage.Store()</code> 上 — goroutine 積壓 → 記憶體上升 → 最終 OOM 或 response timeout。</p>
<p>Queue 的解決方式是把「接收」和「寫入」分開：接收端只做 JSON 驗證 + 寫入 queue（微秒級），處理端從 queue 讀取 + 寫入 DB（毫秒級）。接收端的吞吐量不再受 DB 限制。</p>
<h3 id="取捨">取捨</h3>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>直接寫 DB</th>
          <th>經過 Queue</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>延遲</td>
          <td>事件寫完 DB 即可查詢</td>
          <td>事件要等 worker 消費後才可查詢</td>
      </tr>
      <tr>
          <td>吞吐</td>
          <td>受 DB 寫入速度限制</td>
          <td>受 queue 寫入速度限制（通常遠高於 DB）</td>
      </tr>
      <tr>
          <td>複雜度</td>
          <td>一個元件</td>
          <td>三個元件（collector + queue + worker）</td>
      </tr>
      <tr>
          <td>故障模式</td>
          <td>DB 掛了事件丟失（除非有背壓）</td>
          <td>Queue 做持久化，DB 掛了事件在 queue 等待</td>
      </tr>
  </tbody>
</table>
<p>自用工具場景不需要 queue — 單 collector + SQLite 的直接寫入足夠。Queue 的引入條件是「直接寫 DB 的背壓開始頻繁觸發」。</p>
<h2 id="候選類型">候選類型</h2>
<table>
  <thead>
      <tr>
          <th>Queue</th>
          <th>特點</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Kafka</strong></td>
          <td>高吞吐、持久化、消費者群組</td>
          <td>大規模（&gt; 10 萬 events/sec）、多消費者</td>
      </tr>
      <tr>
          <td><strong>NATS JetStream</strong></td>
          <td>輕量、低延遲、Go 原生</td>
          <td>中型（千 ~ 萬 events/sec）、Go 生態</td>
      </tr>
      <tr>
          <td><strong>Redis Streams</strong></td>
          <td>用既有 Redis、XADD/XREAD API</td>
          <td>中型、已有 Redis 基礎設施</td>
      </tr>
  </tbody>
</table>
<h3 id="選型判斷">選型判斷</h3>
<p>已有 Redis → 先用 Redis Streams（零新增元件）。Go 為主的技術棧 → NATS JetStream（Go 原生 client、單 binary 部署）。需要跨消費者群組或日誌級持久化 → Kafka。</p>
<h3 id="引入條件">引入條件</h3>
<p>Queue 的引入是架構複雜度的顯著上升（一個元件變三個）。明確的觸發條件：</p>
<ul>
<li>背壓（429 回應）頻繁觸發（每天 &gt; 100 次）且持續（不只是瞬間 burst）</li>
<li>寫入延遲的 P95 超過 500ms（DB 成為瓶頸）</li>
<li>需要多個 consumer（同一批事件要送到不同的下游 — analytics DB、alert engine、archive）</li>
</ul>
<h2 id="監控系統的-queue-架構">監控系統的 Queue 架構</h2>





<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">SDK ──→ Collector (ingestion only)
</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">           ├─ 驗證 JSON Schema
</span></span><span class="line"><span class="ln">4</span><span class="cl">           ├─ Redaction
</span></span><span class="line"><span class="ln">5</span><span class="cl">           └─ 寫入 Queue
</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">                 ├── Worker A → PostgreSQL（主 storage）
</span></span><span class="line"><span class="ln">8</span><span class="cl">                 ├── Worker B → 降採樣 → Summary tables
</span></span><span class="line"><span class="ln">9</span><span class="cl">                 └── Worker C → Rule engine → Alert</span></span></code></pre></div><p>Collector 瘦身為 ingestion-only — 只做接收、驗證、redaction 和寫入 queue。Storage 寫入、降採樣、rule engine 都移到 worker 群。Collector 的吞吐瓶頸從 DB 寫入變成 queue 寫入（queue 的寫入吞吐通常是 DB 的 10-100 倍）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>突發流量的分類 → <a href="/blog/devops/07-burst-traffic/burst-classification/" data-link-title="突發流量的分類" data-link-desc="可預期 vs 不可預期的突發流量 — 不同來源、持續時間和倍率決定不同的應對策略">突發流量的分類</a></li>
<li>降級策略 → <a href="/blog/devops/07-burst-traffic/degradation-strategy/" data-link-title="降級策略" data-link-desc="系統超載時犧牲什麼保住什麼 — 動態取樣、事件優先級、功能降級、聚合前移四種策略">降級策略</a></li>
<li>規模分級的完整應對 → <a href="/blog/devops/07-burst-traffic/scale-tier-response/" data-link-title="規模分級應對表" data-link-desc="自用級 → 中型 → 大型 → 商業網站級的四級應對方案 — 每級的觸發條件、架構組成和成本">規模分級應對表</a></li>
<li>Queue 的選型和操作實務 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">backend 非同步佇列</a></li>
</ul>
]]></content:encoded></item><item><title>熔斷器</title><link>https://tarrragon.github.io/blog/devops/03-traffic-management/circuit-breaker/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/03-traffic-management/circuit-breaker/</guid><description>&lt;p>熔斷器保護的是「呼叫外部依賴」的路徑。當外部依賴（資料庫、第三方 API、通知服務）持續失敗時，熔斷器讓後續的呼叫立即失敗（回傳預設值或錯誤），而非每次都等待逾時。等待逾時的代價是佔住 goroutine / thread 不釋放，積累到一定數量就拖垮整個服務。&lt;/p>
&lt;h2 id="三狀態模型">三狀態模型&lt;/h2>
&lt;h3 id="closed正常">Closed（正常）&lt;/h3>
&lt;p>所有呼叫正常通過。熔斷器記錄成功和失敗的計數。&lt;/p>
&lt;h3 id="open熔斷">Open（熔斷）&lt;/h3>
&lt;p>當失敗率或連續失敗次數超過閾值時，熔斷器進入 open 狀態。此後所有呼叫&lt;strong>立即回傳錯誤&lt;/strong>，不實際呼叫外部依賴。&lt;/p>
&lt;p>Open 狀態持續固定時間（如 30 秒），時間到後進入 half-open。&lt;/p>
&lt;h3 id="half-open探測">Half-open（探測）&lt;/h3>
&lt;p>允許少量呼叫（如 1 個）實際通過到外部依賴。如果成功 → 回到 closed；如果失敗 → 回到 open（重設計時器）。&lt;/p>
&lt;p>Half-open 的目的是自動探測依賴是否恢復，不需要人工介入。&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>連續 N 次失敗&lt;/td>
 &lt;td>依賴完全不可用&lt;/td>
 &lt;td>N = 5-10&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗率 &amp;gt; X%&lt;/td>
 &lt;td>依賴間歇性失敗&lt;/td>
 &lt;td>X = 50%，統計窗口 = 10 秒&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>平均延遲 &amp;gt; Y ms&lt;/td>
 &lt;td>依賴變慢但未失敗&lt;/td>
 &lt;td>Y = 依據 SLA 設定&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>「失敗」的定義需要明確：HTTP 5xx 是失敗、4xx 通常不是（client 的問題）、timeout 是失敗、connection refused 是失敗。&lt;/p>
&lt;h2 id="熔斷時的-fallback">熔斷時的 fallback&lt;/h2>
&lt;p>熔斷觸發後，呼叫端收到的是「快速失敗」而非逾時。呼叫端需要有 fallback 策略：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>依賴&lt;/th>
 &lt;th>Fallback&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>通知服務（Slack webhook）&lt;/td>
 &lt;td>記錄到本地 log、恢復後補發&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>外部 API（enrichment）&lt;/td>
 &lt;td>回傳無 enrichment 的原始資料&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>認證服務&lt;/td>
 &lt;td>用本地 cache 的 token 驗證（短暫降級）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>沒有 fallback 的依賴被熔斷 = 對應功能完全不可用。熔斷器保護的是「不讓不可用的功能拖垮整個服務」。&lt;/p>
&lt;h2 id="監控系統的應用">監控系統的應用&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">Collector&lt;/a> 的 rule engine 在規則命中時可能呼叫外部服務（Slack webhook、HTTP POST 到 alert endpoint）。如果外部服務掛了，每個命中的規則都會等待逾時 — 大量規則命中時 goroutine 積壓。&lt;/p>
&lt;p>熔斷器包在 rule engine 的「執行外部動作」環節：連續 5 次外部呼叫失敗 → 熔斷 → 後續規則命中不再嘗試外部呼叫、改寫本地 log → 30 秒後探測一次 → 外部服務恢復 → 恢復正常呼叫。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>被動的流量控制 → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &amp;#43; 回壓訊號的設計、和 rate limit 的區別">背壓機制&lt;/a>&lt;/li>
&lt;li>主動的速率限制 → &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、優先級豁免">Rate Limiting&lt;/a>&lt;/li>
&lt;li>不同工作負載的資源隔離 → &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/bulkhead/" data-link-title="Bulkhead 隔離" data-link-desc="不同工作負載的資源池隔離 — 一個功能過載不拖垮其他功能的隔艙設計">Bulkhead 隔離&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>熔斷器保護的是「呼叫外部依賴」的路徑。當外部依賴（資料庫、第三方 API、通知服務）持續失敗時，熔斷器讓後續的呼叫立即失敗（回傳預設值或錯誤），而非每次都等待逾時。等待逾時的代價是佔住 goroutine / thread 不釋放，積累到一定數量就拖垮整個服務。</p>
<h2 id="三狀態模型">三狀態模型</h2>
<h3 id="closed正常">Closed（正常）</h3>
<p>所有呼叫正常通過。熔斷器記錄成功和失敗的計數。</p>
<h3 id="open熔斷">Open（熔斷）</h3>
<p>當失敗率或連續失敗次數超過閾值時，熔斷器進入 open 狀態。此後所有呼叫<strong>立即回傳錯誤</strong>，不實際呼叫外部依賴。</p>
<p>Open 狀態持續固定時間（如 30 秒），時間到後進入 half-open。</p>
<h3 id="half-open探測">Half-open（探測）</h3>
<p>允許少量呼叫（如 1 個）實際通過到外部依賴。如果成功 → 回到 closed；如果失敗 → 回到 open（重設計時器）。</p>
<p>Half-open 的目的是自動探測依賴是否恢復，不需要人工介入。</p>
<h2 id="熔斷判斷條件">熔斷判斷條件</h2>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>適用場景</th>
          <th>參數</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>連續 N 次失敗</td>
          <td>依賴完全不可用</td>
          <td>N = 5-10</td>
      </tr>
      <tr>
          <td>失敗率 &gt; X%</td>
          <td>依賴間歇性失敗</td>
          <td>X = 50%，統計窗口 = 10 秒</td>
      </tr>
      <tr>
          <td>平均延遲 &gt; Y ms</td>
          <td>依賴變慢但未失敗</td>
          <td>Y = 依據 SLA 設定</td>
      </tr>
  </tbody>
</table>
<p>「失敗」的定義需要明確：HTTP 5xx 是失敗、4xx 通常不是（client 的問題）、timeout 是失敗、connection refused 是失敗。</p>
<h2 id="熔斷時的-fallback">熔斷時的 fallback</h2>
<p>熔斷觸發後，呼叫端收到的是「快速失敗」而非逾時。呼叫端需要有 fallback 策略：</p>
<table>
  <thead>
      <tr>
          <th>依賴</th>
          <th>Fallback</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>通知服務（Slack webhook）</td>
          <td>記錄到本地 log、恢復後補發</td>
      </tr>
      <tr>
          <td>外部 API（enrichment）</td>
          <td>回傳無 enrichment 的原始資料</td>
      </tr>
      <tr>
          <td>認證服務</td>
          <td>用本地 cache 的 token 驗證（短暫降級）</td>
      </tr>
  </tbody>
</table>
<p>沒有 fallback 的依賴被熔斷 = 對應功能完全不可用。熔斷器保護的是「不讓不可用的功能拖垮整個服務」。</p>
<h2 id="監控系統的應用">監控系統的應用</h2>
<p><a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">Collector</a> 的 rule engine 在規則命中時可能呼叫外部服務（Slack webhook、HTTP POST 到 alert endpoint）。如果外部服務掛了，每個命中的規則都會等待逾時 — 大量規則命中時 goroutine 積壓。</p>
<p>熔斷器包在 rule engine 的「執行外部動作」環節：連續 5 次外部呼叫失敗 → 熔斷 → 後續規則命中不再嘗試外部呼叫、改寫本地 log → 30 秒後探測一次 → 外部服務恢復 → 恢復正常呼叫。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>被動的流量控制 → <a href="/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &#43; 回壓訊號的設計、和 rate limit 的區別">背壓機制</a></li>
<li>主動的速率限制 → <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、優先級豁免">Rate Limiting</a></li>
<li>不同工作負載的資源隔離 → <a href="/blog/devops/03-traffic-management/bulkhead/" data-link-title="Bulkhead 隔離" data-link-desc="不同工作負載的資源池隔離 — 一個功能過載不拖垮其他功能的隔艙設計">Bulkhead 隔離</a></li>
</ul>
]]></content:encoded></item><item><title>模組三：流量管控</title><link>https://tarrragon.github.io/blog/devops/03-traffic-management/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/03-traffic-management/</guid><description>&lt;p>回答「收到的流量超過處理能力時怎麼辦」。四種防護機制各自處理不同層面的過載問題。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 背壓機制（下游慢時上游怎麼減速）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Rate Limiting（主動限制每個來源的請求速率）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 熔斷器（依賴服務失敗時怎麼快速失敗而非拖慢自己）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Bulkhead 隔離（不同工作負載的資源池隔離）&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector&lt;/a>：Collector 的 ingestion 防護是本模組的應用場景&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;li>→ &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend 可靠性&lt;/a>：熔斷和 bulkhead 也是 backend 的可靠性設計元件&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「收到的流量超過處理能力時怎麼辦」。四種防護機制各自處理不同層面的過載問題。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 背壓機制（下游慢時上游怎麼減速）</li>
<li><input checked="" disabled="" type="checkbox"> Rate Limiting（主動限制每個來源的請求速率）</li>
<li><input checked="" disabled="" type="checkbox"> 熔斷器（依賴服務失敗時怎麼快速失敗而非拖慢自己）</li>
<li><input checked="" disabled="" type="checkbox"> Bulkhead 隔離（不同工作負載的資源池隔離）</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector</a>：Collector 的 ingestion 防護是本模組的應用場景</li>
<li>→ <a href="/blog/devops/07-burst-traffic/" data-link-title="模組七：突發流量應對" data-link-desc="行銷活動或新聞曝光帶來 10x-100x 流量時怎麼撐 — 突發分類、降級策略、queue 緩衝、規模分級應對">devops 模組七 突發流量</a>：突發流量時這四種機制怎麼組合使用</li>
<li>→ <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend 可靠性</a>：熔斷和 bulkhead 也是 backend 的可靠性設計元件</li>
</ul>
]]></content:encoded></item><item><title>Bulkhead 隔離</title><link>https://tarrragon.github.io/blog/devops/03-traffic-management/bulkhead/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/03-traffic-management/bulkhead/</guid><description>&lt;p>Bulkhead 的概念來自船舶的隔艙設計 — 船體分成多個獨立的水密隔艙，一個隔艙進水不會讓整艘船沉沒。服務設計中，bulkhead 把不同的工作負載隔離到各自的資源池，一個工作負載的過載或故障不會消耗其他工作負載的資源。&lt;/p>
&lt;h2 id="隔離什麼">隔離什麼&lt;/h2>
&lt;p>服務中的共享資源是 bulkhead 的隔離對象：&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>Goroutine/Thread&lt;/td>
 &lt;td>一個慢查詢佔住所有 goroutine，整個服務不回應&lt;/td>
 &lt;td>每類工作分配獨立的 goroutine pool&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料庫連線&lt;/td>
 &lt;td>一個大查詢佔住 connection pool，其他查詢排隊&lt;/td>
 &lt;td>不同工作類型用不同的連線池&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>記憶體&lt;/td>
 &lt;td>一個功能的 buffer 無限增長，OOM 殺掉整個 process&lt;/td>
 &lt;td>每個功能的 buffer 有獨立上限&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CPU&lt;/td>
 &lt;td>一個計算密集任務佔滿 CPU，其他請求延遲&lt;/td>
 &lt;td>cgroup 或 GOMAXPROCS 限制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="實作模式">實作模式&lt;/h2>
&lt;h3 id="獨立-goroutine-pool">獨立 Goroutine Pool&lt;/h3>
&lt;p>Go 中用有限容量的 channel 模擬 goroutine pool：&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="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">ingestPool&lt;/span> &lt;span class="p">=&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">100&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// ingestion 最多 100 goroutine&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">queryPool&lt;/span> &lt;span class="p">=&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">20&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// query 最多 20 goroutine&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">rulePool&lt;/span> &lt;span class="p">=&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">10&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// rule engine 最多 10 goroutine&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;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="kd">func&lt;/span> &lt;span class="nf">handleIngest&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">select&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">case&lt;/span> &lt;span class="nx">ingestPool&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">10&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">ingestPool&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="nf">processIngest&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">12&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">13&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;ingestion overloaded&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">StatusServiceUnavailable&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;p>Ingestion 的 100 個 goroutine 全部被佔用時，新的 ingestion 請求被拒絕（503），但 query 和 rule engine 的 goroutine 不受影響。&lt;/p>
&lt;h3 id="獨立-connection-pool">獨立 Connection Pool&lt;/h3>
&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="c1">// driver name 依實際使用的 driver 而定（modernc.org/sqlite 用 &amp;#34;sqlite&amp;#34;、mattn/go-sqlite3 用 &amp;#34;sqlite3&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 class="nx">ingestDB&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sqlite&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;events.db&amp;#34;&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">ingestDB&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetMaxOpenConns&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// ingestion 專用&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">queryDB&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;sqlite&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;events.db&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="nx">queryDB&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetMaxOpenConns&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// query 專用&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SQLite 的特殊情況：SQLite 是檔案級鎖定，多個連線池打開同一個檔案時仍共享 write lock。連線池隔離在 SQLite 上主要隔離的是 Go 層的 goroutine 等待，不是 DB 層的鎖定。PostgreSQL 的連線池隔離則是真正的資源隔離。&lt;/p>
&lt;h2 id="容量分配">容量分配&lt;/h2>
&lt;p>Bulkhead 的每個隔艙分配多少資源是設計決策。分配依據是「這個工作負載的優先順序和預期併發量」：&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Ingestion&lt;/td>
 &lt;td>高（不能丟事件）&lt;/td>
 &lt;td>高（多 SDK 同時 flush）&lt;/td>
 &lt;td>60%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query&lt;/td>
 &lt;td>中（dashboard 查詢）&lt;/td>
 &lt;td>低（dashboard 定期刷新）&lt;/td>
 &lt;td>25%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rule engine&lt;/td>
 &lt;td>低（觸發可延遲）&lt;/td>
 &lt;td>低（規則命中是少數事件）&lt;/td>
 &lt;td>15%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>分配比例不需要精確 — 重點是每個隔艙有獨立的上限，而非共享一個無差別的總上限。&lt;/p>
&lt;h2 id="監控系統的應用">監控系統的應用&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">Collector&lt;/a> 同時承載 ingestion（接收事件）、query（dashboard 查詢）和 rule engine（規則評估）三種工作。不隔離時，一個複雜的 dashboard 查詢（full table scan）可能佔住所有資料庫連線，讓 ingestion 的寫入也排隊等待。&lt;/p>
&lt;p>Bulkhead 設計讓 ingestion 和 query 各自的過載互不影響：&lt;/p></description><content:encoded><![CDATA[<p>Bulkhead 的概念來自船舶的隔艙設計 — 船體分成多個獨立的水密隔艙，一個隔艙進水不會讓整艘船沉沒。服務設計中，bulkhead 把不同的工作負載隔離到各自的資源池，一個工作負載的過載或故障不會消耗其他工作負載的資源。</p>
<h2 id="隔離什麼">隔離什麼</h2>
<p>服務中的共享資源是 bulkhead 的隔離對象：</p>
<table>
  <thead>
      <tr>
          <th>共享資源</th>
          <th>不隔離時的風險</th>
          <th>隔離方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Goroutine/Thread</td>
          <td>一個慢查詢佔住所有 goroutine，整個服務不回應</td>
          <td>每類工作分配獨立的 goroutine pool</td>
      </tr>
      <tr>
          <td>資料庫連線</td>
          <td>一個大查詢佔住 connection pool，其他查詢排隊</td>
          <td>不同工作類型用不同的連線池</td>
      </tr>
      <tr>
          <td>記憶體</td>
          <td>一個功能的 buffer 無限增長，OOM 殺掉整個 process</td>
          <td>每個功能的 buffer 有獨立上限</td>
      </tr>
      <tr>
          <td>CPU</td>
          <td>一個計算密集任務佔滿 CPU，其他請求延遲</td>
          <td>cgroup 或 GOMAXPROCS 限制</td>
      </tr>
  </tbody>
</table>
<h2 id="實作模式">實作模式</h2>
<h3 id="獨立-goroutine-pool">獨立 Goroutine Pool</h3>
<p>Go 中用有限容量的 channel 模擬 goroutine pool：</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="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nx">ingestPool</span> <span class="p">=</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">100</span><span class="p">)</span>  <span class="c1">// ingestion 最多 100 goroutine</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nx">queryPool</span>  <span class="p">=</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">20</span><span class="p">)</span>   <span class="c1">// query 最多 20 goroutine</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nx">rulePool</span>   <span class="p">=</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">10</span><span class="p">)</span>   <span class="c1">// rule engine 最多 10 goroutine</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></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kd">func</span> <span class="nf">handleIngest</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">select</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">case</span> <span class="nx">ingestPool</span> <span class="o">&lt;-</span> <span class="kd">struct</span><span class="p">{}{}:</span>
</span></span><span class="line"><span class="ln">10</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">ingestPool</span> <span class="p">}()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="nf">processIngest</span><span class="p">(</span><span class="nx">r</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</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;ingestion overloaded&#34;</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusServiceUnavailable</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>Ingestion 的 100 個 goroutine 全部被佔用時，新的 ingestion 請求被拒絕（503），但 query 和 rule engine 的 goroutine 不受影響。</p>
<h3 id="獨立-connection-pool">獨立 Connection Pool</h3>
<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="c1">// driver name 依實際使用的 driver 而定（modernc.org/sqlite 用 &#34;sqlite&#34;、mattn/go-sqlite3 用 &#34;sqlite3&#34;）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">ingestDB</span> <span class="o">:=</span> <span class="nx">sql</span><span class="p">.</span><span class="nf">Open</span><span class="p">(</span><span class="s">&#34;sqlite&#34;</span><span class="p">,</span> <span class="s">&#34;events.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">ingestDB</span><span class="p">.</span><span class="nf">SetMaxOpenConns</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1">// ingestion 專用</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">queryDB</span> <span class="o">:=</span> <span class="nx">sql</span><span class="p">.</span><span class="nf">Open</span><span class="p">(</span><span class="s">&#34;sqlite&#34;</span><span class="p">,</span> <span class="s">&#34;events.db&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">queryDB</span><span class="p">.</span><span class="nf">SetMaxOpenConns</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>   <span class="c1">// query 專用</span></span></span></code></pre></div><p>SQLite 的特殊情況：SQLite 是檔案級鎖定，多個連線池打開同一個檔案時仍共享 write lock。連線池隔離在 SQLite 上主要隔離的是 Go 層的 goroutine 等待，不是 DB 層的鎖定。PostgreSQL 的連線池隔離則是真正的資源隔離。</p>
<h2 id="容量分配">容量分配</h2>
<p>Bulkhead 的每個隔艙分配多少資源是設計決策。分配依據是「這個工作負載的優先順序和預期併發量」：</p>
<table>
  <thead>
      <tr>
          <th>工作負載</th>
          <th>優先順序</th>
          <th>預期併發</th>
          <th>分配</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ingestion</td>
          <td>高（不能丟事件）</td>
          <td>高（多 SDK 同時 flush）</td>
          <td>60%</td>
      </tr>
      <tr>
          <td>Query</td>
          <td>中（dashboard 查詢）</td>
          <td>低（dashboard 定期刷新）</td>
          <td>25%</td>
      </tr>
      <tr>
          <td>Rule engine</td>
          <td>低（觸發可延遲）</td>
          <td>低（規則命中是少數事件）</td>
          <td>15%</td>
      </tr>
  </tbody>
</table>
<p>分配比例不需要精確 — 重點是每個隔艙有獨立的上限，而非共享一個無差別的總上限。</p>
<h2 id="監控系統的應用">監控系統的應用</h2>
<p><a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">Collector</a> 同時承載 ingestion（接收事件）、query（dashboard 查詢）和 rule engine（規則評估）三種工作。不隔離時，一個複雜的 dashboard 查詢（full table scan）可能佔住所有資料庫連線，讓 ingestion 的寫入也排隊等待。</p>
<p>Bulkhead 設計讓 ingestion 和 query 各自的過載互不影響：</p>
<ul>
<li>Ingestion 的 goroutine pool 滿了 → SDK 收到 429 → 離線 buffer 接手</li>
<li>Query 的 goroutine pool 滿了 → dashboard 暫時顯示 loading → 不影響 ingestion</li>
<li>Rule engine 的 goroutine pool 滿了 → 規則評估延遲 → 不影響事件接收和查詢</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>背壓的流量控制 → <a href="/blog/devops/03-traffic-management/backpressure/" data-link-title="背壓機制" data-link-desc="下游處理慢時上游怎麼減速 — 有限 buffer &#43; 回壓訊號的設計、和 rate limit 的區別">背壓機制</a></li>
<li>依賴失敗的快速失敗 → <a href="/blog/devops/03-traffic-management/circuit-breaker/" data-link-title="熔斷器" data-link-desc="依賴服務失敗時怎麼快速失敗而非拖慢自己 — 三狀態模型（closed → open → half-open）和熔斷判斷條件">熔斷器</a></li>
<li>突發流量時的綜合策略 → <a href="/blog/devops/07-burst-traffic/" data-link-title="模組七：突發流量應對" data-link-desc="行銷活動或新聞曝光帶來 10x-100x 流量時怎麼撐 — 突發分類、降級策略、queue 緩衝、規模分級應對">模組七 突發流量</a></li>
</ul>
]]></content:encoded></item><item><title>規模分級應對表</title><link>https://tarrragon.github.io/blog/devops/07-burst-traffic/scale-tier-response/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/07-burst-traffic/scale-tier-response/</guid><description>&lt;p>突發流量的應對方案隨服務規模分成四級。每一級在前一級的基礎上增加元件，複雜度和成本同步上升。選擇哪一級取決於「預期的峰值流量」和「可接受的降級程度」。&lt;/p>
&lt;h2 id="四級分級">四級分級&lt;/h2>
&lt;h3 id="tier-1自用級-100-eventssec">Tier 1：自用級（&amp;lt; 100 events/sec）&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">SDK ──→ Collector (單 binary + SQLite)&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>架構&lt;/td>
 &lt;td>單 Go binary、SQLite embedded&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量控制&lt;/td>
 &lt;td>背壓（channel buffer 10000 + 429）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>突發應對&lt;/td>
 &lt;td>SDK 離線 buffer 吸收短暫 burst&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>降級&lt;/td>
 &lt;td>無（流量不會到需要降級的程度）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本&lt;/td>
 &lt;td>零（自有主機、零外部依賴）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適用&lt;/td>
 &lt;td>自用工具、開發期測試、小型團隊&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Tier 1 的假設是峰值流量不超過 SQLite WAL mode 的寫入能力（每秒數千筆）。自用場景下這個假設幾乎永遠成立。&lt;/p>
&lt;h3 id="tier-2中型100-10000-eventssec">Tier 2：中型（100-10000 events/sec）&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"> ┌─ Collector A ──→ PostgreSQL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">SDK ──→ LB ─┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> └─ Collector B ──→ PostgreSQL&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>架構&lt;/td>
 &lt;td>多 collector + load balancer + PostgreSQL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量控制&lt;/td>
 &lt;td>背壓 + per-SDK rate limit&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>突發應對&lt;/td>
 &lt;td>LB 分散流量 + collector 水平擴展&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>降級&lt;/td>
 &lt;td>動態取樣（超載時 SDK 降到 10%）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本&lt;/td>
 &lt;td>PostgreSQL + LB 的維護（可用 managed service 降低維護成本）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適用&lt;/td>
 &lt;td>使用者數百到數千、有付費能力&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Tier 1 → Tier 2 的觸發：SQLite 的 &lt;code>database is locked&lt;/code> 頻繁出現，或 dashboard 的聚合查詢需要 PostgreSQL 的能力。&lt;/p>
&lt;h3 id="tier-3大型10000-100000-eventssec">Tier 3：大型（10000-100000 events/sec）&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"> ┌─ Collector A ─┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">SDK ──→ LB ─┤ ├─→ Queue ──→ Worker 群 ──→ PostgreSQL
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> └─ Collector B ─┘&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>架構&lt;/td>
 &lt;td>Collector 群 + queue（NATS / Kafka）+ worker 群 + PostgreSQL&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量控制&lt;/td>
 &lt;td>背壓 + rate limit + bulkhead&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>突發應對&lt;/td>
 &lt;td>Queue 做時間緩衝（積壓 → 追趕）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>降級&lt;/td>
 &lt;td>動態取樣 + 事件優先級 + 功能降級&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本&lt;/td>
 &lt;td>Queue + worker 的基礎設施（顯著上升）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適用&lt;/td>
 &lt;td>中大型 SaaS、使用者數萬&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Tier 2 → Tier 3 的觸發：直接寫 PostgreSQL 的背壓頻繁觸發（即使有多個 collector 寫入）。&lt;/p>
&lt;h3 id="tier-4商業網站級-100000-eventssec">Tier 4：商業網站級（&amp;gt; 100000 events/sec）&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">SDK ──→ CDN/Edge ──→ LB ──→ Collector 群 ──→ Kafka ──→ Worker 群 ──→ 分層 DB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ├─ 即時查詢 DB（ClickHouse / TimescaleDB）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> └─ 歸檔 DB（S3 + Athena）&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>架構&lt;/td>
 &lt;td>CDN edge 收集 + Kafka + 分層存儲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量控制&lt;/td>
 &lt;td>CDN rate limit + 全鏈路背壓&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>突發應對&lt;/td>
 &lt;td>Kafka partition 水平擴展 + auto-scaling worker&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>降級&lt;/td>
 &lt;td>全套（動態取樣 + 優先級 + 聚合前移 + 功能降級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>成本&lt;/td>
 &lt;td>基礎設施團隊級別的投入&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適用&lt;/td>
 &lt;td>大型 SaaS、電商、社群平台&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Tier 3 → Tier 4 的觸發：Kafka 單 cluster 的吞吐不夠、或查詢需要跨日誌級的時間序列分析。&lt;/p></description><content:encoded><![CDATA[<p>突發流量的應對方案隨服務規模分成四級。每一級在前一級的基礎上增加元件，複雜度和成本同步上升。選擇哪一級取決於「預期的峰值流量」和「可接受的降級程度」。</p>
<h2 id="四級分級">四級分級</h2>
<h3 id="tier-1自用級-100-eventssec">Tier 1：自用級（&lt; 100 events/sec）</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">SDK ──→ Collector (單 binary + SQLite)</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>維度</th>
          <th>設定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>架構</td>
          <td>單 Go binary、SQLite embedded</td>
      </tr>
      <tr>
          <td>流量控制</td>
          <td>背壓（channel buffer 10000 + 429）</td>
      </tr>
      <tr>
          <td>突發應對</td>
          <td>SDK 離線 buffer 吸收短暫 burst</td>
      </tr>
      <tr>
          <td>降級</td>
          <td>無（流量不會到需要降級的程度）</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>零（自有主機、零外部依賴）</td>
      </tr>
      <tr>
          <td>適用</td>
          <td>自用工具、開發期測試、小型團隊</td>
      </tr>
  </tbody>
</table>
<p>Tier 1 的假設是峰值流量不超過 SQLite WAL mode 的寫入能力（每秒數千筆）。自用場景下這個假設幾乎永遠成立。</p>
<h3 id="tier-2中型100-10000-eventssec">Tier 2：中型（100-10000 events/sec）</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">         ┌─ Collector A ──→ PostgreSQL
</span></span><span class="line"><span class="ln">2</span><span class="cl">SDK ──→ LB ─┤
</span></span><span class="line"><span class="ln">3</span><span class="cl">         └─ Collector B ──→ PostgreSQL</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>維度</th>
          <th>設定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>架構</td>
          <td>多 collector + load balancer + PostgreSQL</td>
      </tr>
      <tr>
          <td>流量控制</td>
          <td>背壓 + per-SDK rate limit</td>
      </tr>
      <tr>
          <td>突發應對</td>
          <td>LB 分散流量 + collector 水平擴展</td>
      </tr>
      <tr>
          <td>降級</td>
          <td>動態取樣（超載時 SDK 降到 10%）</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>PostgreSQL + LB 的維護（可用 managed service 降低維護成本）</td>
      </tr>
      <tr>
          <td>適用</td>
          <td>使用者數百到數千、有付費能力</td>
      </tr>
  </tbody>
</table>
<p>Tier 1 → Tier 2 的觸發：SQLite 的 <code>database is locked</code> 頻繁出現，或 dashboard 的聚合查詢需要 PostgreSQL 的能力。</p>
<h3 id="tier-3大型10000-100000-eventssec">Tier 3：大型（10000-100000 events/sec）</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">         ┌─ Collector A ─┐
</span></span><span class="line"><span class="ln">2</span><span class="cl">SDK ──→ LB ─┤               ├─→ Queue ──→ Worker 群 ──→ PostgreSQL
</span></span><span class="line"><span class="ln">3</span><span class="cl">         └─ Collector B ─┘</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>維度</th>
          <th>設定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>架構</td>
          <td>Collector 群 + queue（NATS / Kafka）+ worker 群 + PostgreSQL</td>
      </tr>
      <tr>
          <td>流量控制</td>
          <td>背壓 + rate limit + bulkhead</td>
      </tr>
      <tr>
          <td>突發應對</td>
          <td>Queue 做時間緩衝（積壓 → 追趕）</td>
      </tr>
      <tr>
          <td>降級</td>
          <td>動態取樣 + 事件優先級 + 功能降級</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>Queue + worker 的基礎設施（顯著上升）</td>
      </tr>
      <tr>
          <td>適用</td>
          <td>中大型 SaaS、使用者數萬</td>
      </tr>
  </tbody>
</table>
<p>Tier 2 → Tier 3 的觸發：直接寫 PostgreSQL 的背壓頻繁觸發（即使有多個 collector 寫入）。</p>
<h3 id="tier-4商業網站級-100000-eventssec">Tier 4：商業網站級（&gt; 100000 events/sec）</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">SDK ──→ CDN/Edge ──→ LB ──→ Collector 群 ──→ Kafka ──→ Worker 群 ──→ 分層 DB
</span></span><span class="line"><span class="ln">2</span><span class="cl">                                                                      ├─ 即時查詢 DB（ClickHouse / TimescaleDB）
</span></span><span class="line"><span class="ln">3</span><span class="cl">                                                                      └─ 歸檔 DB（S3 + Athena）</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>維度</th>
          <th>設定</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>架構</td>
          <td>CDN edge 收集 + Kafka + 分層存儲</td>
      </tr>
      <tr>
          <td>流量控制</td>
          <td>CDN rate limit + 全鏈路背壓</td>
      </tr>
      <tr>
          <td>突發應對</td>
          <td>Kafka partition 水平擴展 + auto-scaling worker</td>
      </tr>
      <tr>
          <td>降級</td>
          <td>全套（動態取樣 + 優先級 + 聚合前移 + 功能降級）</td>
      </tr>
      <tr>
          <td>成本</td>
          <td>基礎設施團隊級別的投入</td>
      </tr>
      <tr>
          <td>適用</td>
          <td>大型 SaaS、電商、社群平台</td>
      </tr>
  </tbody>
</table>
<p>Tier 3 → Tier 4 的觸發：Kafka 單 cluster 的吞吐不夠、或查詢需要跨日誌級的時間序列分析。</p>
<p>多數自架開源工具不需要超過 Tier 2。Tier 3 和 Tier 4 是商業 SaaS 的領域。</p>
<h2 id="規模遷移路徑">規模遷移路徑</h2>
<table>
  <thead>
      <tr>
          <th>遷移</th>
          <th>改什麼</th>
          <th>停機</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Tier 1 → 2</td>
          <td>Storage backend 切 PostgreSQL + 加 LB + 加 collector</td>
          <td>config change + 資料遷移（分鐘級停機）</td>
      </tr>
      <tr>
          <td>Tier 2 → 3</td>
          <td>加 queue + 改 collector 為 ingestion-only + 加 worker</td>
          <td>架構重構（需要開發時間）</td>
      </tr>
      <tr>
          <td>Tier 3 → 4</td>
          <td>加 CDN edge + 分層 DB + auto-scaling</td>
          <td>基礎設施工程（需要專職團隊）</td>
      </tr>
  </tbody>
</table>
<p>每一級的遷移成本遞增。Tier 1 → 2 是 config change 級、Tier 2 → 3 是架構重構級、Tier 3 → 4 是團隊級。選擇起始 tier 時選最低的足夠 tier — 過早引入高 tier 的複雜度是浪費。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>流量管控的四種機制 → <a href="/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">模組三 流量管控</a></li>
<li>容量預備和壓力測試 → <a href="/blog/devops/05-capacity-planning/" data-link-title="模組五：容量規劃與壓力測試" data-link-desc="要準備多少資源才夠 — 壓力測試方法、峰值估算、成本模型、規模拐點的判斷">模組五 容量規劃</a></li>
<li>Collector 的可插拔 storage 架構 → <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">monitoring 模組四 規模演進</a></li>
<li>Queue 的選型 → <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">backend 非同步佇列</a></li>
</ul>
]]></content:encoded></item><item><title>模組四：服務探活與自動恢復</title><link>https://tarrragon.github.io/blog/devops/04-service-health/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/04-service-health/</guid><description>&lt;p>回答「服務掛了怎麼知道、知道了怎麼自動恢復」。探活是所有自動恢復機制的前提。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Health check endpoint 設計（什麼算健康、什麼算不健康、check 的深度）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Liveness vs Readiness（活著 vs 準備好接流量 — Kubernetes 的兩種 probe）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> systemd watchdog + 自動重啟（WatchdogSec + Restart=on-failure）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Process supervisor 的選型（systemd / supervisord / Docker restart policy）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Graceful shutdown（收到 SIGTERM 後的清理流程）&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/dashboard-devops/" data-link-title="DevOps Dashboard 設計" data-link-desc="Collector 和 SDK 是否健康 — 日常監控的服務狀態卡、吞吐量曲線、儲存用量，以及告警觸發後的排障視圖">monitoring 模組四 Dashboard DevOps&lt;/a>：DevOps dashboard 的服務狀態卡依賴 health check&lt;/li>
&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 與服務生命週期合約">backend 部署平台&lt;/a>：部署平台的 health check 整合&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「服務掛了怎麼知道、知道了怎麼自動恢復」。探活是所有自動恢復機制的前提。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input disabled="" type="checkbox"> Health check endpoint 設計（什麼算健康、什麼算不健康、check 的深度）</li>
<li><input disabled="" type="checkbox"> Liveness vs Readiness（活著 vs 準備好接流量 — Kubernetes 的兩種 probe）</li>
<li><input disabled="" type="checkbox"> systemd watchdog + 自動重啟（WatchdogSec + Restart=on-failure）</li>
<li><input disabled="" type="checkbox"> Process supervisor 的選型（systemd / supervisord / Docker restart policy）</li>
<li><input disabled="" type="checkbox"> Graceful shutdown（收到 SIGTERM 後的清理流程）</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/monitoring/04-collector/dashboard-devops/" data-link-title="DevOps Dashboard 設計" data-link-desc="Collector 和 SDK 是否健康 — 日常監控的服務狀態卡、吞吐量曲線、儲存用量，以及告警觸發後的排障視圖">monitoring 模組四 Dashboard DevOps</a>：DevOps dashboard 的服務狀態卡依賴 health check</li>
<li>→ <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">backend 部署平台</a>：部署平台的 health check 整合</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>模組六：高可用與災難復原</title><link>https://tarrragon.github.io/blog/devops/06-high-availability/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/06-high-availability/</guid><description>&lt;p>回答「一個節點掛了服務怎麼不中斷」。高可用的核心是冗餘 — 每個單點故障都有替代路徑。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 單點故障盤點（服務實例 / DB / LB / DNS — 哪些掛了整個系統就掛）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 冗餘設計模式（active-passive / active-active / multi-region）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Failover 機制（自動 vs 手動、failover 時間、資料一致性）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Disaster recovery 策略（RPO / RTO 目標、備份恢復演練）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 高可用的成本（冗餘 = 至少 2x 資源成本 — 值不值得）&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend 可靠性&lt;/a>：Backend 的可靠性設計&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/devops/04-service-health/" data-link-title="模組四：服務探活與自動恢復" data-link-desc="服務掛了怎麼自動發現和恢復 — health check 設計、liveness vs readiness、systemd watchdog、process supervisor">devops 模組四 服務探活&lt;/a>：探活是 failover 的觸發條件&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">Infra 核心服務上 IaC — Stateful 資源保護&lt;/a>：multi-AZ 是 infra 層的可用區冗餘能力，本模組的 HA 策略（健康檢查、自動恢復、failover 機制）建立在這個能力之上&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">Infra 網路地基&lt;/a>：跨可用區的 subnet 與 NAT 冗餘設計是 HA 的網路前提&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「一個節點掛了服務怎麼不中斷」。高可用的核心是冗餘 — 每個單點故障都有替代路徑。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input disabled="" type="checkbox"> 單點故障盤點（服務實例 / DB / LB / DNS — 哪些掛了整個系統就掛）</li>
<li><input disabled="" type="checkbox"> 冗餘設計模式（active-passive / active-active / multi-region）</li>
<li><input disabled="" type="checkbox"> Failover 機制（自動 vs 手動、failover 時間、資料一致性）</li>
<li><input disabled="" type="checkbox"> Disaster recovery 策略（RPO / RTO 目標、備份恢復演練）</li>
<li><input disabled="" type="checkbox"> 高可用的成本（冗餘 = 至少 2x 資源成本 — 值不值得）</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">backend 可靠性</a>：Backend 的可靠性設計</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>：探活是 failover 的觸發條件</li>
<li>→ <a href="/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">Infra 核心服務上 IaC — Stateful 資源保護</a>：multi-AZ 是 infra 層的可用區冗餘能力，本模組的 HA 策略（健康檢查、自動恢復、failover 機制）建立在這個能力之上</li>
<li>→ <a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">Infra 網路地基</a>：跨可用區的 subnet 與 NAT 冗餘設計是 HA 的網路前提</li>
</ul>
]]></content:encoded></item><item><title>模組七：突發流量應對</title><link>https://tarrragon.github.io/blog/devops/07-burst-traffic/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/07-burst-traffic/</guid><description>&lt;p>回答「流量突然暴增時怎麼不掛」。突發流量和穩定高流量的處理策略不同 — 突發有時間限制，撐過去就恢復正常。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 突發流量的分類（可預期 vs 不可預期、持續時間和倍率）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 降級策略（動態取樣、事件優先級、功能降級、聚合前移）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Queue 緩衝（Kafka / NATS / Redis Streams 做 burst buffer）&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> 規模分級應對表（自用 → 中型 → 大型 → 商業網站）&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>← &lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">devops 模組三 流量管控&lt;/a>：背壓和 rate limit 是突發應對的基礎元件&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector&lt;/a>：Collector 的 ingestion scaling 是本模組的應用場景&lt;/li>
&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 的後端實務">backend 非同步佇列&lt;/a>：Queue 的選型和操作實務&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/devops/05-capacity-planning/" data-link-title="模組五：容量規劃與壓力測試" data-link-desc="要準備多少資源才夠 — 壓力測試方法、峰值估算、成本模型、規模拐點的判斷">devops 模組五 容量規劃&lt;/a>：預期突發的容量預備&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/04-collector/data-integrity/" data-link-title="端到端資料完整性" data-link-desc="從 SDK 到 storage 的資料損失地圖 — 每個環節的損失類型、控制策略、完整性指標、被自己 SDK DDoS 的防護">端到端資料完整性&lt;/a>：被自己 SDK DDoS 的三種場景&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「流量突然暴增時怎麼不掛」。突發流量和穩定高流量的處理策略不同 — 突發有時間限制，撐過去就恢復正常。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 突發流量的分類（可預期 vs 不可預期、持續時間和倍率）</li>
<li><input checked="" disabled="" type="checkbox"> 降級策略（動態取樣、事件優先級、功能降級、聚合前移）</li>
<li><input checked="" disabled="" type="checkbox"> Queue 緩衝（Kafka / NATS / Redis Streams 做 burst buffer）</li>
<li><input checked="" disabled="" type="checkbox"> 規模分級應對表（自用 → 中型 → 大型 → 商業網站）</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>← <a href="/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">devops 模組三 流量管控</a>：背壓和 rate limit 是突發應對的基礎元件</li>
<li>→ <a href="/blog/monitoring/04-collector/" data-link-title="模組四：Collector 設計" data-link-desc="收 → 驗 → 存 → 查 → 觸發的完整鏈路 — Go 單一 binary、可插拔 Storage Backend、rule engine">monitoring 模組四 Collector</a>：Collector 的 ingestion scaling 是本模組的應用場景</li>
<li>→ <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">backend 非同步佇列</a>：Queue 的選型和操作實務</li>
<li>→ <a href="/blog/devops/05-capacity-planning/" data-link-title="模組五：容量規劃與壓力測試" data-link-desc="要準備多少資源才夠 — 壓力測試方法、峰值估算、成本模型、規模拐點的判斷">devops 模組五 容量規劃</a>：預期突發的容量預備</li>
<li>→ <a href="/blog/monitoring/04-collector/data-integrity/" data-link-title="端到端資料完整性" data-link-desc="從 SDK 到 storage 的資料損失地圖 — 每個環節的損失類型、控制策略、完整性指標、被自己 SDK DDoS 的防護">端到端資料完整性</a>：被自己 SDK DDoS 的三種場景</li>
</ul>
]]></content:encoded></item><item><title>DevOps Dashboard 設計</title><link>https://tarrragon.github.io/blog/monitoring/04-collector/dashboard-devops/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/04-collector/dashboard-devops/</guid><description>&lt;p>DevOps dashboard 的消費者是維護 collector 的人 — 可能是開發者自己、可能是開源使用者的運維人員。這個 dashboard 不看被監控 app 的業務邏輯，只看 collector 這個基礎設施本身是否健康、各 SDK 實例是否正常回報。&lt;/p>
&lt;p>使用模式是混合型：平時靠告警被動通知，收到通知後到 dashboard 查看細節。日常監控視圖提供「一眼確認系統正常」的能力，告警觸發視圖提供「出事了去哪裡查」的排障路徑。&lt;/p>
&lt;h2 id="日常監控視圖">日常監控視圖&lt;/h2>
&lt;h3 id="服務狀態卡">服務狀態卡&lt;/h3>
&lt;p>一個狀態卡顯示 collector 的存活狀態和各 SDK 實例的最後心跳時間。狀態卡的設計是「綠色代表正常、紅色代表異常」的二元判斷 — 不需要使用者解讀數字。&lt;/p>
&lt;p>Collector 存活的判斷依據是 health endpoint 回應。各 SDK 實例的狀態依據是最後一次 &lt;code>sdk.heartbeat&lt;/code> 事件的時間 — 超過設定的逾時閾值（預設 10 分鐘）標為離線。&lt;/p>
&lt;p>需要的事件：&lt;code>collector.health.check&lt;/code>（collector 自身定期產生）、&lt;code>sdk.heartbeat&lt;/code>（各 SDK 定期送出）、&lt;code>sdk.init&lt;/code>（SDK 啟動時送出、標記上線）。&lt;/p>
&lt;h3 id="吞吐量曲線">吞吐量曲線&lt;/h3>
&lt;p>折線圖顯示過去 24 小時每分鐘收到的事件數量。多個 SDK 實例用不同顏色區分。吞吐量的正常範圍由歷史資料建立基線 — 突然下降代表某個 SDK 停止送資料，突然上升代表 error storm 或重複送出。&lt;/p>
&lt;p>需要的事件：&lt;code>collector.ingestion.count&lt;/code>（collector 每分鐘記錄收到的事件數，按 source.app 分群）。&lt;/p>
&lt;h3 id="儲存用量">儲存用量&lt;/h3>
&lt;p>磁碟使用率的趨勢圖 + 保留策略的執行狀態。開發者需要知道「磁碟什麼時候會滿」和「purge 有沒有正常跑」。&lt;/p>
&lt;p>需要的事件：&lt;code>collector.storage.disk_usage&lt;/code>（定期取樣、metric 類型）、&lt;code>collector.storage.purge.completed&lt;/code>（每次 purge 完成時記錄清了多少空間）。&lt;/p>
&lt;h3 id="sdk-連線列表">SDK 連線列表&lt;/h3>
&lt;p>表格列出所有已知的 SDK 實例，每行顯示：app 名稱、版本、平台、最後回報時間、最後一次 init 時間。表格按「最後回報時間」排序 — 最久沒回報的在最上面，方便發現異常。&lt;/p>
&lt;p>需要的事件：&lt;code>sdk.init&lt;/code>（帶 source 完整資訊）、&lt;code>sdk.heartbeat&lt;/code>（定期更新最後回報時間）。&lt;/p>
&lt;p>Heartbeat 的觸發機制是 flush timer 的副作用 — SDK 的 flush timer 觸發時，如果 buffer 為空且距上次 heartbeat 超過設定間隔（預設 5 分鐘），自動注入一筆 &lt;code>sdk.heartbeat&lt;/code> 事件後送出。不需要獨立的 heartbeat timer。App idle 時 heartbeat 仍會送出，dashboard 的 SDK 連線列表因此能偵測 SDK 是否仍存活。&lt;/p>
&lt;h2 id="告警觸發視圖">告警觸發視圖&lt;/h2>
&lt;p>告警由 rule engine 觸發，觸發後開發者進入 dashboard 查看細節。每種告警條件對應一個排障路徑。&lt;/p>
&lt;h3 id="health-check-失敗">Health check 失敗&lt;/h3>
&lt;p>Collector 的 health endpoint 連續 N 次回應失敗（由外部 uptime check 偵測、如 cron + curl）。&lt;/p>
&lt;p>進入 dashboard 後看：最後一次 &lt;code>collector.health.check&lt;/code> 的時間和結果、collector 的 stderr log（systemd journal）、process 是否存活。如果 collector 已經掛了，dashboard 本身也不可達 — 這時的排障路徑是 SSH 到主機查 systemd 狀態。&lt;/p>
&lt;h3 id="sdk-停止回報">SDK 停止回報&lt;/h3>
&lt;p>某個 SDK 實例超過逾時閾值沒有送 &lt;code>sdk.heartbeat&lt;/code>。可能原因：被監控 app 當掉、網路斷開、SDK 初始化失敗。&lt;/p>
&lt;p>進入 dashboard 後看：該 SDK 的最後事件（什麼類型、什麼時間）、最後 &lt;code>sdk.init&lt;/code> 的 source 資訊（版本、平台）、同時段其他 SDK 是否正常（區分「單一 SDK 問題」和「collector 端問題」）。&lt;/p>
&lt;h3 id="磁碟用量超過閾值">磁碟用量超過閾值&lt;/h3>
&lt;p>&lt;code>collector.storage.disk_usage&lt;/code> 超過 80%。&lt;/p>
&lt;p>進入 dashboard 後看：各 backend 的空間佔比（SQLite DB 大小 + 匯出檔大小）、最近一次 purge 的執行時間和清理量、保留策略的設定值。如果 purge 正常執行但空間仍不足，代表事件產生速度超過清理速度 — 需要調整保留策略或擴容磁碟。&lt;/p>
&lt;h3 id="事件吞吐量異常下降">事件吞吐量異常下降&lt;/h3>
&lt;p>每分鐘事件數從正常基線突然下降超過 50%。&lt;/p></description><content:encoded><![CDATA[<p>DevOps dashboard 的消費者是維護 collector 的人 — 可能是開發者自己、可能是開源使用者的運維人員。這個 dashboard 不看被監控 app 的業務邏輯，只看 collector 這個基礎設施本身是否健康、各 SDK 實例是否正常回報。</p>
<p>使用模式是混合型：平時靠告警被動通知，收到通知後到 dashboard 查看細節。日常監控視圖提供「一眼確認系統正常」的能力，告警觸發視圖提供「出事了去哪裡查」的排障路徑。</p>
<h2 id="日常監控視圖">日常監控視圖</h2>
<h3 id="服務狀態卡">服務狀態卡</h3>
<p>一個狀態卡顯示 collector 的存活狀態和各 SDK 實例的最後心跳時間。狀態卡的設計是「綠色代表正常、紅色代表異常」的二元判斷 — 不需要使用者解讀數字。</p>
<p>Collector 存活的判斷依據是 health endpoint 回應。各 SDK 實例的狀態依據是最後一次 <code>sdk.heartbeat</code> 事件的時間 — 超過設定的逾時閾值（預設 10 分鐘）標為離線。</p>
<p>需要的事件：<code>collector.health.check</code>（collector 自身定期產生）、<code>sdk.heartbeat</code>（各 SDK 定期送出）、<code>sdk.init</code>（SDK 啟動時送出、標記上線）。</p>
<h3 id="吞吐量曲線">吞吐量曲線</h3>
<p>折線圖顯示過去 24 小時每分鐘收到的事件數量。多個 SDK 實例用不同顏色區分。吞吐量的正常範圍由歷史資料建立基線 — 突然下降代表某個 SDK 停止送資料，突然上升代表 error storm 或重複送出。</p>
<p>需要的事件：<code>collector.ingestion.count</code>（collector 每分鐘記錄收到的事件數，按 source.app 分群）。</p>
<h3 id="儲存用量">儲存用量</h3>
<p>磁碟使用率的趨勢圖 + 保留策略的執行狀態。開發者需要知道「磁碟什麼時候會滿」和「purge 有沒有正常跑」。</p>
<p>需要的事件：<code>collector.storage.disk_usage</code>（定期取樣、metric 類型）、<code>collector.storage.purge.completed</code>（每次 purge 完成時記錄清了多少空間）。</p>
<h3 id="sdk-連線列表">SDK 連線列表</h3>
<p>表格列出所有已知的 SDK 實例，每行顯示：app 名稱、版本、平台、最後回報時間、最後一次 init 時間。表格按「最後回報時間」排序 — 最久沒回報的在最上面，方便發現異常。</p>
<p>需要的事件：<code>sdk.init</code>（帶 source 完整資訊）、<code>sdk.heartbeat</code>（定期更新最後回報時間）。</p>
<p>Heartbeat 的觸發機制是 flush timer 的副作用 — SDK 的 flush timer 觸發時，如果 buffer 為空且距上次 heartbeat 超過設定間隔（預設 5 分鐘），自動注入一筆 <code>sdk.heartbeat</code> 事件後送出。不需要獨立的 heartbeat timer。App idle 時 heartbeat 仍會送出，dashboard 的 SDK 連線列表因此能偵測 SDK 是否仍存活。</p>
<h2 id="告警觸發視圖">告警觸發視圖</h2>
<p>告警由 rule engine 觸發，觸發後開發者進入 dashboard 查看細節。每種告警條件對應一個排障路徑。</p>
<h3 id="health-check-失敗">Health check 失敗</h3>
<p>Collector 的 health endpoint 連續 N 次回應失敗（由外部 uptime check 偵測、如 cron + curl）。</p>
<p>進入 dashboard 後看：最後一次 <code>collector.health.check</code> 的時間和結果、collector 的 stderr log（systemd journal）、process 是否存活。如果 collector 已經掛了，dashboard 本身也不可達 — 這時的排障路徑是 SSH 到主機查 systemd 狀態。</p>
<h3 id="sdk-停止回報">SDK 停止回報</h3>
<p>某個 SDK 實例超過逾時閾值沒有送 <code>sdk.heartbeat</code>。可能原因：被監控 app 當掉、網路斷開、SDK 初始化失敗。</p>
<p>進入 dashboard 後看：該 SDK 的最後事件（什麼類型、什麼時間）、最後 <code>sdk.init</code> 的 source 資訊（版本、平台）、同時段其他 SDK 是否正常（區分「單一 SDK 問題」和「collector 端問題」）。</p>
<h3 id="磁碟用量超過閾值">磁碟用量超過閾值</h3>
<p><code>collector.storage.disk_usage</code> 超過 80%。</p>
<p>進入 dashboard 後看：各 backend 的空間佔比（SQLite DB 大小 + 匯出檔大小）、最近一次 purge 的執行時間和清理量、保留策略的設定值。如果 purge 正常執行但空間仍不足，代表事件產生速度超過清理速度 — 需要調整保留策略或擴容磁碟。</p>
<h3 id="事件吞吐量異常下降">事件吞吐量異常下降</h3>
<p>每分鐘事件數從正常基線突然下降超過 50%。</p>
<p>進入 dashboard 後看：吞吐量曲線標注「下降起始時間」、SDK 連線列表確認哪些 SDK 在該時間點後停止回報、collector 的 ingestion error log。</p>
<h2 id="需要的事件總表">需要的事件總表</h2>
<table>
  <thead>
      <tr>
          <th>事件名稱</th>
          <th>類型</th>
          <th>產生者</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>collector.health.check</td>
          <td>lifecycle</td>
          <td>Collector</td>
          <td>服務狀態卡</td>
      </tr>
      <tr>
          <td>collector.started</td>
          <td>lifecycle</td>
          <td>Collector</td>
          <td>部署追蹤</td>
      </tr>
      <tr>
          <td>collector.shutdown</td>
          <td>lifecycle</td>
          <td>Collector</td>
          <td>異常關閉偵測</td>
      </tr>
      <tr>
          <td>collector.ingestion.count</td>
          <td>metric</td>
          <td>Collector</td>
          <td>吞吐量曲線</td>
      </tr>
      <tr>
          <td>collector.storage.disk_usage</td>
          <td>metric</td>
          <td>Collector</td>
          <td>儲存用量圖</td>
      </tr>
      <tr>
          <td>collector.storage.purge.completed</td>
          <td>lifecycle</td>
          <td>Collector</td>
          <td>purge 執行記錄</td>
      </tr>
      <tr>
          <td>sdk.heartbeat</td>
          <td>lifecycle</td>
          <td>SDK</td>
          <td>連線列表、存活判斷</td>
      </tr>
      <tr>
          <td>sdk.init</td>
          <td>lifecycle</td>
          <td>SDK</td>
          <td>版本/平台資訊、上線記錄</td>
      </tr>
      <tr>
          <td>deployment.started</td>
          <td>lifecycle</td>
          <td>CI/CD hook</td>
          <td>部署追蹤</td>
      </tr>
      <tr>
          <td>deployment.completed</td>
          <td>lifecycle</td>
          <td>CI/CD hook</td>
          <td>部署追蹤</td>
      </tr>
      <tr>
          <td>rule.matched</td>
          <td>event</td>
          <td>Collector</td>
          <td>alert 歷史</td>
      </tr>
  </tbody>
</table>
<p>這些事件是 collector 自身的營運事件，和被監控 app 的事件走同一個 Storage interface 儲存。Collector 同時是事件的生產者和消費者 — <code>collector.ingestion.count</code> 由 collector 自己產生、自己儲存、自己在 dashboard 顯示。</p>
<p><code>deployment.started</code> / <code>deployment.completed</code> 這兩個 lifecycle event 在 server-side 部署流程中對應 <a href="/blog/backend/05-deployment-platform/deployment-rollout-drain-rollback/" data-link-title="5.8 Deployment Rollout with Drain and Rollback（實作示範）" data-link-desc="以 checkout service 示範部署切換如何交付 canary evidence、drain signal、release gate 與 incident decision log。">Backend 5.8 Deployment Rollout</a> 的 evidence package——rollout 的每一批切換需要可判讀的部署事件作為證據。自架 collector 場景的部署追蹤規模遠小於 production server-side rollout，但 event schema 設計（timestamp / version / environment / result）可以跟 server-side 的 evidence 欄位對齊，讓未來規模成長時 event 格式不用重新設計。</p>
<h2 id="自動恢復設計">自動恢復設計</h2>
<p>自用工具場景下「凌晨三點 collector 掛了」的處理策略是自動恢復，不需要人介入。</p>
<table>
  <thead>
      <tr>
          <th>機制</th>
          <th>做法</th>
          <th>恢復時間</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>systemd watchdog</td>
          <td><code>WatchdogSec=30s</code>，collector 定期寫 watchdog notify</td>
          <td>30 秒內重啟</td>
      </tr>
      <tr>
          <td>Restart policy</td>
          <td><code>Restart=on-failure</code>、<code>RestartSec=5s</code></td>
          <td>5 秒後自動重啟</td>
      </tr>
      <tr>
          <td>Health endpoint</td>
          <td><code>/health</code> 回應 200 + 最後寫入時間</td>
          <td>外部 check 偵測</td>
      </tr>
      <tr>
          <td>啟動自檢</td>
          <td>collector 啟動時檢查 storage 完整性、重建索引</td>
          <td>啟動時自動修復</td>
      </tr>
  </tbody>
</table>
<p>自動恢復後 collector 送出 <code>collector.started</code> 事件，dashboard 的服務狀態卡從紅轉綠。如果連續重啟（10 分鐘內重啟 3 次以上），systemd 的 <code>StartLimitBurst</code> 阻止無限重啟、改為發送告警通知人工介入。</p>
<h2 id="存取控制">存取控制</h2>
<p>Day-one 的 dashboard 預設無認證 — 同區網內的任何裝置都能打開 dashboard URL。這是同區網信任模型的設計選擇，和 collector 的 HTTP endpoint 無認證一致。</p>
<h3 id="風險告知">風險告知</h3>
<p>無認證的 dashboard 暴露以下資訊給同區網的所有裝置：</p>
<ul>
<li><strong>DevOps dashboard</strong>：SDK 版本、平台、IP、collector 的磁碟用量</li>
<li><strong>Developer dashboard</strong>：error stack trace（可能包含檔案路徑和程式碼片段）、session 回放（使用者操作序列）</li>
<li><strong>中台 dashboard</strong>：行為事件明細、funnel 轉換率</li>
</ul>
<p>家用 LAN 的場景下，家裡的其他裝置（IoT、家人的電腦）也能存取這些資訊。</p>
<h3 id="最小防護">最小防護</h3>
<p>Go 的 <code>net/http</code> middleware 可以用幾行程式碼加 basic auth：</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="nf">basicAuth</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">user</span><span class="p">,</span> <span class="nx">pass</span> <span class="kt">string</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"> 2</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"> 3</span><span class="cl">        <span class="nx">u</span><span class="p">,</span> <span class="nx">p</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="nf">BasicAuth</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">ok</span> <span class="o">||</span> <span class="nx">u</span> <span class="o">!=</span> <span class="nx">user</span> <span class="o">||</span> <span class="nx">p</span> <span class="o">!=</span> <span class="nx">pass</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 5</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;WWW-Authenticate&#34;</span><span class="p">,</span> <span class="s">`Basic realm=&#34;monitor&#34;`</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</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;Unauthorized&#34;</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nx">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">10</span><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>帳密在 collector 的配置檔設定。Day-one 可選（不設就不啟用），但配置檔中應有 commented-out 的範例讓使用者知道這個選項存在。</p>
<h3 id="tripwire">Tripwire</h3>
<p>Collector 暴露到公網或跨網路存取時，dashboard 的認證從可選變成必要。公網上的無認證 dashboard 等於公開了 error stack trace 和行為資料。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Developer dashboard 設計 → <a href="/blog/monitoring/04-collector/dashboard-developer/" data-link-title="Developer Dashboard 設計" data-link-desc="Bug 在哪、多嚴重、怎麼重現 — Error 列表和趨勢的日常監控、Session 回放和 Stack trace 的深入 debug">Developer Dashboard 設計</a></li>
<li>中台 dashboard 設計 → <a href="/blog/monitoring/04-collector/dashboard-business/" data-link-title="中台 Dashboard 設計" data-link-desc="使用者怎麼用、在哪流失、怎麼讓他們回來 — 營運和行銷的日常指標監控與深入分析視圖，全部需要 PostgreSQL 層">中台 Dashboard 設計</a></li>
<li>Rule engine 的告警設計 → <a href="/blog/monitoring/04-collector/rule-engine/" data-link-title="Rule engine 設計" data-link-desc="條件 → 動作 → 模板的三段式規則結構 — 讓 collector 從被動儲存變成主動回應">Rule engine 設計</a></li>
<li>Collector 自我監控的 bootstrapping 問題 → <a href="/blog/monitoring/04-collector/scaling-evolution/" data-link-title="規模演進" data-link-desc="可插拔 Storage Backend 架構 — SQLite 預設、PostgreSQL 觸發切換、時間序列 DB 長期演進">規模演進</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>
</ul>
]]></content:encoded></item><item><title>模組八：成本管理</title><link>https://tarrragon.github.io/blog/devops/08-cost-management/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/08-cost-management/</guid><description>&lt;p>回答「帳單怎麼不失控」。雲端的靈活性讓資源容易加、但也容易忘記關。&lt;/p>
&lt;h2 id="待寫章節">待寫章節&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> 計費模式理解（on-demand / reserved / spot / savings plan — 各自的承諾和折扣）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Right-sizing（實際用量 vs 配置規格的差距 — 過度配置是最大的浪費來源）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 成本監控和告警（每日帳單趨勢、異常支出告警、部門歸屬標記）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 開發環境的成本控制（自動關機、共用環境、spot instance 用於 CI）&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 自架 vs 雲端的成本交叉點&lt;/li>
&lt;/ul>
&lt;h2 id="跨分類引用">跨分類引用&lt;/h2>
&lt;ul>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/devops/05-capacity-planning/" data-link-title="模組五：容量規劃與壓力測試" data-link-desc="要準備多少資源才夠 — 壓力測試方法、峰值估算、成本模型、規模拐點的判斷">devops 模組五 容量規劃&lt;/a>：容量規劃的成本面&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">infra 模組八 治理好習慣&lt;/a>：成本歸因的 tagging 地基 — tag 在 IaC 裡強制長出來，這裡的部門歸屬與帳單拆分才有依據&lt;/li>
&lt;li>→ &lt;a href="https://tarrragon.github.io/blog/monitoring/06-commercial-comparison/" data-link-title="模組六：商業方案對照" data-link-desc="Sentry / Crashlytics / Datadog RUM / Mixpanel — 自架 vs 商業的功能和成本取捨">monitoring 模組六 商業方案&lt;/a>：監控 SaaS 的帳單也是成本管理的一部分&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>回答「帳單怎麼不失控」。雲端的靈活性讓資源容易加、但也容易忘記關。</p>
<h2 id="待寫章節">待寫章節</h2>
<ul>
<li><input disabled="" type="checkbox"> 計費模式理解（on-demand / reserved / spot / savings plan — 各自的承諾和折扣）</li>
<li><input disabled="" type="checkbox"> Right-sizing（實際用量 vs 配置規格的差距 — 過度配置是最大的浪費來源）</li>
<li><input disabled="" type="checkbox"> 成本監控和告警（每日帳單趨勢、異常支出告警、部門歸屬標記）</li>
<li><input disabled="" type="checkbox"> 開發環境的成本控制（自動關機、共用環境、spot instance 用於 CI）</li>
<li><input disabled="" type="checkbox"> 自架 vs 雲端的成本交叉點</li>
</ul>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/devops/05-capacity-planning/" data-link-title="模組五：容量規劃與壓力測試" data-link-desc="要準備多少資源才夠 — 壓力測試方法、峰值估算、成本模型、規模拐點的判斷">devops 模組五 容量規劃</a>：容量規劃的成本面</li>
<li>→ <a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">infra 模組八 治理好習慣</a>：成本歸因的 tagging 地基 — tag 在 IaC 裡強制長出來，這裡的部門歸屬與帳單拆分才有依據</li>
<li>→ <a href="/blog/monitoring/06-commercial-comparison/" data-link-title="模組六：商業方案對照" data-link-desc="Sentry / Crashlytics / Datadog RUM / Mixpanel — 自架 vs 商業的功能和成本取捨">monitoring 模組六 商業方案</a>：監控 SaaS 的帳單也是成本管理的一部分</li>
</ul>
]]></content:encoded></item><item><title>DevOps 實務指南</title><link>https://tarrragon.github.io/blog/devops/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/devops/</guid><description>&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>和 DevOps 的交集&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/" data-link-title="Backend 服務實務指南" data-link-desc="用跨語言教學路線整理資料庫、快取、訊息佇列、觀測、部署、可靠性、資安、事故與容量等後端服務能力">Backend&lt;/a>&lt;/td>
 &lt;td>服務內部的設計（資料庫、快取、佇列、可觀測性）&lt;/td>
 &lt;td>Backend 的部署和可靠性維度在這裡展開&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/infra/" data-link-title="Infra 基礎設施建置指南" data-link-desc="從零循序漸進把雲端基礎設施做起來 — IaC、身分憑證、網路地基、環境分離、核心服務、可觀測性、自動化 review 與治理習慣，含怎麼在組織內推動">Infra&lt;/a>&lt;/td>
 &lt;td>基礎設施地基（IaC、網路、身分、環境分離）&lt;/td>
 &lt;td>DevOps 管的服務跑在 Infra 鋪好的地基上&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">Monitoring&lt;/a>&lt;/td>
 &lt;td>客戶端監控體系（SDK、Collector、Dashboard）&lt;/td>
 &lt;td>Collector 的 ingestion scaling 是這裡的流量管控應用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ci/" data-link-title="CI/CD 教學" data-link-desc="整理 CI/CD 的驗證、建置、發布 gate 與不同部署場域的流程差異，讓每次變更都能被穩定驗證與交付">CI/CD&lt;/a>&lt;/td>
 &lt;td>驗證、建置、發布 gate&lt;/td>
 &lt;td>CI/CD 管線的產出（artifact）是這裡部署的輸入&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/testing/" data-link-title="開發測試實務指南" data-link-desc="整理測試策略分層、協議整合驗證、客戶端可觀測性、錯誤收集與自動化驗證 — 從「測試全過但實機全壞」的結構性盲區出發，建立可操作的品質驗證體系">Testing&lt;/a>&lt;/td>
 &lt;td>測試策略與 mock 邊界&lt;/td>
 &lt;td>服務 fixture 的 health check 和服務探活概念共通&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/ux-design/" data-link-title="UX 設計實務指南" data-link-desc="整理畫面狀態機、導航設計、Gate fallback、輸入機制與使用者行為驗證 — 從「使用者被困在畫面裡出不去」的結構性遺漏出發，建立系統性的 UX 設計方法">UX Design&lt;/a>&lt;/td>
 &lt;td>畫面設計與 gate fallback&lt;/td>
 &lt;td>Server 端限速（429）影響 client 端的重試 UX 和離線 UX&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/linux/dotfile/" data-link-title="Dotfile 工作環境配置指南" data-link-desc="個人開發環境的配置管理 — dotfile 結構設計、同步策略、shell 與終端機配置、平鋪式視窗管理、桌面客製化，從個人工具鏈延伸到團隊環境標準化">Dotfile&lt;/a>&lt;/td>
 &lt;td>個人工作環境配置管理&lt;/td>
 &lt;td>DevOps 工程師的日常工具鏈（多終端機、SSH、log tail）是 dotfile 高度客製的場景&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Backend 教「服務怎麼設計」，DevOps 教「設計好的服務怎麼在 production 活下來」。&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;a href="https://tarrragon.github.io/blog/devops/01-load-balancing/" data-link-title="模組一：負載平衡與反向代理" data-link-desc="流量進來怎麼分給多個服務實例 — nginx / HAProxy / DNS round-robin 的選型和健康檢查路由設計">模組一：負載平衡&lt;/a>&lt;/td>
 &lt;td>反向代理、負載分散、健康檢查路由&lt;/td>
 &lt;td>流量進來怎麼分給多個服務實例&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/02-horizontal-scaling/" data-link-title="模組二：水平擴展" data-link-desc="一個實例不夠時怎麼加第二個 — stateless 設計、shared storage、session 處理的工程約束">模組二：水平擴展&lt;/a>&lt;/td>
 &lt;td>Stateless 設計、shared storage、session 處理&lt;/td>
 &lt;td>一個實例不夠時怎麼加第二個&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">模組三：流量管控&lt;/a>&lt;/td>
 &lt;td>背壓、rate limit、熔斷、bulkhead&lt;/td>
 &lt;td>收到的流量超過處理能力時怎麼辦&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/04-service-health/" data-link-title="模組四：服務探活與自動恢復" data-link-desc="服務掛了怎麼自動發現和恢復 — health check 設計、liveness vs readiness、systemd watchdog、process supervisor">模組四：服務探活&lt;/a>&lt;/td>
 &lt;td>探活、liveness/readiness、自動重啟&lt;/td>
 &lt;td>服務掛了怎麼自動發現和恢復&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/05-capacity-planning/" data-link-title="模組五：容量規劃與壓力測試" data-link-desc="要準備多少資源才夠 — 壓力測試方法、峰值估算、成本模型、規模拐點的判斷">模組五：容量規劃&lt;/a>&lt;/td>
 &lt;td>壓力測試、峰值估算、成本模型&lt;/td>
 &lt;td>要準備多少資源才夠&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/06-high-availability/" data-link-title="模組六：高可用與災難復原" data-link-desc="一個節點掛了服務怎麼不中斷 — 冗餘設計、failover 機制、disaster recovery 策略">模組六：高可用&lt;/a>&lt;/td>
 &lt;td>冗餘、failover、disaster recovery&lt;/td>
 &lt;td>一個節點掛了服務怎麼不中斷&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/07-burst-traffic/" data-link-title="模組七：突發流量應對" data-link-desc="行銷活動或新聞曝光帶來 10x-100x 流量時怎麼撐 — 突發分類、降級策略、queue 緩衝、規模分級應對">模組七：突發流量&lt;/a>&lt;/td>
 &lt;td>突發流量應對、降級策略、queue 緩衝&lt;/td>
 &lt;td>行銷活動或新聞曝光帶來 10x 流量怎麼撐&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">模組八：成本管理&lt;/a>&lt;/td>
 &lt;td>雲端成本、reserved instance、spot instance&lt;/td>
 &lt;td>帳單怎麼不失控&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>第一次部署 production 的開發者&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></description><content:encoded><![CDATA[<p>服務寫完部署上線只是起點。上線後的日常是「流量進來了怎麼分散、服務掛了怎麼恢復、突然爆量怎麼應對、帳單怎麼不失控」。這些問題的解法在部署拓撲、流量路由、健康偵測和容量規劃的設計中。</p>
<h2 id="和其他系列的關係">和其他系列的關係</h2>
<table>
  <thead>
      <tr>
          <th>系列</th>
          <th>聚焦</th>
          <th>和 DevOps 的交集</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/backend/" data-link-title="Backend 服務實務指南" data-link-desc="用跨語言教學路線整理資料庫、快取、訊息佇列、觀測、部署、可靠性、資安、事故與容量等後端服務能力">Backend</a></td>
          <td>服務內部的設計（資料庫、快取、佇列、可觀測性）</td>
          <td>Backend 的部署和可靠性維度在這裡展開</td>
      </tr>
      <tr>
          <td><a href="/blog/infra/" data-link-title="Infra 基礎設施建置指南" data-link-desc="從零循序漸進把雲端基礎設施做起來 — IaC、身分憑證、網路地基、環境分離、核心服務、可觀測性、自動化 review 與治理習慣，含怎麼在組織內推動">Infra</a></td>
          <td>基礎設施地基（IaC、網路、身分、環境分離）</td>
          <td>DevOps 管的服務跑在 Infra 鋪好的地基上</td>
      </tr>
      <tr>
          <td><a href="/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">Monitoring</a></td>
          <td>客戶端監控體系（SDK、Collector、Dashboard）</td>
          <td>Collector 的 ingestion scaling 是這裡的流量管控應用</td>
      </tr>
      <tr>
          <td><a href="/blog/ci/" data-link-title="CI/CD 教學" data-link-desc="整理 CI/CD 的驗證、建置、發布 gate 與不同部署場域的流程差異，讓每次變更都能被穩定驗證與交付">CI/CD</a></td>
          <td>驗證、建置、發布 gate</td>
          <td>CI/CD 管線的產出（artifact）是這裡部署的輸入</td>
      </tr>
      <tr>
          <td><a href="/blog/testing/" data-link-title="開發測試實務指南" data-link-desc="整理測試策略分層、協議整合驗證、客戶端可觀測性、錯誤收集與自動化驗證 — 從「測試全過但實機全壞」的結構性盲區出發，建立可操作的品質驗證體系">Testing</a></td>
          <td>測試策略與 mock 邊界</td>
          <td>服務 fixture 的 health check 和服務探活概念共通</td>
      </tr>
      <tr>
          <td><a href="/blog/ux-design/" data-link-title="UX 設計實務指南" data-link-desc="整理畫面狀態機、導航設計、Gate fallback、輸入機制與使用者行為驗證 — 從「使用者被困在畫面裡出不去」的結構性遺漏出發，建立系統性的 UX 設計方法">UX Design</a></td>
          <td>畫面設計與 gate fallback</td>
          <td>Server 端限速（429）影響 client 端的重試 UX 和離線 UX</td>
      </tr>
      <tr>
          <td><a href="/blog/linux/dotfile/" data-link-title="Dotfile 工作環境配置指南" data-link-desc="個人開發環境的配置管理 — dotfile 結構設計、同步策略、shell 與終端機配置、平鋪式視窗管理、桌面客製化，從個人工具鏈延伸到團隊環境標準化">Dotfile</a></td>
          <td>個人工作環境配置管理</td>
          <td>DevOps 工程師的日常工具鏈（多終端機、SSH、log tail）是 dotfile 高度客製的場景</td>
      </tr>
  </tbody>
</table>
<p>Backend 教「服務怎麼設計」，DevOps 教「設計好的服務怎麼在 production 活下來」。</p>
<h2 id="教學模組">教學模組</h2>
<table>
  <thead>
      <tr>
          <th>模組</th>
          <th>主題</th>
          <th>回答什麼問題</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/devops/01-load-balancing/" data-link-title="模組一：負載平衡與反向代理" data-link-desc="流量進來怎麼分給多個服務實例 — nginx / HAProxy / DNS round-robin 的選型和健康檢查路由設計">模組一：負載平衡</a></td>
          <td>反向代理、負載分散、健康檢查路由</td>
          <td>流量進來怎麼分給多個服務實例</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/02-horizontal-scaling/" data-link-title="模組二：水平擴展" data-link-desc="一個實例不夠時怎麼加第二個 — stateless 設計、shared storage、session 處理的工程約束">模組二：水平擴展</a></td>
          <td>Stateless 設計、shared storage、session 處理</td>
          <td>一個實例不夠時怎麼加第二個</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/03-traffic-management/" data-link-title="模組三：流量管控" data-link-desc="收到的流量超過處理能力時怎麼辦 — 背壓、rate limit、熔斷、bulkhead 四種防護機制">模組三：流量管控</a></td>
          <td>背壓、rate limit、熔斷、bulkhead</td>
          <td>收到的流量超過處理能力時怎麼辦</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/04-service-health/" data-link-title="模組四：服務探活與自動恢復" data-link-desc="服務掛了怎麼自動發現和恢復 — health check 設計、liveness vs readiness、systemd watchdog、process supervisor">模組四：服務探活</a></td>
          <td>探活、liveness/readiness、自動重啟</td>
          <td>服務掛了怎麼自動發現和恢復</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/05-capacity-planning/" data-link-title="模組五：容量規劃與壓力測試" data-link-desc="要準備多少資源才夠 — 壓力測試方法、峰值估算、成本模型、規模拐點的判斷">模組五：容量規劃</a></td>
          <td>壓力測試、峰值估算、成本模型</td>
          <td>要準備多少資源才夠</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/06-high-availability/" data-link-title="模組六：高可用與災難復原" data-link-desc="一個節點掛了服務怎麼不中斷 — 冗餘設計、failover 機制、disaster recovery 策略">模組六：高可用</a></td>
          <td>冗餘、failover、disaster recovery</td>
          <td>一個節點掛了服務怎麼不中斷</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/07-burst-traffic/" data-link-title="模組七：突發流量應對" data-link-desc="行銷活動或新聞曝光帶來 10x-100x 流量時怎麼撐 — 突發分類、降級策略、queue 緩衝、規模分級應對">模組七：突發流量</a></td>
          <td>突發流量應對、降級策略、queue 緩衝</td>
          <td>行銷活動或新聞曝光帶來 10x 流量怎麼撐</td>
      </tr>
      <tr>
          <td><a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">模組八：成本管理</a></td>
          <td>雲端成本、reserved instance、spot instance</td>
          <td>帳單怎麼不失控</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路線">學習路線</h2>
<table>
  <thead>
      <tr>
          <th>路線</th>
          <th>適合讀者</th>
          <th>建議順序</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>單服務營運</td>
          <td>第一次部署 production 的開發者</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>
]]></content:encoded></item></channel></rss>