<?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>Durability on Tarragon</title><link>https://tarrragon.github.io/blog/tags/durability/</link><description>Recent content in Durability on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 23 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/durability/index.xml" rel="self" type="application/rss+xml"/><item><title>3.2 durable queue 與重試策略</title><link>https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/03-message-queue/durable-queue/</guid><description>&lt;p>持久化佇列（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/durable-queue/" data-link-title="Durable Queue" data-link-desc="說明可持久化的 queue 如何在重啟與失敗後保留待處理工作">durable queue&lt;/a>）的核心責任是讓非同步工作在 process、節點或網路故障後仍可被恢復處理。它讓業務動作在失敗後仍有可追蹤、可重試、可隔離的路徑。&lt;/p>
&lt;h2 id="durable-與-ephemeral-的差異">durable 與 ephemeral 的差異&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> 在語意上可分 durable 與 ephemeral。ephemeral queue 側重低延遲與短暫協調，適合可丟失任務；durable queue 側重故障後可恢復，適合正式狀態相關副作用，例如付款通知、發票產生、庫存同步與合規事件記錄。&lt;/p>
&lt;p>這個選擇本質上是失敗代價選擇。若任務丟失可接受，ephemeral 可降低成本；若任務丟失會造成金流、合約或審計問題，durable 是必要基線。&lt;/p>
&lt;h2 id="重試策略">重試策略&lt;/h2>
&lt;p>重試策略的責任是把暫時性故障和系統性故障分開。durable queue 常見的重試組合是：有限次重試、指數退避、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/jitter/" data-link-title="Jitter" data-link-desc="說明重試或排程加入隨機偏移如何降低同步尖峰">jitter&lt;/a> 分散峰值、超過門檻後分流到 &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 如何隔離多次處理失敗的訊息">dead-letter queue&lt;/a>。&lt;/p>
&lt;p>重試上限與間隔要由下游承載能力決定。重試太快會形成故障放大，重試太慢會拖長恢復時間。穩定做法是把重試策略當成服務容量控制的一部分，而不是固定平台預設值。&lt;/p>
&lt;h2 id="dlq-與-requeue-風險">DLQ 與 requeue 風險&lt;/h2>
&lt;p>DLQ 的責任是隔離異常訊息，避免拖垮主消費流程。DLQ 是診斷與修復入口，把它當終點會讓問題沉積。每個進入 DLQ 的訊息，都應能回答：失敗原因是 payload 錯誤、下游不可用、版本不相容，還是消費邏輯缺陷。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/requeue/" data-link-title="Requeue" data-link-desc="說明處理失敗的訊息重新排回 queue 時的風險與控制條件">requeue&lt;/a> 需要明確條件。直接把異常訊息無限 requeue，通常會造成隊列震盪與延遲累積。穩定做法是先隔離、分群、修復，再批次回放。&lt;/p>
&lt;h2 id="ordering-與吞吐取捨">ordering 與吞吐取捨&lt;/h2>
&lt;p>durable queue 在順序與吞吐之間需要明確取捨。全域順序通常成本極高，實務上多採用分區內順序：同一 key 保持順序，不同 key 可並行。這能兼顧一致性需求與處理吞吐。&lt;/p>
&lt;p>順序要求越高，恢復流程越需要明確 checkpoint 與補償策略。否則故障後的重播容易造成亂序副作用，放大修復成本。&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>queue depth 持續上升&lt;/td>
 &lt;td>輸入速率高於消費能力&lt;/td>
 &lt;td>擴消費能力、調整重試節奏、分流高成本任務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>retry ratio 升高且成功率下降&lt;/td>
 &lt;td>故障從暫時性轉為系統性&lt;/td>
 &lt;td>降級下游、縮小重試並啟動隔離策略&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DLQ 量快速增加&lt;/td>
 &lt;td>payload/版本/邏輯異常集中爆發&lt;/td>
 &lt;td>分群診斷、修復邏輯、定向重播&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>requeue 循環導致延遲尖峰&lt;/td>
 &lt;td>缺少隔離邊界與停損機制&lt;/td>
 &lt;td>停止盲目 requeue、先隔離後回放&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>消費恢復後出現大量重複副作用&lt;/td>
 &lt;td>去重與冪等保護不足&lt;/td>
 &lt;td>補 idempotency key 與 side-effect guard&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見誤區">常見誤區&lt;/h2>
