<?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>Bulkhead on Tarragon</title><link>https://tarrragon.github.io/blog/tags/bulkhead/</link><description>Recent content in Bulkhead 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/bulkhead/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>