<?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>模組三案例正文 on Tarragon</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/</link><description>Recent content in 模組三案例正文 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 07 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/index.xml" rel="self" type="application/rss+xml"/><item><title>3.C1 Meta：FOQS 從區域到全域佇列遷移</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/</guid><description>&lt;p>這個案例的核心責任是說明 queue 轉換不只換 broker，還包含路由與可用性模型重整。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>FOQS 從區域安裝轉為全域架構，目標是讓災害期間佇列資料仍可被存取，並控制遷移期間的延遲與可用性風險。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當 queue 成為跨區關鍵路徑，轉換焦點是 discoverability、routing freshness 與 tenant 遷移節奏。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先建立全域路由層，再分批搬遷租戶。&lt;/li>
&lt;li>針對 stale routing 做補貨延遲治理。&lt;/li>
&lt;li>用零停機遷移策略保留客戶端連續性。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.fb.com/2022/01/18/production-engineering/foqs-disaster-ready/">FOQS disaster-ready migration&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 queue 轉換不只換 broker，還包含路由與可用性模型重整。</p>
<h2 id="觀察">觀察</h2>
<p>FOQS 從區域安裝轉為全域架構，目標是讓災害期間佇列資料仍可被存取，並控制遷移期間的延遲與可用性風險。</p>
<h2 id="判讀">判讀</h2>
<p>當 queue 成為跨區關鍵路徑，轉換焦點是 discoverability、routing freshness 與 tenant 遷移節奏。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先建立全域路由層，再分批搬遷租戶。</li>
<li>針對 stale routing 做補貨延遲治理。</li>
<li>用零停機遷移策略保留客戶端連續性。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a> 與 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.fb.com/2022/01/18/production-engineering/foqs-disaster-ready/">FOQS disaster-ready migration</a></li>
</ul>
]]></content:encoded></item><item><title>3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/</guid><description>&lt;p>這個案例的核心責任是把 broker 遷移拆成平台責任、運維責任與資料責任三層。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>CloudHealth 由自管 Kafka 遷移到 Amazon MSK，過程涵蓋 topic、存取控制、觀測與遷移執行節奏。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>這類轉換的實際風險在 ACL、topic policy、client 相容性與 cutover 節奏，服務名稱本身反而是次要問題。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先建立新叢集治理基線（ACL、觀測、部署）。&lt;/li>
&lt;li>分批 topic 遷移並持續監測 lag/錯誤。&lt;/li>
&lt;li>把回退與流量切換條件寫成明確門檻。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/big-data/how-vmware-tanzu-cloudhealth-migrated-from-self-managed-kafka-to-amazon-msk/">VMware CloudHealth Kafka to MSK&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 broker 遷移拆成平台責任、運維責任與資料責任三層。</p>
<h2 id="觀察">觀察</h2>
<p>CloudHealth 由自管 Kafka 遷移到 Amazon MSK，過程涵蓋 topic、存取控制、觀測與遷移執行節奏。</p>
<h2 id="判讀">判讀</h2>
<p>這類轉換的實際風險在 ACL、topic policy、client 相容性與 cutover 節奏，服務名稱本身反而是次要問題。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先建立新叢集治理基線（ACL、觀測、部署）。</li>
<li>分批 topic 遷移並持續監測 lag/錯誤。</li>
<li>把回退與流量切換條件寫成明確門檻。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a> 與 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/big-data/how-vmware-tanzu-cloudhealth-migrated-from-self-managed-kafka-to-amazon-msk/">VMware CloudHealth Kafka to MSK</a></li>
</ul>
]]></content:encoded></item><item><title>3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/</guid><description>&lt;p>這個案例的核心責任是說明 queue 系統的轉換也包含 metadata 治理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>LinkedIn 以 TopicGC 清理未使用 topic，降低 Kafka metadata 壓力並改善 produce/consume 效能。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當 queue 規模擴大，僅靠容量擴充不夠，topic 生命週期與治理自動化會成為可靠性關鍵。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>定義 topic 活躍判準與回收條件。&lt;/li>
&lt;li>自動化清理流程並保留稽核紀錄。&lt;/li>
&lt;li>監控清理前後的性能與穩定性指標。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.linkedin.com/content/engineering/en-us/blog/2022/topicgc_how-linkedin-cleans-up-unused-metadata-for-its-kafka-clu">TopicGC at LinkedIn&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 queue 系統的轉換也包含 metadata 治理。</p>
<h2 id="觀察">觀察</h2>
<p>LinkedIn 以 TopicGC 清理未使用 topic，降低 Kafka metadata 壓力並改善 produce/consume 效能。</p>
<h2 id="判讀">判讀</h2>
<p>當 queue 規模擴大，僅靠容量擴充不夠，topic 生命週期與治理自動化會成為可靠性關鍵。</p>
<h2 id="策略">策略</h2>
<ol>
<li>定義 topic 活躍判準與回收條件。</li>
<li>自動化清理流程並保留稽核紀錄。</li>
<li>監控清理前後的性能與穩定性指標。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a> 與 <a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/content/engineering/en-us/blog/2022/topicgc_how-linkedin-cleans-up-unused-metadata-for-its-kafka-clu">TopicGC at LinkedIn</a></li>
</ul>
]]></content:encoded></item><item><title>3.C4 LinkedIn：Kafka 分層叢集治理</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-tiered-clusters/</guid><description>&lt;p>LinkedIn 的 Kafka 分層叢集案例呈現了 Kafka 在規模化之後，瓶頸從「broker 容量」轉移到「workload 互相干擾」。分層的核心判斷是按業務風險隔離，把叢集當成資源治理單位。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>LinkedIn 是 Kafka 的誕生地，內部 Kafka 叢集承載的工作負載涵蓋即時推薦、搜尋索引更新、analytics pipeline、audit log 跟 monitoring。早期所有 workload 共用少數幾個大叢集，隨流量成長，叢集內不同 workload 的資源競爭開始互相影響。&lt;/p>
&lt;p>LinkedIn 的 Kafka 規模是全球最大的之一 — 數千個 broker、每秒數百萬筆訊息、PB 級資料保留。在這個規模下，單一叢集的容量限制是 broker 數量跟 ZooKeeper 的 metadata 管理上限，但更早觸及的限制是 workload 之間的干擾。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="noisy-neighbor">Noisy neighbor&lt;/h3>
&lt;p>即時推薦系統需要低延遲的 consumer（P99 &amp;lt; 50ms），analytics pipeline 是大量 batch consumer（高吞吐但延遲容忍到秒級）。兩者共用同一組 broker 時，batch consumer 的大範圍 sequential read 佔滿 disk I/O，擠壓即時推薦的 random read latency。&lt;/p>
&lt;p>一個 analytics job 的重跑（backfill 歷史資料）可以讓推薦系統的 consumer lag 從毫秒跳到秒級。在共享叢集中，這種干擾難以預防 — 只能在事後發現、人工協調。&lt;/p>
&lt;h3 id="broker-故障的影響面">Broker 故障的影響面&lt;/h3>
&lt;p>單一叢集中 broker 故障會觸發 partition reassignment，reassignment 的資料搬移佔用 disk I/O 跟網路頻寬。在混合 workload 的叢集中，reassignment 同時影響所有 workload 的效能 — 包括跟故障 broker 無直接關係的 topic。&lt;/p>
&lt;p>叢集越大、topic 越多、reassignment 的影響面越廣。&lt;/p>
&lt;h3 id="容量規劃的模糊邊界">容量規劃的模糊邊界&lt;/h3>
&lt;p>共享叢集的容量規劃沒有清楚的 owner — analytics 團隊說「我們需要更多 retention」、推薦團隊說「我們需要更低 latency」、audit 團隊說「我們的資料不能丟」。三種需求各自合理，但共享叢集無法同時最佳化。&lt;/p>
&lt;h2 id="解法分層叢集">解法：分層叢集&lt;/h2>
&lt;p>LinkedIn 按業務風險跟效能需求把 workload 分配到不同叢集：&lt;/p>
&lt;p>&lt;strong>Tier 1 — 即時關鍵路徑&lt;/strong>：即時推薦、搜尋索引更新、使用者通知。Broker 配置偏向低延遲（SSD、高 IOPS）、replication factor 3、retention 短（保留足夠的 consumer catchup 時間）。&lt;/p>
&lt;p>&lt;strong>Tier 2 — 可靠性要求高但延遲容忍&lt;/strong>：audit log、合規事件、支付事件。配置偏向持久性（replication factor 3、min.insync.replicas 2、acks=all）、retention 長。&lt;/p>
&lt;p>&lt;strong>Tier 3 — 高吞吐分析&lt;/strong>：analytics pipeline、ETL、batch processing。配置偏向吞吐（大 batch size、長 linger.ms、HDD）、retention 最長、容忍偶發 consumer lag。&lt;/p>
&lt;h3 id="分層的判準">分層的判準&lt;/h3>
&lt;p>分層的判準是「這個 workload 故障時，業務影響有多大、多快」：&lt;/p>
&lt;ul>
&lt;li>即時影響使用者體驗 → Tier 1&lt;/li>
&lt;li>影響合規或財務但可容忍分鐘級延遲 → Tier 2&lt;/li>
&lt;li>影響分析準確性但可容忍小時級延遲 → Tier 3&lt;/li>
&lt;/ul>
&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>高（所有 workload 共用資源池）&lt;/td>
 &lt;td>低到中（每層有獨立的保留容量）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>隔離性&lt;/td>
 &lt;td>低（noisy neighbor 互相干擾）&lt;/td>
 &lt;td>高（故障跟效能退化限制在同層）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>運維複雜度&lt;/td>
 &lt;td>低（一組 broker 統一管理）&lt;/td>
 &lt;td>高（多組 broker、各自的監控跟維護）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量規劃清晰度&lt;/td>
 &lt;td>模糊（多種需求混合、難以歸因）&lt;/td>
 &lt;td>清楚（每層的需求跟 owner 明確）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>故障影響面&lt;/td>
 &lt;td>廣（reassignment 影響所有 topic）&lt;/td>
 &lt;td>有限（reassignment 只影響同層）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>分層的成本是資源利用率下降 — 每層都需要保留一定的 headroom 應對高峰，加總起來比共享叢集多。LinkedIn 的判斷是隔離性的價值大於利用率的損失 — 推薦系統一次 P99 退化的業務損失遠大於多幾台 broker 的成本。&lt;/p></description><content:encoded><![CDATA[<p>LinkedIn 的 Kafka 分層叢集案例呈現了 Kafka 在規模化之後，瓶頸從「broker 容量」轉移到「workload 互相干擾」。分層的核心判斷是按業務風險隔離，把叢集當成資源治理單位。</p>
<h2 id="業務背景">業務背景</h2>
<p>LinkedIn 是 Kafka 的誕生地，內部 Kafka 叢集承載的工作負載涵蓋即時推薦、搜尋索引更新、analytics pipeline、audit log 跟 monitoring。早期所有 workload 共用少數幾個大叢集，隨流量成長，叢集內不同 workload 的資源競爭開始互相影響。</p>
<p>LinkedIn 的 Kafka 規模是全球最大的之一 — 數千個 broker、每秒數百萬筆訊息、PB 級資料保留。在這個規模下，單一叢集的容量限制是 broker 數量跟 ZooKeeper 的 metadata 管理上限，但更早觸及的限制是 workload 之間的干擾。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="noisy-neighbor">Noisy neighbor</h3>
<p>即時推薦系統需要低延遲的 consumer（P99 &lt; 50ms），analytics pipeline 是大量 batch consumer（高吞吐但延遲容忍到秒級）。兩者共用同一組 broker 時，batch consumer 的大範圍 sequential read 佔滿 disk I/O，擠壓即時推薦的 random read latency。</p>
<p>一個 analytics job 的重跑（backfill 歷史資料）可以讓推薦系統的 consumer lag 從毫秒跳到秒級。在共享叢集中，這種干擾難以預防 — 只能在事後發現、人工協調。</p>
<h3 id="broker-故障的影響面">Broker 故障的影響面</h3>
<p>單一叢集中 broker 故障會觸發 partition reassignment，reassignment 的資料搬移佔用 disk I/O 跟網路頻寬。在混合 workload 的叢集中，reassignment 同時影響所有 workload 的效能 — 包括跟故障 broker 無直接關係的 topic。</p>
<p>叢集越大、topic 越多、reassignment 的影響面越廣。</p>
<h3 id="容量規劃的模糊邊界">容量規劃的模糊邊界</h3>
<p>共享叢集的容量規劃沒有清楚的 owner — analytics 團隊說「我們需要更多 retention」、推薦團隊說「我們需要更低 latency」、audit 團隊說「我們的資料不能丟」。三種需求各自合理，但共享叢集無法同時最佳化。</p>
<h2 id="解法分層叢集">解法：分層叢集</h2>
<p>LinkedIn 按業務風險跟效能需求把 workload 分配到不同叢集：</p>
<p><strong>Tier 1 — 即時關鍵路徑</strong>：即時推薦、搜尋索引更新、使用者通知。Broker 配置偏向低延遲（SSD、高 IOPS）、replication factor 3、retention 短（保留足夠的 consumer catchup 時間）。</p>
<p><strong>Tier 2 — 可靠性要求高但延遲容忍</strong>：audit log、合規事件、支付事件。配置偏向持久性（replication factor 3、min.insync.replicas 2、acks=all）、retention 長。</p>
<p><strong>Tier 3 — 高吞吐分析</strong>：analytics pipeline、ETL、batch processing。配置偏向吞吐（大 batch size、長 linger.ms、HDD）、retention 最長、容忍偶發 consumer lag。</p>
<h3 id="分層的判準">分層的判準</h3>
<p>分層的判準是「這個 workload 故障時，業務影響有多大、多快」：</p>
<ul>
<li>即時影響使用者體驗 → Tier 1</li>
<li>影響合規或財務但可容忍分鐘級延遲 → Tier 2</li>
<li>影響分析準確性但可容忍小時級延遲 → Tier 3</li>
</ul>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>共享叢集</th>
          <th>分層叢集</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>資源利用率</td>
          <td>高（所有 workload 共用資源池）</td>
          <td>低到中（每層有獨立的保留容量）</td>
      </tr>
      <tr>
          <td>隔離性</td>
          <td>低（noisy neighbor 互相干擾）</td>
          <td>高（故障跟效能退化限制在同層）</td>
      </tr>
      <tr>
          <td>運維複雜度</td>
          <td>低（一組 broker 統一管理）</td>
          <td>高（多組 broker、各自的監控跟維護）</td>
      </tr>
      <tr>
          <td>容量規劃清晰度</td>
          <td>模糊（多種需求混合、難以歸因）</td>
          <td>清楚（每層的需求跟 owner 明確）</td>
      </tr>
      <tr>
          <td>故障影響面</td>
          <td>廣（reassignment 影響所有 topic）</td>
          <td>有限（reassignment 只影響同層）</td>
      </tr>
  </tbody>
</table>
<p>分層的成本是資源利用率下降 — 每層都需要保留一定的 headroom 應對高峰，加總起來比共享叢集多。LinkedIn 的判斷是隔離性的價值大於利用率的損失 — 推薦系統一次 P99 退化的業務損失遠大於多幾台 broker 的成本。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 配置怎麼影響延遲 vs 吞吐 vs 持久性的取捨。</li>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>：不同 tier 的 Kafka 叢集各自有不同的 reliability budget。</li>
<li><a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a>：batch consumer 跟 real-time consumer 的資源消耗差異。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>即時消費者的 consumer lag 因為同叢集的 batch job 而上升</li>
<li>Broker 故障後的 partition reassignment 影響到跟故障無關的 topic</li>
<li>容量規劃會議中不同團隊的需求互相矛盾、無法在同一組配置中滿足</li>
<li>Kafka 叢集的 topic 數量超過 500 個、workload 類型超過三種</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/kafka/running-kafka-scale">Running Kafka at Scale at LinkedIn</a></li>
</ul>
]]></content:encoded></item><item><title>3.C5 Slack：Job Queue 演進到 Kafka + Redis</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/slack-job-queue-kafka-redis/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/slack-job-queue-kafka-redis/</guid><description>&lt;p>這個案例的核心責任是說明工作佇列轉換常是拓樸重整，而不是單點替換。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Slack 在 job queue 擴展中使用 Kafka 與 Redis 分工，處理吞吐與即時性需求。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當背景工作同時要高吞吐與快速反應，單一通道模型通常會變成瓶頸。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>把不同工作類型切到不同傳遞路徑。&lt;/li>
&lt;li>分別治理持久性與即時性目標。&lt;/li>
&lt;li>以 lag、重試與失敗重播驗證穩定性。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://slack.engineering/scaling-slacks-job-queue/">Scaling Slack&amp;rsquo;s Job Queue&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明工作佇列轉換常是拓樸重整，而不是單點替換。</p>
<h2 id="觀察">觀察</h2>
<p>Slack 在 job queue 擴展中使用 Kafka 與 Redis 分工，處理吞吐與即時性需求。</p>
<h2 id="判讀">判讀</h2>
<p>當背景工作同時要高吞吐與快速反應，單一通道模型通常會變成瓶頸。</p>
<h2 id="策略">策略</h2>
<ol>
<li>把不同工作類型切到不同傳遞路徑。</li>
<li>分別治理持久性與即時性目標。</li>
<li>以 lag、重試與失敗重播驗證穩定性。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a> 與 <a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://slack.engineering/scaling-slacks-job-queue/">Scaling Slack&rsquo;s Job Queue</a></li>
</ul>
]]></content:encoded></item><item><title>3.C6 Uber：Kafka 事件平台演進</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/</guid><description>&lt;p>Uber 的 Kafka 演進案例揭露了 MQ 從「幾個團隊自管的 broker」到「全公司共享的事件平台」的治理轉折點。轉折的核心判斷是：規模化之後，broker 容量擴展的成本小於 workload 治理缺失的成本。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Uber 的事件流涵蓋行程追蹤、司機定位、計費事件、推播通知、即時定價、ETA 計算跟 analytics。早期各團隊各自架設 Kafka 叢集，隨著 Kafka 在 Uber 內部的採用率上升，叢集數量跟 topic 數量快速增長，但沒有統一的治理。&lt;/p>
&lt;p>Uber 的 Kafka 規模峰值達到每秒數百萬筆訊息、數十個叢集、數千個 topic。在這個規模下，管理壓力從「單一叢集的 broker 夠不夠」轉到「誰在用、用多少、怎麼收費、故障時誰負責」。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="團隊自管的碎片化">團隊自管的碎片化&lt;/h3>
&lt;p>各團隊各自架設 Kafka 時，每個叢集的版本、配置、監控、備份策略都不同。運維知識散落在各團隊，沒有共享的 runbook 或值班流程。某個團隊的 Kafka 出問題時，其他團隊幫不上忙；知識在人員流動時遺失。&lt;/p>
&lt;p>碎片化的另一個後果是資源浪費。每個團隊各自預留的容量加總起來遠大於集中管理所需。低流量團隊的叢集常年使用率低於 10%，但因為自管模式下沒有共享容量的機制，資源無法調配。&lt;/p>
&lt;h3 id="topic-爆炸與無主-topic">Topic 爆炸與無主 topic&lt;/h3>
&lt;p>沒有 topic 建立的治理流程時，任何人都可以建 topic。Topic 的命名不一致、retention 設定不一致、owner 不明。離職的工程師建立的 topic 仍在接收資料、佔用 broker 資源，但沒人知道這些 topic 服務什麼業務。&lt;/p>
&lt;p>LinkedIn 後來也遇到同樣的問題並開發了 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">TopicGC&lt;/a> 做 topic 生命週期管理。Uber 的解法路線類似 — 把 topic 建立變成需要 owner、retention policy 跟業務標籤的審核流程。&lt;/p>
&lt;h3 id="故障排查的責任不清">故障排查的責任不清&lt;/h3>
&lt;p>叢集內的故障（broker OOM、partition leader 不均衡、consumer lag spike）需要 Kafka 專業知識排查。團隊自管模式下，每個團隊都需要一定程度的 Kafka 運維能力，但多數團隊的核心能力是業務邏輯而非 MQ 運維。&lt;/p>
&lt;p>故障排查的慣性是「先問 Kafka 團隊有沒有人可以幫忙」— 但沒有正式的 Kafka 團隊，所以問的是「上次修過 Kafka 的那個人」。&lt;/p>
&lt;h2 id="解法平台化">解法：平台化&lt;/h2>
&lt;p>Uber 的解法是把 Kafka 從分散自管收斂到集中平台 — 一個專責的 Kafka platform team 統一管理所有叢集、提供標準化的使用介面。&lt;/p>
&lt;h3 id="多租戶治理">多租戶治理&lt;/h3>
&lt;p>平台化的核心是多租戶模型 — 每個業務團隊是一個 tenant，tenant 有 quota（ingestion rate、partition 數量上限、retention 上限）跟 cost attribution。&lt;/p>
&lt;p>Quota 的目的是防止單一 tenant 的爆量拖累整個平台。Cost attribution 的目的是讓 tenant 看到自己的用量跟成本，驅動合理使用。&lt;/p>
&lt;h3 id="標準化-topic-管理">標準化 topic 管理&lt;/h3>
&lt;p>Topic 的建立走 self-service portal — 團隊填寫 owner、業務用途、預估流量、retention 需求，portal 自動配置 topic 並建立監控。沒有 owner 的 topic 不允許建立；owner 離職時 topic 需要交接或標記為候選淘汰。&lt;/p>
&lt;h3 id="統一監控與值班">統一監控與值班&lt;/h3>
&lt;p>Platform team 統一監控所有叢集的 broker 健康（replication lag、under-replicated partitions、disk usage、CPU），提供共用的 dashboard 跟 alert。值班由 platform team 負責 broker 層面的問題，業務層面的問題（consumer 設計錯誤、message 格式不對）由各 tenant team 自行處理。&lt;/p>
&lt;h2 id="取捨">取捨&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>團隊自管&lt;/th>
 &lt;th>平台化&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>自主性&lt;/td>
 &lt;td>高（團隊想怎麼配就怎麼配）&lt;/td>
 &lt;td>低到中（受 quota 跟 policy 約束）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>運維負擔分配&lt;/td>
 &lt;td>分散（每個團隊各自負擔）&lt;/td>
 &lt;td>集中（platform team 吸收 broker 層）&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>大（共享平台故障影響所有 tenant）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>專業知識需求&lt;/td>
 &lt;td>每個團隊都要一些 Kafka 運維知識&lt;/td>
 &lt;td>集中在 platform team&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>平台化的最大風險是共享平台成為單點 — broker 故障影響所有 tenant。Uber 用跟 LinkedIn 類似的分層叢集策略（critical vs best-effort）降低共享風險，但這也讓平台的運維複雜度上升。&lt;/p></description><content:encoded><![CDATA[<p>Uber 的 Kafka 演進案例揭露了 MQ 從「幾個團隊自管的 broker」到「全公司共享的事件平台」的治理轉折點。轉折的核心判斷是：規模化之後，broker 容量擴展的成本小於 workload 治理缺失的成本。</p>
<h2 id="業務背景">業務背景</h2>
<p>Uber 的事件流涵蓋行程追蹤、司機定位、計費事件、推播通知、即時定價、ETA 計算跟 analytics。早期各團隊各自架設 Kafka 叢集，隨著 Kafka 在 Uber 內部的採用率上升，叢集數量跟 topic 數量快速增長，但沒有統一的治理。</p>
<p>Uber 的 Kafka 規模峰值達到每秒數百萬筆訊息、數十個叢集、數千個 topic。在這個規模下，管理壓力從「單一叢集的 broker 夠不夠」轉到「誰在用、用多少、怎麼收費、故障時誰負責」。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="團隊自管的碎片化">團隊自管的碎片化</h3>
<p>各團隊各自架設 Kafka 時，每個叢集的版本、配置、監控、備份策略都不同。運維知識散落在各團隊，沒有共享的 runbook 或值班流程。某個團隊的 Kafka 出問題時，其他團隊幫不上忙；知識在人員流動時遺失。</p>
<p>碎片化的另一個後果是資源浪費。每個團隊各自預留的容量加總起來遠大於集中管理所需。低流量團隊的叢集常年使用率低於 10%，但因為自管模式下沒有共享容量的機制，資源無法調配。</p>
<h3 id="topic-爆炸與無主-topic">Topic 爆炸與無主 topic</h3>
<p>沒有 topic 建立的治理流程時，任何人都可以建 topic。Topic 的命名不一致、retention 設定不一致、owner 不明。離職的工程師建立的 topic 仍在接收資料、佔用 broker 資源，但沒人知道這些 topic 服務什麼業務。</p>
<p>LinkedIn 後來也遇到同樣的問題並開發了 <a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">TopicGC</a> 做 topic 生命週期管理。Uber 的解法路線類似 — 把 topic 建立變成需要 owner、retention policy 跟業務標籤的審核流程。</p>
<h3 id="故障排查的責任不清">故障排查的責任不清</h3>
<p>叢集內的故障（broker OOM、partition leader 不均衡、consumer lag spike）需要 Kafka 專業知識排查。團隊自管模式下，每個團隊都需要一定程度的 Kafka 運維能力，但多數團隊的核心能力是業務邏輯而非 MQ 運維。</p>
<p>故障排查的慣性是「先問 Kafka 團隊有沒有人可以幫忙」— 但沒有正式的 Kafka 團隊，所以問的是「上次修過 Kafka 的那個人」。</p>
<h2 id="解法平台化">解法：平台化</h2>
<p>Uber 的解法是把 Kafka 從分散自管收斂到集中平台 — 一個專責的 Kafka platform team 統一管理所有叢集、提供標準化的使用介面。</p>
<h3 id="多租戶治理">多租戶治理</h3>
<p>平台化的核心是多租戶模型 — 每個業務團隊是一個 tenant，tenant 有 quota（ingestion rate、partition 數量上限、retention 上限）跟 cost attribution。</p>
<p>Quota 的目的是防止單一 tenant 的爆量拖累整個平台。Cost attribution 的目的是讓 tenant 看到自己的用量跟成本，驅動合理使用。</p>
<h3 id="標準化-topic-管理">標準化 topic 管理</h3>
<p>Topic 的建立走 self-service portal — 團隊填寫 owner、業務用途、預估流量、retention 需求，portal 自動配置 topic 並建立監控。沒有 owner 的 topic 不允許建立；owner 離職時 topic 需要交接或標記為候選淘汰。</p>
<h3 id="統一監控與值班">統一監控與值班</h3>
<p>Platform team 統一監控所有叢集的 broker 健康（replication lag、under-replicated partitions、disk usage、CPU），提供共用的 dashboard 跟 alert。值班由 platform team 負責 broker 層面的問題，業務層面的問題（consumer 設計錯誤、message 格式不對）由各 tenant team 自行處理。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>團隊自管</th>
          <th>平台化</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>自主性</td>
          <td>高（團隊想怎麼配就怎麼配）</td>
          <td>低到中（受 quota 跟 policy 約束）</td>
      </tr>
      <tr>
          <td>運維負擔分配</td>
          <td>分散（每個團隊各自負擔）</td>
          <td>集中（platform team 吸收 broker 層）</td>
      </tr>
      <tr>
          <td>資源利用率</td>
          <td>低（各自預留、無法共用）</td>
          <td>高（共享容量、動態分配）</td>
      </tr>
      <tr>
          <td>治理一致性</td>
          <td>低（版本、配置、命名各自為政）</td>
          <td>高（統一版本、統一配置標準）</td>
      </tr>
      <tr>
          <td>故障影響面</td>
          <td>小（自管叢集只影響自己的團隊）</td>
          <td>大（共享平台故障影響所有 tenant）</td>
      </tr>
      <tr>
          <td>專業知識需求</td>
          <td>每個團隊都要一些 Kafka 運維知識</td>
          <td>集中在 platform team</td>
      </tr>
  </tbody>
</table>
<p>平台化的最大風險是共享平台成為單點 — broker 故障影響所有 tenant。Uber 用跟 LinkedIn 類似的分層叢集策略（critical vs best-effort）降低共享風險，但這也讓平台的運維複雜度上升。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 容量規劃跟 topic 管理的基礎。</li>
<li><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency reliability budget</a>：共享 Kafka 平台作為 dependency，tenant team 的 reliability budget 怎麼計算。</li>
<li><a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer design</a>：平台化後 consumer 設計的規範跟限制。</li>
<li><a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 cost attribution</a>：平台成本歸因到 tenant 的做法。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>組織內有 3 個以上團隊各自架設 Kafka、版本跟配置不統一</li>
<li>Topic 數量持續增長但沒人能說清楚哪些 topic 還在用</li>
<li>故障排查依賴特定個人而非共用的 runbook</li>
<li>叢集資源利用率低但各團隊仍要求擴容</li>
<li>管理層問「Kafka 總共花多少錢、誰在用」但沒人能回答</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.uber.com/en-TW/blog/kafka/">Building Uber&rsquo;s Kafka Infrastructure</a></li>
</ul>
]]></content:encoded></item><item><title>3.C7 LinkedIn：Kafka 自動修復治理</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-self-healing-automation/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-kafka-self-healing-automation/</guid><description>&lt;p>這個案例的核心責任是把 queue 可靠性從人力值班轉成自動化機制。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>LinkedIn 在 Kafka 維運中導入自動化治理，降低人工介入與恢復時間波動。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當叢集規模超過人力可及範圍，自動修復與治理工具會成為必要能力。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>明確定義可自動修復的故障類型。&lt;/li>
&lt;li>將自動修復與人工升級條件分離。&lt;/li>
&lt;li>把修復過程納入可觀測證據鏈。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/runbook-lifecycle/" data-link-title="8.16 Runbook Lifecycle 管理" data-link-desc="把 runbook 從一次性文件變成有版本、有演練、會過期的 artifact">8.16&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.linkedin.com/blog">Automating Kafka Self-Healing at LinkedIn&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 queue 可靠性從人力值班轉成自動化機制。</p>
<h2 id="觀察">觀察</h2>
<p>LinkedIn 在 Kafka 維運中導入自動化治理，降低人工介入與恢復時間波動。</p>
<h2 id="判讀">判讀</h2>
<p>當叢集規模超過人力可及範圍，自動修復與治理工具會成為必要能力。</p>
<h2 id="策略">策略</h2>
<ol>
<li>明確定義可自動修復的故障類型。</li>
<li>將自動修復與人工升級條件分離。</li>
<li>把修復過程納入可觀測證據鏈。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2</a> 與 <a href="/blog/backend/08-incident-response/runbook-lifecycle/" data-link-title="8.16 Runbook Lifecycle 管理" data-link-desc="把 runbook 從一次性文件變成有版本、有演練、會過期的 artifact">8.16</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.linkedin.com/blog">Automating Kafka Self-Healing at LinkedIn</a></li>
</ul>
]]></content:encoded></item><item><title>3.C8 Cloudflare：Queues 全球交付模型</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/cloudflare-queues-global-delivery-model/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/cloudflare-queues-global-delivery-model/</guid><description>&lt;p>這個案例的核心責任是把 queue 選型從單區域傳遞提升為全球交付治理。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Cloudflare Queues 以邊緣網路為背景，提供事件傳遞與 consumer 處理能力。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>全球部署下，queue 模型要同時考慮延遲、重試語義與跨區運維一致性。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>明確設定 delivery semantics 與重試策略。&lt;/li>
&lt;li>把 consumer 行為與死信處理流程標準化。&lt;/li>
&lt;li>將 queue lag 與失敗率接入平台觀測。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.cloudflare.com/introducing-cloudflare-queues/">Introducing Cloudflare Queues&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 queue 選型從單區域傳遞提升為全球交付治理。</p>
<h2 id="觀察">觀察</h2>
<p>Cloudflare Queues 以邊緣網路為背景，提供事件傳遞與 consumer 處理能力。</p>
<h2 id="判讀">判讀</h2>
<p>全球部署下，queue 模型要同時考慮延遲、重試語義與跨區運維一致性。</p>
<h2 id="策略">策略</h2>
<ol>
<li>明確設定 delivery semantics 與重試策略。</li>
<li>把 consumer 行為與死信處理流程標準化。</li>
<li>將 queue lag 與失敗率接入平台觀測。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4</a> 與 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/introducing-cloudflare-queues/">Introducing Cloudflare Queues</a></li>
</ul>
]]></content:encoded></item><item><title>3.C9 反例：Queue 語義切換誤配</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/</guid><description>&lt;p>這個反例的核心責任是說明 broker 遷移失敗常發生在語義假設錯置。&lt;/p>
&lt;h2 id="事故長相">事故長相&lt;/h2>
&lt;p>切換 broker 或 consumer group 後，表面上訊息仍然被送達，但業務資料開始出現重複扣款、重複寄信、狀態漏更新這類問題。這種事故很難只靠 queue depth 判斷，因為錯誤發生在「處理語義」而不是「是否有訊息」。&lt;/p>
&lt;h2 id="為什麼會擴大">為什麼會擴大&lt;/h2>
&lt;p>舊系統若依賴特定 offset 行為、重試節奏或 consumer idempotency，新系統即使名稱上提供相近 delivery semantics，也可能在失敗重播時產生不同結果。語義誤配會沿著下游資料寫入擴散。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>回退前要先確認哪一段資料已經被新語義處理過。若直接切回舊 broker，可能讓同一批事件再次被處理。更穩定的做法是先凍結新 consumer，保留 offset 對照與 replay 範圍，再決定補償或重播。&lt;/p>
&lt;h2 id="queue-專屬告警條件">Queue 專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>下游 reconciliation 同時出現重複與遺漏&lt;/li>
&lt;li>DLQ 激增且重播後仍回到相同錯誤&lt;/li>
&lt;li>consumer lag 下降但業務結果沒有收斂&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明 broker 遷移失敗常發生在語義假設錯置。</p>
<h2 id="事故長相">事故長相</h2>
<p>切換 broker 或 consumer group 後，表面上訊息仍然被送達，但業務資料開始出現重複扣款、重複寄信、狀態漏更新這類問題。這種事故很難只靠 queue depth 判斷，因為錯誤發生在「處理語義」而不是「是否有訊息」。</p>
<h2 id="為什麼會擴大">為什麼會擴大</h2>
<p>舊系統若依賴特定 offset 行為、重試節奏或 consumer idempotency，新系統即使名稱上提供相近 delivery semantics，也可能在失敗重播時產生不同結果。語義誤配會沿著下游資料寫入擴散。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>回退前要先確認哪一段資料已經被新語義處理過。若直接切回舊 broker，可能讓同一批事件再次被處理。更穩定的做法是先凍結新 consumer，保留 offset 對照與 replay 範圍，再決定補償或重播。</p>
<h2 id="queue-專屬告警條件">Queue 專屬告警條件</h2>
<ul>
<li>下游 reconciliation 同時出現重複與遺漏</li>
<li>DLQ 激增且重播後仍回到相同錯誤</li>
<li>consumer lag 下降但業務結果沒有收斂</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4</a> 與 <a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10</a>。</p>
]]></content:encoded></item><item><title>3.C10 對照：規模差異下的佇列模型</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/contrast-queue-model-by-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/contrast-queue-model-by-scale/</guid><description>&lt;p>這篇對照的核心責任是說明 queue 選型要跟著流量與組織規模改變。&lt;/p>
&lt;h2 id="小型服務常見判讀">小型服務常見判讀&lt;/h2>
&lt;p>小型服務優先用 managed queue 往往最穩，因為運維成本最低。這時候最容易忽略的是語義邊界：重試次數、死信規則、重播責任如果沒先定義，規模一上來就會出現資料重複與漏處理。&lt;/p>
&lt;h2 id="中型服務常見判讀">中型服務常見判讀&lt;/h2>
&lt;p>中型服務常見問題是 lag 與 DLQ 長期累積。根因通常是 consumer idempotency、重播流程、下游承載能力沒有一起設計，broker 效能本身很少是單點問題。&lt;/p>
&lt;h2 id="大型服務常見判讀">大型服務常見判讀&lt;/h2>
&lt;p>大型服務需要處理跨租戶與跨區壓力。此時若還用單叢集思維，任何一類流量尖峰都會拖垮整體。重點會從「怎麼送訊息」轉成「怎麼隔離失敗」。&lt;/p>
&lt;h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>&lt;code>consumer lag&lt;/code> 連續超出 SLO 窗口&lt;/li>
&lt;li>&lt;code>DLQ&lt;/code> 速率上升且無法在固定時間內回收&lt;/li>
&lt;li>重播後仍出現相同失敗模式&lt;/li>
&lt;/ul>
&lt;p>出現上述條件應先凍結切換，回到前一語義設定，再逐步修正 consumer 契約與重播流程。&lt;/p></description><content:encoded><![CDATA[<p>這篇對照的核心責任是說明 queue 選型要跟著流量與組織規模改變。</p>
<h2 id="小型服務常見判讀">小型服務常見判讀</h2>
<p>小型服務優先用 managed queue 往往最穩，因為運維成本最低。這時候最容易忽略的是語義邊界：重試次數、死信規則、重播責任如果沒先定義，規模一上來就會出現資料重複與漏處理。</p>
<h2 id="中型服務常見判讀">中型服務常見判讀</h2>
<p>中型服務常見問題是 lag 與 DLQ 長期累積。根因通常是 consumer idempotency、重播流程、下游承載能力沒有一起設計，broker 效能本身很少是單點問題。</p>
<h2 id="大型服務常見判讀">大型服務常見判讀</h2>
<p>大型服務需要處理跨租戶與跨區壓力。此時若還用單叢集思維，任何一類流量尖峰都會拖垮整體。重點會從「怎麼送訊息」轉成「怎麼隔離失敗」。</p>
<h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件</h2>
<ul>
<li><code>consumer lag</code> 連續超出 SLO 窗口</li>
<li><code>DLQ</code> 速率上升且無法在固定時間內回收</li>
<li>重播後仍出現相同失敗模式</li>
</ul>
<p>出現上述條件應先凍結切換，回到前一語義設定，再逐步修正 consumer 契約與重播流程。</p>
]]></content:encoded></item><item><title>3.C11 Pinterest：Kafka tiered storage broker-decoupled</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-tiered-storage/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-tiered-storage/</guid><description>&lt;p>這個案例的核心責任是說明 tiered storage 不只是「冷資料 offload」、是 broker 與儲存解耦的架構選擇。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pinterest 從 Kafka broker 卸 ~200 TB/day 熱資料到 S3、2024 年 5 月起 20+ production topic 上線、跟 KIP-405 native tiered storage 不同、採 broker-decoupled 設計。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Broker-decoupled 設計讓 consumer 直接從 S3 拉、broker 不再是熱路徑。揭露「broker resource 跟 cross-AZ network cost」其實該分離治理、而非綁在 broker 容量擴張上。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：tiered storage / 跨層儲存成本。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/pinterest-tiered-storage-for-apache-kafka-%EF%B8%8F-a-broker-decoupled-approach-c33c69e9958b">Pinterest Tiered Storage for Apache Kafka — a Broker-Decoupled Approach&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 tiered storage 不只是「冷資料 offload」、是 broker 與儲存解耦的架構選擇。</p>
<h2 id="觀察">觀察</h2>
<p>Pinterest 從 Kafka broker 卸 ~200 TB/day 熱資料到 S3、2024 年 5 月起 20+ production topic 上線、跟 KIP-405 native tiered storage 不同、採 broker-decoupled 設計。</p>
<h2 id="判讀">判讀</h2>
<p>Broker-decoupled 設計讓 consumer 直接從 S3 拉、broker 不再是熱路徑。揭露「broker resource 跟 cross-AZ network cost」其實該分離治理、而非綁在 broker 容量擴張上。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：tiered storage / 跨層儲存成本。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/pinterest-engineering/pinterest-tiered-storage-for-apache-kafka-%EF%B8%8F-a-broker-decoupled-approach-c33c69e9958b">Pinterest Tiered Storage for Apache Kafka — a Broker-Decoupled Approach</a></li>
</ul>
]]></content:encoded></item><item><title>3.C12 Pinterest：Shallow Mirror 優化 MirrorMaker</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-shallow-mirror/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-pinterest-shallow-mirror/</guid><description>&lt;p>這個案例的核心責任是說明 cross-region replication 的 CPU/memory 成本是被低估的工程議題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pinterest 三個 AWS region（us-east-1 / us-east-2 / eu-west-1）跑 MirrorMaker v1、原版設計把 record 解壓+重壓、memory 用量 2-10x 於網路 bytes、CPU spike 與 OOM 頻繁。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Shallow Mirror 在 RecordBatch 層淺迭代 + ByteBuffer pointer 共享、避開 deserialize/re-compress。揭露「跨區同步不是純 I/O 問題、是 CPU + memory + 網路三維壓力」。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：cross-region MirrorMaker / MirrorMaker 2 配置。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/pinterest-engineering/shallow-mirror-f543b14bb25">Pinterest Shallow Mirror&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 cross-region replication 的 CPU/memory 成本是被低估的工程議題。</p>
<h2 id="觀察">觀察</h2>
<p>Pinterest 三個 AWS region（us-east-1 / us-east-2 / eu-west-1）跑 MirrorMaker v1、原版設計把 record 解壓+重壓、memory 用量 2-10x 於網路 bytes、CPU spike 與 OOM 頻繁。</p>
<h2 id="判讀">判讀</h2>
<p>Shallow Mirror 在 RecordBatch 層淺迭代 + ByteBuffer pointer 共享、避開 deserialize/re-compress。揭露「跨區同步不是純 I/O 問題、是 CPU + memory + 網路三維壓力」。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：cross-region MirrorMaker / MirrorMaker 2 配置。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/pinterest-engineering/shallow-mirror-f543b14bb25">Pinterest Shallow Mirror</a></li>
</ul>
]]></content:encoded></item><item><title>3.C13 Shopify：Debezium CDC over sharded MySQL</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-shopify-debezium-cdc/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-shopify-debezium-cdc/</guid><description>&lt;p>Shopify 的 CDC pipeline 揭露了 sharded monolith 上大規模 log-based CDC 的真實工程壓力。壓力集中在 snapshot 跟 oversized payload，穩態複製本身反而是最穩定的部分。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Shopify 的核心資料儲存是 100+ 個 MySQL shard，每個 shard 承載不同商家的交易資料。下游系統（搜尋索引、analytics、資料倉儲）需要近即時地取得資料變更。原本用 query-based 方案（內部系統 Longboat）輪詢資料庫，但隨 shard 數量跟資料量成長，輪詢的延遲跟資料庫負載壓力持續惡化。&lt;/p>
&lt;p>遷移到 log-based CDC（Debezium over Kafka Connect）後，pipeline 的穩態規模是 ~150 個 Debezium connector 跑在 12 個 Kubernetes pod、Black Friday peak 100K records/sec、P99 latency &amp;lt; 10s。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="snapshot-鎖定-read-replica">Snapshot 鎖定 read replica&lt;/h3>
&lt;p>Debezium 在初始同步（snapshot）時需要取得一致性快照。MySQL connector 的預設行為是對 read replica 取 global read lock，鎖住的時間跟表大小成正比。Shopify 的大表 snapshot 可能鎖住 read replica 數小時，影響線上查詢。&lt;/p>
&lt;p>Shopify 工程師直接向 Debezium 上游貢獻了「lock-free snapshot」機制 — 用 MySQL 的 GTID（Global Transaction ID）確保一致性，取代 global read lock。這個改動後來合併進 Debezium 主線，所有使用者都受益。&lt;/p>
&lt;h3 id="oversized-record">Oversized record&lt;/h3>
&lt;p>MySQL 的 blob / text 欄位可能產生超過 1 MB 的 CDC record。Kafka 的 message size limit（預設 1 MB）會讓這些 record 被 producer 拒絕。調大 &lt;code>max.message.bytes&lt;/code> 是一個選項，但會影響 broker 的記憶體跟 replication 效率。&lt;/p>
&lt;p>Shopify 的解法是把 oversized payload 寫到 GCS（Google Cloud Storage），CDC record 只帶 GCS pointer。Consumer 端在需要完整資料時再從 GCS 取。這個 pattern 把 Kafka 維持在「傳遞事件 metadata」的定位，大型 payload 走 object storage。&lt;/p>
&lt;h3 id="connector-故障隔離">Connector 故障隔離&lt;/h3>
&lt;p>150 個 connector 跑在 12 個 pod 上，一個 connector 的 failure（例如某個 shard 的 MySQL 做了 schema change、binlog 格式不相容）可能影響同 pod 上的其他 connector。Shopify 用 Kafka Connect 的 distributed mode + task rebalance 做故障隔離，但 rebalance 本身在 connector 數量多時有延遲。&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>Snapshot 鎖定&lt;/td>
 &lt;td>Lock-free snapshot（GTID）&lt;/td>
 &lt;td>需要 MySQL 啟用 GTID、upstream contribution 維護成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Oversized record&lt;/td>
 &lt;td>GCS pointer 替代 inline data&lt;/td>
 &lt;td>Consumer 端要多一步 GCS 讀取、增加端到端延遲&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Connector 隔離&lt;/td>
 &lt;td>Distributed mode + rebalance&lt;/td>
 &lt;td>Rebalance storm 在大量 connector 時可能造成全域暫停&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>高峰流量&lt;/td>
 &lt;td>12 pod K8s 部署、水平擴展&lt;/td>
 &lt;td>Pod 數量增加讓 Kafka Connect worker 的 rebalance 更複雜&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="回寫教材的連結">回寫教材的連結&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern&lt;/a>：CDC 是 outbox pattern 的 log-based 替代方案。Shopify 的 case 揭露 CDC 的工程成本集中在 snapshot 跟 schema evolution，outbox 的成本集中在應用層 dual-write。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>：Kafka Connect / CDC 的進階主題。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>：message size limit 跟 broker 資源的關係。&lt;/li>