&lt;p>把 durable queue 視為「寫進去就安全」，會忽略消費與恢復責任。持久化只保證訊息可取回，不保證業務結果已正確提交。&lt;/p>
&lt;p>把 DLQ 當成長期倉庫，也會讓問題持續累積。DLQ 的工程價值在於快速定位異常類型並回到修復流程。&lt;/p>
&lt;h2 id="訊息系統的通知-vs-訊息分類">訊息系統的「通知 vs 訊息」分類&lt;/h2>
&lt;p>訊息系統設計區分兩種 SLO 不同的傳遞責任：&lt;em>transactional 通知&lt;/em> 承擔業務副作用的可靠送達、&lt;em>broadcast 訊息&lt;/em> 承擔大量低成本傳播。兩者用不同 storage、不同重試策略、不同投遞保證。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26 PayPay&lt;/a> — 行動支付每日 3 億訊息、付款通知承擔「確認交易完成」的業務責任、SLO 包含秒級延遲跟高投遞率（用戶付完款後若 30 秒沒收到通知會打客服、產生重複扣款風險）。這層需求嚴於 OTA 推播、需要 durable queue + retry + 重複偵測。&lt;/p>
&lt;p>&lt;strong>分類設計&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Transactional 通知&lt;/strong>（付款收據、訂單狀態變更、配額警告）：承擔業務副作用確認、需 durable + idempotency key 去重、SLO 通常是 &lt;em>秒級延遲 + 99.99% 投遞率&lt;/em>&lt;/li>
&lt;li>&lt;strong>Broadcast 訊息&lt;/strong>（行銷推播、新片發布通知、社群動態）：承擔大量低成本傳播、SLO 是 &lt;em>吞吐量&lt;/em> 跟覆蓋率、允許 best-effort retry&lt;/li>
&lt;/ul>
&lt;p>判讀含義：規模化訊息系統的容量規劃要按類別分開、避免套同一個 broker capacity。3 億訊息 / 天看似一致、但 &lt;em>通知&lt;/em> 跟 &lt;em>訊息&lt;/em> 的工程負擔差數量級。&lt;/p>
&lt;h2 id="下游推送是隱性瓶頸">下游推送是隱性瓶頸&lt;/h2>
&lt;p>訊息系統的真正瓶頸常落在 &lt;em>下游推送通道&lt;/em>（APNs、FCM、SMS gateway、email provider）、不在 broker。下游 quota 是 hard ceiling、超過會被 throttle、訊息積壓回 broker 形成 backlog。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26 PayPay&lt;/a> — DynamoDB 寫入可以撐 3K msg/sec 平均（PayPay 本身用 DynamoDB 作訊息後端、不是傳統 broker）、但 APNs 推送額度成為事故當下的隱性瓶頸。容量規劃要把下游 quota 算進去、不只看訊息後端吞吐。&lt;/p></description><content:encoded><![CDATA[<p>持久化佇列（<a href="/blog/backend/knowledge-cards/durable-queue/" data-link-title="Durable Queue" data-link-desc="說明可持久化的 queue 如何在重啟與失敗後保留待處理工作">durable queue</a>）的核心責任是讓非同步工作在 process、節點或網路故障後仍可被恢復處理。它讓業務動作在失敗後仍有可追蹤、可重試、可隔離的路徑。</p>
<h2 id="durable-與-ephemeral-的差異">durable 與 ephemeral 的差異</h2>
<p><a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> 在語意上可分 durable 與 ephemeral。ephemeral queue 側重低延遲與短暫協調，適合可丟失任務；durable queue 側重故障後可恢復，適合正式狀態相關副作用，例如付款通知、發票產生、庫存同步與合規事件記錄。</p>
<p>這個選擇本質上是失敗代價選擇。若任務丟失可接受，ephemeral 可降低成本；若任務丟失會造成金流、合約或審計問題，durable 是必要基線。</p>
<h2 id="重試策略">重試策略</h2>
<p>重試策略的責任是把暫時性故障和系統性故障分開。durable queue 常見的重試組合是：有限次重試、指數退避、<a href="/blog/backend/knowledge-cards/jitter/" data-link-title="Jitter" data-link-desc="說明重試或排程加入隨機偏移如何降低同步尖峰">jitter</a> 分散峰值、超過門檻後分流到 <a href="/blog/backend/knowledge-cards/dead-letter-queue/" data-link-title="Dead-Letter Queue" data-link-desc="說明 dead-letter queue 如何隔離多次處理失敗的訊息">dead-letter queue</a>。</p>
<p>重試上限與間隔要由下游承載能力決定。重試太快會形成故障放大，重試太慢會拖長恢復時間。穩定做法是把重試策略當成服務容量控制的一部分，而不是固定平台預設值。</p>
<h2 id="dlq-與-requeue-風險">DLQ 與 requeue 風險</h2>
<p>DLQ 的責任是隔離異常訊息，避免拖垮主消費流程。DLQ 是診斷與修復入口，把它當終點會讓問題沉積。每個進入 DLQ 的訊息，都應能回答：失敗原因是 payload 錯誤、下游不可用、版本不相容，還是消費邏輯缺陷。</p>
<p><a href="/blog/backend/knowledge-cards/requeue/" data-link-title="Requeue" data-link-desc="說明處理失敗的訊息重新排回 queue 時的風險與控制條件">requeue</a> 需要明確條件。直接把異常訊息無限 requeue，通常會造成隊列震盪與延遲累積。穩定做法是先隔離、分群、修復，再批次回放。</p>
<h2 id="ordering-與吞吐取捨">ordering 與吞吐取捨</h2>
<p>durable queue 在順序與吞吐之間需要明確取捨。全域順序通常成本極高，實務上多採用分區內順序：同一 key 保持順序，不同 key 可並行。這能兼顧一致性需求與處理吞吐。</p>
<p>順序要求越高，恢復流程越需要明確 checkpoint 與補償策略。否則故障後的重播容易造成亂序副作用，放大修復成本。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>queue depth 持續上升</td>
          <td>輸入速率高於消費能力</td>
          <td>擴消費能力、調整重試節奏、分流高成本任務</td>
      </tr>
      <tr>
          <td>retry ratio 升高且成功率下降</td>
          <td>故障從暫時性轉為系統性</td>
          <td>降級下游、縮小重試並啟動隔離策略</td>
      </tr>
      <tr>
          <td>DLQ 量快速增加</td>
          <td>payload/版本/邏輯異常集中爆發</td>
          <td>分群診斷、修復邏輯、定向重播</td>
      </tr>
      <tr>
          <td>requeue 循環導致延遲尖峰</td>
          <td>缺少隔離邊界與停損機制</td>
          <td>停止盲目 requeue、先隔離後回放</td>
      </tr>
      <tr>
          <td>消費恢復後出現大量重複副作用</td>
          <td>去重與冪等保護不足</td>
          <td>補 idempotency key 與 side-effect guard</td>
      </tr>
  </tbody>
</table>
<h2 id="常見誤區">常見誤區</h2>
<p>把 durable queue 視為「寫進去就安全」，會忽略消費與恢復責任。持久化只保證訊息可取回，不保證業務結果已正確提交。</p>
<p>把 DLQ 當成長期倉庫，也會讓問題持續累積。DLQ 的工程價值在於快速定位異常類型並回到修復流程。</p>
<h2 id="訊息系統的通知-vs-訊息分類">訊息系統的「通知 vs 訊息」分類</h2>
<p>訊息系統設計區分兩種 SLO 不同的傳遞責任：<em>transactional 通知</em> 承擔業務副作用的可靠送達、<em>broadcast 訊息</em> 承擔大量低成本傳播。兩者用不同 storage、不同重試策略、不同投遞保證。</p>
<p>對應 <a href="/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26 PayPay</a> — 行動支付每日 3 億訊息、付款通知承擔「確認交易完成」的業務責任、SLO 包含秒級延遲跟高投遞率（用戶付完款後若 30 秒沒收到通知會打客服、產生重複扣款風險）。這層需求嚴於 OTA 推播、需要 durable queue + retry + 重複偵測。</p>
<p><strong>分類設計</strong>：</p>
<ul>
<li><strong>Transactional 通知</strong>（付款收據、訂單狀態變更、配額警告）：承擔業務副作用確認、需 durable + idempotency key 去重、SLO 通常是 <em>秒級延遲 + 99.99% 投遞率</em></li>
<li><strong>Broadcast 訊息</strong>（行銷推播、新片發布通知、社群動態）：承擔大量低成本傳播、SLO 是 <em>吞吐量</em> 跟覆蓋率、允許 best-effort retry</li>
</ul>
<p>判讀含義：規模化訊息系統的容量規劃要按類別分開、避免套同一個 broker capacity。3 億訊息 / 天看似一致、但 <em>通知</em> 跟 <em>訊息</em> 的工程負擔差數量級。</p>
<h2 id="下游推送是隱性瓶頸">下游推送是隱性瓶頸</h2>
<p>訊息系統的真正瓶頸常落在 <em>下游推送通道</em>（APNs、FCM、SMS gateway、email provider）、不在 broker。下游 quota 是 hard ceiling、超過會被 throttle、訊息積壓回 broker 形成 backlog。</p>
<p>對應 <a href="/blog/backend/09-performance-capacity/cases/paypay-mobile-payment-messaging/" data-link-title="9.C26 PayPay：行動支付每日 3 億訊息的 DynamoDB 後端" data-link-desc="日本最大行動支付 PayPay 每日 3 億訊息、用 DynamoDB 處理通知與訊息功能、支撐次秒級反應">9.C26 PayPay</a> — DynamoDB 寫入可以撐 3K msg/sec 平均（PayPay 本身用 DynamoDB 作訊息後端、不是傳統 broker）、但 APNs 推送額度成為事故當下的隱性瓶頸。容量規劃要把下游 quota 算進去、不只看訊息後端吞吐。</p>
<p><strong>設計含義</strong>：</p>
<ul>
<li><strong>下游 quota 視為容量上限</strong>：APNs / FCM / SMS 的 daily quota 是 hard ceiling、訊息後端規劃要對應</li>
<li><strong>下游通道多元化</strong>：用 APNs / FCM / SMS / in-app notification 多通道分攤 quota 壓力、單通道飽和時其他通道仍可送出（具體降級策略需依各組織業務規則設計）</li>
<li><strong>重試節奏跟下游容量對齊</strong>：consumer 重試節奏依下游剩餘 quota 動態調整、讓重試節奏跟容量同步</li>
</ul>
<p>判讀重點：訊息系統事故當下、先看下游推送通道狀態（APNs status、FCM error rate）、再看訊息後端。下游 throttle 引發 backlog 是規模化訊息系統最常見的瓶頸來源。下游推送 quota 的攻擊面對照見 <a href="/blog/backend/03-message-queue/red-team-delivery-layer/" data-link-title="3.5 攻擊者視角（紅隊）：傳遞層弱點判讀" data-link-desc="從重複投遞、重放濫用、毒訊息與容量壓力，盤點 message delivery 的主要弱點">3.5 multi-tenant broker 配額耗盡</a>。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>durable queue 的重試與隔離節奏可用 <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> 回寫。先看事件中的 backlog、retry、DLQ 變化，再回到本章判讀是重試策略失衡，還是隔離邊界不清楚。
這個案例主要支撐的是「重試隔離與停損門檻」判讀，不直接支撐 outbox 交易切分；若事件核心是資料提交與發布不一致，應轉到 3.3 與 1.3。</p>
<p>當重試量上升且主隊列延遲同步拉高時，先拆分重試通道並收斂 DLQ 分流條件，再把停損門檻接到 <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 規則推送安全閘門</a>。</p>
<h2 id="跨模組路由">跨模組路由</h2>
<p>durable queue 是非同步可靠性的起點，不是終點。</p>
<ol>
<li>與 3.4 的交接：消費與恢復語意落在 <a href="/blog/backend/03-message-queue/consumer-design/" data-link-title="3.4 consumer 設計與去重" data-link-desc="整理 consumer、checkpoint 與 replay safety">consumer 設計與去重</a>。</li>
<li>與 3.3 的交接：發布一致性落在 <a href="/blog/backend/03-message-queue/outbox-pattern/" data-link-title="3.3 outbox pattern 與發佈一致性" data-link-desc="把 transaction 與 event publish 分離">outbox pattern</a>。</li>
<li>與 4.20 的交接：queue depth、retry、DLQ 指標進入 <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">Observability Evidence Package</a>。</li>
<li>與 6.12 的交接：重試與重播驗證進入 <a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">Idempotency 與 Replay 驗證</a>。</li>
<li>與 8.19 的交接：故障隔離與回放決策進入 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">Incident Decision Log</a>。</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 設計與去重</a>。要看 queue 切換失敗模式，接著讀 <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>
]]></content:encoded></item></channel></rss>