&lt;/ul>
&lt;h2 id="判讀徵兆">判讀徵兆&lt;/h2>
&lt;p>讀者在自己的系統看到以下訊號時，應該回讀本案例：&lt;/p></description><content:encoded><![CDATA[<p>Shopify 的 CDC pipeline 揭露了 sharded monolith 上大規模 log-based CDC 的真實工程壓力。壓力集中在 snapshot 跟 oversized payload，穩態複製本身反而是最穩定的部分。</p>
<h2 id="業務背景">業務背景</h2>
<p>Shopify 的核心資料儲存是 100+ 個 MySQL shard，每個 shard 承載不同商家的交易資料。下游系統（搜尋索引、analytics、資料倉儲）需要近即時地取得資料變更。原本用 query-based 方案（內部系統 Longboat）輪詢資料庫，但隨 shard 數量跟資料量成長，輪詢的延遲跟資料庫負載壓力持續惡化。</p>
<p>遷移到 log-based CDC（Debezium over Kafka Connect）後，pipeline 的穩態規模是 ~150 個 Debezium connector 跑在 12 個 Kubernetes pod、Black Friday peak 100K records/sec、P99 latency &lt; 10s。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="snapshot-鎖定-read-replica">Snapshot 鎖定 read replica</h3>
<p>Debezium 在初始同步（snapshot）時需要取得一致性快照。MySQL connector 的預設行為是對 read replica 取 global read lock，鎖住的時間跟表大小成正比。Shopify 的大表 snapshot 可能鎖住 read replica 數小時，影響線上查詢。</p>
<p>Shopify 工程師直接向 Debezium 上游貢獻了「lock-free snapshot」機制 — 用 MySQL 的 GTID（Global Transaction ID）確保一致性，取代 global read lock。這個改動後來合併進 Debezium 主線，所有使用者都受益。</p>
<h3 id="oversized-record">Oversized record</h3>
<p>MySQL 的 blob / text 欄位可能產生超過 1 MB 的 CDC record。Kafka 的 message size limit（預設 1 MB）會讓這些 record 被 producer 拒絕。調大 <code>max.message.bytes</code> 是一個選項，但會影響 broker 的記憶體跟 replication 效率。</p>
<p>Shopify 的解法是把 oversized payload 寫到 GCS（Google Cloud Storage），CDC record 只帶 GCS pointer。Consumer 端在需要完整資料時再從 GCS 取。這個 pattern 把 Kafka 維持在「傳遞事件 metadata」的定位，大型 payload 走 object storage。</p>
<h3 id="connector-故障隔離">Connector 故障隔離</h3>
<p>150 個 connector 跑在 12 個 pod 上，一個 connector 的 failure（例如某個 shard 的 MySQL 做了 schema change、binlog 格式不相容）可能影響同 pod 上的其他 connector。Shopify 用 Kafka Connect 的 distributed mode + task rebalance 做故障隔離，但 rebalance 本身在 connector 數量多時有延遲。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<table>
  <thead>
      <tr>
          <th>挑戰</th>
          <th>解法</th>
          <th>取捨</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Snapshot 鎖定</td>
          <td>Lock-free snapshot（GTID）</td>
          <td>需要 MySQL 啟用 GTID、upstream contribution 維護成本</td>
      </tr>
      <tr>
          <td>Oversized record</td>
          <td>GCS pointer 替代 inline data</td>
          <td>Consumer 端要多一步 GCS 讀取、增加端到端延遲</td>
      </tr>
      <tr>
          <td>Connector 隔離</td>
          <td>Distributed mode + rebalance</td>
          <td>Rebalance storm 在大量 connector 時可能造成全域暫停</td>
      </tr>
      <tr>
          <td>高峰流量</td>
          <td>12 pod K8s 部署、水平擴展</td>
          <td>Pod 數量增加讓 Kafka Connect worker 的 rebalance 更複雜</td>
      </tr>
  </tbody>
</table>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern</a>：CDC 是 outbox pattern 的 log-based 替代方案。Shopify 的 case 揭露 CDC 的工程成本集中在 snapshot 跟 schema evolution，outbox 的成本集中在應用層 dual-write。</li>
<li><a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>：Kafka Connect / CDC 的進階主題。</li>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：message size limit 跟 broker 資源的關係。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>CDC snapshot 過程持續數小時、鎖住 read replica 影響線上查詢</li>
<li>CDC record size 頻繁超過 Kafka 的 message size limit</li>
<li>Kafka Connect connector 數量超過 50 個、rebalance 時間開始明顯增長</li>
<li>從 query-based 同步（輪詢）切換到 log-based CDC 的評估階段</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://shopify.engineering/capturing-every-change-shopify-sharded-monolith">Capturing Every Change From Shopify&rsquo;s Sharded Monolith</a></li>
</ul>
]]></content:encoded></item><item><title>3.C14 Yelp：Schematizer 自建 Schema Registry</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-yelp-schematizer/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-yelp-schematizer/</guid><description>&lt;p>這個案例的核心責任是說明 schema 治理是 data pipeline 的核心責任、不是 add-on。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Yelp data pipeline 一天數十億訊息、跨數百個 service、數千 schema、用自建 Schematizer 強制所有 message 走 Avro schema、訊息只帶 schema ID。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Schematizer 不只是 schema store、還做 schema evolution compatibility 與 topic 自動分配（不相容 schema 強制新 topic）。揭露 producer / consumer schema 治理要拉到平台層、靠工具強制、不靠人約定。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：Schema Registry / Schema evolution。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineeringblog.yelp.com/2016/08/more-than-just-a-schema-store.html">Yelp Schematizer: More than just a schema store&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 schema 治理是 data pipeline 的核心責任、不是 add-on。</p>
<h2 id="觀察">觀察</h2>
<p>Yelp data pipeline 一天數十億訊息、跨數百個 service、數千 schema、用自建 Schematizer 強制所有 message 走 Avro schema、訊息只帶 schema ID。</p>
<h2 id="判讀">判讀</h2>
<p>Schematizer 不只是 schema store、還做 schema evolution compatibility 與 topic 自動分配（不相容 schema 強制新 topic）。揭露 producer / consumer schema 治理要拉到平台層、靠工具強制、不靠人約定。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：Schema Registry / Schema evolution。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineeringblog.yelp.com/2016/08/more-than-just-a-schema-store.html">Yelp Schematizer: More than just a schema store</a></li>
</ul>
]]></content:encoded></item><item><title>3.C15 Airbnb：Spark Streaming Kafka reader rebalance</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-airbnb-spark-streaming-rebalance/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-airbnb-spark-streaming-rebalance/</guid><description>&lt;p>這個案例的核心責任是說明 stream processor 與 Kafka partition 數的緊耦合是 production scaling 瓶頸。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb logging pipeline 跨多個 topic、event size 從幾百 bytes 到幾百 KB、QPS 跨數個量級差異、Spark 一個 partition 對一個 task 造成 data skew、catch-up 一個 4 小時 lag 要再花 4 小時。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 balanced Spark Kafka reader、把 parallelism 從 partition 數解耦、按 event volume × size 重新分派 work。揭露 partition 數不該等同 consumer parallelism、要看 event 形狀。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：Consumer 設計 / consumer lag / rebalance / partition + consumer group。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/airbnb-engineering/scaling-spark-streaming-for-logging-event-ingestion-4a03141d135d">Scaling Spark Streaming for Logging Event Ingestion&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 stream processor 與 Kafka partition 數的緊耦合是 production scaling 瓶頸。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb logging pipeline 跨多個 topic、event size 從幾百 bytes 到幾百 KB、QPS 跨數個量級差異、Spark 一個 partition 對一個 task 造成 data skew、catch-up 一個 4 小時 lag 要再花 4 小時。</p>
<h2 id="判讀">判讀</h2>
<p>自建 balanced Spark Kafka reader、把 parallelism 從 partition 數解耦、按 event volume × size 重新分派 work。揭露 partition 數不該等同 consumer parallelism、要看 event 形狀。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：Consumer 設計 / consumer lag / rebalance / partition + consumer group。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/airbnb-engineering/scaling-spark-streaming-for-logging-event-ingestion-4a03141d135d">Scaling Spark Streaming for Logging Event Ingestion</a></li>
</ul>
]]></content:encoded></item><item><title>3.C16 Robinhood：Faust Python stream processing</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/</guid><description>&lt;p>這個案例的核心責任是說明語言生態與 stream framework 的選型張力。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Robinhood 每天處理 billions of events / TB 資料、用於 risk signal、order quality、market data、fraud detection；team 多為 Python、不想用 JVM 生態。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>把 Kafka Streams 的 stateful streaming 模式（topology、tables、windowing）移植到 Python library 形式、不需要 Yarn / Mesos resource manager。揭露 stream processing framework 選型常被語言生態主導、不是技術 feature。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：跨語言 client / Streams framework / stream processing on Kafka。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/robinhood-engineering/faust-stream-processing-for-python-a66d3a51212d">Faust: Stream Processing for Python&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明語言生態與 stream framework 的選型張力。</p>
<h2 id="觀察">觀察</h2>
<p>Robinhood 每天處理 billions of events / TB 資料、用於 risk signal、order quality、market data、fraud detection；team 多為 Python、不想用 JVM 生態。</p>
<h2 id="判讀">判讀</h2>
<p>把 Kafka Streams 的 stateful streaming 模式（topology、tables、windowing）移植到 Python library 形式、不需要 Yarn / Mesos resource manager。揭露 stream processing framework 選型常被語言生態主導、不是技術 feature。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：跨語言 client / Streams framework / stream processing on Kafka。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/robinhood-engineering/faust-stream-processing-for-python-a66d3a51212d">Faust: Stream Processing for Python</a></li>
</ul>
]]></content:encoded></item><item><title>3.C17 Walmart：Messaging Proxy Service 解 rebalance storm</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-walmart-mps-rebalance/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-walmart-mps-rebalance/</guid><description>&lt;p>這個案例的核心責任是說明 partition-consumer 1:1 模型在大規模 K8s 環境的擴張極限。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Walmart 每天 trillions of message、25K+ Kafka consumer 跑在 WCNP Kubernetes 多雲環境；最大痛點是 pod scaling / deploy / heartbeat fail 觸發 consumer rebalance、lag spike。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 Messaging Proxy Service（MPS、Kafka Connect sink connector）、把 consumer 從 partition-bound 解耦成 stateless REST service、可獨立 auto-scale、不用增 partition；內建 DLQ 處理 poison pill。揭露「consumer 該跟 partition 數綁定」這個假設在 K8s 規模化下不再成立。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：rebalance storm / consumer lag / multi-tenant 配額。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/walmartglobaltech/reliably-processing-trillions-of-kafka-messages-per-day-23494f553ef9">Reliably Processing Trillions of Kafka Messages Per Day&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 partition-consumer 1:1 模型在大規模 K8s 環境的擴張極限。</p>
<h2 id="觀察">觀察</h2>
<p>Walmart 每天 trillions of message、25K+ Kafka consumer 跑在 WCNP Kubernetes 多雲環境；最大痛點是 pod scaling / deploy / heartbeat fail 觸發 consumer rebalance、lag spike。</p>
<h2 id="判讀">判讀</h2>
<p>自建 Messaging Proxy Service（MPS、Kafka Connect sink connector）、把 consumer 從 partition-bound 解耦成 stateless REST service、可獨立 auto-scale、不用增 partition；內建 DLQ 處理 poison pill。揭露「consumer 該跟 partition 數綁定」這個假設在 K8s 規模化下不再成立。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：rebalance storm / consumer lag / multi-tenant 配額。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/walmartglobaltech/reliably-processing-trillions-of-kafka-messages-per-day-23494f553ef9">Reliably Processing Trillions of Kafka Messages Per Day</a></li>
</ul>
]]></content:encoded></item><item><title>3.C18 Wix：Greyhound TLLSR 解 consumer 卡住</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-greyhound-troubleshooting/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-greyhound-troubleshooting/</guid><description>&lt;p>這個案例的核心責任是說明大規模 multi-tenant Kafka 的營運可視性需求遠超原生 metric。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Wix 2000+ microservice、每天 66 billion Kafka 訊息、用自建 Greyhound（JVM library + polyglot sidecar）抽象 Kafka；troubleshooting 痛點是「卡住的 consumer 看不到原因、只能寫 DB 修復腳本」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>TLLSR 框架（Trace / Lookup / Longest-running / Skip-replay / Redistribute）解 single-partition lag、單筆 poison pill、handler 卡住等情境；consumer lag alert &amp;gt; 30 分鐘觸發。揭露原生 lag metric 無法定位「卡在哪」、需要 message-level trace + 操作介面。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：consumer lag / observability / multi-tenant / poison message。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/wix-engineering/troubleshooting-kafka-for-2000-microservices-at-wix-986ee382fd1e">Troubleshooting Kafka for 2000 Microservices at Wix&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明大規模 multi-tenant Kafka 的營運可視性需求遠超原生 metric。</p>
<h2 id="觀察">觀察</h2>
<p>Wix 2000+ microservice、每天 66 billion Kafka 訊息、用自建 Greyhound（JVM library + polyglot sidecar）抽象 Kafka；troubleshooting 痛點是「卡住的 consumer 看不到原因、只能寫 DB 修復腳本」。</p>
<h2 id="判讀">判讀</h2>
<p>TLLSR 框架（Trace / Lookup / Longest-running / Skip-replay / Redistribute）解 single-partition lag、單筆 poison pill、handler 卡住等情境；consumer lag alert &gt; 30 分鐘觸發。揭露原生 lag metric 無法定位「卡在哪」、需要 message-level trace + 操作介面。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：consumer lag / observability / multi-tenant / poison message。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/wix-engineering/troubleshooting-kafka-for-2000-microservices-at-wix-986ee382fd1e">Troubleshooting Kafka for 2000 Microservices at Wix</a></li>
</ul>
]]></content:encoded></item><item><title>3.C19 Wix：Multi-cluster Kafka zero-downtime 遷移</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-multi-cluster-migration/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-wix-multi-cluster-migration/</guid><description>&lt;p>這個案例的核心責任是說明 single mega-cluster 的 metadata scaling ceiling 與分群策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Wix cluster metadata 從 2019 年 5K topic / 45K partition 漲到 20K topic / 200K partition、每日 record 從 450M 漲到 2.5B、controller startup 與 broker stability 受 metadata 量壓垮。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不用 MirrorMaker、自建 Replicator service + Migration Orchestrator、用 Kafka topic 當控制平面協調 consumer 切換 + offset mapping；按 SLA 切多 cluster。揭露「topic / partition 數量」是 broker 級別的物理上限、不能無限擴張。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：cross-region MirrorMaker / topic 生命週期 / 分層叢集策略。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">3.C3 LinkedIn TopicGC&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/wix-engineering/migrating-to-a-multi-cluster-managed-kafka-with-0-downtime-b936655f888e">Migrating to a Multi-Cluster Managed Kafka with 0 Downtime&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 single mega-cluster 的 metadata scaling ceiling 與分群策略。</p>
<h2 id="觀察">觀察</h2>
<p>Wix cluster metadata 從 2019 年 5K topic / 45K partition 漲到 20K topic / 200K partition、每日 record 從 450M 漲到 2.5B、controller startup 與 broker stability 受 metadata 量壓垮。</p>
<h2 id="判讀">判讀</h2>
<p>不用 MirrorMaker、自建 Replicator service + Migration Orchestrator、用 Kafka topic 當控制平面協調 consumer 切換 + offset mapping；按 SLA 切多 cluster。揭露「topic / partition 數量」是 broker 級別的物理上限、不能無限擴張。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：cross-region MirrorMaker / topic 生命週期 / 分層叢集策略。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/linkedin-topicgc-kafka-governance/" data-link-title="3.C3 LinkedIn：TopicGC 與 Kafka 治理轉換" data-link-desc="Kafka topic 從手動治理轉自動治理對叢集的影響。">3.C3 LinkedIn TopicGC</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/wix-engineering/migrating-to-a-multi-cluster-managed-kafka-with-0-downtime-b936655f888e">Migrating to a Multi-Cluster Managed Kafka with 0 Downtime</a></li>
</ul>
]]></content:encoded></item><item><title>3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/</guid><description>&lt;p>Spotify 從 Kafka 遷出到 GCP Pub/Sub 的決策揭露了兩件事：broker 的可靠性保證是版本特性而非 Kafka 的不變量；以及「升級到新版」跟「換到另一個系統」之間的決策判準。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Spotify 的事件傳遞系統（Event Delivery）負責把使用者行為事件（播放、搜尋、推薦互動）從客戶端送到資料管線。系統跨 5 個 datacenter 運行 Kafka 0.7，production peak 700K events/sec、pressure test 達到 2M events/sec。事件資料是推薦系統、analytics 跟廣告計費的輸入，遺失事件直接影響商業決策的準確性。&lt;/p>
&lt;p>2016 年，Spotify 決定把 Event Delivery 從 Kafka 遷移到 GCP Pub/Sub，而非升級到當時已發布的 Kafka 0.8+。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="mirrormaker-的-best-effort-語意">MirrorMaker 的 best-effort 語意&lt;/h3>
&lt;p>Kafka 0.7 的跨 datacenter replication 工具 MirrorMaker 在 best-effort mode 下會丟失資料但向 producer 回報成功。對 Spotify 的場景，producer 端認為事件已送達，但跨 datacenter 的 mirror 實際上丟了一部分。丟失比例在正常情況下很低，但在 broker restart 或網路抖動時可以升高到影響 analytics 準確性的程度。&lt;/p>
&lt;p>這個問題的根源是 Kafka 0.7 的 producer 沒有 idempotent 保證，MirrorMaker 的 consumer offset commit 跟 producer ack 之間有 gap。&lt;/p>
&lt;h3 id="broker-restart-後-producer-無法自動恢復">Broker restart 後 producer 無法自動恢復&lt;/h3>
&lt;p>Kafka 0.7 的 producer 在 broker restart 後可能進入無法自動恢復的狀態 — 需要人工重啟 producer process。在 5 個 datacenter、數百個 producer instance 的規模下，每次 broker 維護操作都需要人工介入恢復 producer，運維成本跟 broker 數量成正比。&lt;/p>
&lt;h3 id="為什麼不升級到-kafka-08">為什麼不升級到 Kafka 0.8+&lt;/h3>
&lt;p>Kafka 0.8 引入了 replication、新的 consumer API 跟更可靠的 producer。但 Spotify 評估後認為升級的成本接近重新部署：&lt;/p>
&lt;ul>
&lt;li>Kafka 0.7 到 0.8 的 wire protocol 不相容，需要全量遷移而非滾動升級&lt;/li>
&lt;li>所有 producer / consumer 的 client library 都要更換&lt;/li>
&lt;li>Spotify 同時在向 GCP 遷移基礎設施，Kafka 的自管運維模式跟 GCP 的託管方向不一致&lt;/li>
&lt;/ul>
&lt;p>相比之下，GCP Pub/Sub 提供了託管的 exactly-once 語意、跨 region replication、零運維。遷移成本跟升級 Kafka 版本的成本相當，但遷移後的長期運維成本低得多。&lt;/p>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>留在 Kafka（升級 0.8+）&lt;/th>
 &lt;th>遷到 GCP Pub/Sub&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>一次性遷移成本&lt;/td>
 &lt;td>中（全量遷移、不可滾動升級）&lt;/td>
 &lt;td>中（同樣需要改所有 client）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>長期運維成本&lt;/td>
 &lt;td>高（自管 broker × 5 DC）&lt;/td>
 &lt;td>低（託管、零 broker 維護）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可靠性保證&lt;/td>
 &lt;td>0.8+ 有 replication、改善大&lt;/td>
 &lt;td>Pub/Sub 原生 exactly-once&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨 region replication&lt;/td>
 &lt;td>需要自建 MirrorMaker 2.0&lt;/td>
 &lt;td>原生支援&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生態鎖定&lt;/td>
 &lt;td>Kafka 生態成熟&lt;/td>
 &lt;td>GCP 鎖定、跨雲成本高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Spotify 的判斷是：在同時進行 GCP 遷移的背景下，維護自管 Kafka 的投資回報比不上切換到託管方案。這個判斷跟 Kafka 本身的能力無關 — Kafka 0.8+ 的可靠性已經解決了 0.7 的問題。決策的關鍵變數是「組織正在往哪走」，不只是「技術上哪個更好」。&lt;/p></description><content:encoded><![CDATA[<p>Spotify 從 Kafka 遷出到 GCP Pub/Sub 的決策揭露了兩件事：broker 的可靠性保證是版本特性而非 Kafka 的不變量；以及「升級到新版」跟「換到另一個系統」之間的決策判準。</p>
<h2 id="業務背景">業務背景</h2>
<p>Spotify 的事件傳遞系統（Event Delivery）負責把使用者行為事件（播放、搜尋、推薦互動）從客戶端送到資料管線。系統跨 5 個 datacenter 運行 Kafka 0.7，production peak 700K events/sec、pressure test 達到 2M events/sec。事件資料是推薦系統、analytics 跟廣告計費的輸入，遺失事件直接影響商業決策的準確性。</p>
<p>2016 年，Spotify 決定把 Event Delivery 從 Kafka 遷移到 GCP Pub/Sub，而非升級到當時已發布的 Kafka 0.8+。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="mirrormaker-的-best-effort-語意">MirrorMaker 的 best-effort 語意</h3>
<p>Kafka 0.7 的跨 datacenter replication 工具 MirrorMaker 在 best-effort mode 下會丟失資料但向 producer 回報成功。對 Spotify 的場景，producer 端認為事件已送達，但跨 datacenter 的 mirror 實際上丟了一部分。丟失比例在正常情況下很低，但在 broker restart 或網路抖動時可以升高到影響 analytics 準確性的程度。</p>
<p>這個問題的根源是 Kafka 0.7 的 producer 沒有 idempotent 保證，MirrorMaker 的 consumer offset commit 跟 producer ack 之間有 gap。</p>
<h3 id="broker-restart-後-producer-無法自動恢復">Broker restart 後 producer 無法自動恢復</h3>
<p>Kafka 0.7 的 producer 在 broker restart 後可能進入無法自動恢復的狀態 — 需要人工重啟 producer process。在 5 個 datacenter、數百個 producer instance 的規模下，每次 broker 維護操作都需要人工介入恢復 producer，運維成本跟 broker 數量成正比。</p>
<h3 id="為什麼不升級到-kafka-08">為什麼不升級到 Kafka 0.8+</h3>
<p>Kafka 0.8 引入了 replication、新的 consumer API 跟更可靠的 producer。但 Spotify 評估後認為升級的成本接近重新部署：</p>
<ul>
<li>Kafka 0.7 到 0.8 的 wire protocol 不相容，需要全量遷移而非滾動升級</li>
<li>所有 producer / consumer 的 client library 都要更換</li>
<li>Spotify 同時在向 GCP 遷移基礎設施，Kafka 的自管運維模式跟 GCP 的託管方向不一致</li>
</ul>
<p>相比之下，GCP Pub/Sub 提供了託管的 exactly-once 語意、跨 region replication、零運維。遷移成本跟升級 Kafka 版本的成本相當，但遷移後的長期運維成本低得多。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>留在 Kafka（升級 0.8+）</th>
          <th>遷到 GCP Pub/Sub</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>一次性遷移成本</td>
          <td>中（全量遷移、不可滾動升級）</td>
          <td>中（同樣需要改所有 client）</td>
      </tr>
      <tr>
          <td>長期運維成本</td>
          <td>高（自管 broker × 5 DC）</td>
          <td>低（託管、零 broker 維護）</td>
      </tr>
      <tr>
          <td>可靠性保證</td>
          <td>0.8+ 有 replication、改善大</td>
          <td>Pub/Sub 原生 exactly-once</td>
      </tr>
      <tr>
          <td>跨 region replication</td>
          <td>需要自建 MirrorMaker 2.0</td>
          <td>原生支援</td>
      </tr>
      <tr>
          <td>生態鎖定</td>
          <td>Kafka 生態成熟</td>
          <td>GCP 鎖定、跨雲成本高</td>
      </tr>
  </tbody>
</table>
<p>Spotify 的判斷是：在同時進行 GCP 遷移的背景下，維護自管 Kafka 的投資回報比不上切換到託管方案。這個判斷跟 Kafka 本身的能力無關 — Kafka 0.8+ 的可靠性已經解決了 0.7 的問題。決策的關鍵變數是「組織正在往哪走」，不只是「技術上哪個更好」。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>：cross-region replication 跟 MirrorMaker 的進階主題。Spotify 的案例是「早期版本限制」的歷史教訓，Kafka 3.x 的 KRaft + idempotent producer 已解決這些問題。</li>
<li><a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a>：託管 MQ 的定位跟適用場景。</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：exactly-once 語意的工程實踐。Spotify 案例揭露 exactly-once 在早期 Kafka 版本不成立。</li>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 版本跟可靠性保證的關係。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>使用舊版 Kafka（&lt; 2.0）且跨 region replication 的資料完整性無法驗證</li>
<li>Broker restart 後需要人工重啟 producer、運維成本跟 broker 數量成正比</li>
<li>組織正在做基礎設施遷移（on-prem → cloud），考慮是否同步切換 MQ</li>
<li>評估「升級現有系統 vs 遷移到新系統」的決策框架</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2017/03/spotifys-event-delivery-the-road-to-the-cloud-part-ii">Spotify&rsquo;s Event Delivery — The Road to the Cloud (Part II)</a></li>
</ul>
]]></content:encoded></item><item><title>3.C21 Goldman Sachs：MSK 遷移 with MirrorMaker 2</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-goldman-sachs-msk-migration/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-goldman-sachs-msk-migration/</guid><description>&lt;p>這個案例的核心責任是說明 MM2 在 production cutover 的真實 tuning 與 LB 整合 pitfall。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Global Investment Research 把 ~12 microservice / 30 instance 從 on-prem Kafka 遷到 MSK；用 MM2 同步 topic / ACL / consumer group / offset、選擇 atomic cutover、整體耗時 ~7 小時。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>把 MM2 預設的 prefixed topic 改成 identical name；遇到 flush timeout（5s → 30s）、request size、NLB idle timeout 350s vs client 540s 衝突。揭露 managed 服務遷移的細節風險集中在「LB / timeout / topic naming」這些 client 端配置、不在 broker 本身。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：cross-region MirrorMaker / managed broker 遷移 / ACL 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/" data-link-title="3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK" data-link-desc="自管 Kafka 遷移到託管平台時的治理重點。">3.C2 VMware → MSK&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/big-data/how-goldman-sachs-migrated-from-their-on-premises-apache-kafka-cluster-to-amazon-msk/">How Goldman Sachs Migrated from On-Premises Apache Kafka to Amazon MSK&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 MM2 在 production cutover 的真實 tuning 與 LB 整合 pitfall。</p>
<h2 id="觀察">觀察</h2>
<p>Global Investment Research 把 ~12 microservice / 30 instance 從 on-prem Kafka 遷到 MSK；用 MM2 同步 topic / ACL / consumer group / offset、選擇 atomic cutover、整體耗時 ~7 小時。</p>
<h2 id="判讀">判讀</h2>
<p>把 MM2 預設的 prefixed topic 改成 identical name；遇到 flush timeout（5s → 30s）、request size、NLB idle timeout 350s vs client 540s 衝突。揭露 managed 服務遷移的細節風險集中在「LB / timeout / topic naming」這些 client 端配置、不在 broker 本身。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：cross-region MirrorMaker / managed broker 遷移 / ACL 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/vmware-kafka-to-msk/" data-link-title="3.C2 VMware Tanzu CloudHealth：Kafka 轉 Amazon MSK" data-link-desc="自管 Kafka 遷移到託管平台時的治理重點。">3.C2 VMware → MSK</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/big-data/how-goldman-sachs-migrated-from-their-on-premises-apache-kafka-cluster-to-amazon-msk/">How Goldman Sachs Migrated from On-Premises Apache Kafka to Amazon MSK</a></li>
</ul>
]]></content:encoded></item><item><title>3.C22 Trivago：KEDA scale-to-zero by Kafka lag</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/</guid><description>&lt;p>這個案例的核心責任是說明 event-driven workload 該按 backlog 而非 resource usage scale 的設計判準。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Trivago 跨 3 個 region 跑 50+ Kafka sink service、每個 always-on 用 1 CPU + 1 GB；CPU/mem-based autoscaling 無效（sink 多為 I/O bottleneck、CPU 平坦）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>KEDA 以 consumer lag 為 scaling signal、minReplicaCount=0 達到 scale-to-zero、daily replica-hour 從 50 降到 1-2。揭露「resource usage 不等於工作量」、event-driven 場景該看 backlog signal。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Kafka 進階主題：consumer lag / autoscaling / multi-tenant 配額。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tech.trivago.com/post/2026-02-18-from-always-on-to-on-demand-scaling-kafka-sinks-with-keda">From Always-On to On-Demand: Scaling Kafka Sinks with KEDA&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 event-driven workload 該按 backlog 而非 resource usage scale 的設計判準。</p>
<h2 id="觀察">觀察</h2>
<p>Trivago 跨 3 個 region 跑 50+ Kafka sink service、每個 always-on 用 1 CPU + 1 GB；CPU/mem-based autoscaling 無效（sink 多為 I/O bottleneck、CPU 平坦）。</p>
<h2 id="判讀">判讀</h2>
<p>KEDA 以 consumer lag 為 scaling signal、minReplicaCount=0 達到 scale-to-zero、daily replica-hour 從 50 降到 1-2。揭露「resource usage 不等於工作量」、event-driven 場景該看 backlog signal。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Kafka 進階主題：consumer lag / autoscaling / multi-tenant 配額。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://tech.trivago.com/post/2026-02-18-from-always-on-to-on-demand-scaling-kafka-sinks-with-keda">From Always-On to On-Demand: Scaling Kafka Sinks with KEDA</a></li>
</ul>
]]></content:encoded></item><item><title>3.C23 Bloomberg：多租戶 vhost + 自助平台化</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/</guid><description>&lt;p>Bloomberg 的 RabbitMQ 平台化案例揭露了 broker 從幾個團隊的工具演變成上百個團隊的共享基礎設施時，治理責任邊界應該前置設計，而非在規模化之後補救。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Bloomberg 有 5000+ 工程師，內部系統涵蓋金融資料處理、交易系統、新聞分發與分析平台。RabbitMQ 的使用從最初幾個團隊的 microservice 解耦開始，逐步擴展到上百個團隊。到 2019 年，Bloomberg 的 RabbitMQ 基礎設施每週處理超過 2 億條訊息，尖峰每秒數萬條。&lt;/p>
&lt;p>這個規模下，原本由平台團隊手動配置的 queue / exchange / binding 模式無法持續 — 上百個團隊各自有不同的 queue 需求，平台團隊成為所有變更的人工瓶頸。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="多租戶隔離">多租戶隔離&lt;/h3>
&lt;p>多個團隊共用同一個 RabbitMQ cluster 時，一個團隊的 queue 爆量或 consumer 故障可能影響其他團隊的訊息處理。RabbitMQ 的 Erlang scheduler 是共用的 — 一個 queue 的 message accumulation 會消耗 broker 的記憶體跟 CPU，影響同 cluster 上所有 queue 的效能。&lt;/p>
&lt;p>隔離需要在 broker 層實作，client 端的 best practice（限制 message size、設定 TTL）只能降低風險但無法保證隔離。&lt;/p>
&lt;h3 id="自助配置的安全邊界">自助配置的安全邊界&lt;/h3>
&lt;p>讓上百個團隊自助建立 queue / exchange / binding 需要明確的安全邊界 — 團隊 A 能在自己的 namespace 建立資源，但不能存取團隊 B 的 queue。RabbitMQ 的 vhost 機制提供了這個隔離單位，但 vhost 的建立跟權限配置本身需要自動化。&lt;/p>
&lt;h3 id="容量規劃與配額">容量規劃與配額&lt;/h3>
&lt;p>共享 cluster 的容量被所有租戶分攤。沒有配額機制時，一個團隊的 queue 可以無限增長直到 broker 記憶體告警、觸發 flow control、影響全部租戶。配額需要在 queue 層面設定上限（max-length、max-length-bytes），同時提供超出配額時的降級策略而非直接拒絕。&lt;/p>
&lt;h2 id="解法vhost-分層--自助平台">解法：vhost 分層 + 自助平台&lt;/h2>
&lt;h3 id="vhost-作為租戶邊界">Vhost 作為租戶邊界&lt;/h3>
&lt;p>Bloomberg 把 vhost 作為多租戶隔離的基本單位。每個團隊（或每個應用）分配一個 vhost，vhost 內的 queue / exchange / binding 只對該團隊可見。跨 vhost 的訊息傳遞透過 shovel 或 federation plugin，需要顯式配置，預設不互通。&lt;/p>
&lt;p>Vhost 的隔離粒度是「資源可見性 + 權限」而非「硬體資源」。同 cluster 上的 vhost 仍然共用 Erlang runtime 跟記憶體。完全的硬體隔離需要獨立 cluster — Bloomberg 對高敏感度的工作負載（交易相關）使用專用 cluster，一般業務共用大 cluster + vhost 隔離。&lt;/p>
&lt;h3 id="自助-vhost-註冊">自助 vhost 註冊&lt;/h3>
&lt;p>Bloomberg 建立了內部自助平台，團隊透過 API 或內部 portal 申請 vhost。申請時需要提供：應用名稱、預期的 message rate、保留期限、是否需要 HA（mirrored / quorum queue）。平台自動建立 vhost、設定權限、分配連線端點。&lt;/p>
&lt;p>自助流程的價值是去除平台團隊的人工瓶頸。新團隊從申請到拿到可用的 RabbitMQ 端點，時間從「提 ticket 等平台團隊排程」縮短到「填表 → 自動配置 → 立即可用」。&lt;/p>
&lt;h3 id="配額與監控">配額與監控&lt;/h3>
&lt;p>每個 vhost 有預設配額（max-length、max-connections）。超出配額時 broker 行為可配 — drop-head（丟最舊的訊息）或 reject-publish（拒絕新訊息）。配額不是懲罰機制，是保護共享 cluster 的防線。&lt;/p>
&lt;p>監控用 RabbitMQ 的 management plugin + Prometheus exporter，按 vhost 維度匯出 queue depth、message rate、connection count。每個 vhost 的 dashboard 對應到 owner 團隊，讓團隊自行判讀自己的使用狀況。&lt;/p></description><content:encoded><![CDATA[<p>Bloomberg 的 RabbitMQ 平台化案例揭露了 broker 從幾個團隊的工具演變成上百個團隊的共享基礎設施時，治理責任邊界應該前置設計，而非在規模化之後補救。</p>
<h2 id="業務背景">業務背景</h2>
<p>Bloomberg 有 5000+ 工程師，內部系統涵蓋金融資料處理、交易系統、新聞分發與分析平台。RabbitMQ 的使用從最初幾個團隊的 microservice 解耦開始，逐步擴展到上百個團隊。到 2019 年，Bloomberg 的 RabbitMQ 基礎設施每週處理超過 2 億條訊息，尖峰每秒數萬條。</p>
<p>這個規模下，原本由平台團隊手動配置的 queue / exchange / binding 模式無法持續 — 上百個團隊各自有不同的 queue 需求，平台團隊成為所有變更的人工瓶頸。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="多租戶隔離">多租戶隔離</h3>
<p>多個團隊共用同一個 RabbitMQ cluster 時，一個團隊的 queue 爆量或 consumer 故障可能影響其他團隊的訊息處理。RabbitMQ 的 Erlang scheduler 是共用的 — 一個 queue 的 message accumulation 會消耗 broker 的記憶體跟 CPU，影響同 cluster 上所有 queue 的效能。</p>
<p>隔離需要在 broker 層實作，client 端的 best practice（限制 message size、設定 TTL）只能降低風險但無法保證隔離。</p>
<h3 id="自助配置的安全邊界">自助配置的安全邊界</h3>
<p>讓上百個團隊自助建立 queue / exchange / binding 需要明確的安全邊界 — 團隊 A 能在自己的 namespace 建立資源，但不能存取團隊 B 的 queue。RabbitMQ 的 vhost 機制提供了這個隔離單位，但 vhost 的建立跟權限配置本身需要自動化。</p>
<h3 id="容量規劃與配額">容量規劃與配額</h3>
<p>共享 cluster 的容量被所有租戶分攤。沒有配額機制時，一個團隊的 queue 可以無限增長直到 broker 記憶體告警、觸發 flow control、影響全部租戶。配額需要在 queue 層面設定上限（max-length、max-length-bytes），同時提供超出配額時的降級策略而非直接拒絕。</p>
<h2 id="解法vhost-分層--自助平台">解法：vhost 分層 + 自助平台</h2>
<h3 id="vhost-作為租戶邊界">Vhost 作為租戶邊界</h3>
<p>Bloomberg 把 vhost 作為多租戶隔離的基本單位。每個團隊（或每個應用）分配一個 vhost，vhost 內的 queue / exchange / binding 只對該團隊可見。跨 vhost 的訊息傳遞透過 shovel 或 federation plugin，需要顯式配置，預設不互通。</p>
<p>Vhost 的隔離粒度是「資源可見性 + 權限」而非「硬體資源」。同 cluster 上的 vhost 仍然共用 Erlang runtime 跟記憶體。完全的硬體隔離需要獨立 cluster — Bloomberg 對高敏感度的工作負載（交易相關）使用專用 cluster，一般業務共用大 cluster + vhost 隔離。</p>
<h3 id="自助-vhost-註冊">自助 vhost 註冊</h3>
<p>Bloomberg 建立了內部自助平台，團隊透過 API 或內部 portal 申請 vhost。申請時需要提供：應用名稱、預期的 message rate、保留期限、是否需要 HA（mirrored / quorum queue）。平台自動建立 vhost、設定權限、分配連線端點。</p>
<p>自助流程的價值是去除平台團隊的人工瓶頸。新團隊從申請到拿到可用的 RabbitMQ 端點，時間從「提 ticket 等平台團隊排程」縮短到「填表 → 自動配置 → 立即可用」。</p>
<h3 id="配額與監控">配額與監控</h3>
<p>每個 vhost 有預設配額（max-length、max-connections）。超出配額時 broker 行為可配 — drop-head（丟最舊的訊息）或 reject-publish（拒絕新訊息）。配額不是懲罰機制，是保護共享 cluster 的防線。</p>
<p>監控用 RabbitMQ 的 management plugin + Prometheus exporter，按 vhost 維度匯出 queue depth、message rate、connection count。每個 vhost 的 dashboard 對應到 owner 團隊，讓團隊自行判讀自己的使用狀況。</p>
<h2 id="取捨">取捨</h2>
<p><strong>Vhost 隔離 vs 硬體隔離</strong>：vhost 隔離成本低（不需要額外 cluster），但隔離程度有限 — Erlang scheduler 跟記憶體仍然共用。Bloomberg 的做法是多數團隊用 vhost 隔離、高敏感度工作負載用專用 cluster，兩者共存。</p>
<p><strong>自助配置 vs 中央管控</strong>：自助配置加速團隊迭代，但也增加了 configuration drift 的風險。Bloomberg 透過配額跟自動化審計（定期掃描 vhost 的 queue 狀態、alert 異常 pattern）平衡自助跟管控。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 的多租戶治理責任</li>
<li><a href="/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">3.C6 Uber Kafka 平台</a>：Kafka 生態的多租戶治理比較 — Kafka 用 topic-level ACL + quota，RabbitMQ 用 vhost</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：平台團隊跟服務團隊的 ownership 邊界</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>以下訊號出現時，應該回讀本案例：</p>
<ul>
<li>RabbitMQ 的使用團隊數從個位數增長到雙位數、平台團隊成為配置瓶頸</li>
<li>單一 cluster 上的 queue 數量超過數百個、owner 不明</li>
<li>某個團隊的 queue 爆量影響了其他團隊的 consumer 效能</li>
<li>新團隊要用 RabbitMQ 但平台團隊的 ticket 要排隊數天</li>
<li>沒有 per-team 的 message rate 或 queue depth 監控</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cloudamqp.com/blog/growing-a-farm-of-rabbits.html">Growing a Farm of Rabbits at Bloomberg</a></li>
</ul>
]]></content:encoded></item><item><title>3.C24 SoundCloud：AMQP fan-out 音訊處理 pipeline</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-soundcloud-fanout-audio/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-soundcloud-fanout-audio/</guid><description>&lt;p>這個案例的核心責任是說明 fan-out 處理 pipeline 該按處理類型拆隊列、不該共用 queue。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>上傳音訊後用 RabbitMQ 觸發 transcode + 波形圖 + follower 通知。當 Skrillex 等大號上傳時、要避免同步寫 Cassandra 千萬次。每秒 20-30,000 條 persistent message。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不同處理類型分開隊列、各自獨立 scale。揭露 fan-out 不是「broadcast 同一份工作」、而是「同事件觸發多種獨立 pipeline」、每種 pipeline 的 throughput / latency 要求不同。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Prefetch + consumer 併發 / classic queue vs Streams（log fan-out 場景）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blogs.vmware.com/tanzu/scaling-with-rabbitmq-soundcloud">Scaling with RabbitMQ at SoundCloud (VMware Tanzu)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.infoq.com/presentations/amqp-soundcloud/">AMQP at SoundCloud (InfoQ)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 fan-out 處理 pipeline 該按處理類型拆隊列、不該共用 queue。</p>
<h2 id="觀察">觀察</h2>
<p>上傳音訊後用 RabbitMQ 觸發 transcode + 波形圖 + follower 通知。當 Skrillex 等大號上傳時、要避免同步寫 Cassandra 千萬次。每秒 20-30,000 條 persistent message。</p>
<h2 id="判讀">判讀</h2>
<p>不同處理類型分開隊列、各自獨立 scale。揭露 fan-out 不是「broadcast 同一份工作」、而是「同事件觸發多種獨立 pipeline」、每種 pipeline 的 throughput / latency 要求不同。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Prefetch + consumer 併發 / classic queue vs Streams（log fan-out 場景）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blogs.vmware.com/tanzu/scaling-with-rabbitmq-soundcloud">Scaling with RabbitMQ at SoundCloud (VMware Tanzu)</a></li>
<li><a href="https://www.infoq.com/presentations/amqp-soundcloud/">AMQP at SoundCloud (InfoQ)</a></li>
</ul>
]]></content:encoded></item><item><title>3.C25 Indeed：Delay queue + DLQ 三層 escalation</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-indeed-delay-dlq-escalation/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-indeed-delay-dlq-escalation/</guid><description>&lt;p>這個案例的核心責任是說明 retry 策略要跟 queue 拓樸結合設計，分層延遲 + &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ&lt;/a> 的三層 escalation 能避免 head-of-line blocking。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Indeed 是全球最大的求職搜尋引擎之一，每天處理 35M+ 筆職缺資料的索引、更新與推送。職缺資料從雇主端進入系統後，需要經過解析、標準化、索引、推送到搜尋引擎等多個處理步驟，每個步驟由 RabbitMQ 串接的 consumer 處理。&lt;/p>
&lt;p>這個規模下，任何一個處理步驟的暫時失敗（downstream service timeout、資料格式異常、外部 API rate limit）都會產生需要 retry 的訊息。每天有數十萬筆訊息需要至少一次 retry。&lt;/p>
&lt;h2 id="技術挑戰head-of-line-blocking">技術挑戰：Head-of-line blocking&lt;/h2>
&lt;p>Indeed 原本的 retry 策略是 consumer 處理失敗時把訊息 requeue（&lt;code>basic.nack&lt;/code> with &lt;code>requeue=true&lt;/code>）。RabbitMQ 的 requeue 行為是把訊息放回 queue 的 head — 下一次 consumer 拿到的還是這條失敗的訊息。&lt;/p>
&lt;p>當一條訊息因為 downstream timeout 反覆失敗時，它會持續佔住 queue head，阻塞後面所有等待處理的訊息。單一 consumer 的時間被一條失敗訊息反覆消耗，其他正常的訊息延遲累積。在 35M+ 筆/天的吞吐量下，一條 head-of-line blocking 訊息就能讓整個 pipeline 的 processing lag 從秒級升到分鐘級。&lt;/p>
&lt;p>這個問題的根源是 retry 策略跟 queue 拓樸耦合在一起 — requeue 把 retry 決策留在同一個 queue 裡，讓失敗訊息跟正常訊息搶同一條通道。&lt;/p>
&lt;h2 id="解法三層-escalation">解法：三層 escalation&lt;/h2>
&lt;p>Indeed 設計了一個三層 escalation 模型，把失敗訊息依嚴重程度逐層隔離：&lt;/p>
&lt;h3 id="第一層immediate-retry同-queue">第一層：Immediate retry（同 queue）&lt;/h3>
&lt;p>Consumer 處理失敗時，先在 client 端做短暫 backoff（數百毫秒到數秒），然後 ack 原訊息、重新 publish 到同一個 queue 的 tail（而非 requeue 到 head）。&lt;/p>
&lt;p>這層處理的是暫態錯誤 — downstream 偶發的 500、短暫的 network hiccup。多數訊息在第一層就能恢復。重新 publish 到 tail 確保失敗訊息排在正常訊息後面，不阻塞其他訊息。&lt;/p>
&lt;h3 id="第二層delay-queue">第二層：Delay queue&lt;/h3>
&lt;p>第一層 retry N 次仍然失敗的訊息，透過 RabbitMQ 的 Dead Letter Exchange（DLX）路由到 delay queue。Delay queue 用 &lt;code>x-message-ttl&lt;/code> 設定延遲時間（例如 30 秒、1 分鐘、5 分鐘），TTL 到期後訊息透過另一個 DLX 路由回原始 queue 的 tail。&lt;/p>
&lt;p>Indeed 用多個不同 TTL 的 delay queue 實作 exponential backoff — 第一次進 delay 等 30 秒、第二次等 1 分鐘、第三次等 5 分鐘。這個做法利用 RabbitMQ 原生的 DLX + TTL 機制，不需要額外的 scheduler 或 cron job。&lt;/p>
&lt;p>這層處理的是持續性錯誤 — downstream 在做 deployment、外部 API 在做 maintenance。延遲重試讓 downstream 有時間恢復，同時失敗訊息完全離開主 queue、不影響正常處理。&lt;/p>
&lt;h3 id="第三層dead-letter-queue">第三層：Dead Letter Queue&lt;/h3>
&lt;p>Delay queue retry M 次後仍然失敗的訊息進入 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ&lt;/a>。DLQ 中的訊息不再自動重試，需要人工審視或批次 replay。&lt;/p></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 retry 策略要跟 queue 拓樸結合設計，分層延遲 + <a href="/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ</a> 的三層 escalation 能避免 head-of-line blocking。</p>
<h2 id="業務背景">業務背景</h2>
<p>Indeed 是全球最大的求職搜尋引擎之一，每天處理 35M+ 筆職缺資料的索引、更新與推送。職缺資料從雇主端進入系統後，需要經過解析、標準化、索引、推送到搜尋引擎等多個處理步驟，每個步驟由 RabbitMQ 串接的 consumer 處理。</p>
<p>這個規模下，任何一個處理步驟的暫時失敗（downstream service timeout、資料格式異常、外部 API rate limit）都會產生需要 retry 的訊息。每天有數十萬筆訊息需要至少一次 retry。</p>
<h2 id="技術挑戰head-of-line-blocking">技術挑戰：Head-of-line blocking</h2>
<p>Indeed 原本的 retry 策略是 consumer 處理失敗時把訊息 requeue（<code>basic.nack</code> with <code>requeue=true</code>）。RabbitMQ 的 requeue 行為是把訊息放回 queue 的 head — 下一次 consumer 拿到的還是這條失敗的訊息。</p>
<p>當一條訊息因為 downstream timeout 反覆失敗時，它會持續佔住 queue head，阻塞後面所有等待處理的訊息。單一 consumer 的時間被一條失敗訊息反覆消耗，其他正常的訊息延遲累積。在 35M+ 筆/天的吞吐量下，一條 head-of-line blocking 訊息就能讓整個 pipeline 的 processing lag 從秒級升到分鐘級。</p>
<p>這個問題的根源是 retry 策略跟 queue 拓樸耦合在一起 — requeue 把 retry 決策留在同一個 queue 裡，讓失敗訊息跟正常訊息搶同一條通道。</p>
<h2 id="解法三層-escalation">解法：三層 escalation</h2>
<p>Indeed 設計了一個三層 escalation 模型，把失敗訊息依嚴重程度逐層隔離：</p>
<h3 id="第一層immediate-retry同-queue">第一層：Immediate retry（同 queue）</h3>
<p>Consumer 處理失敗時，先在 client 端做短暫 backoff（數百毫秒到數秒），然後 ack 原訊息、重新 publish 到同一個 queue 的 tail（而非 requeue 到 head）。</p>
<p>這層處理的是暫態錯誤 — downstream 偶發的 500、短暫的 network hiccup。多數訊息在第一層就能恢復。重新 publish 到 tail 確保失敗訊息排在正常訊息後面，不阻塞其他訊息。</p>
<h3 id="第二層delay-queue">第二層：Delay queue</h3>
<p>第一層 retry N 次仍然失敗的訊息，透過 RabbitMQ 的 Dead Letter Exchange（DLX）路由到 delay queue。Delay queue 用 <code>x-message-ttl</code> 設定延遲時間（例如 30 秒、1 分鐘、5 分鐘），TTL 到期後訊息透過另一個 DLX 路由回原始 queue 的 tail。</p>
<p>Indeed 用多個不同 TTL 的 delay queue 實作 exponential backoff — 第一次進 delay 等 30 秒、第二次等 1 分鐘、第三次等 5 分鐘。這個做法利用 RabbitMQ 原生的 DLX + TTL 機制，不需要額外的 scheduler 或 cron job。</p>
<p>這層處理的是持續性錯誤 — downstream 在做 deployment、外部 API 在做 maintenance。延遲重試讓 downstream 有時間恢復，同時失敗訊息完全離開主 queue、不影響正常處理。</p>
<h3 id="第三層dead-letter-queue">第三層：Dead Letter Queue</h3>
<p>Delay queue retry M 次後仍然失敗的訊息進入 <a href="/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">DLQ</a>。DLQ 中的訊息不再自動重試，需要人工審視或批次 replay。</p>
<p>DLQ 的價值是把「目前無法處理」的訊息安全保存，不讓它們無限消耗 retry 資源。Indeed 的維運團隊定期檢查 DLQ 中的訊息 — 按 error type 分群、判斷是 bug（需要修 code 再 replay）還是資料問題（需要修正資料再 replay）。</p>
<h2 id="取捨">取捨</h2>
<p><strong>犧牲的是 delivery order</strong>。訊息從 delay queue 回到主 queue tail 時，已經不在原始的位置。對 Indeed 的職缺處理來說，order 不影響正確性 — 職缺更新是 idempotent 的，最終狀態正確即可。對 order-sensitive 的場景，這個模型需要額外的 ordering 機制。</p>
<p><strong>增加的是拓樸複雜度</strong>。三層 escalation 涉及主 queue + 多個 delay queue + DLQ + 多個 DLX 的 binding。RabbitMQ 的 exchange / queue / binding 組合需要明確規劃跟文件化，否則維運時搞不清楚訊息的路由路徑。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>：DLX + TTL 是 RabbitMQ 原生的 durable 機制</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：retry 策略跟 consumer 的 ack/nack 行為</li>
<li><a href="/blog/backend/03-message-queue/vendors/rabbitmq/dlq-retry-escalation/" data-link-title="RabbitMQ DLQ 與分層 retry：別把失敗訊息 requeue 回隊首" data-link-desc="RabbitMQ 處理失敗訊息最常見的錯是直接 requeue 回原隊列——它回到隊首、反覆失敗、把後面的訊息全卡住（head-of-line blocking）。正解是用 dead-letter exchange &#43; TTL 組出 work → delay → DLQ 的分層 escalation。本文展開 DLX 求值模型、實機驗證的三層拓樸、5 個把 retry 寫成無限迴圈與隊列阻塞的 production 踩坑，以及 retry 拓樸的容量邊界">RabbitMQ DLQ retry escalation</a>：DLX 配置的實作細節</li>
<li><a href="/blog/backend/03-message-queue/cases/uber-kafka-infrastructure-evolution/" data-link-title="3.C6 Uber：Kafka 事件平台演進" data-link-desc="事件平台從團隊自管走向多租戶共享基礎設施。">3.C6 Uber Kafka 平台</a>：Kafka 生態的 retry topic 跟 DLQ 設計比較</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>以下訊號出現時，應該回讀本案例：</p>
<ul>
<li>Consumer 的 processing lag 在特定時段突然升高、但訊息產生速率沒變</li>
<li>同一條訊息的 retry 佔據 consumer 的大部分處理時間</li>
<li>Requeue 後訊息立刻又被同一個 consumer 取到、進入 retry 迴圈</li>
<li>DLQ 中的訊息堆積、沒有定期審視跟 replay 的機制</li>
<li>Retry 策略只有 client 端 backoff、沒有 queue 拓樸層面的隔離</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.indeedblog.com/blog/2017/06/delaying-messages/">Delaying Messages with RabbitMQ at Indeed</a></li>
<li><a href="https://engineering.indeedblog.com/talks/get-job-35-million-times-day-using-rabbitmq/">Get a Job 35 Million Times a Day Using RabbitMQ (talk)</a></li>
</ul>
]]></content:encoded></item><item><title>3.C26 GoCardless：Hutch + 單一 topic exchange service mesh</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/</guid><description>&lt;p>這個案例的核心責任是說明小規模時單 vhost + 統一 routing key 規範可作為 service mesh 基礎。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>單一 RabbitMQ cluster 作為所有服務之間的通訊中樞、自家 Hutch（Ruby lib）2013 從 production 抽出開源。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>routing key 格式 &lt;code>service.subject.action&lt;/code>（如 &lt;code>paysvc.payment.chargedback&lt;/code>）、單一 topic exchange、JSON 序列化（多語言可讀）。揭露小規模單 cluster 可以用「routing key 命名規範」取代複雜 exchange 拓樸。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Exchange types 與 routing 設計 / 多 vhost（單 vhost 服務 mesh 的反向案例）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &amp;#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg&lt;/a>（規模化後的對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://gocardless.com/blog/hutch-inter-service-communication-with-rabbitmq/">Hutch: Inter-Service Communication with RabbitMQ&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明小規模時單 vhost + 統一 routing key 規範可作為 service mesh 基礎。</p>
<h2 id="觀察">觀察</h2>
<p>單一 RabbitMQ cluster 作為所有服務之間的通訊中樞、自家 Hutch（Ruby lib）2013 從 production 抽出開源。</p>
<h2 id="判讀">判讀</h2>
<p>routing key 格式 <code>service.subject.action</code>（如 <code>paysvc.payment.chargedback</code>）、單一 topic exchange、JSON 序列化（多語言可讀）。揭露小規模單 cluster 可以用「routing key 命名規範」取代複雜 exchange 拓樸。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Exchange types 與 routing 設計 / 多 vhost（單 vhost 服務 mesh 的反向案例）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg</a>（規模化後的對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://gocardless.com/blog/hutch-inter-service-communication-with-rabbitmq/">Hutch: Inter-Service Communication with RabbitMQ</a></li>
</ul>
]]></content:encoded></item><item><title>3.C27 Zalando：RabbitMQ on AWS 自動化 master selection</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-zalando-aws-master-selection/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-zalando-aws-master-selection/</guid><description>&lt;p>這個案例的核心責任是說明雲端 cluster 治理在 K8s 之前的工程模式。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Communication platform 用 RabbitMQ cluster、跑在 EC2 / Docker container 上、用 supervisord 並行 sidekick + RabbitMQ。AWS 帳號限制每 region 5 個 Elastic IP。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 sidekick 服務查 AWS API 動態識別 cluster、指定最老 instance 當 master、master 死後晉升下一個最老 node。跨版本升級用 federation 上游接到新 cluster 過渡。揭露「cluster master selection」跟「IP 限制」是雲端部署的早期關鍵限制。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Erlang clustering + network partition / Federation + Shovel / RabbitMQ Cluster Operator（K8s 之前的雲端 cluster 治理）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.zalando.com/posts/2018/02/rabbit-in-the-cloud.html">Rabbit in the Cloud (Zalando Engineering)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明雲端 cluster 治理在 K8s 之前的工程模式。</p>
<h2 id="觀察">觀察</h2>
<p>Communication platform 用 RabbitMQ cluster、跑在 EC2 / Docker container 上、用 supervisord 並行 sidekick + RabbitMQ。AWS 帳號限制每 region 5 個 Elastic IP。</p>
<h2 id="判讀">判讀</h2>
<p>自建 sidekick 服務查 AWS API 動態識別 cluster、指定最老 instance 當 master、master 死後晉升下一個最老 node。跨版本升級用 federation 上游接到新 cluster 過渡。揭露「cluster master selection」跟「IP 限制」是雲端部署的早期關鍵限制。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Erlang clustering + network partition / Federation + Shovel / RabbitMQ Cluster Operator（K8s 之前的雲端 cluster 治理）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.zalando.com/posts/2018/02/rabbit-in-the-cloud.html">Rabbit in the Cloud (Zalando Engineering)</a></li>
</ul>
]]></content:encoded></item><item><title>3.C28 WeWork：Consistent hash exchange 保證帳戶順序</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-consistent-hash-ordering/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-consistent-hash-ordering/</guid><description>&lt;p>這個案例的核心責任是說明 RabbitMQ 也能做「per-key ordering」、用 consistent hash exchange 模擬 partition。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>訊息順序對某些業務流程關鍵、但全局排序代價高。WeWork 採固定數量 queue + 用 account ID hash 路由到特定 queue。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>每個 queue 一個 SideKiq worker + exclusive consumer 保證單帳戶順序。文後發現 RabbitMQ Consistent Hashing plugin 已內建類似機制（類似 Kafka 分區）。揭露 partition-level ordering 不是 Kafka 專屬、在 broker model 可用 hash exchange 達成。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Exchange types / Prefetch + consumer 併發（partition-level ordering 模式）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>（partition + key 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.cloudamqp.com/blog/weworks-good-enough-order%20guarantee.html">WeWork&amp;rsquo;s &amp;ldquo;Good Enough&amp;rdquo; Order Guarantee&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 RabbitMQ 也能做「per-key ordering」、用 consistent hash exchange 模擬 partition。</p>
<h2 id="觀察">觀察</h2>
<p>訊息順序對某些業務流程關鍵、但全局排序代價高。WeWork 採固定數量 queue + 用 account ID hash 路由到特定 queue。</p>
<h2 id="判讀">判讀</h2>
<p>每個 queue 一個 SideKiq worker + exclusive consumer 保證單帳戶順序。文後發現 RabbitMQ Consistent Hashing plugin 已內建類似機制（類似 Kafka 分區）。揭露 partition-level ordering 不是 Kafka 專屬、在 broker model 可用 hash exchange 達成。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Exchange types / Prefetch + consumer 併發（partition-level ordering 模式）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>（partition + key 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.cloudamqp.com/blog/weworks-good-enough-order%20guarantee.html">WeWork&rsquo;s &ldquo;Good Enough&rdquo; Order Guarantee</a></li>
</ul>
]]></content:encoded></item><item><title>3.C29 WeWork：Bunny + Puma 多執行緒 channel pool</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-bunny-channel-pool/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wework-bunny-channel-pool/</guid><description>&lt;p>這個案例的核心責任是說明 AMQP client 的 connection / channel 邊界跟執行緒模型緊密耦合。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>從 Unicorn 切到 Puma 後遇到 &lt;code>ConnectionClosedError&lt;/code>、根因是快取 Bunny channel 在多執行緒間共享。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>AMQP channel 不應跨執行緒共用、改用 &lt;code>connection_pool&lt;/code> gem 管理 channel pool。揭露 AMQP 不是 stateless HTTP-style client、channel 是 statefull 物件、多 thread 模型要特別處理。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Prefetch + consumer 併發（client library 層的 connection / channel 邊界）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://wework.github.io/ruby/rails/bunny/rabbitmq/threads/concurrency/puma/errors/2015/11/12/bunny-threads/">Bunny Threads in Puma at WeWork&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 AMQP client 的 connection / channel 邊界跟執行緒模型緊密耦合。</p>
<h2 id="觀察">觀察</h2>
<p>從 Unicorn 切到 Puma 後遇到 <code>ConnectionClosedError</code>、根因是快取 Bunny channel 在多執行緒間共享。</p>
<h2 id="判讀">判讀</h2>
<p>AMQP channel 不應跨執行緒共用、改用 <code>connection_pool</code> gem 管理 channel pool。揭露 AMQP 不是 stateless HTTP-style client、channel 是 statefull 物件、多 thread 模型要特別處理。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Prefetch + consumer 併發（client library 層的 connection / channel 邊界）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://wework.github.io/ruby/rails/bunny/rabbitmq/threads/concurrency/puma/errors/2015/11/12/bunny-threads/">Bunny Threads in Puma at WeWork</a></li>
</ul>
]]></content:encoded></item><item><title>3.C30 Runtastic：Mirrored queue 網路負載瓶頸</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-runtastic-mirrored-queue-bottleneck/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-runtastic-mirrored-queue-bottleneck/</guid><description>&lt;p>Runtastic 的案例暴露了 RabbitMQ mirrored queue 的網路成本被嚴重低估。Mirrored queue 的可靠性提升代價是 message 在 cluster 內的網路複製量跟 mirror 數成正比，而這個成本在日常流量下可能不可見、只在壓力測試或突發流量時才暴露。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Runtastic 是 Adidas 旗下的健身追蹤平台，使用者透過 app 記錄跑步、騎車、重訓等運動資料。2020 年 COVID-19 lockdown 期間，居家運動需求爆增，平台的 concurrent user 數量在數週內翻倍。&lt;/p>
&lt;p>Runtastic 的後端架構是 microservice 架構，RabbitMQ 是服務間訊息傳遞的核心。運動資料記錄、通知推送、社交功能（好友排行、挑戰）、analytics 事件都透過 RabbitMQ 的 queue 串接。&lt;/p>
&lt;h2 id="技術挑戰mirroring-的隱藏網路成本">技術挑戰：Mirroring 的隱藏網路成本&lt;/h2>
&lt;p>Runtastic 的 RabbitMQ cluster 使用 mirrored queue（&lt;code>ha-mode: all&lt;/code>）確保訊息在 broker 故障時不遺失。Mirrored queue 把每條訊息同步複製到 cluster 中所有 node — 3 node cluster 代表每條訊息的網路傳輸量是原始大小的 3 倍。&lt;/p>
&lt;p>日常流量下，mirroring 的額外網路負載在 cluster 的頻寬容量之內，效能影響不明顯。但 lockdown 後流量翻倍時，mirroring 的網路負載跟著翻倍 — 更準確地說是翻 2×N 倍（流量 2 倍 × mirror 數 N）。&lt;/p>
&lt;p>Runtastic 的 cluster 使用了共享的網路元件（network switch / load balancer），mirroring 的流量把共享網路元件的頻寬壓到極限。表現是 broker 間的 mirroring 延遲上升 → publisher confirm 延遲上升 → producer 端的 publish latency 從毫秒跳到秒級 → 上游服務開始 timeout。&lt;/p>
&lt;p>問題的隱蔽性在於：日常監控只看 broker 的 CPU、memory、disk，沒有把 inter-node network throughput 作為關鍵指標。網路瓶頸在 broker-level metric 上的表現是「publish confirm 變慢」，容易被誤判為 broker 過載而非網路飽和。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="performance-test-定位瓶頸">Performance test 定位瓶頸&lt;/h3>
&lt;p>Runtastic 在事件發生後用 performance test 重現問題。測試揭露了 mirroring 流量跟 broker 間網路頻寬的關係 — 把 message rate 從日常的 X 推到 2X 時，inter-node traffic 超過 switch 容量，publish confirm latency 開始非線性增長。&lt;/p>
&lt;p>Performance test 的關鍵是把 inter-node network throughput 加入監控維度。RabbitMQ 3.8 的 Prometheus integration 提供了 &lt;code>rabbitmq_raft_term_total&lt;/code>、&lt;code>rabbitmq_channel_messages_published_total&lt;/code> 等指標，但 inter-node bandwidth 需要從 OS 層（&lt;code>node_exporter&lt;/code> 的 network bytes）或 switch 層取得。&lt;/p>
&lt;h3 id="調整-mirroring-配置">調整 mirroring 配置&lt;/h3>
&lt;p>Runtastic 從 &lt;code>ha-mode: all&lt;/code>（所有 node 都 mirror）調整為 &lt;code>ha-mode: exactly, ha-params: 2&lt;/code>（只 mirror 到 2 個 node）。這把每條訊息的網路複製量從 N 倍降到 2 倍，在可靠性（2 個 copy 可以容忍 1 node failure）跟網路成本之間取得平衡。&lt;/p></description><content:encoded><![CDATA[<p>Runtastic 的案例暴露了 RabbitMQ mirrored queue 的網路成本被嚴重低估。Mirrored queue 的可靠性提升代價是 message 在 cluster 內的網路複製量跟 mirror 數成正比，而這個成本在日常流量下可能不可見、只在壓力測試或突發流量時才暴露。</p>
<h2 id="業務背景">業務背景</h2>
<p>Runtastic 是 Adidas 旗下的健身追蹤平台，使用者透過 app 記錄跑步、騎車、重訓等運動資料。2020 年 COVID-19 lockdown 期間，居家運動需求爆增，平台的 concurrent user 數量在數週內翻倍。</p>
<p>Runtastic 的後端架構是 microservice 架構，RabbitMQ 是服務間訊息傳遞的核心。運動資料記錄、通知推送、社交功能（好友排行、挑戰）、analytics 事件都透過 RabbitMQ 的 queue 串接。</p>
<h2 id="技術挑戰mirroring-的隱藏網路成本">技術挑戰：Mirroring 的隱藏網路成本</h2>
<p>Runtastic 的 RabbitMQ cluster 使用 mirrored queue（<code>ha-mode: all</code>）確保訊息在 broker 故障時不遺失。Mirrored queue 把每條訊息同步複製到 cluster 中所有 node — 3 node cluster 代表每條訊息的網路傳輸量是原始大小的 3 倍。</p>
<p>日常流量下，mirroring 的額外網路負載在 cluster 的頻寬容量之內，效能影響不明顯。但 lockdown 後流量翻倍時，mirroring 的網路負載跟著翻倍 — 更準確地說是翻 2×N 倍（流量 2 倍 × mirror 數 N）。</p>
<p>Runtastic 的 cluster 使用了共享的網路元件（network switch / load balancer），mirroring 的流量把共享網路元件的頻寬壓到極限。表現是 broker 間的 mirroring 延遲上升 → publisher confirm 延遲上升 → producer 端的 publish latency 從毫秒跳到秒級 → 上游服務開始 timeout。</p>
<p>問題的隱蔽性在於：日常監控只看 broker 的 CPU、memory、disk，沒有把 inter-node network throughput 作為關鍵指標。網路瓶頸在 broker-level metric 上的表現是「publish confirm 變慢」，容易被誤判為 broker 過載而非網路飽和。</p>
<h2 id="解法">解法</h2>
<h3 id="performance-test-定位瓶頸">Performance test 定位瓶頸</h3>
<p>Runtastic 在事件發生後用 performance test 重現問題。測試揭露了 mirroring 流量跟 broker 間網路頻寬的關係 — 把 message rate 從日常的 X 推到 2X 時，inter-node traffic 超過 switch 容量，publish confirm latency 開始非線性增長。</p>
<p>Performance test 的關鍵是把 inter-node network throughput 加入監控維度。RabbitMQ 3.8 的 Prometheus integration 提供了 <code>rabbitmq_raft_term_total</code>、<code>rabbitmq_channel_messages_published_total</code> 等指標，但 inter-node bandwidth 需要從 OS 層（<code>node_exporter</code> 的 network bytes）或 switch 層取得。</p>
<h3 id="調整-mirroring-配置">調整 mirroring 配置</h3>
<p>Runtastic 從 <code>ha-mode: all</code>（所有 node 都 mirror）調整為 <code>ha-mode: exactly, ha-params: 2</code>（只 mirror 到 2 個 node）。這把每條訊息的網路複製量從 N 倍降到 2 倍，在可靠性（2 個 copy 可以容忍 1 node failure）跟網路成本之間取得平衡。</p>
<p>對可靠性要求最高的 queue（交易相關），維持 <code>ha-mode: all</code> 但把這些 queue 移到頻寬更高的專屬 network segment。</p>
<h3 id="遷移到-quorum-queue-的動機">遷移到 Quorum queue 的動機</h3>
<p>Mirrored queue 的另一個問題是同步機制 — 新 mirror 加入時需要全量同步（sync），sync 期間 queue 可能暫停接受新訊息。RabbitMQ 3.8 引入的 Quorum queue 用 Raft consensus 取代 mirrored queue 的 GM（Guaranteed Multicast），在網路效率跟故障恢復上都有改進。</p>
<p>Runtastic 的案例是「為什麼應該評估從 mirrored queue 遷到 quorum queue」的典型動機 — mirrored queue 的網路成本跟同步行為在規模化時成為瓶頸。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>ha-mode: all</th>
          <th>ha-mode: exactly 2</th>
          <th>Quorum queue</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>網路成本</td>
          <td>每條訊息 × N node</td>
          <td>每條訊息 × 2 node</td>
          <td>每條訊息 × majority</td>
      </tr>
      <tr>
          <td>可容忍的故障</td>
          <td>N-1 node failure</td>
          <td>1 node failure</td>
          <td>minority node failure</td>
      </tr>
      <tr>
          <td>新 node 加入</td>
          <td>全量同步（可能暫停 queue）</td>
          <td>全量同步（影響面小）</td>
          <td>Raft log replay（漸進）</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>小 cluster、低流量</td>
          <td>中 cluster、中流量</td>
          <td>中大 cluster、推薦路徑</td>
      </tr>
  </tbody>
</table>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 的 replication 跟 network 成本的關係</li>
<li><a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a>：mirrored queue vs quorum queue 的詳細比較</li>
<li><a href="/blog/backend/03-message-queue/vendors/rabbitmq/queue-types-classic-quorum-stream/" data-link-title="RabbitMQ Queue Type 選型：Classic、Quorum、Stream 的責任邊界與容量取捨" data-link-desc="RabbitMQ 3.x 三種 queue type 的選型 deep article — classic queue（mirrored 已 deprecated）、quorum queue（Raft 一致性、取代 mirrored）、stream（3.9&#43; append-only log、可重複消費）。涵蓋三種模型在 throughput / retention / replay / 記憶體成本的判讀、宣告語意差異（實機驗證）、4 個 production 故障演練（mirrored 網路放大 / quorum loss / stream retention 超量 / classic→quorum in-flight message），與容量規劃。">RabbitMQ queue types</a>：Classic / Mirrored / Quorum / Stream 四種 queue type 的取捨</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a>：broker 的 inter-node 網路作為 pipeline 健康指標</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>以下訊號出現時，應該回讀本案例：</p>
<ul>
<li>RabbitMQ cluster 使用 <code>ha-mode: all</code> 且 node 數量 &gt; 3</li>
<li>Publish confirm latency 在流量上升時非線性增長</li>
<li>Broker 的 CPU / memory / disk 指標正常但 publish 變慢</li>
<li>Broker 間的 network traffic 佔比超過 cluster 總頻寬的 50%</li>
<li>新 mirror 加入時 queue 出現暫停或大量延遲</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://seventhstate.io/portfolio/portfolio-runtastic/">Runtastic RabbitMQ Performance Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>3.C31 Mozilla Pulse：命名前綴 + ACL 取代 vhost 多租戶</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-mozilla-pulse-naming-isolation/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-mozilla-pulse-naming-isolation/</guid><description>&lt;p>這個案例的核心責任是說明多租戶隔離可用「ACL + naming convention」取代 vhost、適合社群協作場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pulse 是 Mozilla 自動化 / 基礎設施工具間的 managed RabbitMQ cluster、用 AMQP 0-9-1 + RabbitMQ 擴充、由 CloudAMQP 託管於 pulse.mozilla.org:5671（AMQP over TLS）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>技術上不需 vhost、改用權限限制 + 命名前綴（&lt;code>exchange/&amp;lt;username&amp;gt;/*&lt;/code>、&lt;code>queue/&amp;lt;username&amp;gt;/*&lt;/code>）做隔離。PulseGuardian 跑在 Heroku 管理使用者 / queue / exchange。揭露多租戶隔離不一定要 vhost、權限粒度可以拉到 resource naming 層。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：多 vhost + 多租戶（反向案例：用 ACL + naming 取代 vhost）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &amp;#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg vhost 多租戶&lt;/a>（對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://wiki.mozilla.org/Auto-tools/Projects/Pulse">Mozilla Pulse Wiki&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pulse.mozilla.org/api/">Pulse API&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明多租戶隔離可用「ACL + naming convention」取代 vhost、適合社群協作場景。</p>
<h2 id="觀察">觀察</h2>
<p>Pulse 是 Mozilla 自動化 / 基礎設施工具間的 managed RabbitMQ cluster、用 AMQP 0-9-1 + RabbitMQ 擴充、由 CloudAMQP 託管於 pulse.mozilla.org:5671（AMQP over TLS）。</p>
<h2 id="判讀">判讀</h2>
<p>技術上不需 vhost、改用權限限制 + 命名前綴（<code>exchange/&lt;username&gt;/*</code>、<code>queue/&lt;username&gt;/*</code>）做隔離。PulseGuardian 跑在 Heroku 管理使用者 / queue / exchange。揭露多租戶隔離不一定要 vhost、權限粒度可以拉到 resource naming 層。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：多 vhost + 多租戶（反向案例：用 ACL + naming 取代 vhost）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/rabbitmq-bloomberg-multi-tenant-vhost/" data-link-title="3.C23 Bloomberg：多租戶 vhost &#43; 自助平台化" data-link-desc="Bloomberg 從幾個團隊推到上百個團隊、靠自助 vhost 註冊跟專用叢集分離應用與 broker。">3.C23 Bloomberg vhost 多租戶</a>（對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://wiki.mozilla.org/Auto-tools/Projects/Pulse">Mozilla Pulse Wiki</a></li>
<li><a href="https://pulse.mozilla.org/api/">Pulse API</a></li>
</ul>
]]></content:encoded></item><item><title>3.C32 LoyaltyLion：監控數千 RabbitMQ queue</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-loyaltylion-monitoring-thousands/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-loyaltylion-monitoring-thousands/</guid><description>&lt;p>這個案例的核心責任是說明大規模 queue topology 的監控議題超出 Management plugin 能力範圍。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>LoyaltyLion 跑數千個 RabbitMQ queue、用 rabbitmqctl 跑 recurring script 抓 queue 資訊、透過 statsd 送到 Datadog。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>大規模 queue 拓撲下管理 plugin API 不夠用、需自寫採集腳本。揭露 queue 數量上萬時、原生 monitoring 介面（HTTP API、Management UI）會變成瓶頸、需要 metrics agent 模式。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Prefetch + consumer 併發（大規模 queue topology 的監控議題）/ RabbitMQ Cluster Operator（運維邊界）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">4 觀測模組&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.loyaltylion.com/monitoring-thousands-of-rabbitmq-queues-with-datadog-d3168c088ea6">Monitoring Thousands of RabbitMQ Queues with Datadog&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明大規模 queue topology 的監控議題超出 Management plugin 能力範圍。</p>
<h2 id="觀察">觀察</h2>
<p>LoyaltyLion 跑數千個 RabbitMQ queue、用 rabbitmqctl 跑 recurring script 抓 queue 資訊、透過 statsd 送到 Datadog。</p>
<h2 id="判讀">判讀</h2>
<p>大規模 queue 拓撲下管理 plugin API 不夠用、需自寫採集腳本。揭露 queue 數量上萬時、原生 monitoring 介面（HTTP API、Management UI）會變成瓶頸、需要 metrics agent 模式。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Prefetch + consumer 併發（大規模 queue topology 的監控議題）/ RabbitMQ Cluster Operator（運維邊界）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">4 觀測模組</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.loyaltylion.com/monitoring-thousands-of-rabbitmq-queues-with-datadog-d3168c088ea6">Monitoring Thousands of RabbitMQ Queues with Datadog</a></li>
</ul>
]]></content:encoded></item><item><title>3.C33 Wargaming：World of Tanks 戰後 dossier 解耦</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wargaming-game-portal-decoupling/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-wargaming-game-portal-decoupling/</guid><description>&lt;p>這個案例的核心責任是說明 game server / web portal 異步解耦、queue 吸收戰後事件 burst。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>World of Tanks server 全 Linux、用 RabbitMQ 作為 web service stack 核心。每場戰鬥結束後玩家 tank dossier 寫入 message queue、讓 game portal 顯示最新統計而不增加 game server load。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Queue 是 game server 與 portal 的解耦邊界、subscription 也走 RabbitMQ。揭露遊戲場景的「戰後事件 burst」適合用 queue 吸收、不該打到 game server 內部狀態。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>RabbitMQ 進階主題：Federation + Shovel（多 region game server 同步）/ 多 vhost + 多租戶（多遊戲共用 broker）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.linuxfoundation.org/blog/blog/wargaming-mobilizes-with-linux-and-open-source">Wargaming Mobilizes with Linux and Open Source (Linux Foundation)&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://ftr.wot-news.com/2014/07/17/wargaming-public-api-part-2/">Wargaming Public API&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 game server / web portal 異步解耦、queue 吸收戰後事件 burst。</p>
<h2 id="觀察">觀察</h2>
<p>World of Tanks server 全 Linux、用 RabbitMQ 作為 web service stack 核心。每場戰鬥結束後玩家 tank dossier 寫入 message queue、讓 game portal 顯示最新統計而不增加 game server load。</p>
<h2 id="判讀">判讀</h2>
<p>Queue 是 game server 與 portal 的解耦邊界、subscription 也走 RabbitMQ。揭露遊戲場景的「戰後事件 burst」適合用 queue 吸收、不該打到 game server 內部狀態。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>RabbitMQ 進階主題：Federation + Shovel（多 region game server 同步）/ 多 vhost + 多租戶（多遊戲共用 broker）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/rabbitmq/" data-link-title="RabbitMQ" data-link-desc="Classic message broker、AMQP routing 為主">RabbitMQ vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.linuxfoundation.org/blog/blog/wargaming-mobilizes-with-linux-and-open-source">Wargaming Mobilizes with Linux and Open Source (Linux Foundation)</a></li>
<li><a href="http://ftr.wot-news.com/2014/07/17/wargaming-public-api-part-2/">Wargaming Public API</a></li>
</ul>
]]></content:encoded></item><item><title>3.C34 Netlify：NATS 當全球 metrics/logs 統一資料平面</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-netlify-data-plane-fanout/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-netlify-data-plane-fanout/</guid><description>&lt;p>Netlify 的 NATS 選型示範了 subject-based fan-out 在跨雲觀測資料平面的優勢 — 協議極簡帶來的是部署簡單跟 client 整合成本低，代價是放棄持久化保證。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Netlify 是靜態網站跟 serverless function 的部署平台，服務 70,000+ 網站、近月 10 億 page view。基礎設施橫跨 Rackspace、AWS、GCP、Digital Ocean 四個雲端供應商。每個服務節點都會產生 metrics 跟 logs，需要一條統一的資料路徑把這些訊號從各地收集到中央觀測系統。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="跨雲統一資料平面">跨雲統一資料平面&lt;/h3>
&lt;p>四個雲的服務各自有不同的網路拓樸跟存取方式。觀測資料需要跨雲收集到同一個目的地（Elasticsearch），但直接讓每個服務 HTTP POST 到 Elasticsearch 會有連線管理、背壓、格式轉換的問題分散在每個服務裡。&lt;/p>
&lt;p>Netlify 需要一個中介層 — 各服務把 metrics / logs 推到中介層，中介層負責 fan-out 到下游消費者（Elasticsearch、即時 dashboard、告警系統）。&lt;/p>
&lt;h3 id="選型nats-vs-rabbitmq">選型：NATS vs RabbitMQ&lt;/h3>
&lt;p>Netlify 評估了 RabbitMQ 跟 NATS。RabbitMQ 在功能上更完整（持久化 queue、DLQ、ack 機制），但 Netlify 的觀測資料場景有三個特性讓 NATS 更合適：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>資料可丟&lt;/strong>：metrics 跟 logs 是 best-effort 的觀測資料，遺失幾秒的資料不影響業務 — 持久化保證帶來的運維成本大於收益&lt;/li>
&lt;li>&lt;strong>Fan-out 是主要模式&lt;/strong>：同一份資料要被多個消費者訂閱（Elasticsearch、即時 tail、告警），NATS 的 subject-based pub/sub 天然支援，RabbitMQ 需要設 exchange + 多個 binding&lt;/li>
&lt;li>&lt;strong>部署極簡&lt;/strong>：NATS server 是單一 binary、零依賴、幾秒鐘啟動，跨四個雲部署時每個雲跑一個 NATS node 的運維成本遠低於 RabbitMQ cluster&lt;/li>
&lt;/ul>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;h3 id="架構">架構&lt;/h3>
&lt;p>Netlify 用 Core NATS（非 JetStream）搭建觀測資料平面：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Producer 端&lt;/strong>：用 logrus 的 NATS hook 讓所有 Go 服務的 structured log 自動推到 NATS subject；另用 log-tail 工具從 file-based log 讀取推送&lt;/li>
&lt;li>&lt;strong>Consumer 端&lt;/strong>：一個 elastinats 消費者訂閱 NATS subject、批次寫入 Elasticsearch；其他消費者可以各自訂閱同一個 subject 做即時處理&lt;/li>
&lt;/ul>
&lt;p>Subject 的命名用階層式結構（例如 &lt;code>logs.production.api&lt;/code>），讓消費者可以用 wildcard 訂閱整個子樹（&lt;code>logs.production.*&lt;/code>）或特定服務。&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>放棄（Core NATS）&lt;/td>
 &lt;td>NATS server 重啟時 in-flight 的訊息遺失&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ack 機制&lt;/td>
 &lt;td>放棄（fire-and-forget）&lt;/td>
 &lt;td>Consumer 處理失敗的訊息不會被重送&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>跨雲連接&lt;/td>
 &lt;td>NATS cluster&lt;/td>
 &lt;td>需要跨雲的網路連線、延遲影響 cluster 一致性&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Consumer 擴展&lt;/td>
 &lt;td>多個訂閱者各自訂閱&lt;/td>
 &lt;td>每個消費者收到全量資料、沒有 consumer group 的分攤機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Core NATS 的 fire-and-forget 語意在觀測資料場景是有意的選擇 — 觀測資料的價值隨時間快速衰減，遺失一秒鐘的 metrics 不影響趨勢判讀。如果場景需要持久化（例：audit log、交易事件），Core NATS 就不適合，需要 JetStream 或其他有持久化保證的 broker。&lt;/p>
&lt;h2 id="回寫教材的連結">回寫教材的連結&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>：Core NATS 的 fire-and-forget 是 broker 可靠性光譜的一端（at-most-once），Kafka 跟 RabbitMQ 在另一端（at-least-once / durable）&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a>：Core NATS vs JetStream 的選型判準 — 本案例是純 Core NATS 的代表場景&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a>：Netlify 的 NATS 資料平面在觀測 pipeline 架構中扮演 collector 跟 storage 之間的 transport 層&lt;/li>
&lt;/ul>
&lt;h2 id="判讀徵兆">判讀徵兆&lt;/h2>
&lt;p>讀者在自己的系統看到以下訊號時，應該回讀本案例：&lt;/p></description><content:encoded><![CDATA[<p>Netlify 的 NATS 選型示範了 subject-based fan-out 在跨雲觀測資料平面的優勢 — 協議極簡帶來的是部署簡單跟 client 整合成本低，代價是放棄持久化保證。</p>
<h2 id="業務背景">業務背景</h2>
<p>Netlify 是靜態網站跟 serverless function 的部署平台，服務 70,000+ 網站、近月 10 億 page view。基礎設施橫跨 Rackspace、AWS、GCP、Digital Ocean 四個雲端供應商。每個服務節點都會產生 metrics 跟 logs，需要一條統一的資料路徑把這些訊號從各地收集到中央觀測系統。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="跨雲統一資料平面">跨雲統一資料平面</h3>
<p>四個雲的服務各自有不同的網路拓樸跟存取方式。觀測資料需要跨雲收集到同一個目的地（Elasticsearch），但直接讓每個服務 HTTP POST 到 Elasticsearch 會有連線管理、背壓、格式轉換的問題分散在每個服務裡。</p>
<p>Netlify 需要一個中介層 — 各服務把 metrics / logs 推到中介層，中介層負責 fan-out 到下游消費者（Elasticsearch、即時 dashboard、告警系統）。</p>
<h3 id="選型nats-vs-rabbitmq">選型：NATS vs RabbitMQ</h3>
<p>Netlify 評估了 RabbitMQ 跟 NATS。RabbitMQ 在功能上更完整（持久化 queue、DLQ、ack 機制），但 Netlify 的觀測資料場景有三個特性讓 NATS 更合適：</p>
<ul>
<li><strong>資料可丟</strong>：metrics 跟 logs 是 best-effort 的觀測資料，遺失幾秒的資料不影響業務 — 持久化保證帶來的運維成本大於收益</li>
<li><strong>Fan-out 是主要模式</strong>：同一份資料要被多個消費者訂閱（Elasticsearch、即時 tail、告警），NATS 的 subject-based pub/sub 天然支援，RabbitMQ 需要設 exchange + 多個 binding</li>
<li><strong>部署極簡</strong>：NATS server 是單一 binary、零依賴、幾秒鐘啟動，跨四個雲部署時每個雲跑一個 NATS node 的運維成本遠低於 RabbitMQ cluster</li>
</ul>
<h2 id="解法與取捨">解法與取捨</h2>
<h3 id="架構">架構</h3>
<p>Netlify 用 Core NATS（非 JetStream）搭建觀測資料平面：</p>
<ul>
<li><strong>Producer 端</strong>：用 logrus 的 NATS hook 讓所有 Go 服務的 structured log 自動推到 NATS subject；另用 log-tail 工具從 file-based log 讀取推送</li>
<li><strong>Consumer 端</strong>：一個 elastinats 消費者訂閱 NATS subject、批次寫入 Elasticsearch；其他消費者可以各自訂閱同一個 subject 做即時處理</li>
</ul>
<p>Subject 的命名用階層式結構（例如 <code>logs.production.api</code>），讓消費者可以用 wildcard 訂閱整個子樹（<code>logs.production.*</code>）或特定服務。</p>
<h3 id="取捨">取捨</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>選擇</th>
          <th>代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>持久化</td>
          <td>放棄（Core NATS）</td>
          <td>NATS server 重啟時 in-flight 的訊息遺失</td>
      </tr>
      <tr>
          <td>Ack 機制</td>
          <td>放棄（fire-and-forget）</td>
          <td>Consumer 處理失敗的訊息不會被重送</td>
      </tr>
      <tr>
          <td>跨雲連接</td>
          <td>NATS cluster</td>
          <td>需要跨雲的網路連線、延遲影響 cluster 一致性</td>
      </tr>
      <tr>
          <td>Consumer 擴展</td>
          <td>多個訂閱者各自訂閱</td>
          <td>每個消費者收到全量資料、沒有 consumer group 的分攤機制</td>
      </tr>
  </tbody>
</table>
<p>Core NATS 的 fire-and-forget 語意在觀測資料場景是有意的選擇 — 觀測資料的價值隨時間快速衰減，遺失一秒鐘的 metrics 不影響趨勢判讀。如果場景需要持久化（例：audit log、交易事件），Core NATS 就不適合，需要 JetStream 或其他有持久化保證的 broker。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：Core NATS 的 fire-and-forget 是 broker 可靠性光譜的一端（at-most-once），Kafka 跟 RabbitMQ 在另一端（at-least-once / durable）</li>
<li><a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a>：Core NATS vs JetStream 的選型判準 — 本案例是純 Core NATS 的代表場景</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a>：Netlify 的 NATS 資料平面在觀測 pipeline 架構中扮演 collector 跟 storage 之間的 transport 層</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>觀測資料（metrics / logs）需要跨多個雲或多個 datacenter 收集到中央系統</li>
<li>現有的 broker（RabbitMQ / Kafka）在觀測資料場景的運維成本跟資料價值不成比例</li>
<li>Fan-out 是主要消費模式 — 同一份資料需要被多個下游系統訂閱</li>
<li>對 message delivery 的可靠性要求是 best-effort 而非 at-least-once</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/netlify-nats-blog/">Why Netlify chose NATS</a></li>
</ul>
]]></content:encoded></item><item><title>3.C35 Form3：NATS JetStream 多雲低延遲支付</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-form3-multi-cloud-payments/</guid><description>&lt;p>這個案例的核心責任是說明 JetStream Leaf Node 在跨地理 / 跨雲 durability 拓樸的關鍵角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Form3 服務 Tier-1 銀行（含 Mastercard、Square 等）、要求 500ms 端到端 SLA、AWS SNS/SQS 約 300ms 延遲吃掉預算。在 Faster Payments 機房資源受限下、用 NATS + JetStream 替換 legacy pub/sub bus、達到約 6× 延遲改善並做到「AWS 整個 region 掛掉時不喪失處理能力」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 JetStream 的 Leaf Node 做跨雲橋接、把 on-prem Faster Payments 機房跟雲端 cluster 連起來。揭露金融支付對端到端 latency 預算的硬要求逼出特定 broker 選型、不是「Kafka / SQS 通用化」。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream stream 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS&lt;/a>（跨區對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.synadia.com/blog/how-form3-built-a-multi-cloud-low-latency-payments-service-with-nats-io-jetstream">How Form3 Built a Multi-Cloud Low-Latency Payments Service with NATS JetStream (Synadia blog)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 JetStream Leaf Node 在跨地理 / 跨雲 durability 拓樸的關鍵角色。</p>
<h2 id="觀察">觀察</h2>
<p>Form3 服務 Tier-1 銀行（含 Mastercard、Square 等）、要求 500ms 端到端 SLA、AWS SNS/SQS 約 300ms 延遲吃掉預算。在 Faster Payments 機房資源受限下、用 NATS + JetStream 替換 legacy pub/sub bus、達到約 6× 延遲改善並做到「AWS 整個 region 掛掉時不喪失處理能力」。</p>
<h2 id="判讀">判讀</h2>
<p>用 JetStream 的 Leaf Node 做跨雲橋接、把 on-prem Faster Payments 機房跟雲端 cluster 連起來。揭露金融支付對端到端 latency 預算的硬要求逼出特定 broker 選型、不是「Kafka / SQS 通用化」。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream stream 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/meta-foqs-global-migration/" data-link-title="3.C1 Meta：FOQS 從區域到全域佇列遷移" data-link-desc="佇列架構如何在不中斷下升級成 disaster-ready 模式。">3.C1 Meta FOQS</a>（跨區對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.synadia.com/blog/how-form3-built-a-multi-cloud-low-latency-payments-service-with-nats-io-jetstream">How Form3 Built a Multi-Cloud Low-Latency Payments Service with NATS JetStream (Synadia blog)</a></li>
</ul>
]]></content:encoded></item><item><title>3.C36 Intelecy：工業 IoT 即時感測 + 多租戶</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/</guid><description>&lt;p>這個案例的核心責任是說明 edge gateway 從本地 KV 演進到 JetStream 的決策訊號。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Intelecy 在工廠端 gateway 接「數萬個 sensor」、要求 &amp;lt; 2 秒往返延遲做即時 ML 推論、需要多租戶安全隔離與雲端無鎖定方案。Gateway 把 process data 寫進 Synadia Cloud topic。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>從 BoltDB 本地快取 → JetStream 持久化的演進、揭露「無 durable layer 時 edge gateway 自己要做存儲、加 JetStream 後可放掉本地 BoltDB」的決策訊號。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：JetStream stream 設計 / Subject-based ACL + 多租戶（sensor 隔離）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &amp;#43; JetStream &amp;#43; KV &amp;#43; Object Store。">3.C37 MachineMetrics&lt;/a>（同類對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.synadia.com/blog/how-intelecy-optimizes-factory-processes-with-nats-ngs-and-jetstream">How Intelecy Optimizes Factory Processes with NATS, NGS and JetStream&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 edge gateway 從本地 KV 演進到 JetStream 的決策訊號。</p>
<h2 id="觀察">觀察</h2>
<p>Intelecy 在工廠端 gateway 接「數萬個 sensor」、要求 &lt; 2 秒往返延遲做即時 ML 推論、需要多租戶安全隔離與雲端無鎖定方案。Gateway 把 process data 寫進 Synadia Cloud topic。</p>
<h2 id="判讀">判讀</h2>
<p>從 BoltDB 本地快取 → JetStream 持久化的演進、揭露「無 durable layer 時 edge gateway 自己要做存儲、加 JetStream 後可放掉本地 BoltDB」的決策訊號。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：JetStream stream 設計 / Subject-based ACL + 多租戶（sensor 隔離）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">3.C37 MachineMetrics</a>（同類對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.synadia.com/blog/how-intelecy-optimizes-factory-processes-with-nats-ngs-and-jetstream">How Intelecy Optimizes Factory Processes with NATS, NGS and JetStream</a></li>
</ul>
]]></content:encoded></item><item><title>3.C37 MachineMetrics：邊緣到雲端工廠資料管線</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/</guid><description>&lt;p>這個案例的核心責任是說明工業 IoT 完整的 edge-to-cloud NATS 整合（Leaf Node + JetStream + KV + Object Store + Auth）。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>跨「數百個客戶廠區、數千台機台」的 Industrial IoT、單機產出最高 1000 Hz 採樣、工廠網路斷斷續續、Kinesis 等 cloud-only 工具無法跑在資源受限 edge 上。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 Leaf Node 做 hub-and-spoke 把邊緣設備串到雲端、Edge 端用 JetStream 做本地持久化（取代 SQLite）抵抗網路斷線、用 KV store 做 config / 短期 cache、Object Store 派發 WASM 模組、Decentralized Auth 隔離客戶。揭露「broker 的功能集合」決定它能不能取代多套 edge 工具。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream KV + Object Store / Subject-based ACL + 多租戶。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/" data-link-title="3.C36 Intelecy：工業 IoT 即時感測 &amp;#43; 多租戶" data-link-desc="Intelecy 工廠 gateway 接數萬感測器、&amp;lt; 2 秒往返延遲做即時 ML、從 BoltDB 本地快取演進到 JetStream 持久化。">3.C36 Intelecy&lt;/a>（同類對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.synadia.com/customer-stories/machinemetrics">MachineMetrics Customer Story (Synadia)&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明工業 IoT 完整的 edge-to-cloud NATS 整合（Leaf Node + JetStream + KV + Object Store + Auth）。</p>
<h2 id="觀察">觀察</h2>
<p>跨「數百個客戶廠區、數千台機台」的 Industrial IoT、單機產出最高 1000 Hz 採樣、工廠網路斷斷續續、Kinesis 等 cloud-only 工具無法跑在資源受限 edge 上。</p>
<h2 id="判讀">判讀</h2>
<p>用 Leaf Node 做 hub-and-spoke 把邊緣設備串到雲端、Edge 端用 JetStream 做本地持久化（取代 SQLite）抵抗網路斷線、用 KV store 做 config / 短期 cache、Object Store 派發 WASM 模組、Decentralized Auth 隔離客戶。揭露「broker 的功能集合」決定它能不能取代多套 edge 工具。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Cluster + Supercluster + Leaf node / JetStream KV + Object Store / Subject-based ACL + 多租戶。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/nats-intelecy-industrial-iot/" data-link-title="3.C36 Intelecy：工業 IoT 即時感測 &#43; 多租戶" data-link-desc="Intelecy 工廠 gateway 接數萬感測器、&lt; 2 秒往返延遲做即時 ML、從 BoltDB 本地快取演進到 JetStream 持久化。">3.C36 Intelecy</a>（同類對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.synadia.com/customer-stories/machinemetrics">MachineMetrics Customer Story (Synadia)</a></li>
</ul>
]]></content:encoded></item><item><title>3.C38 Clarifai：NATS Streaming ML 平台非同步任務</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-clarifai-async-task-queue/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-clarifai-async-task-queue/</guid><description>&lt;p>這個案例的核心責任是說明 NATS Streaming（JetStream 前身）的 queue group + at-least-once 在 ML worker pool 的角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Clarifai 做 custom model 訓練、任務從幾秒到幾分鐘、原本同步呼叫遇到 rolling deployment 會掉訊息。三週內把一個服務遷到 NATS、5 個月內擴展到 5 個服務、每日 100k+ 訊息、100% uptime。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 NATS Streaming 的 at-least-once delivery + queue subscription group 做 worker pool、每個微服務連到三個獨立 NATS Streaming 實例做 fanout 隔離。揭露 ML 任務的長尾處理時間特別需要 at-least-once + redelivery、不能容忍 rolling deploy 掉訊息。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：JetStream consumer 設計（NATS Streaming 是前身）/ Queue groups。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nats.io/blog/how-clarifai-uses-nats-and-kubernetes-for-machine-learning/">How Clarifai Uses NATS and Kubernetes for Machine Learning&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 NATS Streaming（JetStream 前身）的 queue group + at-least-once 在 ML worker pool 的角色。</p>
<h2 id="觀察">觀察</h2>
<p>Clarifai 做 custom model 訓練、任務從幾秒到幾分鐘、原本同步呼叫遇到 rolling deployment 會掉訊息。三週內把一個服務遷到 NATS、5 個月內擴展到 5 個服務、每日 100k+ 訊息、100% uptime。</p>
<h2 id="判讀">判讀</h2>
<p>用 NATS Streaming 的 at-least-once delivery + queue subscription group 做 worker pool、每個微服務連到三個獨立 NATS Streaming 實例做 fanout 隔離。揭露 ML 任務的長尾處理時間特別需要 at-least-once + redelivery、不能容忍 rolling deploy 掉訊息。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：JetStream consumer 設計（NATS Streaming 是前身）/ Queue groups。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/how-clarifai-uses-nats-and-kubernetes-for-machine-learning/">How Clarifai Uses NATS and Kubernetes for Machine Learning</a></li>
</ul>
]]></content:encoded></item><item><title>3.C39 Choria：NATS 管 50 萬 server fleet</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-choria-orchestration-fleet/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-choria-orchestration-fleet/</guid><description>&lt;p>這個案例的核心責任是說明 fire-and-forget RPC + scatter-gather pattern 是 NATS Core 的典型場景。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Choria 是 Puppet MCollective 的現代化替代品、目標管理數萬到數十萬節點的 fleet 同時下指令。評估過多個 broker、選 NATS 因為「單 binary、無 Zookeeper 依賴、Ruby client 品質好」、實測「單 server 300MB RAM 管 2000+ 機器」、4GB 節點可達 50 萬 server。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>MCollective 的 fire-and-forget RPC 語意正好對應 NATS Core 的 stateless best-effort + request-reply pattern、用 wildcard subject + queue group 做 parallel scatter-gather RPC。揭露 server orchestration 場景不需要 persistence、Core NATS 已足夠。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Request/Reply pattern / Queue groups / Cluster + Supercluster + Leaf node（Choria Federation Broker = 跨地理 federation）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nats.io/blog/nats-for-the-marionette-collective/">NATS for the Marionette Collective (Choria)&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://choria.io/docs/concepts/">Choria Architecture Docs&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 fire-and-forget RPC + scatter-gather pattern 是 NATS Core 的典型場景。</p>
<h2 id="觀察">觀察</h2>
<p>Choria 是 Puppet MCollective 的現代化替代品、目標管理數萬到數十萬節點的 fleet 同時下指令。評估過多個 broker、選 NATS 因為「單 binary、無 Zookeeper 依賴、Ruby client 品質好」、實測「單 server 300MB RAM 管 2000+ 機器」、4GB 節點可達 50 萬 server。</p>
<h2 id="判讀">判讀</h2>
<p>MCollective 的 fire-and-forget RPC 語意正好對應 NATS Core 的 stateless best-effort + request-reply pattern、用 wildcard subject + queue group 做 parallel scatter-gather RPC。揭露 server orchestration 場景不需要 persistence、Core NATS 已足夠。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Request/Reply pattern / Queue groups / Cluster + Supercluster + Leaf node（Choria Federation Broker = 跨地理 federation）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/nats-for-the-marionette-collective/">NATS for the Marionette Collective (Choria)</a></li>
<li><a href="https://choria.io/docs/concepts/">Choria Architecture Docs</a></li>
</ul>
]]></content:encoded></item><item><title>3.C40 Resgate：WebSocket-to-NATS realtime API gateway</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-resgate-realtime-api-gateway/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-resgate-realtime-api-gateway/</guid><description>&lt;p>這個案例的核心責任是說明「subject hierarchy 即 access control 邊界」的設計範例。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Resgate 把 NATS subject 暴露成 REST + WebSocket、客戶端跨多 Resgate 實例自動同步狀態、事件延遲 &amp;lt; 1ms。需要同時支援 pub-sub 跟 request-reply、選 NATS 因為「performance、simplicity、兩種模式都原生支援」。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>subject 設計遵循 &lt;code>get.{service}.{resource}&lt;/code> / &lt;code>event.{service}.{resource}.{event-type}&lt;/code> 的命名規約、是「subject 階層當 schema」的典型範例。揭露 subject 命名是 NATS 的 API contract 起點、不是隨意命名。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Request/Reply pattern / Subject-based ACL + 多租戶（subject hierarchy 即 access control 邊界）/ Core NATS vs JetStream（純 Core）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/" data-link-title="3.C26 GoCardless：Hutch &amp;#43; 單一 topic exchange service mesh" data-link-desc="GoCardless 單一 RabbitMQ cluster 作所有 service 通訊中樞、routing key 用 service.subject.action 格式、JSON 多語言可讀。">3.C26 GoCardless Hutch routing key&lt;/a>（命名規約對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://resgate.io/blog/introducing-resgate/">Introducing Resgate&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「subject hierarchy 即 access control 邊界」的設計範例。</p>
<h2 id="觀察">觀察</h2>
<p>Resgate 把 NATS subject 暴露成 REST + WebSocket、客戶端跨多 Resgate 實例自動同步狀態、事件延遲 &lt; 1ms。需要同時支援 pub-sub 跟 request-reply、選 NATS 因為「performance、simplicity、兩種模式都原生支援」。</p>
<h2 id="判讀">判讀</h2>
<p>subject 設計遵循 <code>get.{service}.{resource}</code> / <code>event.{service}.{resource}.{event-type}</code> 的命名規約、是「subject 階層當 schema」的典型範例。揭露 subject 命名是 NATS 的 API contract 起點、不是隨意命名。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Request/Reply pattern / Subject-based ACL + 多租戶（subject hierarchy 即 access control 邊界）/ Core NATS vs JetStream（純 Core）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/rabbitmq-gocardless-hutch-service-mesh/" data-link-title="3.C26 GoCardless：Hutch &#43; 單一 topic exchange service mesh" data-link-desc="GoCardless 單一 RabbitMQ cluster 作所有 service 通訊中樞、routing key 用 service.subject.action 格式、JSON 多語言可讀。">3.C26 GoCardless Hutch routing key</a>（命名規約對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://resgate.io/blog/introducing-resgate/">Introducing Resgate</a></li>
</ul>
]]></content:encoded></item><item><title>3.C41 i-flow：NATS 做 OT/IT 跨層整合 bus</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-iflow-ot-it-integration/</guid><description>&lt;p>這個案例的核心責任是說明 OT/IT 整合場景的多工廠 leaf node 拓樸。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>i-flow 是工業數據整合平台、每日 4 億筆 data operation、提供 200+ OT/IT 系統 connector、客戶含 Fortune 500 工廠（Bosch、Sto、Lenze）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 NATS 當 OT/IT 跨層整合 bus、邊緣端負責 connect / harmonize / publish。揭露多工廠場景該用 leaf node hub-and-spoke、不該每工廠自管 cluster。&lt;strong>注意&lt;/strong>：此案例技術細節較淺、引用時要補其他案例的具體 stream / consumer 設計。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>NATS 進階主題：Cluster + Supercluster + Leaf node（多工廠 leaf node 連 central）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &amp;#43; JetStream &amp;#43; KV &amp;#43; Object Store。">3.C37 MachineMetrics&lt;/a>（技術細節更深的對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://nats.io/blog/i-flow-case-study/">i-flow Case Study&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 OT/IT 整合場景的多工廠 leaf node 拓樸。</p>
<h2 id="觀察">觀察</h2>
<p>i-flow 是工業數據整合平台、每日 4 億筆 data operation、提供 200+ OT/IT 系統 connector、客戶含 Fortune 500 工廠（Bosch、Sto、Lenze）。</p>
<h2 id="判讀">判讀</h2>
<p>用 NATS 當 OT/IT 跨層整合 bus、邊緣端負責 connect / harmonize / publish。揭露多工廠場景該用 leaf node hub-and-spoke、不該每工廠自管 cluster。<strong>注意</strong>：此案例技術細節較淺、引用時要補其他案例的具體 stream / consumer 設計。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>NATS 進階主題：Cluster + Supercluster + Leaf node（多工廠 leaf node 連 central）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/nats/" data-link-title="NATS" data-link-desc="Lightweight messaging、JetStream 加持久化與 streams">NATS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/nats-machinemetrics-edge-to-cloud/" data-link-title="3.C37 MachineMetrics：邊緣到雲端工廠資料管線" data-link-desc="MachineMetrics 跨數百工廠、數千機台、1000Hz 採樣、Kinesis 無法跑在 edge、改 NATS Leaf Node &#43; JetStream &#43; KV &#43; Object Store。">3.C37 MachineMetrics</a>（技術細節更深的對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://nats.io/blog/i-flow-case-study/">i-flow Case Study</a></li>
</ul>
]]></content:encoded></item><item><title>3.C42 Bitso：Reliable Redis Streams 抽象 + 自建 DLQ</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-bitso-reliable-streams/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-bitso-reliable-streams/</guid><description>&lt;p>這個案例的核心責任是說明 Redis Streams 沒有原生 DLQ、要在 application 層自建抽象。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Bitso 的 Order Engine 微服務需要 thousands of messages/sec/stream + 亞毫秒延遲、撐住 BTC 價格暴動的流量尖峰；先後評估 Kafka（latency）跟 SQS（vendor lock-in + latency）後選 Redis Streams、團隊本來就熟 Redis、已在 mission-critical service 跑超過半年。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>自建 &amp;ldquo;Reliable Redis Streams&amp;rdquo; 抽象層（StreamRedisOperations adapter / ReliableStream interface / MessageReadingLoop）封裝 readMessages + readPendingMessages、加上 Redis Streams 沒有原生支援的 DLQ（N 次 retry 後路由）、走 idempotent processing 接受重複勝過遺失。揭露 Redis Streams 是「資料結構」、不是「broker 系統」、可靠性責任在 application 層。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Sentinel + Cluster 可靠性。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/bitso-engineering/the-redis-streams-we-have-known-and-loved-e9e596d49a22">The Redis Streams We Have Known and Loved&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Redis Streams 沒有原生 DLQ、要在 application 層自建抽象。</p>
<h2 id="觀察">觀察</h2>
<p>Bitso 的 Order Engine 微服務需要 thousands of messages/sec/stream + 亞毫秒延遲、撐住 BTC 價格暴動的流量尖峰；先後評估 Kafka（latency）跟 SQS（vendor lock-in + latency）後選 Redis Streams、團隊本來就熟 Redis、已在 mission-critical service 跑超過半年。</p>
<h2 id="判讀">判讀</h2>
<p>自建 &ldquo;Reliable Redis Streams&rdquo; 抽象層（StreamRedisOperations adapter / ReliableStream interface / MessageReadingLoop）封裝 readMessages + readPendingMessages、加上 Redis Streams 沒有原生支援的 DLQ（N 次 retry 後路由）、走 idempotent processing 接受重複勝過遺失。揭露 Redis Streams 是「資料結構」、不是「broker 系統」、可靠性責任在 application 層。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Sentinel + Cluster 可靠性。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/bitso-engineering/the-redis-streams-we-have-known-and-loved-e9e596d49a22">The Redis Streams We Have Known and Loved</a></li>
</ul>
]]></content:encoded></item><item><title>3.C43 Arcjet：Redis Streams 取代 Kafka 省 6 位數 $</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-arcjet-replace-kafka/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-arcjet-replace-kafka/</guid><description>&lt;p>Arcjet 用 Redis Streams 取代 Kafka 的案例揭露了中小規模場景下「Kafka 的 managed 成本 vs Redis Streams 的運維成本」的具體取捨 — 省下六位數年費的代價是自寫 retention 治理跟監控工具。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Arcjet 是 security / bot detection 平台，處理每個 HTTP request 的安全判斷。核心需求是 low-latency 的請求處理 — 安全判斷要在幾毫秒內完成，不能拖慢使用者的 request。&lt;/p>
&lt;p>系統架構中有一段 event-driven pipeline 負責把安全事件從 detection layer 傳遞到 analytics 跟 alerting。原本評估用 Kafka 做這段 pipeline，但 managed Kafka 的年費落在六位數美金 — 對 Arcjet 的流量規模跟業務階段，這個成本不合理。&lt;/p>
&lt;p>Arcjet 的基礎設施已經有 Redis 做 cache。把 Redis 從純 cache 升級到 cache + Streams，利用既有的 Redis infrastructure 承擔 event pipeline，總成本約 $1k/year。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="redis-streams-沒有自動-retention">Redis Streams 沒有自動 retention&lt;/h3>
&lt;p>Kafka 的 retention 是內建功能 — 設定 &lt;code>log.retention.hours&lt;/code> 後 broker 自動刪除到期資料。Redis Streams 沒有內建的自動 retention — stream 資料會持續累積，直到手動 &lt;code>XTRIM&lt;/code> 或 &lt;code>XDEL&lt;/code>。&lt;/p>
&lt;p>在生產環境下，不處理 retention 意味著 Redis 的記憶體持續成長，最終觸發 eviction policy 或 OOM。對 Arcjet 來說 Redis 同時做 cache 跟 Streams，Streams 的記憶體成長會擠壓 cache 的可用空間。&lt;/p>
&lt;h3 id="consumer-group-進度追蹤">Consumer group 進度追蹤&lt;/h3>
&lt;p>Redis Streams 的 consumer group 會追蹤每個 consumer 的讀取進度（last delivered ID）。做 &lt;code>XTRIM&lt;/code> 時需要確保不刪除尚未被所有 consumer group 確認的訊息 — 否則 consumer 會丟失未處理的事件。&lt;/p>
&lt;p>Kafka 的 log compaction 跟 retention 自動處理這個問題（consumer offset 以前的 segment 才會被清理）。Redis Streams 需要 application 自己確認所有 consumer group 的進度，再決定 trim 的位置。&lt;/p>
&lt;h3 id="單機-redis-的可靠性邊界">單機 Redis 的可靠性邊界&lt;/h3>
&lt;p>Redis 的持久化機制（RDB snapshot + AOF）提供的是 best-effort 的持久性，跟 Kafka 的 replication-based 持久化保證不同。Redis crash + restart 時，AOF 的最後幾筆寫入可能遺失（取決於 &lt;code>appendfsync&lt;/code> 設定）。&lt;/p>
&lt;p>對 Arcjet 的安全事件場景，偶爾丟失幾筆事件可以接受（security detection 的結果是即時判斷，事後的 analytics 容忍小量遺失）。如果場景是金融交易或 audit log，這個可靠性邊界就不夠。&lt;/p>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;h3 id="自建-janitor-process">自建 Janitor process&lt;/h3>
&lt;p>Arcjet 自寫了一個 Janitor process 處理 Redis Streams 的 retention：&lt;/p></description><content:encoded><![CDATA[<p>Arcjet 用 Redis Streams 取代 Kafka 的案例揭露了中小規模場景下「Kafka 的 managed 成本 vs Redis Streams 的運維成本」的具體取捨 — 省下六位數年費的代價是自寫 retention 治理跟監控工具。</p>
<h2 id="業務背景">業務背景</h2>
<p>Arcjet 是 security / bot detection 平台，處理每個 HTTP request 的安全判斷。核心需求是 low-latency 的請求處理 — 安全判斷要在幾毫秒內完成，不能拖慢使用者的 request。</p>
<p>系統架構中有一段 event-driven pipeline 負責把安全事件從 detection layer 傳遞到 analytics 跟 alerting。原本評估用 Kafka 做這段 pipeline，但 managed Kafka 的年費落在六位數美金 — 對 Arcjet 的流量規模跟業務階段，這個成本不合理。</p>
<p>Arcjet 的基礎設施已經有 Redis 做 cache。把 Redis 從純 cache 升級到 cache + Streams，利用既有的 Redis infrastructure 承擔 event pipeline，總成本約 $1k/year。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="redis-streams-沒有自動-retention">Redis Streams 沒有自動 retention</h3>
<p>Kafka 的 retention 是內建功能 — 設定 <code>log.retention.hours</code> 後 broker 自動刪除到期資料。Redis Streams 沒有內建的自動 retention — stream 資料會持續累積，直到手動 <code>XTRIM</code> 或 <code>XDEL</code>。</p>
<p>在生產環境下，不處理 retention 意味著 Redis 的記憶體持續成長，最終觸發 eviction policy 或 OOM。對 Arcjet 來說 Redis 同時做 cache 跟 Streams，Streams 的記憶體成長會擠壓 cache 的可用空間。</p>
<h3 id="consumer-group-進度追蹤">Consumer group 進度追蹤</h3>
<p>Redis Streams 的 consumer group 會追蹤每個 consumer 的讀取進度（last delivered ID）。做 <code>XTRIM</code> 時需要確保不刪除尚未被所有 consumer group 確認的訊息 — 否則 consumer 會丟失未處理的事件。</p>
<p>Kafka 的 log compaction 跟 retention 自動處理這個問題（consumer offset 以前的 segment 才會被清理）。Redis Streams 需要 application 自己確認所有 consumer group 的進度，再決定 trim 的位置。</p>
<h3 id="單機-redis-的可靠性邊界">單機 Redis 的可靠性邊界</h3>
<p>Redis 的持久化機制（RDB snapshot + AOF）提供的是 best-effort 的持久性，跟 Kafka 的 replication-based 持久化保證不同。Redis crash + restart 時，AOF 的最後幾筆寫入可能遺失（取決於 <code>appendfsync</code> 設定）。</p>
<p>對 Arcjet 的安全事件場景，偶爾丟失幾筆事件可以接受（security detection 的結果是即時判斷，事後的 analytics 容忍小量遺失）。如果場景是金融交易或 audit log，這個可靠性邊界就不夠。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<h3 id="自建-janitor-process">自建 Janitor process</h3>
<p>Arcjet 自寫了一個 Janitor process 處理 Redis Streams 的 retention：</p>
<ol>
<li>定期檢查每個 stream 的長度（<code>XLEN</code>）</li>
<li>查詢所有 consumer group 的 pending entry list（PEL）跟最後確認位置</li>
<li>計算安全的 trim 位置（所有 consumer group 都已確認的最舊 ID）</li>
<li>執行 <code>XTRIM stream MINID &lt;safe-id&gt;</code> 刪除已確認的舊資料</li>
</ol>
<p>Janitor 的執行頻率根據實際處理速度（~100 msgs/min）設定 — 不需要非常頻繁，但不能完全不跑。</p>
<h3 id="取捨">取捨</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Managed Kafka</th>
          <th>Redis Streams + Janitor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>年成本</td>
          <td>六位數 USD</td>
          <td>~$1k USD</td>
      </tr>
      <tr>
          <td>Retention 管理</td>
          <td>內建自動</td>
          <td>自寫 Janitor</td>
      </tr>
      <tr>
          <td>持久化保證</td>
          <td>Replication-based（強）</td>
          <td>AOF/RDB（best-effort）</td>
      </tr>
      <tr>
          <td>Consumer group</td>
          <td>原生支援、offset commit 自動</td>
          <td>原生支援、但 trim 要手動協調</td>
      </tr>
      <tr>
          <td>生態工具</td>
          <td>Kafka Connect、Schema Registry</td>
          <td>無（自建）</td>
      </tr>
      <tr>
          <td>擴展性</td>
          <td>Partition 水平擴展</td>
          <td>單 Redis 受限、Cluster 模式複雜</td>
      </tr>
      <tr>
          <td>運維知識</td>
          <td>Kafka 運維（或交給 managed）</td>
          <td>Redis 運維 + 自建 Janitor 維護</td>
      </tr>
  </tbody>
</table>
<h3 id="適用邊界">適用邊界</h3>
<p>Redis Streams 取代 Kafka 的適用邊界：</p>
<ul>
<li><strong>流量規模</strong>：每分鐘數百到數千筆（超過每秒數萬筆需要 Redis Cluster 或多 stream）</li>
<li><strong>持久化要求</strong>：容忍偶爾丟失少量訊息（best-effort）</li>
<li><strong>已有 Redis</strong>：不需要額外部署 Redis、利用既有 infrastructure</li>
<li><strong>Kafka 功能不需要</strong>：不需要 Kafka Connect、Schema Registry、long-term retention、跨 region replication</li>
</ul>
<p>超過這些邊界時，Redis Streams 的自建成本（Janitor + 監控 + retention 治理 + 可靠性補償）會逐漸接近 managed Kafka 的費用，成本優勢消失。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a>：XCLAIM / PEL recovery 的進階主題</li>
<li><a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>：成本對照 — Kafka 的固定成本高但功能完整</li>
<li><a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>：Redis Streams 的持久化機制跟 Kafka 的 replication 在 durability 光譜上的位置</li>
<li><a href="/blog/backend/03-message-queue/broker-basics/" data-link-title="3.1 broker 基礎與投遞模型" data-link-desc="先理解 broker、queue、consumer 與 delivery semantics">3.1 broker basics</a>：broker 選型時成本是一級決策維度</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>Managed Kafka 的月帳單跟實際流量量級不成比例（低流量但高成本）</li>
<li>已有 Redis infrastructure、考慮把 event pipeline 合併到 Redis</li>
<li>Event pipeline 的流量在每秒數百筆以下、持久化要求是 best-effort</li>
<li>Redis 記憶體持續成長但不確定 Streams 的 retention 有沒有正確執行</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.arcjet.com/replacing-kafka-with-redis-streams/">Replacing Kafka with Redis Streams</a></li>
</ul>
]]></content:encoded></item><item><title>3.C44 Harness：CD 微服務 async state transfer</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-harness-event-driven-state/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-harness-event-driven-state/</guid><description>&lt;p>這個案例的核心責任是說明 Redis Streams 在 production 落地的三類經常性議題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Harness 為 CD 微服務之間的 async state transfer 採用 Redis Streams、避開「每個 service 都要知道怎麼跟其他 service 講話」的 brittle HTTP 模式；初始規模 a few thousand msgs/min、Kafka 在此規模 overkill、又能複用已存在的 Redis 基建。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>落地後揭露三類問題：監控缺口（自寫 app 追 consumer lag）、需要主動 MAXLEN truncation、head-of-line blocking 要用 XAUTOCLAIM 重派並設計 redelivery 策略。揭露「Redis Streams 適合中小規模」這個聲明、實際包含三件 production work。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Memory + retention 取捨。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.harness.io/blog/event-driven-architecture-redis-streams">Event-Driven Architecture with Redis Streams&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Redis Streams 在 production 落地的三類經常性議題。</p>
<h2 id="觀察">觀察</h2>
<p>Harness 為 CD 微服務之間的 async state transfer 採用 Redis Streams、避開「每個 service 都要知道怎麼跟其他 service 講話」的 brittle HTTP 模式；初始規模 a few thousand msgs/min、Kafka 在此規模 overkill、又能複用已存在的 Redis 基建。</p>
<h2 id="判讀">判讀</h2>
<p>落地後揭露三類問題：監控缺口（自寫 app 追 consumer lag）、需要主動 MAXLEN truncation、head-of-line blocking 要用 XAUTOCLAIM 重派並設計 redelivery 策略。揭露「Redis Streams 適合中小規模」這個聲明、實際包含三件 production work。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：Consumer group + PEL / XCLAIM + 失敗接管 / Memory + retention 取捨。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.harness.io/blog/event-driven-architecture-redis-streams">Event-Driven Architecture with Redis Streams</a></li>
</ul>
]]></content:encoded></item><item><title>3.C45 Klaxit：Rust + Redis Streams 處理 Heroku Logplex</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-klaxit-rust-log-pipeline/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-klaxit-rust-log-pipeline/</guid><description>&lt;p>這個案例的核心責任是說明 Redis Streams 在高吞吐 log ingestion 的 consumer group 分流。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Klaxit 用 Redis Streams 處理 Heroku Logplex 匯流的 log、自動偵測並修復 Heroku 平台層 perf 問題（在使用者察覺前）；正式 production 跑超過 6 個月、是團隊第一個 Rust project。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>揭露 high-throughput log ingestion 對 Redis Streams 的壓力：用 consumer group 分流到多個 Rust worker、需要長時間穩定運轉。揭露 client library 品質決定 Redis Streams 在小眾語言（Rust）的可行性。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：XADD / XREAD / XREADGROUP 操作 / Consumer group + PEL。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://dev.to/goodtouch/consuming-high-throughput-redis-streams-with-rust-580c">Consuming High-Throughput Redis Streams with Rust&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Redis Streams 在高吞吐 log ingestion 的 consumer group 分流。</p>
<h2 id="觀察">觀察</h2>
<p>Klaxit 用 Redis Streams 處理 Heroku Logplex 匯流的 log、自動偵測並修復 Heroku 平台層 perf 問題（在使用者察覺前）；正式 production 跑超過 6 個月、是團隊第一個 Rust project。</p>
<h2 id="判讀">判讀</h2>
<p>揭露 high-throughput log ingestion 對 Redis Streams 的壓力：用 consumer group 分流到多個 Rust worker、需要長時間穩定運轉。揭露 client library 品質決定 Redis Streams 在小眾語言（Rust）的可行性。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：XADD / XREAD / XREADGROUP 操作 / Consumer group + PEL。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://dev.to/goodtouch/consuming-high-throughput-redis-streams-with-rust-580c">Consuming High-Throughput Redis Streams with Rust</a></li>
</ul>
]]></content:encoded></item><item><title>3.C46 Learning.com：Redis 事件源退場（反例）</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-learning-com-event-source-retreat/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-learning-com-event-source-retreat/</guid><description>&lt;p>這個反例的核心責任是說明 Redis 不適合長期事件儲存、揭露「Redis-as-event-store」的退場路徑。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Learning.com 把 microservice 之間的 event store 放 Redis 上、一年內累積到 GB/週的 memory 成長、AOF fsync + EBS 磁碟 I/O 變成 latency 痛點。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>揭露「Redis 不適合長期事件儲存」的退場路徑：event 移到 PostgreSQL、Redis 留做訊息佇列 + snapshot；中途靠 syncTimeout 調整、提升 IOPS、調整 AOF fsync 緩解。揭露 broker 選型要看「長期存儲是 source-of-truth 還是 transient」。&lt;strong>注意&lt;/strong>：此文討論的是 Redis-as-event-store 整體、Streams 是其中一塊、引用時要小心區分。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：Memory + retention 取捨 / Sentinel + Cluster 可靠性（持久化選型）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/lcom-techblog/a-year-with-redis-event-sourcing-lessons-learned-6736068e17cc">A Year with Redis Event Sourcing - Lessons Learned&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明 Redis 不適合長期事件儲存、揭露「Redis-as-event-store」的退場路徑。</p>
<h2 id="觀察">觀察</h2>
<p>Learning.com 把 microservice 之間的 event store 放 Redis 上、一年內累積到 GB/週的 memory 成長、AOF fsync + EBS 磁碟 I/O 變成 latency 痛點。</p>
<h2 id="判讀">判讀</h2>
<p>揭露「Redis 不適合長期事件儲存」的退場路徑：event 移到 PostgreSQL、Redis 留做訊息佇列 + snapshot；中途靠 syncTimeout 調整、提升 IOPS、調整 AOF fsync 緩解。揭露 broker 選型要看「長期存儲是 source-of-truth 還是 transient」。<strong>注意</strong>：此文討論的是 Redis-as-event-store 整體、Streams 是其中一塊、引用時要小心區分。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：Memory + retention 取捨 / Sentinel + Cluster 可靠性（持久化選型）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">3.3 outbox pattern</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/lcom-techblog/a-year-with-redis-event-sourcing-lessons-learned-6736068e17cc">A Year with Redis Event Sourcing - Lessons Learned</a></li>
</ul>
]]></content:encoded></item><item><title>3.C47 PHP 微服務：Redis Streams + S3 hybrid storage</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-mateusz-php-microservices/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/redis-streams-mateusz-php-microservices/</guid><description>&lt;p>這個案例的核心責任是說明 in-memory 訊息的 payload 限制要靠 hybrid storage 解決。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>PHP 雙微服務之間的可靠通訊、Kafka 在 PHP 生態工具薄弱、團隊無 Kafka 經驗、production 跑數月後寫此文；明確覆蓋 XADD / XREADGROUP / consumer group / MAXLEN / MINID / XDEL / XACK / XACKDEL（Redis 8.2+）/ XTRIM。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>揭露 in-memory 訊息的 payload 限制：用 payload compression + S3 hybrid storage（大 payload 存 S3、stream 只放 reference）；用 MAXLEN/MINID 控制 stream 成長。揭露 broker 選型常被「語言生態 client 品質」主導、不是純技術 feature。&lt;strong>注意&lt;/strong>：作者是個人工程師、production 經驗但非知名公司。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Redis Streams 進階主題：XADD/XREAD/XREADGROUP 操作 / Retention (MAXLEN/MINID) / Memory + retention 取捨。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &amp;#43; consumer group">Redis Streams vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/" data-link-title="3.C16 Robinhood：Faust Python stream processing" data-link-desc="Robinhood 每天 billions of events、Python 團隊不想用 JVM 生態、把 Kafka Streams 移植到 Python。">3.C16 Robinhood Faust&lt;/a>（語言生態對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://dev.to/mtk3d/beyond-the-hype-why-we-chose-redis-streams-over-kafka-for-our-microservices-dmc">Beyond the Hype: Why We Chose Redis Streams Over Kafka for Our Microservices&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 in-memory 訊息的 payload 限制要靠 hybrid storage 解決。</p>
<h2 id="觀察">觀察</h2>
<p>PHP 雙微服務之間的可靠通訊、Kafka 在 PHP 生態工具薄弱、團隊無 Kafka 經驗、production 跑數月後寫此文；明確覆蓋 XADD / XREADGROUP / consumer group / MAXLEN / MINID / XDEL / XACK / XACKDEL（Redis 8.2+）/ XTRIM。</p>
<h2 id="判讀">判讀</h2>
<p>揭露 in-memory 訊息的 payload 限制：用 payload compression + S3 hybrid storage（大 payload 存 S3、stream 只放 reference）；用 MAXLEN/MINID 控制 stream 成長。揭露 broker 選型常被「語言生態 client 品質」主導、不是純技術 feature。<strong>注意</strong>：作者是個人工程師、production 經驗但非知名公司。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Redis Streams 進階主題：XADD/XREAD/XREADGROUP 操作 / Retention (MAXLEN/MINID) / Memory + retention 取捨。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/redis-streams/" data-link-title="Redis Streams" data-link-desc="Redis 生態內的 streams、append-only log &#43; consumer group">Redis Streams vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/kafka-robinhood-faust-python-streaming/" data-link-title="3.C16 Robinhood：Faust Python stream processing" data-link-desc="Robinhood 每天 billions of events、Python 團隊不想用 JVM 生態、把 Kafka Streams 移植到 Python。">3.C16 Robinhood Faust</a>（語言生態對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://dev.to/mtk3d/beyond-the-hype-why-we-chose-redis-streams-over-kafka-for-our-microservices-dmc">Beyond the Hype: Why We Chose Redis Streams Over Kafka for Our Microservices</a></li>
</ul>
]]></content:encoded></item><item><title>3.C48 Airbnb Dynein：SQS 分散式延遲任務排程</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-dynein-delayed-jobs/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-dynein-delayed-jobs/</guid><description>&lt;p>這個案例的核心責任是說明 SQS at-least-once + DLQ 模型在工作排程的對齊邏輯。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 構建 Dynein 分散式延遲任務排程系統取代 Resque（受限於單 Redis 實例）。明確選 SQS、利用 at-least-once delivery、dead letter queue、individual message acknowledgment、access control 與 encryption-at-rest。每個 scheduler instance 達 ~1000 QPS、可水平擴展。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>at-least-once 對工作排程「不丟資料」假設足夠、SQS wrap DynamoDB 處理 &amp;gt; 15 分鐘 delay、DLQ 分離「短暫失敗」與「永久毒訊息」。揭露 managed queue 在工作排程的取捨：trade ordering 換 scaling。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard vs FIFO / DLQ 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/airbnb-engineering/dynein-building-a-distributed-delayed-job-queueing-system-93ab10f05f99">Dynein: Building a Distributed Delayed Job Queueing System&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS at-least-once + DLQ 模型在工作排程的對齊邏輯。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 構建 Dynein 分散式延遲任務排程系統取代 Resque（受限於單 Redis 實例）。明確選 SQS、利用 at-least-once delivery、dead letter queue、individual message acknowledgment、access control 與 encryption-at-rest。每個 scheduler instance 達 ~1000 QPS、可水平擴展。</p>
<h2 id="判讀">判讀</h2>
<p>at-least-once 對工作排程「不丟資料」假設足夠、SQS wrap DynamoDB 處理 &gt; 15 分鐘 delay、DLQ 分離「短暫失敗」與「永久毒訊息」。揭露 managed queue 在工作排程的取捨：trade ordering 換 scaling。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard vs FIFO / DLQ 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/airbnb-engineering/dynein-building-a-distributed-delayed-job-queueing-system-93ab10f05f99">Dynein: Building a Distributed Delayed Job Queueing System</a></li>
</ul>
]]></content:encoded></item><item><title>3.C49 Airbnb Inspekt：Visibility timeout 當 retry budget</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-inspekt-data-protection/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-airbnb-inspekt-data-protection/</guid><description>&lt;p>這個案例的核心責任是說明 visibility timeout 不只是「處理時間」、可當隱式的 retry 機制。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 的 Inspekt 隱私資料掃描系統用 SQS task queue 派發 scan task（每 table/object/app 一個 message）、Scanner nodes 水平 pull。&amp;ldquo;each message reappears N times back into the queue until a scanner node deletes it&amp;rdquo; 是 visibility timeout 在實戰的應用。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>用 message 重現次數做 retry budget、scanner 失敗時不用自管 retry table。揭露 SQS 的「不刪除即重現」是設計、不是 bug、可以當隱式 retry 機制用。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Visibility timeout + in-flight messages。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/airbnb-engineering/automating-data-protection-at-scale-part-2-c2b8d2068216">Automating Data Protection at Scale Part 2&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 visibility timeout 不只是「處理時間」、可當隱式的 retry 機制。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 的 Inspekt 隱私資料掃描系統用 SQS task queue 派發 scan task（每 table/object/app 一個 message）、Scanner nodes 水平 pull。&ldquo;each message reappears N times back into the queue until a scanner node deletes it&rdquo; 是 visibility timeout 在實戰的應用。</p>
<h2 id="判讀">判讀</h2>
<p>用 message 重現次數做 retry budget、scanner 失敗時不用自管 retry table。揭露 SQS 的「不刪除即重現」是設計、不是 bug、可以當隱式 retry 機制用。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Visibility timeout + in-flight messages。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/airbnb-engineering/automating-data-protection-at-scale-part-2-c2b8d2068216">Automating Data Protection at Scale Part 2</a></li>
</ul>
]]></content:encoded></item><item><title>3.C50 Capital One：Visibility timeout 設計與 Lambda event source</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-capital-one-visibility-timeout/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-capital-one-visibility-timeout/</guid><description>&lt;p>Capital One 的 SQS + Lambda 實務揭露了 visibility timeout 的雙邊風險 — 太短導致重複處理、太長延遲 retry — 以及 Lambda event source mapping 的 scaling 行為跟直覺不同的地方。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Capital One 是美國大型金融機構，tech blog 公開分享了 SQS + Lambda 的 event-driven 架構實踐。金融場景的 message 處理對正確性要求極高 — 重複處理一筆交易跟遺失一筆交易的代價都是具體的金錢損失。&lt;/p>
&lt;p>SQS 是 AWS 原生的 managed queue，Lambda 是 serverless compute。兩者搭配的 event source mapping 是 AWS 上最常見的 event-driven 入門架構 — 看起來簡單（SQS → Lambda 自動觸發），但 visibility timeout 跟 Lambda scaling 的互動有不少實務細節。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="visibility-timeout-的雙邊風險">Visibility timeout 的雙邊風險&lt;/h3>
&lt;p>SQS 的 visibility timeout 定義了「consumer 取走訊息後，其他 consumer 多久之後才能再看到這筆訊息」。它是 SQS 的核心容錯機制 — consumer 處理失敗（crash、timeout）時，visibility timeout 到期後訊息重新出現在 queue 裡，讓其他 consumer 接手。&lt;/p>
&lt;p>&lt;strong>Timeout 太短&lt;/strong>：consumer 還在處理中、visibility timeout 已到期、另一個 consumer 取走同一筆訊息開始處理 — 重複處理。金融場景的重複處理可能導致重複扣款或重複退款。&lt;/p>
&lt;p>&lt;strong>Timeout 太長&lt;/strong>：consumer 處理失敗、需要等很久 visibility timeout 才到期、訊息才重新出現 — retry 延遲。原本幾秒就能被其他 consumer 接手的訊息，要等 15 分鐘才 retry。&lt;/p>
&lt;p>Capital One 的實務建議是 visibility timeout 設為「最大預期處理時間 + 少量緩衝」。例如：最大處理時間 30 秒 → visibility timeout 設 45 秒。&lt;/p>
&lt;h3 id="lambda-event-source-mapping-的-scaling-行為">Lambda event source mapping 的 scaling 行為&lt;/h3>
&lt;p>Lambda 跟 SQS 的整合透過 event source mapping — Lambda 服務自動從 SQS long polling 取訊息、觸發 Lambda function。使用者不需要自己寫 polling 邏輯。&lt;/p>
&lt;p>Capital One 揭露的 scaling 行為跟「Lambda 自動擴展」的直覺不同：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>初始狀態&lt;/strong>：Lambda 啟動 5 個 long polling connection（poller）&lt;/li>
&lt;li>&lt;strong>Scale up&lt;/strong>：每分鐘最多新增 60 個 poller instance（每個 instance 處理一批 message）&lt;/li>
&lt;li>&lt;strong>上限&lt;/strong>：最多 1000 個並行 batch&lt;/li>
&lt;/ul>
&lt;p>這意味著突發流量（queue 瞬間湧入大量訊息）的消化速度不是即時的 — Lambda 需要數分鐘才能 scale 到足夠的並行度。在這段 ramp-up 期間，queue depth 會持續增長。&lt;/p>
&lt;h3 id="batch-size-跟-visibility-timeout-的互動">Batch size 跟 visibility timeout 的互動&lt;/h3>
&lt;p>Lambda event source mapping 預設 batch size = 10 — 一次取 10 筆訊息、用一個 Lambda invocation 處理。如果 batch 中的某一筆處理特別慢，整個 batch 的處理時間會被拉長。&lt;/p></description><content:encoded><![CDATA[<p>Capital One 的 SQS + Lambda 實務揭露了 visibility timeout 的雙邊風險 — 太短導致重複處理、太長延遲 retry — 以及 Lambda event source mapping 的 scaling 行為跟直覺不同的地方。</p>
<h2 id="業務背景">業務背景</h2>
<p>Capital One 是美國大型金融機構，tech blog 公開分享了 SQS + Lambda 的 event-driven 架構實踐。金融場景的 message 處理對正確性要求極高 — 重複處理一筆交易跟遺失一筆交易的代價都是具體的金錢損失。</p>
<p>SQS 是 AWS 原生的 managed queue，Lambda 是 serverless compute。兩者搭配的 event source mapping 是 AWS 上最常見的 event-driven 入門架構 — 看起來簡單（SQS → Lambda 自動觸發），但 visibility timeout 跟 Lambda scaling 的互動有不少實務細節。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="visibility-timeout-的雙邊風險">Visibility timeout 的雙邊風險</h3>
<p>SQS 的 visibility timeout 定義了「consumer 取走訊息後，其他 consumer 多久之後才能再看到這筆訊息」。它是 SQS 的核心容錯機制 — consumer 處理失敗（crash、timeout）時，visibility timeout 到期後訊息重新出現在 queue 裡，讓其他 consumer 接手。</p>
<p><strong>Timeout 太短</strong>：consumer 還在處理中、visibility timeout 已到期、另一個 consumer 取走同一筆訊息開始處理 — 重複處理。金融場景的重複處理可能導致重複扣款或重複退款。</p>
<p><strong>Timeout 太長</strong>：consumer 處理失敗、需要等很久 visibility timeout 才到期、訊息才重新出現 — retry 延遲。原本幾秒就能被其他 consumer 接手的訊息，要等 15 分鐘才 retry。</p>
<p>Capital One 的實務建議是 visibility timeout 設為「最大預期處理時間 + 少量緩衝」。例如：最大處理時間 30 秒 → visibility timeout 設 45 秒。</p>
<h3 id="lambda-event-source-mapping-的-scaling-行為">Lambda event source mapping 的 scaling 行為</h3>
<p>Lambda 跟 SQS 的整合透過 event source mapping — Lambda 服務自動從 SQS long polling 取訊息、觸發 Lambda function。使用者不需要自己寫 polling 邏輯。</p>
<p>Capital One 揭露的 scaling 行為跟「Lambda 自動擴展」的直覺不同：</p>
<ul>
<li><strong>初始狀態</strong>：Lambda 啟動 5 個 long polling connection（poller）</li>
<li><strong>Scale up</strong>：每分鐘最多新增 60 個 poller instance（每個 instance 處理一批 message）</li>
<li><strong>上限</strong>：最多 1000 個並行 batch</li>
</ul>
<p>這意味著突發流量（queue 瞬間湧入大量訊息）的消化速度不是即時的 — Lambda 需要數分鐘才能 scale 到足夠的並行度。在這段 ramp-up 期間，queue depth 會持續增長。</p>
<h3 id="batch-size-跟-visibility-timeout-的互動">Batch size 跟 visibility timeout 的互動</h3>
<p>Lambda event source mapping 預設 batch size = 10 — 一次取 10 筆訊息、用一個 Lambda invocation 處理。如果 batch 中的某一筆處理特別慢，整個 batch 的處理時間會被拉長。</p>
<p>Visibility timeout 要覆蓋整個 batch 的處理時間（包含最慢的那一筆），否則 batch 還在處理中、早期取走的訊息 visibility timeout 到期、被其他 poller 重新取走 — 導致重複處理。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<table>
  <thead>
      <tr>
          <th>設計參數</th>
          <th>建議值</th>
          <th>取捨</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Visibility timeout</td>
          <td>最大處理時間 + 緩衝（例 45 秒）</td>
          <td>太短重複、太長延遲 retry</td>
      </tr>
      <tr>
          <td>Batch size</td>
          <td>依處理時間變異度調整</td>
          <td>Batch 大省 invocation 費用、但延長 visibility 需求</td>
      </tr>
      <tr>
          <td>DLQ</td>
          <td>設定 maxReceiveCount（例 3 次）</td>
          <td>避免 poison message 無限 retry</td>
      </tr>
      <tr>
          <td>Concurrency limit</td>
          <td>依下游承受能力設定</td>
          <td>避免 Lambda 爆量壓垮下游 DB</td>
      </tr>
  </tbody>
</table>
<h3 id="idempotency-作為安全網">Idempotency 作為安全網</h3>
<p>Visibility timeout 無法完全避免重複處理（網路分區、Lambda timeout 等邊界條件）。Capital One 的做法是在 Lambda function 內實作 <a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> — 用 message ID 做去重，確保同一筆訊息被多次處理時結果相同。</p>
<p>Idempotency 把 visibility timeout 的精確度要求降低 — 即使偶爾重複處理，業務結果仍然正確。Visibility timeout 仍然需要合理設定（降低不必要的重複 invocation 成本），但 idempotency 是「即使設錯也不會造成業務錯誤」的安全網。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a>：visibility timeout、in-flight limit、Lambda event source 的進階主題</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：at-least-once 語意下的 consumer 端 idempotency</li>
<li><a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>：visibility timeout 是 SQS 的 delivery guarantee 機制</li>
<li><a href="/blog/backend/03-message-queue/queue-consumer-retry-replay-handoff/" data-link-title="3.8 Queue Consumer Retry 與 Replay Handoff（實作示範）" data-link-desc="以 order_created consumer 示範 queue 路徑如何交付 idempotency evidence、DLQ handling、replay runbook 與 incident decision log。">3.8 queue consumer retry replay handoff</a>：DLQ + maxReceiveCount 的 retry 升級策略</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>SQS + Lambda 架構中出現訊息重複處理（CloudWatch 的 <code>ApproximateNumberOfMessagesNotVisible</code> 跟 <code>NumberOfMessagesReceived</code> 比例異常）</li>
<li>Lambda function 的 timeout 跟 SQS visibility timeout 的關係沒有明確設計</li>
<li>突發流量時 queue depth 持續增長、Lambda 的 concurrent execution 沒有立刻跟上</li>
<li>Batch processing 中的慢訊息拖慢整個 batch、造成 visibility timeout 到期</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.capitalone.com/tech/cloud/using-aws-solutions-for-event-driven-serverless-architectures/">Using AWS Solutions for Event-Driven Serverless Architectures</a></li>
</ul>
]]></content:encoded></item><item><title>3.C51 Atlassian JiRT：Kinesis + SQS subscription</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/</guid><description>&lt;p>這個案例的核心責任是說明 SQS 作為 streaming source 的 per-consumer subscription 模式。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Atlassian 內部 event bus StreamHub 底層用 Kinesis、但「每個 consumer 自己準備 SQS queue 接收 event」。JiRT 即時服務透過此模式把輪詢式（~1 min）改成 event-driven（秒級）。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>在 Kinesis 上面疊 SQS 讓 consumer 各自設定 retention、各自獨立 visibility timeout。揭露「stream + per-consumer queue」是 fan-out 場景的常見複合 pattern、不是 streaming vs queue 二選一。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard vs FIFO / SQS 作為 fan-out subscriber。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>（streaming + queue 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.atlassian.com/blog/atlassian-engineering/using-an-event-driven-architecture-to-improve-jira-software-responsiveness">Using an Event-Driven Architecture to Improve Jira Software Responsiveness&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS 作為 streaming source 的 per-consumer subscription 模式。</p>
<h2 id="觀察">觀察</h2>
<p>Atlassian 內部 event bus StreamHub 底層用 Kinesis、但「每個 consumer 自己準備 SQS queue 接收 event」。JiRT 即時服務透過此模式把輪詢式（~1 min）改成 event-driven（秒級）。</p>
<h2 id="判讀">判讀</h2>
<p>在 Kinesis 上面疊 SQS 讓 consumer 各自設定 retention、各自獨立 visibility timeout。揭露「stream + per-consumer queue」是 fan-out 場景的常見複合 pattern、不是 streaming vs queue 二選一。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard vs FIFO / SQS 作為 fan-out subscriber。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>（streaming + queue 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.atlassian.com/blog/atlassian-engineering/using-an-event-driven-architecture-to-improve-jira-software-responsiveness">Using an Event-Driven Architecture to Improve Jira Software Responsiveness</a></li>
</ul>
]]></content:encoded></item><item><title>3.C52 Nielsen：Spark on EKS 雙 SQS 工作流</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-nielsen-spark-eks-dual-queue/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-nielsen-spark-eks-dual-queue/</guid><description>&lt;p>這個案例的核心責任是說明 SQS queue depth 作為 autoscale 訊號的真實案例。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Nielsen 每日處理 25 TB / 30 billion event。架構用兩個 SQS queue：work queue（待處理工作項）+ completion queue（回報完成）。Lambda 從 DB 拉檔案、組成 work item 推進 work queue、EKS pod 拉取處理、處理完寫 completion queue。基於 queue depth 自動擴 pod。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不用直接 Lambda invoke（pod 上跑長時間 Spark workload）、queue depth 當 backlog signal driving autoscale。揭露長 workload 場景該用 pod + queue depth、不是 Lambda function。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：CloudWatch metric + alarm / Standard queue / 長 workload autoscaling。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/" data-link-title="3.C22 Trivago：KEDA scale-to-zero by Kafka lag" data-link-desc="Trivago 50&amp;#43; Kafka sink、CPU/mem autoscaling 無效（I/O bottleneck）、KEDA 以 consumer lag 為訊號達到 scale-to-zero。">3.C22 Trivago KEDA&lt;/a>（lag-based autoscale 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/architecture/how-nielsen-uses-serverless-concepts-on-amazon-eks-for-big-data-processing-with-spark-workloads/">How Nielsen Uses Serverless Concepts on Amazon EKS for Big Data Spark Workloads&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS queue depth 作為 autoscale 訊號的真實案例。</p>
<h2 id="觀察">觀察</h2>
<p>Nielsen 每日處理 25 TB / 30 billion event。架構用兩個 SQS queue：work queue（待處理工作項）+ completion queue（回報完成）。Lambda 從 DB 拉檔案、組成 work item 推進 work queue、EKS pod 拉取處理、處理完寫 completion queue。基於 queue depth 自動擴 pod。</p>
<h2 id="判讀">判讀</h2>
<p>不用直接 Lambda invoke（pod 上跑長時間 Spark workload）、queue depth 當 backlog signal driving autoscale。揭露長 workload 場景該用 pod + queue depth、不是 Lambda function。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：CloudWatch metric + alarm / Standard queue / 長 workload autoscaling。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/kafka-trivago-keda-scale-to-zero/" data-link-title="3.C22 Trivago：KEDA scale-to-zero by Kafka lag" data-link-desc="Trivago 50&#43; Kafka sink、CPU/mem autoscaling 無效（I/O bottleneck）、KEDA 以 consumer lag 為訊號達到 scale-to-zero。">3.C22 Trivago KEDA</a>（lag-based autoscale 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/architecture/how-nielsen-uses-serverless-concepts-on-amazon-eks-for-big-data-processing-with-spark-workloads/">How Nielsen Uses Serverless Concepts on Amazon EKS for Big Data Spark Workloads</a></li>
</ul>
]]></content:encoded></item><item><title>3.C53 FINRA：S3 → SQS notification 大檔上傳</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-finra-large-file-service/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-finra-large-file-service/</guid><description>&lt;p>這個案例的核心責任是說明 S3 event notification 是 SQS 最經典 trigger、合規場景的 IAM 多層設定。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>FINRA 金融監管機構、處理 broker-dealer 上傳大檔。Large File Service 用 S3 → SQS 通知模式：使用者上傳完 loading dock bucket、S3 推 SQS message 給 LFS、移檔後再推 &amp;ldquo;file available&amp;rdquo; SQS message 給下游。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>S3 通知是 SQS 最經典 trigger、KMS + bucket policy + queue 權限的合規場景（金融業要保留稽核軌跡）。揭露金融場景的 IAM 設計不是一道權限、是多層稽核軌跡。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：SQS + Lambda event source / IAM + Cross-account。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">7 security 模組&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.finra.org/about/how-we-operate/technology/blog/large-file-service-securely-uploading-large-files-to-s3">FINRA Large File Service&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 S3 event notification 是 SQS 最經典 trigger、合規場景的 IAM 多層設定。</p>
<h2 id="觀察">觀察</h2>
<p>FINRA 金融監管機構、處理 broker-dealer 上傳大檔。Large File Service 用 S3 → SQS 通知模式：使用者上傳完 loading dock bucket、S3 推 SQS message 給 LFS、移檔後再推 &ldquo;file available&rdquo; SQS message 給下游。</p>
<h2 id="判讀">判讀</h2>
<p>S3 通知是 SQS 最經典 trigger、KMS + bucket policy + queue 權限的合規場景（金融業要保留稽核軌跡）。揭露金融場景的 IAM 設計不是一道權限、是多層稽核軌跡。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：SQS + Lambda event source / IAM + Cross-account。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">7 security 模組</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.finra.org/about/how-we-operate/technology/blog/large-file-service-securely-uploading-large-files-to-s3">FINRA Large File Service</a></li>
</ul>
]]></content:encoded></item><item><title>3.C54 Twitch EventSub：SNS+SQS fan-out 給第三方</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twitch-eventsub-fanout/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twitch-eventsub-fanout/</guid><description>&lt;p>這個案例的核心責任是說明 SNS-SQS fan-out + dispatcher pattern 的實戰。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Twitch 內部 Event Bus 發佈 ~1660 events/sec 到 SNS。EventSub（給第三方應用訂閱 Twitch 事件）用 SQS 接收 async notification、再由 Dispatcher fan-out 給各訂閱者。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>fan-out 後每個 consumer 要自己一個 queue。揭露 SNS → SQS 是 AWS 生態的 fan-out 標配、SQS 是第三方訂閱的 buffer 層、Dispatcher 是 application 級別的分發責任。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard queue + SQS + Lambda / SNS-SQS fan-out。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/" data-link-title="3.C51 Atlassian JiRT：Kinesis &amp;#43; SQS subscription" data-link-desc="Atlassian StreamHub Kinesis 底層、每 consumer 自己一個 SQS queue、JiRT 把輪詢 1 min 改成秒級 event-driven。">3.C51 Atlassian JiRT&lt;/a>（subscription 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.twitch.tv/en/2023/09/28/twitch-state-of-engineering-2023/">Twitch State of Engineering 2023&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SNS-SQS fan-out + dispatcher pattern 的實戰。</p>
<h2 id="觀察">觀察</h2>
<p>Twitch 內部 Event Bus 發佈 ~1660 events/sec 到 SNS。EventSub（給第三方應用訂閱 Twitch 事件）用 SQS 接收 async notification、再由 Dispatcher fan-out 給各訂閱者。</p>
<h2 id="判讀">判讀</h2>
<p>fan-out 後每個 consumer 要自己一個 queue。揭露 SNS → SQS 是 AWS 生態的 fan-out 標配、SQS 是第三方訂閱的 buffer 層、Dispatcher 是 application 級別的分發責任。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard queue + SQS + Lambda / SNS-SQS fan-out。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/sqs-atlassian-jirt-kinesis-sqs/" data-link-title="3.C51 Atlassian JiRT：Kinesis &#43; SQS subscription" data-link-desc="Atlassian StreamHub Kinesis 底層、每 consumer 自己一個 SQS queue、JiRT 把輪詢 1 min 改成秒級 event-driven。">3.C51 Atlassian JiRT</a>（subscription 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.twitch.tv/en/2023/09/28/twitch-state-of-engineering-2023/">Twitch State of Engineering 2023</a></li>
</ul>
]]></content:encoded></item><item><title>3.C55 SmugMug：SQS 驅動可重放搜尋管線</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-smugmug-search-pipeline/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-smugmug-search-pipeline/</guid><description>&lt;p>這個案例的核心責任是說明 SQS 作為「workload generator」的分散式平行化角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>SmugMug 用 SQS 兩種模式：(1) backfill — script 推 DynamoDB scan-segment 指令進 SQS、Lambda 拉取做平行掃描寫 OpenSearch、(2) 鏡像查詢 — production query 推副本 SQS、Lambda replay 到 replica domain。每小時可 index &amp;gt; 1 billion document、不影響 production。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>SQS 作為「workload generator」分散式平行化、不需協調 worker 數量。揭露 SQS 不只是「事件 queue」、也是「並行任務分散」的協調基礎。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard queue / Long polling / SQS + Lambda event source。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/big-data/smugmugs-durable-search-pipelines-for-amazon-opensearch-service/">SmugMug&amp;rsquo;s Durable Search Pipelines for Amazon OpenSearch Service&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 SQS 作為「workload generator」的分散式平行化角色。</p>
<h2 id="觀察">觀察</h2>
<p>SmugMug 用 SQS 兩種模式：(1) backfill — script 推 DynamoDB scan-segment 指令進 SQS、Lambda 拉取做平行掃描寫 OpenSearch、(2) 鏡像查詢 — production query 推副本 SQS、Lambda replay 到 replica domain。每小時可 index &gt; 1 billion document、不影響 production。</p>
<h2 id="判讀">判讀</h2>
<p>SQS 作為「workload generator」分散式平行化、不需協調 worker 數量。揭露 SQS 不只是「事件 queue」、也是「並行任務分散」的協調基礎。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard queue / Long polling / SQS + Lambda event source。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/big-data/smugmugs-durable-search-pipelines-for-amazon-opensearch-service/">SmugMug&rsquo;s Durable Search Pipelines for Amazon OpenSearch Service</a></li>
</ul>
]]></content:encoded></item><item><title>3.C56 PostNL EBE：完整 DLQ + retention + redrive 設計</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/</guid><description>&lt;p>這個案例的核心責任是業內真正完整的 DLQ + redrive + retention 設計案例、不是 demo 規模。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>PostNL（荷蘭最大物流商、每天 6.9M 信件 + 1.1M 包裹）的 Event Broker E-commerce 系統每天處理 ~10M message。完整列出 SQS 配置：每 producer/consumer 隔離 stack（最小爆炸半徑）、3 天 replay via EventBridge、exponential backoff with jitter、24 小時內最多 retry 100 次、final DLQ 允許 consumer 自己 redrive。max receive count 設 1 觸發 DLQ 告警。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「每 producer/consumer 隔離 stack」是 mission-critical 系統的 blast radius 設計、不只是 queue 配置。揭露 production-grade SQS 設計含三件事：隔離 + retry 政策 + redrive 流程。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：DLQ 設計 / CloudWatch alarm / Cost 模型。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/postnl-engineering/design-a-mission-critical-serverless-application-for-high-resilience-2858bf11360a">Designing a Mission-Critical Serverless Application for High Resilience&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是業內真正完整的 DLQ + redrive + retention 設計案例、不是 demo 規模。</p>
<h2 id="觀察">觀察</h2>
<p>PostNL（荷蘭最大物流商、每天 6.9M 信件 + 1.1M 包裹）的 Event Broker E-commerce 系統每天處理 ~10M message。完整列出 SQS 配置：每 producer/consumer 隔離 stack（最小爆炸半徑）、3 天 replay via EventBridge、exponential backoff with jitter、24 小時內最多 retry 100 次、final DLQ 允許 consumer 自己 redrive。max receive count 設 1 觸發 DLQ 告警。</p>
<h2 id="判讀">判讀</h2>
<p>「每 producer/consumer 隔離 stack」是 mission-critical 系統的 blast radius 設計、不只是 queue 配置。揭露 production-grade SQS 設計含三件事：隔離 + retry 政策 + redrive 流程。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：DLQ 設計 / CloudWatch alarm / Cost 模型。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://medium.com/postnl-engineering/design-a-mission-critical-serverless-application-for-high-resilience-2858bf11360a">Designing a Mission-Critical Serverless Application for High Resilience</a></li>
</ul>
]]></content:encoded></item><item><title>3.C57 Lob：自家 fork @lob/sqs-consumer 修 FIFO bug</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-lob-sqs-consumer-library/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-lob-sqs-consumer-library/</guid><description>&lt;p>這個案例的核心責任是說明真實 production library 維護成本、FIFO consumer 的隱性 bug。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Lob（programmatic mail API）原本用 bbc/sqs-consumer 但被鎖在 AWS SDK v2。他們 fork 出 @lob/sqs-consumer：支援 SDK v3（模組化 import 縮 bundle、TypeScript 一級支援、async/await）、修正原 library 對 FIFO queue 的 bug。SQS 用在 Lob API 跟其他內部 service。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>不能只靠 SDK 原生 API、SDK 升級會逼出 library 維護議題。揭露「FIFO queue 跟 standard queue 的 client 行為差異」是 library 層的隱性 bug 來源。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Standard vs FIFO / Long polling / Client library 維護。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.lob.com/blog/lob-sqs-consumer">@lob/sqs-consumer&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明真實 production library 維護成本、FIFO consumer 的隱性 bug。</p>
<h2 id="觀察">觀察</h2>
<p>Lob（programmatic mail API）原本用 bbc/sqs-consumer 但被鎖在 AWS SDK v2。他們 fork 出 @lob/sqs-consumer：支援 SDK v3（模組化 import 縮 bundle、TypeScript 一級支援、async/await）、修正原 library 對 FIFO queue 的 bug。SQS 用在 Lob API 跟其他內部 service。</p>
<h2 id="判讀">判讀</h2>
<p>不能只靠 SDK 原生 API、SDK 升級會逼出 library 維護議題。揭露「FIFO queue 跟 standard queue 的 client 行為差異」是 library 層的隱性 bug 來源。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Standard vs FIFO / Long polling / Client library 維護。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.lob.com/blog/lob-sqs-consumer">@lob/sqs-consumer</a></li>
</ul>
]]></content:encoded></item><item><title>3.C58 Twilio：SQS 緩衝高流量 webhook</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/</guid><description>&lt;p>這個案例的核心責任是說明 webhook → SQS buffer 是 Twilio 推薦的 pattern、FIFO TPS 上限的分片實務。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Twilio 自己 engineering blog 教使用者用 SQS 緩衝來自 Twilio 的高流量 SMS / status callback webhook（避免下游 app 來不及處理）。用 separate queue 區分 SMS vs status callback、long polling 減少空 API call、特別點出 FIFO 300 TPS 上限要分 queue。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Webhook 是 push、下游可能來不及、SQS 當 buffer 是常見 pattern。揭露 FIFO 的 300 TPS 上限是 hard limit、要設計分片才能擴張。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Long polling / Standard vs FIFO。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.twilio.com/en-us/blog/handling-high-volume-inbound-sms-and-webhooks-with-twilio-functions-and-amazon-sqs-html">Handling High Volume Inbound SMS and Webhooks with Twilio Functions and Amazon SQS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 webhook → SQS buffer 是 Twilio 推薦的 pattern、FIFO TPS 上限的分片實務。</p>
<h2 id="觀察">觀察</h2>
<p>Twilio 自己 engineering blog 教使用者用 SQS 緩衝來自 Twilio 的高流量 SMS / status callback webhook（避免下游 app 來不及處理）。用 separate queue 區分 SMS vs status callback、long polling 減少空 API call、特別點出 FIFO 300 TPS 上限要分 queue。</p>
<h2 id="判讀">判讀</h2>
<p>Webhook 是 push、下游可能來不及、SQS 當 buffer 是常見 pattern。揭露 FIFO 的 300 TPS 上限是 hard limit、要設計分片才能擴張。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Long polling / Standard vs FIFO。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/durable-queue/" data-link-title="3.2 durable queue 與重試策略" data-link-desc="整理持久化佇列、DLQ 與重試流程">3.2 durable queue</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.twilio.com/en-us/blog/handling-high-volume-inbound-sms-and-webhooks-with-twilio-functions-and-amazon-sqs-html">Handling High Volume Inbound SMS and Webhooks with Twilio Functions and Amazon SQS</a></li>
</ul>
]]></content:encoded></item><item><title>3.C59 Rapid7：SQS 100 億 message/day 規模</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-rapid7-scale-billion-messages/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-rapid7-scale-billion-messages/</guid><description>&lt;p>這個案例的核心責任是建立 SQS 在 10 billion+/day 規模下的成本結構與量級參考點。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Rapid7 Platform Software Architect 公開引述：「SQS 是我們架構的關鍵元件、讓我們 scale 到處理 10s of billions of messages per day。」是 AWS 官方文中具名客戶 quote、非 marketing 概括。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>SQS 在百億訊息/日規模下仍可用、是 scale 的具體量級參考點。揭露 SQS request-based 計費在這個規模下、cost 模型該被認真評估。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>SQS 進階主題：Cost 模型 / Standard queue。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本取捨&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/blogs/aws/amazon-sqs-15-years-and-still-queueing/">Amazon SQS — 15 Years and Still Queueing&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是建立 SQS 在 10 billion+/day 規模下的成本結構與量級參考點。</p>
<h2 id="觀察">觀察</h2>
<p>Rapid7 Platform Software Architect 公開引述：「SQS 是我們架構的關鍵元件、讓我們 scale 到處理 10s of billions of messages per day。」是 AWS 官方文中具名客戶 quote、非 marketing 概括。</p>
<h2 id="判讀">判讀</h2>
<p>SQS 在百億訊息/日規模下仍可用、是 scale 的具體量級參考點。揭露 SQS request-based 計費在這個規模下、cost 模型該被認真評估。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>SQS 進階主題：Cost 模型 / Standard queue。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/aws-sqs/" data-link-title="AWS SQS" data-link-desc="AWS managed queue、簡單可靠、無 ordering（standard）">SQS vendor 頁</a> 與 <a href="/blog/backend/00-service-selection/cost-risk-tradeoffs/" data-link-title="0.6 成本、風險與選型取捨" data-link-desc="用人力成本、雲端成本、操作成本與失敗代價判斷後端能力投入順序">0.6 成本取捨</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws.amazon.com/blogs/aws/amazon-sqs-15-years-and-still-queueing/">Amazon SQS — 15 Years and Still Queueing</a></li>
</ul>
]]></content:encoded></item><item><title>3.C60 Spotify：Event Delivery 從 Kafka 遷到 Pub/Sub</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-event-delivery-platform/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-event-delivery-platform/</guid><description>&lt;p>Spotify 把全球 event delivery 從 Kafka 遷到 Cloud Pub/Sub 的案例揭露了大規模 pull subscription 的工程現實 — at-least-once 語意意味著應用層去重不可省。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Spotify 的 Event Delivery 系統負責把所有使用者行為事件（播放、搜尋、推薦互動、廣告曝光）從客戶端經由資料管線送到下游消費者。事件是推薦引擎、A/B test、廣告計費跟 analytics 的核心輸入。&lt;/p>
&lt;p>遷移到 GCP Pub/Sub 後的系統規模：每個 event type 一個 topic、~15 個 microservice 跑在 ~2500 VM 上、Q1 2019 高峰 8M events/sec、每日 350 TB raw event 流量。遷出 Kafka 的動機跟技術評估見 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20 Spotify 遷出 Kafka（反例）&lt;/a>。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="at-least-once-語意下的重複">At-least-once 語意下的重複&lt;/h3>
&lt;p>Cloud Pub/Sub（早期版本）提供 at-least-once delivery — 同一筆訊息可能被 deliver 多次。在每日 350 TB 的流量下，「偶爾重複」的頻率足以影響 analytics 數據跟廣告計費的準確性。&lt;/p>
&lt;p>Pub/Sub 的重複來源有兩個：ack deadline 到期前 consumer 還沒處理完、訊息被重新 deliver 給其他 consumer；以及 Pub/Sub backend 的內部 redelivery（罕見但非零）。&lt;/p>
&lt;h3 id="pull-subscription-的流控">Pull subscription 的流控&lt;/h3>
&lt;p>Pull subscription 讓 consumer 主動從 Pub/Sub 拉取訊息（vs push subscription 由 Pub/Sub 推送到 HTTP endpoint）。Pull 的好處是 consumer 可以控制自己的消費速度，避免被推送壓垮。&lt;/p>
&lt;p>大規模 pull subscription 的挑戰在於流控的精細度 — 每個 consumer VM 要設定合理的 maxOutstandingMessages 跟 maxOutstandingBytes，太大會讓 consumer 記憶體不足、太小會浪費 Pub/Sub 的吞吐能力。Spotify 的 2500 VM 各自獨立做 pull，需要在 fleet 級別保持流控的一致性。&lt;/p>
&lt;h3 id="每個-event-type-一個-topic-的治理">每個 event type 一個 topic 的治理&lt;/h3>
&lt;p>Spotify 按 event type 建立 topic（例如 &lt;code>play-event&lt;/code>、&lt;code>search-event&lt;/code>、&lt;code>ad-impression&lt;/code>）。Event type 數量成長後，topic 數量跟著增長。每個 topic 需要獨立的 subscription、monitoring、ack deadline 設定跟 retention policy。&lt;/p>
&lt;p>Topic 治理的工程問題是「誰 own 這個 topic、schema 變更怎麼協調、retention 該設多久」。Spotify 自建了 event delivery 平台層（Event Delivery Platform）來管理 topic lifecycle — 包括 topic 建立 / 刪除的 self-service API、schema registry、consumer group 管理。&lt;/p>
&lt;h2 id="解法與取捨">解法與取捨&lt;/h2>
&lt;h3 id="自建-deduplication-層">自建 deduplication 層&lt;/h3>
&lt;p>Spotify 在 consumer 端自建去重機制。每筆 event 帶 unique event ID，consumer 在處理前查 dedup store（記憶體 + 外部 cache）確認是否已處理過。已處理的 event 直接 ack、跳過處理邏輯。&lt;/p></description><content:encoded><![CDATA[<p>Spotify 把全球 event delivery 從 Kafka 遷到 Cloud Pub/Sub 的案例揭露了大規模 pull subscription 的工程現實 — at-least-once 語意意味著應用層去重不可省。</p>
<h2 id="業務背景">業務背景</h2>
<p>Spotify 的 Event Delivery 系統負責把所有使用者行為事件（播放、搜尋、推薦互動、廣告曝光）從客戶端經由資料管線送到下游消費者。事件是推薦引擎、A/B test、廣告計費跟 analytics 的核心輸入。</p>
<p>遷移到 GCP Pub/Sub 後的系統規模：每個 event type 一個 topic、~15 個 microservice 跑在 ~2500 VM 上、Q1 2019 高峰 8M events/sec、每日 350 TB raw event 流量。遷出 Kafka 的動機跟技術評估見 <a href="/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20 Spotify 遷出 Kafka（反例）</a>。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="at-least-once-語意下的重複">At-least-once 語意下的重複</h3>
<p>Cloud Pub/Sub（早期版本）提供 at-least-once delivery — 同一筆訊息可能被 deliver 多次。在每日 350 TB 的流量下，「偶爾重複」的頻率足以影響 analytics 數據跟廣告計費的準確性。</p>
<p>Pub/Sub 的重複來源有兩個：ack deadline 到期前 consumer 還沒處理完、訊息被重新 deliver 給其他 consumer；以及 Pub/Sub backend 的內部 redelivery（罕見但非零）。</p>
<h3 id="pull-subscription-的流控">Pull subscription 的流控</h3>
<p>Pull subscription 讓 consumer 主動從 Pub/Sub 拉取訊息（vs push subscription 由 Pub/Sub 推送到 HTTP endpoint）。Pull 的好處是 consumer 可以控制自己的消費速度，避免被推送壓垮。</p>
<p>大規模 pull subscription 的挑戰在於流控的精細度 — 每個 consumer VM 要設定合理的 maxOutstandingMessages 跟 maxOutstandingBytes，太大會讓 consumer 記憶體不足、太小會浪費 Pub/Sub 的吞吐能力。Spotify 的 2500 VM 各自獨立做 pull，需要在 fleet 級別保持流控的一致性。</p>
<h3 id="每個-event-type-一個-topic-的治理">每個 event type 一個 topic 的治理</h3>
<p>Spotify 按 event type 建立 topic（例如 <code>play-event</code>、<code>search-event</code>、<code>ad-impression</code>）。Event type 數量成長後，topic 數量跟著增長。每個 topic 需要獨立的 subscription、monitoring、ack deadline 設定跟 retention policy。</p>
<p>Topic 治理的工程問題是「誰 own 這個 topic、schema 變更怎麼協調、retention 該設多久」。Spotify 自建了 event delivery 平台層（Event Delivery Platform）來管理 topic lifecycle — 包括 topic 建立 / 刪除的 self-service API、schema registry、consumer group 管理。</p>
<h2 id="解法與取捨">解法與取捨</h2>
<h3 id="自建-deduplication-層">自建 deduplication 層</h3>
<p>Spotify 在 consumer 端自建去重機制。每筆 event 帶 unique event ID，consumer 在處理前查 dedup store（記憶體 + 外部 cache）確認是否已處理過。已處理的 event 直接 ack、跳過處理邏輯。</p>
<p>Dedup store 的挑戰是大小跟 TTL — 要記住多久以前的 event ID 才夠。TTL 太短會漏掉 late redelivery（Pub/Sub 在 ack deadline 之後才重新 deliver）、TTL 太長 dedup store 太大。Spotify 用滑動視窗（retention 跟 ack deadline 的倍數）設定 TTL。</p>
<h3 id="取捨">取捨</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Pub/Sub + 自建 dedup</th>
          <th>自管 Kafka 0.8+</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>運維成本</td>
          <td>低（Pub/Sub 全託管）</td>
          <td>高（自管 broker × 多 region）</td>
      </tr>
      <tr>
          <td>語意保證</td>
          <td>At-least-once + 應用層 dedup</td>
          <td>At-least-once（idempotent 0.11+）</td>
      </tr>
      <tr>
          <td>跨 region replication</td>
          <td>原生支援</td>
          <td>需要 MirrorMaker 或自建</td>
      </tr>
      <tr>
          <td>流控精細度</td>
          <td>Pull subscription 可控</td>
          <td>Consumer group 自動分配</td>
      </tr>
      <tr>
          <td>Topic 治理</td>
          <td>需要自建平台層</td>
          <td>Kafka 生態工具（Confluent 等）</td>
      </tr>
      <tr>
          <td>Dedup 成本</td>
          <td>額外的 cache / store 成本</td>
          <td>Idempotent producer 減少需求</td>
      </tr>
  </tbody>
</table>
<p>自建 dedup 的成本是 Spotify 選 Pub/Sub 的額外付出。這個代價在託管方案的運維節省面前被接受 — 維護一個 dedup cache 的成本遠低於維護跨 5 個 datacenter 的 Kafka broker fleet。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a>：push vs pull subscription、ack deadline、ordering 跟 DLT 的進階主題</li>
<li><a href="/blog/backend/03-message-queue/cases/kafka-spotify-event-delivery-exodus/" data-link-title="3.C20 Spotify：Event Delivery 從 Kafka 遷出（反例）" data-link-desc="Spotify Kafka 0.7 MirrorMaker best-effort 會掉資料但回報成功、broker restart 後 producer 無法恢復、決定遷到 GCP Pub/Sub。">3.C20 Spotify 遷出 Kafka</a>：遷出 Kafka 的動機跟決策判準</li>
<li><a href="/blog/backend/03-message-queue/processing-recovery-semantics/" data-link-title="3.6 Processing Semantics 與 Recovery Semantics" data-link-desc="說明投遞成功、處理成功與恢復成功為何是三個不同判斷。">3.6 processing recovery semantics</a>：at-least-once 語意下的 dedup 策略</li>
<li><a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract replay boundary</a>：event schema 跟 topic lifecycle 的治理</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>使用 GCP Pub/Sub 且下游消費者偶爾處理到重複事件</li>
<li>Pull subscription 的 consumer 記憶體使用不穩定、maxOutstandingMessages 設定不合理</li>
<li>Topic 數量持續增長但缺少統一的 lifecycle 管理</li>
<li>從自管 Kafka 遷移到 GCP Pub/Sub 的評估階段</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2019/11/spotifys-event-delivery-life-in-the-cloud">Spotify&rsquo;s Event Delivery — Life in the Cloud</a></li>
</ul>
]]></content:encoded></item><item><title>3.C61 Spotify：Autoscaling Pub/Sub consumer 反效果</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-autoscaling-consumers/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-autoscaling-consumers/</guid><description>&lt;p>這個案例的核心責任是說明「subscription backlog 不等於 consumer healthy」、autoscaling 跟 ack deadline 的耦合風險。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>下游 Cloud Storage export 失敗時、consumer 不 ack 仍持續消耗 CPU 處理同批訊息、造成 autoscaling 把 CPU 越拉越高的反效果；解法是 exponential backoff 抑制 CPU 消耗。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「Subscription backlog 不等於 consumer healthy」— 訊息未 ack 累積跟 autoscaling 的耦合風險。揭露 autoscale signal 該看「處理成功率」而非「CPU + backlog」。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Ack deadline / autoscaling signal 設計。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.atspotify.com/2017/11/autoscaling-pub-sub-consumers">Autoscaling Pub/Sub Consumers&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「subscription backlog 不等於 consumer healthy」、autoscaling 跟 ack deadline 的耦合風險。</p>
<h2 id="觀察">觀察</h2>
<p>下游 Cloud Storage export 失敗時、consumer 不 ack 仍持續消耗 CPU 處理同批訊息、造成 autoscaling 把 CPU 越拉越高的反效果；解法是 exponential backoff 抑制 CPU 消耗。</p>
<h2 id="判讀">判讀</h2>
<p>「Subscription backlog 不等於 consumer healthy」— 訊息未 ack 累積跟 autoscaling 的耦合風險。揭露 autoscale signal 該看「處理成功率」而非「CPU + backlog」。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Ack deadline / autoscaling signal 設計。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 紅隊章</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2017/11/autoscaling-pub-sub-consumers">Autoscaling Pub/Sub Consumers</a></li>
</ul>
]]></content:encoded></item><item><title>3.C62 Spotify：Pub/Sub → GCS reliable export</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-cloud-storage-export/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-spotify-cloud-storage-export/</guid><description>&lt;p>這個案例的核心責任是說明 ack 是 end-to-end commit 信號、不是 buffer-flush 信號。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Consumer 只在下游 Completionist 回 200 OK 才 ack 回 Pub/Sub、並用「Oldest Unacknowledged Message」metric 判斷 hourly bucket 何時可安全關閉；ack semantics 直接綁定下游 commit。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>ack 是 end-to-end commit 信號、不是 buffer-flush 信號。揭露為什麼後來原生 GCS subscription 有價值（Spotify 早期沒有原生、自建管線）。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Ack deadline / Cloud Storage subscription（早期無原生、自建對照）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.atspotify.com/2017/04/reliable-export-of-cloud-pubsub-streams-to-cloud-storage">Reliable Export of Cloud Pub/Sub Streams to Cloud Storage&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 ack 是 end-to-end commit 信號、不是 buffer-flush 信號。</p>
<h2 id="觀察">觀察</h2>
<p>Consumer 只在下游 Completionist 回 200 OK 才 ack 回 Pub/Sub、並用「Oldest Unacknowledged Message」metric 判斷 hourly bucket 何時可安全關閉；ack semantics 直接綁定下游 commit。</p>
<h2 id="判讀">判讀</h2>
<p>ack 是 end-to-end commit 信號、不是 buffer-flush 信號。揭露為什麼後來原生 GCS subscription 有價值（Spotify 早期沒有原生、自建管線）。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Ack deadline / Cloud Storage subscription（早期無原生、自建對照）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/event-contract-replay-boundary/" data-link-title="3.7 Event Contract 與 Replay Boundary" data-link-desc="說明 event schema、idempotency key、replay window 與補償如何先於 broker 選型。">3.7 event contract / replay boundary</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.atspotify.com/2017/04/reliable-export-of-cloud-pubsub-streams-to-cloud-storage">Reliable Export of Cloud Pub/Sub Streams to Cloud Storage</a></li>
</ul>
]]></content:encoded></item><item><title>3.C63 Mercari Actionable History：ack deadline 是 batch-level</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-actionable-history/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-actionable-history/</guid><description>&lt;p>這個案例的核心責任是揭露 Pub/Sub client lib 「ack deadline 是 batch-level」這個真實的工程陷阱。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Merpay 支付流水帳服務用 Pub/Sub 做 async messaging、靠 nack 控制處理順序；踩到「ack deadline 是整批 batch 而非單訊息」、acked 訊息會跟同 batch 其他 expired/nacked 訊息一起 redeliver 的設計細節。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「ack deadline 是 batch-level」是 Pub/Sub client lib 真實的工程陷阱；idempotency 是處理 duplicate 的必要設計、新出的 exactly-once delivery 才有機會降低重複量。揭露 client lib 的批次語意會「污染」單訊息 ack。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Ack deadline / Push vs Pull / Ordering key（exactly-once / ordering 章節）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20221212-merpay-actionable-history-displaying-millions-of-payments-with-lightning-speed/">Merpay Actionable History: Displaying Millions of Payments with Lightning Speed&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是揭露 Pub/Sub client lib 「ack deadline 是 batch-level」這個真實的工程陷阱。</p>
<h2 id="觀察">觀察</h2>
<p>Merpay 支付流水帳服務用 Pub/Sub 做 async messaging、靠 nack 控制處理順序；踩到「ack deadline 是整批 batch 而非單訊息」、acked 訊息會跟同 batch 其他 expired/nacked 訊息一起 redeliver 的設計細節。</p>
<h2 id="判讀">判讀</h2>
<p>「ack deadline 是 batch-level」是 Pub/Sub client lib 真實的工程陷阱；idempotency 是處理 duplicate 的必要設計、新出的 exactly-once delivery 才有機會降低重複量。揭露 client lib 的批次語意會「污染」單訊息 ack。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Ack deadline / Push vs Pull / Ordering key（exactly-once / ordering 章節）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/failure-queue-semantics-mismatch-cutover/" data-link-title="3.C9 反例：Queue 語義切換誤配" data-link-desc="at-least-once / exactly-once 語義誤配導致資料重複與遺漏。">3.C9 反例：語義誤配</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20221212-merpay-actionable-history-displaying-millions-of-payments-with-lightning-speed/">Merpay Actionable History: Displaying Millions of Payments with Lightning Speed</a></li>
</ul>
]]></content:encoded></item><item><title>3.C64 Mercari Item Feed：DLT 防 poison message 阻塞</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-item-feed-dlt/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-item-feed-dlt/</guid><description>&lt;p>這個案例的核心責任是說明 DLT 在防止 poison message 阻塞 pipeline 的角色。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>商品 feed 同步用 pull subscription + 自家 batch requester、成功時 ack 整批、失敗時 nack 讓 Pub/Sub 重送；重試多次仍失敗則送 Dead-letter topic、後續訊息優先處理；topic 同時當突發流量的緩衝。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>直接示範 DLT 在防止 poison message 阻塞 pipeline 的角色、以及把 topic 當 load-leveling queue 的設計。揭露「topic = buffer + dispatch」雙重角色。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Dead-letter topic / Push vs Pull subscription。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/" data-link-title="3.C56 PostNL EBE：完整 DLQ &amp;#43; retention &amp;#43; redrive 設計" data-link-desc="PostNL 物流每天 1000 萬訊息、每 producer/consumer 隔離 stack、24h 內 100 次 retry、final DLQ 可 consumer redrive。">3.C56 PostNL EBE&lt;/a>（DLQ 設計對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/">Mercari&amp;rsquo;s Seamless Item Feed Integration&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 DLT 在防止 poison message 阻塞 pipeline 的角色。</p>
<h2 id="觀察">觀察</h2>
<p>商品 feed 同步用 pull subscription + 自家 batch requester、成功時 ack 整批、失敗時 nack 讓 Pub/Sub 重送；重試多次仍失敗則送 Dead-letter topic、後續訊息優先處理；topic 同時當突發流量的緩衝。</p>
<h2 id="判讀">判讀</h2>
<p>直接示範 DLT 在防止 poison message 阻塞 pipeline 的角色、以及把 topic 當 load-leveling queue 的設計。揭露「topic = buffer + dispatch」雙重角色。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Dead-letter topic / Push vs Pull subscription。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/sqs-postnl-mission-critical-ebe/" data-link-title="3.C56 PostNL EBE：完整 DLQ &#43; retention &#43; redrive 設計" data-link-desc="PostNL 物流每天 1000 萬訊息、每 producer/consumer 隔離 stack、24h 內 100 次 retry、final DLQ 可 consumer redrive。">3.C56 PostNL EBE</a>（DLQ 設計對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20241212-mercaris-seamless-item-feed-integration-bridging-the-gap-between-systems/">Mercari&rsquo;s Seamless Item Feed Integration</a></li>
</ul>
]]></content:encoded></item><item><title>3.C65 Mercari LINE：Pull subscription 對齊外部 RPS</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-line-flow-control/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-line-flow-control/</guid><description>&lt;p>這個案例的核心責任是說明「下游有 RPS 限制」是 Pull subscription 勝過 push 的典型情境。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Braze webhook 進來後轉成 Pub/Sub event、下游 LINE worker pull subscription「精確控制每秒處理訊息數」、因為外部 LINE API 有 RPS 限制。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>push 會把流量瞬間打到 endpoint、pull 可由 consumer 自行 throttle。揭露 push vs pull 不是「實作偏好」、是「下游能否接受 push 衝擊」的判讀。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Push vs Pull subscription。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/" data-link-title="3.C58 Twilio：SQS 緩衝高流量 webhook" data-link-desc="Twilio 教用 SQS 緩衝 SMS / status callback webhook、分 queue（SMS vs callback）、long polling 減 cost、FIFO 300 TPS 上限要分片。">3.C58 Twilio webhook buffer&lt;/a>（webhook + buffer 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20231212-flow-control-challenges-in-mercaris-line-integration/">Flow Control Challenges in Mercari&amp;rsquo;s LINE Integration&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明「下游有 RPS 限制」是 Pull subscription 勝過 push 的典型情境。</p>
<h2 id="觀察">觀察</h2>
<p>Braze webhook 進來後轉成 Pub/Sub event、下游 LINE worker pull subscription「精確控制每秒處理訊息數」、因為外部 LINE API 有 RPS 限制。</p>
<h2 id="判讀">判讀</h2>
<p>push 會把流量瞬間打到 endpoint、pull 可由 consumer 自行 throttle。揭露 push vs pull 不是「實作偏好」、是「下游能否接受 push 衝擊」的判讀。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Push vs Pull subscription。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/sqs-twilio-webhook-buffer/" data-link-title="3.C58 Twilio：SQS 緩衝高流量 webhook" data-link-desc="Twilio 教用 SQS 緩衝 SMS / status callback webhook、分 queue（SMS vs callback）、long polling 減 cost、FIFO 300 TPS 上限要分片。">3.C58 Twilio webhook buffer</a>（webhook + buffer 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20231212-flow-control-challenges-in-mercaris-line-integration/">Flow Control Challenges in Mercari&rsquo;s LINE Integration</a></li>
</ul>
]]></content:encoded></item><item><title>3.C66 Mercari B2C：自建 PubSub gRPC Pusher</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-b2c-grpc-pusher/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-mercari-b2c-grpc-pusher/</guid><description>&lt;p>這個案例的核心責任是說明原生 push subscription 在特定場景的限制、逼出自建層的工程選擇。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>全球商品同步系統、自建 in-house「PubSub gRPC Pusher」（Pub/Sub 的 gRPC 版 push subscription）解決高吞吐 / 長 job / 彈性 RPS；同時用 message ID 做去重、timestamp 驗證解決重複 + 亂序。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>原生 HTTP push subscription 在「長 job + 高吞吐 + 動態 rate」場景的限制、逼出自建層的工程選擇。揭露 managed broker 的「原生功能」不是所有場景的終點。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Push vs Pull subscription / Ordering key（亂序的 application-level 處理）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://engineering.mercari.com/en/blog/entry/20251009-from-local-to-global-building-seamless-b2c-product-integration-at-mercari/">From Local to Global: Building Seamless B2C Product Integration at Mercari&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明原生 push subscription 在特定場景的限制、逼出自建層的工程選擇。</p>
<h2 id="觀察">觀察</h2>
<p>全球商品同步系統、自建 in-house「PubSub gRPC Pusher」（Pub/Sub 的 gRPC 版 push subscription）解決高吞吐 / 長 job / 彈性 RPS；同時用 message ID 做去重、timestamp 驗證解決重複 + 亂序。</p>
<h2 id="判讀">判讀</h2>
<p>原生 HTTP push subscription 在「長 job + 高吞吐 + 動態 rate」場景的限制、逼出自建層的工程選擇。揭露 managed broker 的「原生功能」不是所有場景的終點。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Push vs Pull subscription / Ordering key（亂序的 application-level 處理）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">3.4 consumer 設計</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://engineering.mercari.com/en/blog/entry/20251009-from-local-to-global-building-seamless-b2c-product-integration-at-mercari/">From Local to Global: Building Seamless B2C Product Integration at Mercari</a></li>
</ul>
]]></content:encoded></item><item><title>3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/</guid><description>&lt;p>這個案例的核心責任是說明大規模遊戲 telemetry 的 ingest backbone 設計。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Pokémon GO frontend 把玩家事件 publish 到 Pub/Sub topic 餵分析 pipeline、再進 BigQuery streaming；高峰 ~1M TPS、Pub/Sub 是 managed service 因此 SRE 維運成本低。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>Pub/Sub 在 publisher 突發流量下作為 elastic buffer、下游 BigQuery streaming 是常見組合。揭露「managed service 的 SRE 成本」是大規模遊戲場景的關鍵選型理由。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：BigQuery subscription（原生 BQ subscription 出現前的 Dataflow pattern）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/" data-link-title="3.C68 Wix：Pub/Sub decouple &amp;#43; Dataflow &amp;#43; BQ archive" data-link-desc="Wix App Engine 收 clickstream 進 Pub/Sub、Dataflow 進 Datastore &amp;lt; 100ms、BigQuery 並行存 raw recovery。">3.C68 Wix clickstream&lt;/a>（同類組合）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/how-pok%C3%A9mon-go-scales-millions-requests">How Pokémon GO Scales to Millions of Requests&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明大規模遊戲 telemetry 的 ingest backbone 設計。</p>
<h2 id="觀察">觀察</h2>
<p>Pokémon GO frontend 把玩家事件 publish 到 Pub/Sub topic 餵分析 pipeline、再進 BigQuery streaming；高峰 ~1M TPS、Pub/Sub 是 managed service 因此 SRE 維運成本低。</p>
<h2 id="判讀">判讀</h2>
<p>Pub/Sub 在 publisher 突發流量下作為 elastic buffer、下游 BigQuery streaming 是常見組合。揭露「managed service 的 SRE 成本」是大規模遊戲場景的關鍵選型理由。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：BigQuery subscription（原生 BQ subscription 出現前的 Dataflow pattern）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/" data-link-title="3.C68 Wix：Pub/Sub decouple &#43; Dataflow &#43; BQ archive" data-link-desc="Wix App Engine 收 clickstream 進 Pub/Sub、Dataflow 進 Datastore &lt; 100ms、BigQuery 並行存 raw recovery。">3.C68 Wix clickstream</a>（同類組合）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/topics/developers-practitioners/how-pok%C3%A9mon-go-scales-millions-requests">How Pokémon GO Scales to Millions of Requests</a></li>
</ul>
]]></content:encoded></item><item><title>3.C68 Wix：Pub/Sub decouple + Dataflow + BQ archive</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-wix-clickstream-dashboard/</guid><description>&lt;p>這個案例的核心責任是「Pub/Sub buffer + Dataflow stream processor + BQ archive」的教科書組合。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>App Engine 收 clickstream → 進 Cloud Pub/Sub queue、再由 Dataflow streaming 處理進 Datastore、dashboard 端到端 latency &amp;lt; 100ms；BigQuery 並行存 raw data 做 recovery。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「Pub/Sub 當 decouple buffer + Dataflow 當 stream processor + BigQuery 當 raw archive」的 textbook 組合、可作為 BigQuery subscription 出現前的對比 case（為什麼後來原生 BQ subscription 能省掉 Dataflow 中介層）。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：BigQuery subscription / Push vs Pull。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/" data-link-title="3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest" data-link-desc="Pokémon GO frontend publish 玩家事件、~1M TPS、Pub/Sub elastic buffer、下游 BigQuery streaming。">3.C67 Niantic Pokémon GO&lt;/a>（同類組合）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/customers/wix">Wix Customer Story&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是「Pub/Sub buffer + Dataflow stream processor + BQ archive」的教科書組合。</p>
<h2 id="觀察">觀察</h2>
<p>App Engine 收 clickstream → 進 Cloud Pub/Sub queue、再由 Dataflow streaming 處理進 Datastore、dashboard 端到端 latency &lt; 100ms；BigQuery 並行存 raw data 做 recovery。</p>
<h2 id="判讀">判讀</h2>
<p>「Pub/Sub 當 decouple buffer + Dataflow 當 stream processor + BigQuery 當 raw archive」的 textbook 組合、可作為 BigQuery subscription 出現前的對比 case（為什麼後來原生 BQ subscription 能省掉 Dataflow 中介層）。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：BigQuery subscription / Push vs Pull。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/cases/pubsub-niantic-pokemon-go-telemetry/" data-link-title="3.C67 Niantic Pokémon GO：Pub/Sub 當 telemetry ingest" data-link-desc="Pokémon GO frontend publish 玩家事件、~1M TPS、Pub/Sub elastic buffer、下游 BigQuery streaming。">3.C67 Niantic Pokémon GO</a>（同類組合）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/customers/wix">Wix Customer Story</a></li>
</ul>
]]></content:encoded></item><item><title>3.C69 Twitter Ad Engagement：把 stream 切成多 topic 做 partition</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-twitter-ad-engagement/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/cases/pubsub-twitter-ad-engagement/</guid><description>&lt;p>這個案例的核心責任是說明 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Twitter 把 on-prem 服務的 Avro-formatted 訊息 push 到 Pub/Sub（兩條 stream、較不關鍵但量大的那條 ~80K msg/s 切成 6 個 topic）、下游用 Dataflow + Beam 處理進 Bigtable / BigQuery。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>「把單一 high-volume stream 切成多 topic 做 partition」是 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。揭露 Pub/Sub 跟 Kafka 的選型差異不是 feature parity、是不同的擴張模型。&lt;/p>
&lt;h2 id="對應大綱">對應大綱&lt;/h2>
&lt;p>Pub/Sub 進階主題：Schema enforcement（Avro 是常見 schema 候選）/ Ordering key（topic 切分 vs ordering key 的取捨）。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁&lt;/a>（partition 對照）。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/blog/products/data-analytics/modernizing-twitters-ad-engagement-analytics-platform">Modernizing Twitter&amp;rsquo;s Ad Engagement Analytics Platform&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。</p>
<h2 id="觀察">觀察</h2>
<p>Twitter 把 on-prem 服務的 Avro-formatted 訊息 push 到 Pub/Sub（兩條 stream、較不關鍵但量大的那條 ~80K msg/s 切成 6 個 topic）、下游用 Dataflow + Beam 處理進 Bigtable / BigQuery。</p>
<h2 id="判讀">判讀</h2>
<p>「把單一 high-volume stream 切成多 topic 做 partition」是 Pub/Sub 沒有 Kafka-style partition 概念下的應對策略。揭露 Pub/Sub 跟 Kafka 的選型差異不是 feature parity、是不同的擴張模型。</p>
<h2 id="對應大綱">對應大綱</h2>
<p>Pub/Sub 進階主題：Schema enforcement（Avro 是常見 schema 候選）/ Ordering key（topic 切分 vs ordering key 的取捨）。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/03-message-queue/vendors/google-pubsub/" data-link-title="Google Cloud Pub/Sub" data-link-desc="GCP managed pub/sub、global routing、push/pull">Pub/Sub vendor 頁</a> 與 <a href="/blog/backend/03-message-queue/vendors/kafka/" data-link-title="Apache Kafka" data-link-desc="Distributed event streaming platform、log-based 模型">Kafka vendor 頁</a>（partition 對照）。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/data-analytics/modernizing-twitters-ad-engagement-analytics-platform">Modernizing Twitter&rsquo;s Ad Engagement Analytics Platform</a></li>
</ul>
]]></content:encoded></item></channel></rss>