<?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/06-reliability/</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>Fri, 01 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/06-reliability/index.xml" rel="self" type="application/rss+xml"/><item><title>6.1 CI pipeline</title><link>https://tarrragon.github.io/blog/backend/06-reliability/ci-pipeline/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/ci-pipeline/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合流程如何在合併前驗證品質與相容性">CI pipeline&lt;/a> 把快速回饋、慢速驗證與可重現產物切成不同層，讓每次變更都能在一致條件下被判讀。&lt;/p>
&lt;p>這一層關心的是「變更能不能被穩定驗證」。pipeline 的價值在於分層、隔離與可追蹤，讓 flaky 訊號不會直接污染放行判斷。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>CI 的健康度先看回饋節奏，再看訊號品質。fast path 應該覆蓋最常見的破壞面，slow path 負責深層驗證，artifact 則要能從同一份輸入重播。&lt;/p>
&lt;p>判讀時先看四件事：&lt;/p>
&lt;ul>
&lt;li>stage 是否按成本與風險分層&lt;/li>
&lt;li>artifact 是否重用，不是每次從 source 重建&lt;/li>
&lt;li>environment variables 是否封裝，避免跨環境漂移&lt;/li>
&lt;li>flaky test 是否有治理路徑，而不是只靠 retry&lt;/li>
&lt;/ul>
&lt;h2 id="分層策略">分層策略&lt;/h2>
&lt;p>CI 分層的責任是讓不同成本的驗證跑在不同時機，讓最常見的破壞面最快被攔住，高成本驗證只在值得時跑。&lt;/p>
&lt;h3 id="fast-path">Fast path&lt;/h3>
&lt;p>fast path 在每次 push 觸發，目標是 5 分鐘內回饋。涵蓋 lint、type check、unit test 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/contract/" data-link-title="Boundary Contract" data-link-desc="說明跨邊界約定如何維持相容與可驗證">contract&lt;/a> test。這一層只驗證單一變更的語法與邏輯正確性，不碰外部依賴。&lt;/p>
&lt;p>fast path 結果可信的條件是測試不依賴外部狀態。當 unit test 需要真實 DB 或 broker，它就不再屬於 fast path — 移到 slow path，或用 contract test 替代跨服務驗證。&lt;/p>
&lt;h3 id="slow-path">Slow path&lt;/h3>
&lt;p>slow path 在 merge request 觸發，允許較長執行時間（15-45 分鐘）。涵蓋 integration test、security scan、load baseline 與跨服務 schema 相容性。這一層用真實依賴驗證變更在服務邊界上的行為。&lt;/p>
&lt;p>Microsoft 的&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">變更治理實踐&lt;/a>把變更按風險分層，高風險變更（schema migration、payment path、config rollout）走更完整的 slow path，低風險變更只需 fast path 通過。這種分層讓 CI 資源集中在真正需要深層驗證的變更上，同時維持低風險變更的交付速度。&lt;/p>
&lt;h3 id="scheduled-path">Scheduled path&lt;/h3>
&lt;p>scheduled path 定期（每日或每週）執行，涵蓋 full regression、&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/fuzz-campaign/" data-link-title="6.3 fuzz campaign" data-link-desc="用自動化輸入探索覆蓋未知邊界：target 設計、corpus 管理、crash reproduction 與 CI 整合">fuzz campaign&lt;/a>、chaos smoke test 與長時間 soak test。這一層驗證的是累積退化，而不是單次變更的破壞。&lt;/p>
&lt;p>scheduled path 的判讀不看單次 pass/fail，而是看趨勢：coverage delta 是否持續下降、fuzz corpus 是否收斂、regression 新增 failure 是否集中在特定模組。&lt;/p>
&lt;h2 id="artifact-管理">Artifact 管理&lt;/h2>
&lt;p>Artifact 讓同一份 build output 能從 CI 一路到 staging 到 production，每一步都可重播。&lt;/p>
&lt;p>immutable artifact 的核心約束是 build 一次、部署多次。CI 產出的 container image 或 binary 帶版本標籤（commit hash + build number），後續環境不重新 build，只替換 config。這樣才能確保 staging 驗證通過的產物跟 production 部署的產物是同一份。&lt;/p>
&lt;p>cache 策略影響 CI 回饋速度與可信度的平衡。dependency cache（npm / go mod / pip）加速 build，但需要定期 invalidation 避免過期依賴殘留。build output cache 則需要嚴格的 key 設計，確保 source 變更後不會沿用舊 artifact。&lt;/p>
&lt;p>Stripe 的&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">零停機遷移實踐&lt;/a>對 artifact 有額外要求：交易路徑的變更需要 artifact 能重播到相同狀態，確保 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency&lt;/a> 驗證在 CI 與 production 看到一致的行為。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合流程如何在合併前驗證品質與相容性">CI pipeline</a> 把快速回饋、慢速驗證與可重現產物切成不同層，讓每次變更都能在一致條件下被判讀。</p>
<p>這一層關心的是「變更能不能被穩定驗證」。pipeline 的價值在於分層、隔離與可追蹤，讓 flaky 訊號不會直接污染放行判斷。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>CI 的健康度先看回饋節奏，再看訊號品質。fast path 應該覆蓋最常見的破壞面，slow path 負責深層驗證，artifact 則要能從同一份輸入重播。</p>
<p>判讀時先看四件事：</p>
<ul>
<li>stage 是否按成本與風險分層</li>
<li>artifact 是否重用，不是每次從 source 重建</li>
<li>environment variables 是否封裝，避免跨環境漂移</li>
<li>flaky test 是否有治理路徑，而不是只靠 retry</li>
</ul>
<h2 id="分層策略">分層策略</h2>
<p>CI 分層的責任是讓不同成本的驗證跑在不同時機，讓最常見的破壞面最快被攔住，高成本驗證只在值得時跑。</p>
<h3 id="fast-path">Fast path</h3>
<p>fast path 在每次 push 觸發，目標是 5 分鐘內回饋。涵蓋 lint、type check、unit test 與 <a href="/blog/backend/knowledge-cards/contract/" data-link-title="Boundary Contract" data-link-desc="說明跨邊界約定如何維持相容與可驗證">contract</a> test。這一層只驗證單一變更的語法與邏輯正確性，不碰外部依賴。</p>
<p>fast path 結果可信的條件是測試不依賴外部狀態。當 unit test 需要真實 DB 或 broker，它就不再屬於 fast path — 移到 slow path，或用 contract test 替代跨服務驗證。</p>
<h3 id="slow-path">Slow path</h3>
<p>slow path 在 merge request 觸發，允許較長執行時間（15-45 分鐘）。涵蓋 integration test、security scan、load baseline 與跨服務 schema 相容性。這一層用真實依賴驗證變更在服務邊界上的行為。</p>
<p>Microsoft 的<a href="/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">變更治理實踐</a>把變更按風險分層，高風險變更（schema migration、payment path、config rollout）走更完整的 slow path，低風險變更只需 fast path 通過。這種分層讓 CI 資源集中在真正需要深層驗證的變更上，同時維持低風險變更的交付速度。</p>
<h3 id="scheduled-path">Scheduled path</h3>
<p>scheduled path 定期（每日或每週）執行，涵蓋 full regression、<a href="/blog/backend/06-reliability/fuzz-campaign/" data-link-title="6.3 fuzz campaign" data-link-desc="用自動化輸入探索覆蓋未知邊界：target 設計、corpus 管理、crash reproduction 與 CI 整合">fuzz campaign</a>、chaos smoke test 與長時間 soak test。這一層驗證的是累積退化，而不是單次變更的破壞。</p>
<p>scheduled path 的判讀不看單次 pass/fail，而是看趨勢：coverage delta 是否持續下降、fuzz corpus 是否收斂、regression 新增 failure 是否集中在特定模組。</p>
<h2 id="artifact-管理">Artifact 管理</h2>
<p>Artifact 讓同一份 build output 能從 CI 一路到 staging 到 production，每一步都可重播。</p>
<p>immutable artifact 的核心約束是 build 一次、部署多次。CI 產出的 container image 或 binary 帶版本標籤（commit hash + build number），後續環境不重新 build，只替換 config。這樣才能確保 staging 驗證通過的產物跟 production 部署的產物是同一份。</p>
<p>cache 策略影響 CI 回饋速度與可信度的平衡。dependency cache（npm / go mod / pip）加速 build，但需要定期 invalidation 避免過期依賴殘留。build output cache 則需要嚴格的 key 設計，確保 source 變更後不會沿用舊 artifact。</p>
<p>Stripe 的<a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">零停機遷移實踐</a>對 artifact 有額外要求：交易路徑的變更需要 artifact 能重播到相同狀態，確保 <a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> 驗證在 CI 與 production 看到一致的行為。</p>
<h2 id="flaky-test-治理">Flaky test 治理</h2>
<p>flaky test 的責任是讓 CI 訊號維持可信度。當 flaky 率持續上升，團隊會開始忽略 CI 結果，pipeline 從可靠性 gate 退化成形式流程。</p>
<h3 id="識別">識別</h3>
<p>flaky 識別靠 retry 分析。當同一個 test case 在同一份 commit 上連續跑出不同結果，那就是 flaky 候選。按連續失敗 / 成功交替的頻率排序，比按失敗率排序更能抓到高噪音來源。</p>
<h3 id="隔離">隔離</h3>
<p>quarantine queue 是把已識別的 flaky test 從 gate-blocking path 移到 non-blocking path。quarantine 的目的是保護 gate 判讀可信度，同時維持 flaky 修復的追蹤壓力。quarantine 不是永久停靠 — 超過修復期限的 flaky test 必須決定是修復還是刪除。</p>
<h3 id="判讀門檻">判讀門檻</h3>
<p>flaky 率超過 5% 時，CI gate 的訊號開始失真：團隊無法確定 failure 是真回歸還是 flaky。超過 10% 時，CI pipeline 實質上失去 gate 功能 — retry 變成常態，failure 預設被忽略。此時應暫停新功能開發，集中修復 flaky backlog。這些門檻是基於中大型測試套件（500+ test cases）的經驗值。測試套件較小時，單一 flaky test 的比率衝擊更大，門檻應更低。</p>
<h2 id="environment-隔離">Environment 隔離</h2>
<p>CI 環境的隔離程度決定了測試結果的可信度下限。</p>
<h3 id="runner-隔離">Runner 隔離</h3>
<p>shared runner 會把不同 PR 的測試跑在同一台機器上。當 integration test 需要佔用 port、寫入 local state 或消耗大量記憶體，跨 job 干擾就會出現。ephemeral runner（每次 job 用乾淨環境）消除這類問題，但成本更高。判斷點是測試是否依賴 local state — 有依賴就用 ephemeral。</p>
<h3 id="secret-管理">Secret 管理</h3>
<p>CI secret（API key、DB credential、cloud token）需要按環境隔離。staging secret 不應該在 PR pipeline 可用，production secret 不應該在 staging pipeline 可用。secret 洩露的常見路徑是 CI log 輸出與 artifact 殘留 — 兩處都需要遮罩。</p>
<h3 id="load-test-資源池">Load test 資源池</h3>
<p>LinkedIn 的<a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">容量 headroom 實踐</a>把自動化壓測接進 CI。當 load test 跑在 CI 環境時，需要獨立資源池，避免壓測流量影響其他 pipeline job 的執行速度與穩定性。load test runner 的 quota 跟一般 CI runner 分開管理。</p>
<h2 id="ci-作為-release-gate-輸入">CI 作為 Release Gate 輸入</h2>
<p>CI 的最終產出不只是 pass/fail，而是一組可供 <a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">release gate</a> 判讀的 evidence。</p>
<table>
  <thead>
      <tr>
          <th>產出</th>
          <th>判讀用途</th>
          <th>下游消費者</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>pipeline status</td>
          <td>所有 stage 是否通過</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a></td>
      </tr>
      <tr>
          <td>test coverage delta</td>
          <td>本次變更是否降低覆蓋率</td>
          <td><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 perf regression gate</a></td>
      </tr>
      <tr>
          <td>artifact checksum</td>
          <td>部署產物是否與 CI 產出一致</td>
          <td><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 evidence handoff</a></td>
      </tr>
      <tr>
          <td>flaky rate snapshot</td>
          <td>gate 判讀可信度是否在可接受範圍</td>
          <td><a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18 reliability metrics</a></td>
      </tr>
  </tbody>
</table>
<p>Google 的 <a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">error budget 政策</a>把 CI 定位成 release gate 的前置訊號來源：CI pipeline 產出的 evidence 直接進入 error budget 判讀流程。當 budget 消耗加速時，CI gate 的門檻隨之提高 — 從只需 fast path 通過，升級到要求 slow path 全部通過加人工 review。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">Google</a>：CI pipeline status 是 error budget 政策的前置訊號，budget 消耗速度直接影響 CI gate 門檻高低。</li>
<li><a href="/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">Microsoft</a>：按變更風險分層走不同 CI path，高風險變更需要更完整的 slow path 驗證。</li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">LinkedIn L1</a>：容量 headroom 綁值班分層，CI 回饋是容量決策的輸入。</li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">LinkedIn L2</a>：自動化壓測接進 CI，load test 需要獨立資源池避免干擾其他 pipeline job。</li>
<li><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe</a>：交易路徑的 idempotency 測試在 CI 跑，artifact 必須能重播到相同狀態。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>意義</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CI 時長 &gt; 30 min</td>
          <td>fast path 混入了 slow path 測試</td>
          <td>重新分層，把 integration test 移到 merge gate</td>
      </tr>
      <tr>
          <td>fast / slow 沒分層</td>
          <td>每次 push 跑全部測試，回饋太慢</td>
          <td>拆 fast path（&lt; 5 min）與 slow path（&lt; 45 min）</td>
      </tr>
      <tr>
          <td>flaky 率 &gt; 5%</td>
          <td>gate 判讀可信度開始下降</td>
          <td>啟動 quarantine + 集中修復週期</td>
      </tr>
      <tr>
          <td>artifact 每次重建</td>
          <td>無法確認 staging 跟 production 同份</td>
          <td>改成 build once、deploy many</td>
      </tr>
      <tr>
          <td>env var 跨環境寫死</td>
          <td>staging 與 prod 行為不同</td>
          <td>改用 per-environment secret injection</td>
      </tr>
      <tr>
          <td>retry 成功率 &gt; 20% 且被視為 pipeline 通過</td>
          <td>真回歸被 flaky retry 遮蓋</td>
          <td>retry pass 不等於 gate pass，需人工確認</td>
      </tr>
      <tr>
          <td>flaky test 無 owner、修復靠志願者</td>
          <td>test 跟 team 責任未對齊</td>
          <td>建立 test ownership registry、每個 test file 或 suite 有明確 owner team</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10 contract testing</a>：把跨服務契約納入 CI fast path</li>
<li><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 perf regression gate</a>：把效能 baseline 變成 CI slow path gate</li>
<li><a href="/blog/backend/06-reliability/environment-parity/" data-link-title="6.15 Environment Parity 與漂移控制" data-link-desc="把 staging / preprod / prod 之間的差異視為一級風險，按漂移來源分類偵測與治理">6.15 environment parity</a>：CI 環境隔離是 parity 的前置條件</li>
<li><a href="/blog/backend/06-reliability/test-data-management/" data-link-title="6.16 Test Data Management" data-link-desc="把 fixture / seed / production-like data 作為跨模組共用 artifact，治理資料層次、遮罩策略與可重現性">6.16 test data</a>：把 fixture / seed 納入 CI artifact 管理</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：CI evidence 是 release gate 的主要輸入</li>
<li><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 evidence handoff</a>：CI artifact checksum 進入證據交接</li>
</ul>
]]></content:encoded></item><item><title>6.2 load test</title><link>https://tarrragon.github.io/blog/backend/06-reliability/load-testing/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/load-testing/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>當系統需要回答「這個流量撐不撐得住」，&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/load-test/" data-link-title="Load Test" data-link-desc="說明在預期流量下驗證容量、延遲與降級策略的測試">load test&lt;/a> 把真實 workload model 變成可重播的壓力情境，找出吞吐、延遲與瓶頸轉折點。&lt;/p>
&lt;p>這一頁關心的是實際流量長什麼樣，不是把數字推高而已。模型若不接近 production shape，壓測結果就只是在驗證假場景。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>Load test 的品質先看模型是否貼近流量結構，再看系統在 saturation 前後的行為。曲線在 saturation 前後如何變形才是關鍵，單點 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/throughput/" data-link-title="Throughput" data-link-desc="整理系統單位時間內可處理的工作量">throughput&lt;/a> 只是其中一個讀數。&lt;/p>
&lt;p>判讀時的關鍵面向：&lt;/p>
&lt;ul>
&lt;li>workload 是否包含尖峰、長尾與不同 cohort&lt;/li>
&lt;li>latency 是否在接近飽和時快速劣化&lt;/li>
&lt;li>bottleneck 是否能被定位到具體 resource&lt;/li>
&lt;li>load 結果是否能回寫到 capacity planning&lt;/li>
&lt;/ul>
&lt;h2 id="workload-model-設計">Workload model 設計&lt;/h2>
&lt;p>Workload model 的責任是把 production 流量結構轉成可重播的測試情境。模型越接近真實流量的形狀，壓測結果對容量決策的支撐力越高。&lt;/p>
&lt;p>設計 workload model 時先分析三個維度：&lt;/p>
&lt;p>&lt;strong>Traffic shape&lt;/strong>：production 流量很少是均勻的。峰值時段的 request rate 可能是均值的數倍到數十倍，而且峰值持續時間、上升斜率與衰退曲線各有差異。Shopify 的 BFCM 流量結構是短時間爆量加上高寫入比例；若模型只用日均流量推算，會漏掉峰值集中在數小時內的壓力集中度。模型需要把 peak / off-peak / burst 三種時段分開描述。&lt;/p>
&lt;p>&lt;strong>Cohort 拆分&lt;/strong>：讀與寫的資源消耗模式不同，混合比例會改變瓶頸位置。API gateway 層可能由讀主導，但 checkout 或 order-create 路徑的寫入比例明顯偏高。把不同 cohort（讀 / 寫 / 混合 / 背景任務）分開量測，才能判斷瓶頸是在哪個路徑上出現。&lt;/p>
&lt;p>&lt;strong>資料量對齊&lt;/strong>：staging 環境的資料量常與 production 差一到兩個數量級。query plan、index scan、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool&lt;/a> 飽和與 cache 行為都跟資料量高度相關。模型要盡可能用 production-like 資料量，或至少在結果判讀時標註資料量差異帶來的偏移。&lt;/p>
&lt;p>LinkedIn 的實踐揭露另一個面向：workload model 會隨時間漂移。流量結構、使用者行為與功能上線都會改變真實壓力形狀。當 load-test 模型不再定期校準，壓測結果與 production 壓力之間的差距會持續擴大。定期用 production traffic replay 或 access log 分析重建模型，是維持壓測可信度的必要動作。&lt;/p>
&lt;p>判斷 workload model 是否仍然有效的實務做法：把最近一次 load test 的 latency distribution 與 production 同時段的 latency distribution 對齊。若兩者的 p50 / p95 / p99 比率偏離超過 20%，模型已經需要校準。20% 是通用起點。latency 敏感的服務（交易、即時通訊）應使用更嚴格的門檻（10%），batch 類服務可適度放寬。偏離來源通常是三個之一：流量結構變了（新功能改變 read/write 比例）、資料量成長了（query plan 改變）、依賴行為變了（上游回應時間漂移）。&lt;/p>
&lt;h2 id="saturation-與瓶頸定位">Saturation 與瓶頸定位&lt;/h2>
&lt;p>Saturation 的轉折點決定了系統的實際容量上限 — 在什麼負載下，系統從線性擴展轉為劣化。&lt;/p>
&lt;p>判讀 saturation 先看 latency curve。在低負載時，latency 通常穩定；隨著負載上升，會出現一個 inflection point，之後 latency 開始加速上升。這個轉折點通常比 throughput ceiling 更早出現，是真正的容量邊界。&lt;/p>
&lt;p>在 inflection point 之後，系統行為會進入幾種退化模式。逐漸退化型的 latency 緩慢爬升，通常來自 queue 堆積或 GC 壓力加重；崩落型的 latency 在某個點突然跳升數倍，通常來自 connection pool 耗盡或 thread pool 飽和。兩種退化的應對策略不同：逐漸退化有 load shedding 的緩衝空間，崩落型需要提早在更低負載觸發限流。壓測結果需要標註系統屬於哪種退化模式，這個資訊直接影響 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/stop-condition/" data-link-title="Stop Condition" data-link-desc="說明變更、實驗或事故處理何時必須暫停、回退或改路線">stop condition&lt;/a> 的門檻設定。&lt;/p>
&lt;p>瓶頸定位需要對齊資源層。常見瓶頸包括 CPU saturation、memory pressure、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool&lt;/a> 耗盡、queue depth 堆積與 disk I/O。壓測時需要同步觀測這些資源指標，才能把 latency 劣化歸因到具體 resource。歸因的價值在於讓擴容或優化的投資方向可決策：CPU 瓶頸指向 compute scaling、connection pool 瓶頸指向 pool config 或 connection reuse、queue depth 瓶頸可能指向 consumer 吞吐不足。若只看 latency 劣化但不做歸因，團隊容易直覺式擴容，花了成本卻沒打到真正瓶頸。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>當系統需要回答「這個流量撐不撐得住」，<a href="/blog/backend/knowledge-cards/load-test/" data-link-title="Load Test" data-link-desc="說明在預期流量下驗證容量、延遲與降級策略的測試">load test</a> 把真實 workload model 變成可重播的壓力情境，找出吞吐、延遲與瓶頸轉折點。</p>
<p>這一頁關心的是實際流量長什麼樣，不是把數字推高而已。模型若不接近 production shape，壓測結果就只是在驗證假場景。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Load test 的品質先看模型是否貼近流量結構，再看系統在 saturation 前後的行為。曲線在 saturation 前後如何變形才是關鍵，單點 <a href="/blog/backend/knowledge-cards/throughput/" data-link-title="Throughput" data-link-desc="整理系統單位時間內可處理的工作量">throughput</a> 只是其中一個讀數。</p>
<p>判讀時的關鍵面向：</p>
<ul>
<li>workload 是否包含尖峰、長尾與不同 cohort</li>
<li>latency 是否在接近飽和時快速劣化</li>
<li>bottleneck 是否能被定位到具體 resource</li>
<li>load 結果是否能回寫到 capacity planning</li>
</ul>
<h2 id="workload-model-設計">Workload model 設計</h2>
<p>Workload model 的責任是把 production 流量結構轉成可重播的測試情境。模型越接近真實流量的形狀，壓測結果對容量決策的支撐力越高。</p>
<p>設計 workload model 時先分析三個維度：</p>
<p><strong>Traffic shape</strong>：production 流量很少是均勻的。峰值時段的 request rate 可能是均值的數倍到數十倍，而且峰值持續時間、上升斜率與衰退曲線各有差異。Shopify 的 BFCM 流量結構是短時間爆量加上高寫入比例；若模型只用日均流量推算，會漏掉峰值集中在數小時內的壓力集中度。模型需要把 peak / off-peak / burst 三種時段分開描述。</p>
<p><strong>Cohort 拆分</strong>：讀與寫的資源消耗模式不同，混合比例會改變瓶頸位置。API gateway 層可能由讀主導，但 checkout 或 order-create 路徑的寫入比例明顯偏高。把不同 cohort（讀 / 寫 / 混合 / 背景任務）分開量測，才能判斷瓶頸是在哪個路徑上出現。</p>
<p><strong>資料量對齊</strong>：staging 環境的資料量常與 production 差一到兩個數量級。query plan、index scan、<a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a> 飽和與 cache 行為都跟資料量高度相關。模型要盡可能用 production-like 資料量，或至少在結果判讀時標註資料量差異帶來的偏移。</p>
<p>LinkedIn 的實踐揭露另一個面向：workload model 會隨時間漂移。流量結構、使用者行為與功能上線都會改變真實壓力形狀。當 load-test 模型不再定期校準，壓測結果與 production 壓力之間的差距會持續擴大。定期用 production traffic replay 或 access log 分析重建模型，是維持壓測可信度的必要動作。</p>
<p>判斷 workload model 是否仍然有效的實務做法：把最近一次 load test 的 latency distribution 與 production 同時段的 latency distribution 對齊。若兩者的 p50 / p95 / p99 比率偏離超過 20%，模型已經需要校準。20% 是通用起點。latency 敏感的服務（交易、即時通訊）應使用更嚴格的門檻（10%），batch 類服務可適度放寬。偏離來源通常是三個之一：流量結構變了（新功能改變 read/write 比例）、資料量成長了（query plan 改變）、依賴行為變了（上游回應時間漂移）。</p>
<h2 id="saturation-與瓶頸定位">Saturation 與瓶頸定位</h2>
<p>Saturation 的轉折點決定了系統的實際容量上限 — 在什麼負載下，系統從線性擴展轉為劣化。</p>
<p>判讀 saturation 先看 latency curve。在低負載時，latency 通常穩定；隨著負載上升，會出現一個 inflection point，之後 latency 開始加速上升。這個轉折點通常比 throughput ceiling 更早出現，是真正的容量邊界。</p>
<p>在 inflection point 之後，系統行為會進入幾種退化模式。逐漸退化型的 latency 緩慢爬升，通常來自 queue 堆積或 GC 壓力加重；崩落型的 latency 在某個點突然跳升數倍，通常來自 connection pool 耗盡或 thread pool 飽和。兩種退化的應對策略不同：逐漸退化有 load shedding 的緩衝空間，崩落型需要提早在更低負載觸發限流。壓測結果需要標註系統屬於哪種退化模式，這個資訊直接影響 <a href="/blog/backend/knowledge-cards/stop-condition/" data-link-title="Stop Condition" data-link-desc="說明變更、實驗或事故處理何時必須暫停、回退或改路線">stop condition</a> 的門檻設定。</p>
<p>瓶頸定位需要對齊資源層。常見瓶頸包括 CPU saturation、memory pressure、<a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a> 耗盡、queue depth 堆積與 disk I/O。壓測時需要同步觀測這些資源指標，才能把 latency 劣化歸因到具體 resource。歸因的價值在於讓擴容或優化的投資方向可決策：CPU 瓶頸指向 compute scaling、connection pool 瓶頸指向 pool config 或 connection reuse、queue depth 瓶頸可能指向 consumer 吞吐不足。若只看 latency 劣化但不做歸因，團隊容易直覺式擴容，花了成本卻沒打到真正瓶頸。</p>
<p>Pinterest 的快取可靠性案例揭露一種不直覺的瓶頸類型：cache 命中率崩落時，瓶頸會從 compute 層移到 storage throughput。回源壓力瞬間上升，資料層的 I/O 成為新瓶頸。這種情境在純 compute 壓測中看不到，需要特別設計包含 cache miss 場景的 workload。實務上，cache miss 場景可以用兩種方式模擬：清空 cache 後立即打流量（cold start），或在壓測過程中讓部分 key 過期（partial eviction）。兩者暴露的瓶頸位置可能不同，cold start 偏向 storage 吞吐、partial eviction 偏向 connection pool 與 retry 放大。</p>
<h2 id="load-test-與容量規劃的接口">Load test 與容量規劃的接口</h2>
<p>Load test 的產出不只是 pass/fail，它是容量規劃的主要輸入。壓測結果要能轉成 headroom 計算與成本預測。</p>
<p><strong>Headroom 計算</strong>：peak load 佔 capacity ceiling 的比率決定安全緩衝。比率超過 70-80% 時，任何流量突增或依賴劣化都可能觸發 saturation。headroom 的安全值跟系統的退化模式綁在一起：崩落型退化的系統需要更大 headroom，因為從健康到故障的過渡窗口很短。LinkedIn 的做法是把 headroom 預算綁到值班分層，當 headroom 低於門檻時自動升級 <a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 層級，讓容量風險直接轉成團隊行動。</p>
<p><strong>成本曲線</strong>：擴容的邊際成本會在跨越 availability zone、region 或 tier 邊界時跳升。load test 結果要標註「容量到多少時需要跨越哪個擴容邊界」，讓容量規劃能把成本跳升點納入決策。這類資訊在高峰前特別有價值：團隊能提前決定是靠 load shedding 撐過峰值，還是提前擴容跨區，兩者的成本與風險完全不同。</p>
<p><strong>隔離單位的容量量測</strong>：全域容量規劃在多租戶或 cell-based 架構下會失真。Amazon 的做法是按 cell 獨立量測 saturation，每個隔離單位有自己的 headroom，避免一個 cell 的容量需求拖動全域擴容。這種設計讓 load test 的量測粒度從「整個服務」降到「每個隔離單位」，容量決策更精準。</p>
<p>load test 結果的完整路由是：壓測產出 saturation point 與 headroom ratio → 餵給 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 容量與成本邊界</a> 做容量預算 → 餵給 <a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate</a> 做持續守護。</p>
<h2 id="持續性-load-test-與事件性壓測">持續性 load test 與事件性壓測</h2>
<p>Load test 的執行模式依用途分兩類，兩者設計邏輯不同。</p>
<p><strong>持續性 load test</strong> 跑在 CI pipeline 中，用固定 workload 做 baseline regression 偵測。每次變更跑同一套 scenario，比較 latency 與 throughput 是否偏離 baseline。這類測試的 workload 不需要貼近峰值，但需要穩定到能偵測 5-10% 的 regression。連到 <a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate</a> 做自動化 gate。</p>
<p><strong>事件性壓測</strong> 針對特定事件（產品上線、促銷、峰值季節）做一次性或年度壓測。workload 設計要貼近該事件的流量形狀與資料量。Shopify 把 game day 做成年度制度化流程：每輪 BFCM 前跑容量驗證，演練結果回寫 resiliency matrix 與 runbook，讓下一輪從更高基準開始。事件性壓測的關鍵是結果留存與回寫，不是跑完就結束。</p>
<p>兩類測試的分工：持續性負責守住 baseline，事件性負責探索邊界。只跑持續性會漏掉峰值場景；只跑事件性會漏掉漸進退化。</p>
<p>判斷要用哪一類時，先問兩個問題。第一，這個服務是否有可預期的流量事件（促銷、賽季、發布日）？有的話，事件性壓測是必要的，因為峰值壓力的形狀跟日常完全不同。第二，這個服務的變更頻率是否超過每週一次？是的話，持續性 load test 是必要的，因為 regression 可能在任何一次 deploy 進入。多數生產系統兩類都需要。</p>
<h2 id="環境與工具考量">環境與工具考量</h2>
<p><strong>Staging vs production</strong>：staging 壓測控制成本低、風險低，但跟 production 的差異（資料量、網路拓撲、依賴行為）會讓結果偏移。Production load test（dark traffic、shadow read、canary traffic）結果更可信，但需要嚴格的 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a> 控制與 <a href="/blog/backend/knowledge-cards/stop-condition/" data-link-title="Stop Condition" data-link-desc="說明變更、實驗或事故處理何時必須暫停、回退或改路線">stop condition</a> 設計。選擇哪種環境取決於系統成熟度與風險承受能力。</p>
<p><strong>Synthetic traffic 的限制</strong>：synthetic 請求不帶真實 session、auth token 或 cache warm-up 狀態，行為與真實使用者不同。對 cache 敏感的系統，synthetic traffic 可能打出比真實流量更高的 miss rate，產生虛假瓶頸。對 auth 與 session 敏感的系統，synthetic 請求可能繞過 rate limit 或 WAF 路徑，壓測結果會低估 production 的真實負載。判讀時要標註 synthetic 與 real traffic 的行為差異，避免把假瓶頸或假安全當結論。</p>
<p><strong>資料隔離</strong>：production load test 需要確保測試流量不會污染真實資料。常見做法包括 shadow read（讀路徑複製、寫路徑丟棄）、test tenant 隔離（獨立資料空間）、與 feature flag 控制的 dark traffic。每種做法的隔離強度與實作成本不同，選擇時要對齊系統的資料敏感度。</p>
<p>工具選擇路由：CI-first 場景偏向 CLI 工具（<a href="/blog/backend/06-reliability/vendors/k6/" data-link-title="k6" data-link-desc="現代 load test、JS scripting、Grafana Labs">k6</a>）、JVM 生態偏向 <a href="/blog/backend/06-reliability/vendors/gatling/" data-link-title="Gatling" data-link-desc="JVM-based load test、Scala / Java / Kotlin DSL、強型別 scenario、HAR-driven recording">Gatling</a>、Python 團隊偏向 <a href="/blog/backend/06-reliability/vendors/locust/" data-link-title="Locust" data-link-desc="Python-based load test、distributed、易擴展">Locust</a>、既有 .jmx 資產偏向 <a href="/blog/backend/06-reliability/vendors/jmeter/" data-link-title="Apache JMeter" data-link-desc="老牌 load test 工具、GUI &#43; plugins">JMeter</a>。工具對照見 <a href="/blog/backend/06-reliability/vendors/" data-link-title="可靠性 Vendor 清單" data-link-desc="規劃 CI、壓測、chaos engineering 與 SLO 工具的服務頁撰寫順序與判準">vendors/</a>。</p>
<h2 id="load-test-結果的證據留存">Load test 結果的證據留存</h2>
<p>Load test 結果需要結構化留存，讓下游（容量規劃、release gate、事故決策）可以直接調用，而不是每次都要重跑或找人解釋。</p>
<p>留存的最小欄位：workload model 版本、測試環境、saturation point（latency inflection 的 RPS）、throughput ceiling、主要瓶頸歸因、headroom ratio、退化模式分類、測試日期。這些欄位讓 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 verification evidence handoff</a> 可以把 load test 結論直接納入 release 決策，也讓 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 容量與成本邊界</a> 可以追蹤 saturation point 隨時間的變化趨勢。</p>
<p>若結果只以 dashboard 截圖或口頭摘要留存，下次壓測時團隊無法判斷「是系統變了還是模型變了」，校準失去基準。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify H1</a>：高峰型流量要求 load model 涵蓋短時間爆量與高寫入比例，game day 把事件性壓測制度化。</li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">LinkedIn L1</a>：headroom 預算綁值班分層，load-test drift 需要定期校準模型。</li>
<li><a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest P1</a>：cache 命中率崩落改變瓶頸位置，壓測要涵蓋 cache miss 場景。</li>
<li><a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon A1</a>：cell-based architecture 讓容量規劃按隔離單位量測，避免全域擴容失控。</li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">LinkedIn L2</a>：自動化壓測接入 CI pipeline，用 production traffic replay 定期更新 saturation point，讓容量預測的輸入持續校準。</li>
</ul>
<h2 id="產業情境電商與零售">產業情境：電商與零售</h2>
<p>電商流量的核心特徵是可預期的季節性峰值（雙十一、Black Friday、Prime Day）與不可預期的閃購爆量。兩者對 workload model 的需求不同，混用同一套模型會讓壓測結論對其中一種場景失真。</p>
<p>季節性峰值的 workload model 需要涵蓋三個電商特有維度：流量上升斜率（開賣瞬間的階梯式爆增 vs 活動期間的漸進增長）、讀寫比例變化（瀏覽階段讀為主 → 結帳階段寫入爆增）、庫存查詢的 cache miss 率（熱門商品快取因庫存變動頻繁失效）。<a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify 的 BFCM 容量治理</a>把這類峰值的容量驗證制度化為年度 game day。</p>
<p>閃購型流量的特徵是持續時間極短（分鐘級）但倍率極高（日常的 10-50 倍）。常規壓測用日均流量推算會完全漏掉這種尖峰，需要獨立的 burst scenario 模擬開賣瞬間的並發衝擊。</p>
<p>轉換率是電商特有的穩態指標。load test 的判讀不只看 latency 和 error rate，還要看結帳轉換率是否在壓力下劣化。研究顯示 latency 上升 100ms 可能讓轉換率下降 1-7%，這個商業影響在純技術指標中看不到。壓測結果要同時記錄技術指標與業務指標，容量決策才能對齊商業價值。</p>
<h2 id="操作判讀">操作判讀</h2>
<table>
  <thead>
      <tr>
          <th>觀察到的狀況</th>
          <th>可能原因</th>
          <th>下一步行動</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>壓測通過但 production peak 仍故障</td>
          <td>workload model 未涵蓋峰值形狀或 cohort 比例</td>
          <td>用 access log 重建 peak 時段模型</td>
      </tr>
      <tr>
          <td>latency 在低負載就開始劣化</td>
          <td>staging 資料量不足、query plan 與 production 不同</td>
          <td>用 production-like 資料量重測</td>
      </tr>
      <tr>
          <td>throughput ceiling 遠高於 production</td>
          <td>synthetic traffic 繞過 auth/cache 路徑</td>
          <td>加入 realistic session 與 cache miss scenario</td>
      </tr>
      <tr>
          <td>壓測結果每月差異大</td>
          <td>workload model drift</td>
          <td>建立定期校準流程、對比 p50/p95 偏移</td>
      </tr>
      <tr>
          <td>瓶頸定位不出來</td>
          <td>缺少資源層同步觀測</td>
          <td>壓測時同步收 CPU / memory / pool / queue 指標</td>
      </tr>
      <tr>
          <td>cache miss 場景未被覆蓋</td>
          <td>workload 只有 warm cache 情境</td>
          <td>參考 <a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest P1</a> 設計 cold start scenario</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>workload 是合成的、跟 production traffic shape 不同</li>
<li>壓測通過但 production peak 失敗、模型未涵蓋實際模式</li>
<li>只測 throughput、不測 saturation 與 cost curve</li>
<li>bottleneck 識別靠經驗、無系統定位流程</li>
<li>capacity 規劃靠一次性 load test 結論、無持續對齊</li>
<li>load-test 模型超過 6 個月未校準、drift 累積</li>
</ul>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 容量與成本邊界</a>：load test 餵給容量規劃輸入</li>
<li><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 performance regression gate</a>：load baseline 升級為持續 gate</li>
<li><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety boundary</a>：production load test 的 blast radius 與 stop condition</li>
<li><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 steady state definition</a>：load test 驗證 saturation 前後的穩態維持</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：load test 結果作為 release 放行的容量證據</li>
<li><a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18 reliability metrics</a>：把流量與可靠性指標接起來</li>
</ul>
]]></content:encoded></item><item><title>6.3 fuzz campaign</title><link>https://tarrragon.github.io/blog/backend/06-reliability/fuzz-campaign/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/fuzz-campaign/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/fuzz-test/" data-link-title="Fuzz Test" data-link-desc="說明用隨機與變異輸入驗證解析器與邊界處理健壯性">Fuzz test&lt;/a> 把沒想過的輸入轉成可重播、可修補的失敗案例，補齊人工列舉無法觸及的邊界盲區。&lt;/p>
&lt;p>這一頁處理的是輸入空間的盲區。當 API、parser、codec 或 schema 的邊界不清楚時，fuzz 比人工列案例更能覆蓋非預期路徑。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 fuzz 的品質先看 target 選擇是否對準高風險輸入邊界，再看 corpus 是否持續收斂，最後看 crash 是否能轉成可回歸的修復。&lt;/p>
&lt;p>重點判斷：&lt;/p>
&lt;ul>
&lt;li>fuzz target 是否足夠小，能對準單一責任&lt;/li>
&lt;li>corpus 是否持續收斂，coverage delta 是否仍為正&lt;/li>
&lt;li>crash reproduction 是否可重播到同一條路徑&lt;/li>
&lt;li>修補後是否回寫成 regression test&lt;/li>
&lt;/ul>
&lt;h2 id="fuzz-target-設計">Fuzz target 設計&lt;/h2>
&lt;p>Fuzz target 是 fuzz campaign 的最小驗證單位，責任是把外部輸入導入一個可觀測邊界的函式。&lt;/p>
&lt;p>好的 target 對準單一 parser、codec、serializer 或 validation function，函式簽章接受原始位元組（如 &lt;code>func([]byte)&lt;/code> 或等效形式）。target 選擇的判準有三個：這個函式是否直接處理外部輸入、邊界行為是否不清楚、crash 是否有業務影響。&lt;/p>
&lt;p>target 粒度影響 fuzz 的效率與判讀價值。target 太大（整個 HTTP handler 含 auth / routing / DB 存取）會讓 crash 難以定位到具體邊界，因為 fuzz engine 需要同時探索太多分支，coverage 增長慢且 crash 歸因模糊。target 太小（單一 if 分支）會讓 coverage 增長無意義，因為分支行為已經被 unit test 覆蓋。&lt;/p>
&lt;p>常見的高價值 target 類型：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Target 類型&lt;/th>
 &lt;th>典型邊界風險&lt;/th>
 &lt;th>範例&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Protocol parser&lt;/td>
 &lt;td>畸形封包、長度溢位、巢狀深度&lt;/td>
 &lt;td>HTTP header parser、gRPC frame decoder&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Schema deserializer&lt;/td>
 &lt;td>型別不匹配、缺欄位、巢狀物件遞迴&lt;/td>
 &lt;td>JSON/Protobuf/MessagePack deserializer&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Image / media codec&lt;/td>
 &lt;td>buffer overflow、memory allocation&lt;/td>
 &lt;td>PNG decoder、PDF parser&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Validation function&lt;/td>
 &lt;td>邊界值、正則回溯、encoding 混淆&lt;/td>
 &lt;td>email validator、URL parser、SQL escaper&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Config parser&lt;/td>
 &lt;td>非預期組合、環境變數注入&lt;/td>
 &lt;td>YAML/TOML config loader&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="corpus-管理">Corpus 管理&lt;/h2>
&lt;p>Corpus 累積有效的輸入種子，讓 fuzz engine 能從已知邊界往外探索。corpus 品質直接決定 fuzz campaign 的探索效率。&lt;/p>
&lt;p>初始 corpus 從三個來源收集：unit test 的既有 fixture（已知的合法與邊界輸入）、production sample 脫敏後的真實請求（反映實際流量的輸入結構）、schema 範例與文件中的合法樣本。初始 corpus 的重點是涵蓋主要合法路徑，讓 fuzz engine 從合法輸入開始 mutation，更容易觸達邊界。&lt;/p>
&lt;p>持續擴充靠 coverage-guided mutation。fuzz engine 每次產生的 mutated input 若觸發了新的 code path（新分支、新呼叫），這個 input 會自動加入 corpus。隨著 campaign 進行，corpus 會累積越來越多能觸達深層分支的種子。&lt;/p>
&lt;p>corpus 品質的判讀指標是 coverage delta trend — 每個時段新增的 code path 數量。coverage delta 持續為正代表 corpus 仍在有效探索；coverage delta 趨近零代表當前 target 的探索接近飽和，應考慮三個方向：切換到新 target、調整 mutation dictionary（加入 domain-specific token）、或擴充初始 corpus 的多樣性。&lt;/p>
&lt;p>corpus 需要持久化管理。corpus 檔案應納入版本控制或 artifact storage，跨 CI job 保留。每次 fuzz campaign 結束時，新發現的有效種子合併回 corpus；crash input 在修復後轉成 regression fixture，從 fuzz corpus 移到 test fixture。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/fuzz-test/" data-link-title="Fuzz Test" data-link-desc="說明用隨機與變異輸入驗證解析器與邊界處理健壯性">Fuzz test</a> 把沒想過的輸入轉成可重播、可修補的失敗案例，補齊人工列舉無法觸及的邊界盲區。</p>
<p>這一頁處理的是輸入空間的盲區。當 API、parser、codec 或 schema 的邊界不清楚時，fuzz 比人工列案例更能覆蓋非預期路徑。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 fuzz 的品質先看 target 選擇是否對準高風險輸入邊界，再看 corpus 是否持續收斂，最後看 crash 是否能轉成可回歸的修復。</p>
<p>重點判斷：</p>
<ul>
<li>fuzz target 是否足夠小，能對準單一責任</li>
<li>corpus 是否持續收斂，coverage delta 是否仍為正</li>
<li>crash reproduction 是否可重播到同一條路徑</li>
<li>修補後是否回寫成 regression test</li>
</ul>
<h2 id="fuzz-target-設計">Fuzz target 設計</h2>
<p>Fuzz target 是 fuzz campaign 的最小驗證單位，責任是把外部輸入導入一個可觀測邊界的函式。</p>
<p>好的 target 對準單一 parser、codec、serializer 或 validation function，函式簽章接受原始位元組（如 <code>func([]byte)</code> 或等效形式）。target 選擇的判準有三個：這個函式是否直接處理外部輸入、邊界行為是否不清楚、crash 是否有業務影響。</p>
<p>target 粒度影響 fuzz 的效率與判讀價值。target 太大（整個 HTTP handler 含 auth / routing / DB 存取）會讓 crash 難以定位到具體邊界，因為 fuzz engine 需要同時探索太多分支，coverage 增長慢且 crash 歸因模糊。target 太小（單一 if 分支）會讓 coverage 增長無意義，因為分支行為已經被 unit test 覆蓋。</p>
<p>常見的高價值 target 類型：</p>
<table>
  <thead>
      <tr>
          <th>Target 類型</th>
          <th>典型邊界風險</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Protocol parser</td>
          <td>畸形封包、長度溢位、巢狀深度</td>
          <td>HTTP header parser、gRPC frame decoder</td>
      </tr>
      <tr>
          <td>Schema deserializer</td>
          <td>型別不匹配、缺欄位、巢狀物件遞迴</td>
          <td>JSON/Protobuf/MessagePack deserializer</td>
      </tr>
      <tr>
          <td>Image / media codec</td>
          <td>buffer overflow、memory allocation</td>
          <td>PNG decoder、PDF parser</td>
      </tr>
      <tr>
          <td>Validation function</td>
          <td>邊界值、正則回溯、encoding 混淆</td>
          <td>email validator、URL parser、SQL escaper</td>
      </tr>
      <tr>
          <td>Config parser</td>
          <td>非預期組合、環境變數注入</td>
          <td>YAML/TOML config loader</td>
      </tr>
  </tbody>
</table>
<h2 id="corpus-管理">Corpus 管理</h2>
<p>Corpus 累積有效的輸入種子，讓 fuzz engine 能從已知邊界往外探索。corpus 品質直接決定 fuzz campaign 的探索效率。</p>
<p>初始 corpus 從三個來源收集：unit test 的既有 fixture（已知的合法與邊界輸入）、production sample 脫敏後的真實請求（反映實際流量的輸入結構）、schema 範例與文件中的合法樣本。初始 corpus 的重點是涵蓋主要合法路徑，讓 fuzz engine 從合法輸入開始 mutation，更容易觸達邊界。</p>
<p>持續擴充靠 coverage-guided mutation。fuzz engine 每次產生的 mutated input 若觸發了新的 code path（新分支、新呼叫），這個 input 會自動加入 corpus。隨著 campaign 進行，corpus 會累積越來越多能觸達深層分支的種子。</p>
<p>corpus 品質的判讀指標是 coverage delta trend — 每個時段新增的 code path 數量。coverage delta 持續為正代表 corpus 仍在有效探索；coverage delta 趨近零代表當前 target 的探索接近飽和，應考慮三個方向：切換到新 target、調整 mutation dictionary（加入 domain-specific token）、或擴充初始 corpus 的多樣性。</p>
<p>corpus 需要持久化管理。corpus 檔案應納入版本控制或 artifact storage，跨 CI job 保留。每次 fuzz campaign 結束時，新發現的有效種子合併回 corpus；crash input 在修復後轉成 regression fixture，從 fuzz corpus 移到 test fixture。</p>
<h2 id="crash-reproduction-與-minimization">Crash reproduction 與 minimization</h2>
<p>Fuzz 找到 crash 後的處理流程是 reproduce → minimize → fix → 回灌 regression test。</p>
<p><strong>Reproduce</strong>：用 fuzz engine 產出的 crash input 在相同環境重跑，確認 crash 可穩定觸發。不可穩定觸發的 crash 通常來自 race condition 或環境差異，需要額外的 concurrency 或環境控制才能定位。</p>
<p><strong>Minimize</strong>：minimization 把觸發 crash 的輸入縮到最小等效形式，讓 root cause 更容易定位。自動化 minimizer（如 Go 內建的 fuzz minimizer、libFuzzer 的 <code>-minimize_crash=1</code>）會反覆刪減 input 中的位元組，保留能觸發同一 crash 的最小子集。minimized input 通常比原始 input 短一到兩個數量級，讓開發者能直接看出觸發條件。</p>
<p><strong>Fix 與 regression test</strong>：修復 crash 後，用 minimized input 作為 fixture 寫成 regression test。這個 test 確保同類 bug 不再出現，也讓未來的 refactor 不會重新打開已修復的邊界。regression test 歸入 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">CI pipeline</a> 的 fast path，每次 push 都跑。</p>
<h2 id="ci-整合">CI 整合</h2>
<p>Fuzz 在 CI 的執行模式跟 unit test 不同。unit test 有明確的 pass/fail 結束條件，fuzz campaign 是開放式探索，執行時間越長覆蓋越廣。</p>
<p>CI 整合分兩種模式，對齊 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a> 的分層策略：</p>
<p><strong>Fast path regression</strong>（30 秒至 5 分鐘）：用既有 corpus 跑 fuzz，確認已知邊界沒退化。這個模式的目標是 regression 檢查，每次 push 觸發。corpus 裡的種子已經覆蓋了過去發現的邊界，短時間跑完可以確保修復沒被破壞、新變更沒引入已知類型的 crash。</p>
<p><strong>Scheduled exploration</strong>（小時級）：定期（每日或每週）跑長時間 fuzz，讓 engine 有足夠時間做深層 mutation 與路徑探索。新發現的種子合併回 corpus，crash input 產生 issue 或 alert。這個模式的 coverage delta 是判讀 campaign 價值的主要指標。</p>
<p>CI 整合的關鍵是 corpus 持久化。corpus 必須跨 job 保存（cache、artifact storage 或版本控制），每次 job 從上一次的 corpus 繼續探索。若 corpus 每次從零開始，fuzz engine 會重複探索已知路徑，浪費運算資源。</p>
<h2 id="coverage-門檻與收斂判讀">Coverage 門檻與收斂判讀</h2>
<p>Fuzz coverage 跟 unit test coverage 的意義不同。unit test coverage 衡量的是「多少行被執行過」，fuzz coverage 衡量的是「多少邊界被探索過」。同一個函式的 fuzz coverage 可以隨 corpus 擴充持續增長，因為 mutation 會觸發不同的分支組合。</p>
<p>判讀 fuzz campaign 是否仍有價值靠兩個指標：coverage delta trend（每小時新增多少 code path）與 corpus size growth（每小時新增多少有效種子）。兩者同時趨近零代表當前 target 的探索飽和。</p>
<p>飽和訊號指引兩個決策。第一，是否切換 target — 當前 target 的邊界已被充分探索，把 fuzz 資源移到另一個高風險 target 的邊際價值更高。第二，是否調整 mutation dictionary — 加入 domain-specific token（如 SQL keyword、JSON structure token、protocol magic bytes）可以讓 engine 更有效地觸達 domain-aware 的邊界。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/google/" data-link-title="Google" data-link-desc="Google SRE 實踐原典：SLI / SLO / Error Budget / Postmortem 文化">Google</a>：OSS-Fuzz 對大量基礎元件（parser、codec、serializer）做持續 fuzz，corpus 跨版本累積，crash 自動提 issue 並追蹤修復。這個規模的 fuzz campaign 說明 corpus 持久化與自動化 crash 處理是可擴展的前提。</li>
<li><a href="/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">Stripe</a>：API 與 serialization 邊界的 fuzz 需要 domain-specific dictionary（支付欄位、currency code、idempotency key 格式），通用 mutation 難以觸達業務語意上的邊界 crash。</li>
<li><a href="/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">GitHub</a>：webhook payload 與 schema 邊界的 fuzz 適合用 schema-aware fuzzer，從 OpenAPI / JSON Schema 產生結構化 mutation，覆蓋嵌套物件與型別邊界。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>fuzz corpus 從未更新、覆蓋率停滯</td>
          <td>campaign 已失去探索價值 — 檢查是否需要換 target 或調整 mutation strategy</td>
          <td>換 target 或加 mutation dictionary</td>
      </tr>
      <tr>
          <td>crash 復現靠人工 minimization</td>
          <td>minimization 應自動化 — 手動 minimization 耗時且不可重複</td>
          <td>啟用 fuzzer 內建 minimizer 或接 CI 自動化</td>
      </tr>
      <tr>
          <td>fuzz 找到 bug 沒回灌成 regression test</td>
          <td>修復後邊界可能被再次打開 — regression fixture 應歸入 CI fast path</td>
          <td>把 minimized input 加入 CI regression 套件</td>
      </tr>
      <tr>
          <td>input boundary 無 spec、fuzz 範圍模糊</td>
          <td>target 選擇需要對齊 — 先定義哪些函式直接處理外部輸入</td>
          <td>盤點外部輸入函式、建立 target 清單</td>
      </tr>
      <tr>
          <td>production 出 crash 但 fuzz 沒抓到</td>
          <td>fuzz target 未覆蓋該輸入路徑 — 把 production crash input 加入 corpus</td>
          <td>補 target + 把 crash input 加入 seed</td>
      </tr>
      <tr>
          <td>coverage delta 持續為零但仍在跑長時間 fuzz</td>
          <td>資源浪費 — 飽和後應切換 target 或調整 dictionary</td>
          <td>停止當前 campaign、轉移資源到新 target</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>：fuzz regression 歸入 fast path、exploration 歸入 scheduled path</li>
<li><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10 contract testing</a>：schema fuzz 與契約驗證互補，contract 定義已知邊界、fuzz 探索未知邊界</li>
<li><a href="/blog/backend/06-reliability/test-data-management/" data-link-title="6.16 Test Data Management" data-link-desc="把 fixture / seed / production-like data 作為跨模組共用 artifact，治理資料層次、遮罩策略與可重現性">6.16 test data</a>：fuzz 找到的 crash input 沉澱成 seed 與 fixture</li>
<li><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety boundary</a>：長時間 fuzz campaign 在 production-like 環境跑時需要資源邊界控制</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：security-relevant fuzz crash 可作為 release 阻擋條件</li>
<li><a href="/blog/backend/08-incident-response/incident-pattern-library/" data-link-title="8.9 事故型態庫入口" data-link-desc="把跨服務的共通事故型態抽成型態卡，作為新事故的判讀錨點">8.9 事故型態庫</a>：recurrent crash pattern 抽象化成型態</li>
</ul>
]]></content:encoded></item><item><title>6.4 chaos testing</title><link>https://tarrragon.github.io/blog/backend/06-reliability/chaos-testing/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/chaos-testing/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/chaos-test/" data-link-title="Chaos Test" data-link-desc="說明透過受控故障注入驗證系統在異常條件下的恢復能力">Chaos test&lt;/a> 是在可控條件下主動注入故障，驗證系統是否能在真實依賴失效時維持 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state&lt;/a> 與可接受的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a>。&lt;/p>
&lt;p>這一頁關心的是失效時系統怎麼退化。chaos 的價值在於判讀系統收到故障後的退化行為是否符合預期。沒有先定義 steady state，chaos 只會變成故障展示，不會變成判讀工具。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 chaos 的重點是對控制面、資料面與依賴鏈的回復能力做驗證，而不是單純證明服務死過一次。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>是否先定義 steady state 與成功條件&lt;/li>
&lt;li>故障是否真的落在常見依賴與控制點&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a> 是否可量測、可縮限&lt;/li>
&lt;li>recovery path 是否能在演練後被重播&lt;/li>
&lt;/ul>
&lt;h2 id="故障注入的設計流程">故障注入的設計流程&lt;/h2>
&lt;p>一輪有效的 chaos 驗證從穩態定義開始。先知道系統正常時應維持什麼行為，再設計注入去測試這個行為是否可持續。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>步驟&lt;/th>
 &lt;th>核心問題&lt;/th>
 &lt;th>產出&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>定義穩態&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>限制 blast radius&lt;/td>
 &lt;td>實驗範圍怎麼控制&lt;/td>
 &lt;td>服務 / 區域 / 流量&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>設定停止條件&lt;/td>
 &lt;td>何時立即停止實驗&lt;/td>
 &lt;td>abort trigger&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>穩態定義是整個流程的錨點。Netflix 的 chaos 實踐把 steady state 放在驗證循環的第一步 — 先定義穩態指標（&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">SLI&lt;/a>、business KPI、queue lag），再用故障注入去測試這些指標是否能在壓力下維持。沒有穩態定義的故障注入只能產出「系統被打壞了」的結論，無法回答「系統是否按預期退化」。&lt;/p>
&lt;p>假設設計決定實驗能學到什麼。好的假設會說明「當 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker&lt;/a> 節點離線時，訊息消費延遲應在 30 秒內回線，checkout 成功率應維持在 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">SLO&lt;/a> 門檻內」，而不只是「關掉 broker 看看會怎樣」。假設越具體，實驗結果的判讀價值越高。&lt;/p>
&lt;p>Blast radius 需要同時包含技術範圍與客戶範圍。技術範圍是 service、region、cluster、dependency；客戶範圍是 tenant、plan、traffic percentage 或 internal-only cohort。從最小範圍開始，逐步放大，每一步都要確認停止條件仍可執行。&lt;/p>
&lt;p>停止條件讓實驗可控。當 SLO &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate&lt;/a> 超門檻、customer impact 出現或 cost 異常上升時，實驗應立即終止。停止條件要連到可觀測訊號，不能靠臨場討論決定是否繼續。&lt;/p>
&lt;h2 id="注入類型與層次">注入類型與層次&lt;/h2>
&lt;p>故障注入按依賴類型分層。不同依賴的失效模式不同，預期退化也不同，實驗設計需要對應調整。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>注入類型&lt;/th>
 &lt;th>打到的依賴&lt;/th>
 &lt;th>預期退化&lt;/th>
 &lt;th>結果可信條件&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Broker outage&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker&lt;/a> 節點或 partition&lt;/td>
 &lt;td>消費延遲上升、DLQ 累積&lt;/td>
 &lt;td>流量接近 production pattern&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DB latency&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/database/" data-link-title="Database" data-link-desc="說明 database 在後端系統中如何承擔正式狀態、查詢與一致性責任">database&lt;/a> 連線或查詢&lt;/td>
 &lt;td>請求排隊、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a> 觸發&lt;/td>
 &lt;td>connection pool 配置與 production 一致&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Node restart&lt;/td>
 &lt;td>應用節點&lt;/td>
 &lt;td>短暫不可用、load balancer 切流&lt;/td>
 &lt;td>readiness probe 與 graceful shutdown 配置一致&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Network jitter&lt;/td>
 &lt;td>跨服務通訊&lt;/td>
 &lt;td>latency 抖動、retry 上升&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/jitter/" data-link-title="Jitter" data-link-desc="說明重試或排程加入隨機偏移如何降低同步尖峰">jitter&lt;/a> 模式接近真實 ISP / cloud&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Broker outage 驗證的是非同步依賴的容錯能力。當 broker 節點或 partition 不可用時，生產端應有 retry 與 fallback，消費端應能在恢復後 drain backlog 而不是 replay storm。測試時需要確認 DLQ 設定正確、消費 lag 有監控、恢復後的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure&lt;/a> 不會壓垮下游。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/chaos-test/" data-link-title="Chaos Test" data-link-desc="說明透過受控故障注入驗證系統在異常條件下的恢復能力">Chaos test</a> 是在可控條件下主動注入故障，驗證系統是否能在真實依賴失效時維持 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a> 與可接受的 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a>。</p>
<p>這一頁關心的是失效時系統怎麼退化。chaos 的價值在於判讀系統收到故障後的退化行為是否符合預期。沒有先定義 steady state，chaos 只會變成故障展示，不會變成判讀工具。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 chaos 的重點是對控制面、資料面與依賴鏈的回復能力做驗證，而不是單純證明服務死過一次。</p>
<p>重點訊號包括：</p>
<ul>
<li>是否先定義 steady state 與成功條件</li>
<li>故障是否真的落在常見依賴與控制點</li>
<li><a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a> 是否可量測、可縮限</li>
<li>recovery path 是否能在演練後被重播</li>
</ul>
<h2 id="故障注入的設計流程">故障注入的設計流程</h2>
<p>一輪有效的 chaos 驗證從穩態定義開始。先知道系統正常時應維持什麼行為，再設計注入去測試這個行為是否可持續。</p>
<table>
  <thead>
      <tr>
          <th>步驟</th>
          <th>核心問題</th>
          <th>產出</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>定義穩態</td>
          <td>服務正常時應維持什麼行為</td>
          <td>穩態指標與門檻</td>
      </tr>
      <tr>
          <td>設計假設</td>
          <td>失效發生後系統仍應維持什麼</td>
          <td>可證偽假設</td>
      </tr>
      <tr>
          <td>限制 blast radius</td>
          <td>實驗範圍怎麼控制</td>
          <td>服務 / 區域 / 流量</td>
      </tr>
      <tr>
          <td>設定停止條件</td>
          <td>何時立即停止實驗</td>
          <td>abort trigger</td>
      </tr>
  </tbody>
</table>
<p>穩態定義是整個流程的錨點。Netflix 的 chaos 實踐把 steady state 放在驗證循環的第一步 — 先定義穩態指標（<a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">SLI</a>、business KPI、queue lag），再用故障注入去測試這些指標是否能在壓力下維持。沒有穩態定義的故障注入只能產出「系統被打壞了」的結論，無法回答「系統是否按預期退化」。</p>
<p>假設設計決定實驗能學到什麼。好的假設會說明「當 <a href="/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker</a> 節點離線時，訊息消費延遲應在 30 秒內回線，checkout 成功率應維持在 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">SLO</a> 門檻內」，而不只是「關掉 broker 看看會怎樣」。假設越具體，實驗結果的判讀價值越高。</p>
<p>Blast radius 需要同時包含技術範圍與客戶範圍。技術範圍是 service、region、cluster、dependency；客戶範圍是 tenant、plan、traffic percentage 或 internal-only cohort。從最小範圍開始，逐步放大，每一步都要確認停止條件仍可執行。</p>
<p>停止條件讓實驗可控。當 SLO <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> 超門檻、customer impact 出現或 cost 異常上升時，實驗應立即終止。停止條件要連到可觀測訊號，不能靠臨場討論決定是否繼續。</p>
<h2 id="注入類型與層次">注入類型與層次</h2>
<p>故障注入按依賴類型分層。不同依賴的失效模式不同，預期退化也不同，實驗設計需要對應調整。</p>
<table>
  <thead>
      <tr>
          <th>注入類型</th>
          <th>打到的依賴</th>
          <th>預期退化</th>
          <th>結果可信條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Broker outage</td>
          <td><a href="/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker</a> 節點或 partition</td>
          <td>消費延遲上升、DLQ 累積</td>
          <td>流量接近 production pattern</td>
      </tr>
      <tr>
          <td>DB latency</td>
          <td><a href="/blog/backend/knowledge-cards/database/" data-link-title="Database" data-link-desc="說明 database 在後端系統中如何承擔正式狀態、查詢與一致性責任">database</a> 連線或查詢</td>
          <td>請求排隊、<a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 觸發</td>
          <td>connection pool 配置與 production 一致</td>
      </tr>
      <tr>
          <td>Node restart</td>
          <td>應用節點</td>
          <td>短暫不可用、load balancer 切流</td>
          <td>readiness probe 與 graceful shutdown 配置一致</td>
      </tr>
      <tr>
          <td>Network jitter</td>
          <td>跨服務通訊</td>
          <td>latency 抖動、retry 上升</td>
          <td><a href="/blog/backend/knowledge-cards/jitter/" data-link-title="Jitter" data-link-desc="說明重試或排程加入隨機偏移如何降低同步尖峰">jitter</a> 模式接近真實 ISP / cloud</td>
      </tr>
  </tbody>
</table>
<p>Broker outage 驗證的是非同步依賴的容錯能力。當 broker 節點或 partition 不可用時，生產端應有 retry 與 fallback，消費端應能在恢復後 drain backlog 而不是 replay storm。測試時需要確認 DLQ 設定正確、消費 lag 有監控、恢復後的 <a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a> 不會壓垮下游。</p>
<p>DB latency 驗證的是同步依賴在退化時的行為。延遲注入比完全斷線更接近真實故障 — production 常見的是 slow query、connection pool exhaustion 或 replica lag，而不是 database 完全離線。測試時需要確認 timeout 是否會級聯：一個慢查詢拖住連線，其他請求開始排隊，最終 thread pool 或 goroutine 耗盡。</p>
<p>Node restart 驗證的是服務在節點層級的恢復能力。graceful shutdown 是否正確 drain 連線、readiness probe 是否能阻止 load balancer 過早送流量、cold start 是否會因 cache miss 或 JIT warmup 造成短暫效能劣化。</p>
<p>Network jitter 驗證的是跨服務通訊的韌性。jitter 注入需要模擬真實的 latency distribution（長尾、間歇性），而不是固定延遲。測試時需要關注 retry 行為：固定 retry 在 jitter 環境下可能放大流量，需要搭配 <a href="/blog/backend/knowledge-cards/retry-budget/" data-link-title="Retry Budget" data-link-desc="說明重試次數如何受整體容量與錯誤預算限制">retry budget</a> 控制。</p>
<h3 id="注入粒度instance-level-vs-request-path">注入粒度：instance-level vs request-path</h3>
<p>故障注入有兩個主要粒度，適用場景不同。</p>
<p>Instance-level injection（如 Chaos Monkey）在節點層注入故障 — 關閉 instance、斷開網路、暫停程序。這個粒度驗證的是基礎設施韌性：load balancer 能否切流、auto-scaling 能否補位、graceful shutdown 能否完成。優點是簡單、接近真實硬體故障；缺點是粒度粗，無法精準驗證特定依賴路徑。</p>
<p>Request-path injection（如 FIT）在請求路徑層注入故障 — 對特定 API call、dependency request 或 service-to-service 通訊植入 timeout、error 或延遲。這個粒度驗證的是應用韌性：fallback 是否生效、circuit breaker 是否觸發、retry 是否安全。優點是精準、blast radius 小；缺點是需要更深的 instrumentation，建置成本較高。</p>
<p>兩者不互斥。instance-level injection 適合驗證基礎設施層的回復能力，request-path injection 適合驗證應用層的容錯邏輯。團隊可以從 instance-level 開始建立 chaos 習慣，再逐步引入 request-path injection 提升驗證精度。第三種粒度是 infrastructure-level injection（AZ failure / region failure），由 cloud provider 的 chaos 工具（如 AWS FIS、Azure Chaos Studio）支援，驗證的是跨 AZ 冗餘與 failover 路由。</p>
<h2 id="執行時段與環境">執行時段與環境</h2>
<p>故障注入的執行時段與環境直接影響驗證價值。</p>
<h3 id="business-hours-vs-off-peak">Business hours vs off-peak</h3>
<p>在 business hours 執行 chaos 能同時驗證系統韌性與團隊應變能力。人員在線可即時觀測、依賴流量接近真實、通訊鏈條（值班升級、跨團隊協作、內外部狀態更新）被完整測到。off-peak 雖然短期風險低，但測到的多是「工具可執行」，不是「服務在真實壓力下可承受」。</p>
<p>選擇 business hours 執行的前提是 guardrails 到位：時段限制在可支援的工作時間、blast radius 從小範圍開始、abort trigger 連到明確門檻、事後回寫進工程控制面。風險來自 guardrails 的缺失。</p>
<h3 id="staging-vs-production">Staging vs production</h3>
<p>Staging 適合驗證工具整合與基礎假設：注入能否生效、dashboard 能否呈現訊號、stop condition 能否觸發。但 staging 與 production 之間通常存在環境漂移 — traffic pattern 不同、dependency 配置不同、<a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a> 大小不同、cache warmup 狀態不同。在 staging 通過的實驗，不能直接等同於 production 可承受。</p>
<p>Production chaos 的價值在於驗證真實依賴路徑。它需要從最小 cohort 開始（internal traffic、canary region、特定 tenant），搭配完整 stop condition 與 rollback path。Production chaos 需要 stop condition 作為安全網。團隊可以從簡單的 stop condition（如 error rate 超門檻就停止）起步，隨經驗累積逐步精細化。</p>
<h2 id="證據結構與回寫">證據結構與回寫</h2>
<p>Chaos 實驗的產出是可決策的證據。當實驗結果能直接回答「這個依賴的容錯能力是否足夠」，chaos 才從測試活動升級為可靠性控制面。</p>
<table>
  <thead>
      <tr>
          <th>證據欄位</th>
          <th>核心問題</th>
          <th>決策用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Steady-state impact</td>
          <td>注入後穩態指標是否維持</td>
          <td>判斷容錯能力是否符合預期</td>
      </tr>
      <tr>
          <td>Abort trigger record</td>
          <td>停止條件是否被觸發、何時觸發</td>
          <td>判斷是否需要凍結或回退</td>
      </tr>
      <tr>
          <td>Fallback result</td>
          <td>降級路徑是否可用、恢復是否收斂</td>
          <td>判斷事故時能否安全止血</td>
      </tr>
      <tr>
          <td>Dependency drift</td>
          <td>受影響依賴是否落在預期範圍</td>
          <td>判斷 blast radius 是否可接受</td>
      </tr>
  </tbody>
</table>
<p>Steady-state impact 是最核心的證據欄位。它回答的問題是「系統在故障期間是否維持了服務承諾」。若 <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">SLI</a> 維持在 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">SLO</a> 門檻內，代表容錯機制有效；若偏離，需要記錄偏離幅度、持續時間與影響範圍。</p>
<p>Abort trigger record 讓團隊知道 stop condition 是否可執行。若停止條件被觸發但執行延遲，代表觀測或通訊鏈條有缺口；若停止條件沒被觸發但影響已擴大，代表門檻設定需要校準。</p>
<p>這四個欄位接到 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 verification evidence handoff</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> 的放行輸入。release decision 從「主觀討論」轉成「政策驅動」：有證據支持容錯能力 → 放行；abort 被觸發 → 凍結並修復；fallback 失敗 → 補 action item 再重驗。</p>
<h2 id="規模差異">規模差異</h2>
<p>Chaos 的設計在不同規模下差異顯著。單服務 chaos 與跨區 chaos 打到的系統層不同，blast radius 控制方式也不同。</p>
<h3 id="單服務-chaos">單服務 chaos</h3>
<p>單服務 chaos 驗證的是一個服務對其直接依賴的容錯能力。blast radius 限在該服務的 instance、replica 或 traffic cohort 內。適合驗證 circuit breaker、fallback、timeout、retry 與 graceful degradation。</p>
<h3 id="跨區-chaos-與-failure-localization">跨區 chaos 與 failure localization</h3>
<p>跨區 chaos 驗證的是故障在區域或依賴鏈上的擴散行為。Amazon 的 cell-based architecture 把多租戶服務的故障域限制在 cell 內 — 一個 cell 的異常不會擴散到其他 cell，恢復策略從全域搶救轉為分批收斂。Meta 的 region failover 實踐則關注控制面故障的跨區擴散 — 當核心網路或 BGP 配置異常跨越區域邊界，恢復動作本身可能成為新的放大器。</p>
<p>兩者共同的判讀重點是：故障是否被限制在預期邊界內。單服務 chaos 的邊界是 instance 與 dependency；跨區 chaos 的邊界是 region、cell 與 shared dependency。blast radius 越大，stop condition 與 rollback path 的設計要求越高。</p>
<h2 id="產業情境串流與媒體服務">產業情境：串流與媒體服務</h2>
<p>串流服務的故障注入需要考慮觀眾正在觀看的即時性。CDN 節點失效、origin server 延遲或 transcoding pipeline 中斷都會直接造成 buffering 或畫質降級，使用者的容忍窗口以秒計。</p>
<p>串流的 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a> 指標跟一般 web service 不同：buffering ratio（觀眾看到轉圈的時間比例）、bitrate stability（畫質是否頻繁跳動）、video start time（按下播放到第一幀的延遲）。這些指標直接反映觀看體驗，chaos 實驗的假設必須用這些指標定義穩態，而非只用 HTTP success rate。</p>
<p>CDN 有多層快取（edge / mid-tier / origin），某一層失效時流量會 fallback 到下一層。chaos 要驗證的是 fallback 路徑能否承受突增的回源流量，以及 adaptive bitrate 策略是否能平滑過渡到較低畫質，而非直接中斷播放。回源流量的放大倍數取決於該層的快取命中率 — 命中率越高的層失效，回源放大越劇烈。</p>
<p>直播事件的 chaos 約束更嚴格。VOD 內容可重試、可重播，直播沒有第二次機會。直播前的 chaos 演練需要模擬「直播進行中 CDN 節點失效」的場景，驗證備援路徑的切換速度是否在觀眾可感知門檻（通常 2-5 秒）內。Netflix 的 chaos 實踐原始動機即是保護串流觀看體驗，其 <a href="/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">steady state hypothesis</a> 的設計直接適用於串流場景。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">Netflix：Steady State、Chaos 與 FIT</a>：把故障注入變成科學化驗證循環，四元素（steady state / hypothesis / blast radius / abort condition）提供 chaos 設計的結構。FIT 把注入粒度推進到 request path，讓測試更接近真實依賴路徑。</li>
<li><a href="/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/" data-link-title="Netflix：Business-Hours Chaos 與 Guardrails" data-link-desc="Chaos Monkey 為何刻意在 business hours 執行：把即時應變能力納入驗證，並用 guardrails 限制實驗風險。">Netflix：Business-Hours Chaos Guardrails</a>：business hours 執行的前提是 guardrails 到位（時段限制、範圍限制、abort trigger、事後回寫），驗證的不只是系統韌性，也包含團隊應變能力。</li>
<li><a href="/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/" data-link-title="Netflix：FIT 證據交接與 Release Gate 回寫" data-link-desc="用 Failure Injection Testing 產出的證據直接驅動 release gate：把實驗結果轉成可放行、可凍結、可回退的決策欄位。">Netflix：FIT 證據交接</a>：把 FIT 輸出結構化成四個決策欄位，讓實驗結果直接驅動 release gate。</li>
<li><a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon：Shuffle Sharding 與 Cell 邊界</a>：cell-based architecture 讓恢復策略從全域搶救轉為分批收斂，是跨區 chaos 設計的前提。</li>
<li><a href="/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">Meta：Region Failover 邊界治理</a>：跨區依賴與控制面故障的回復順序，說明 blast radius 在大規模系統中的擴散治理。</li>
<li><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify：BFCM 容量治理與 Game Day</a>：game day 把演練、壓測與隔離單位連成一條線，適合補充高峰型場景的 chaos 設計。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<p>判讀 chaos 的品質不只看實驗是否通過，要看實驗設計是否能產出可信結論。</p>
<ul>
<li><strong>chaos experiment 只測 happy path 的故障</strong>：只關掉不重要的服務、只在低流量時段跑，通過了也無法證明高價值路徑的容錯能力。判讀條件：注入目標是否對應服務的關鍵依賴路徑。行動：把注入目標對齊服務的 top-3 關鍵依賴。</li>
<li><strong>broker / DB / network 故障無自動演練、靠真事故學</strong>：沒有定期 chaos 的團隊只能從真實事故中學習，學習成本高且機會不可控。判讀條件：chaos 是否有固定節奏，而非只在事故後才啟動。行動：排入季度 chaos sprint、從最小 blast radius 開始。</li>
<li><strong>chaos 暴露問題沒修、紀錄堆積</strong>：實驗發現缺口但 action item 沒有 owner、沒有 deadline，同類問題反覆出現。判讀條件：action item 是否進入 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a> 並被追蹤。行動：每次 chaos 結束後 action item 指定 owner + deadline。</li>
<li><strong>production chaos 只在低流量時段跑、訊號失真</strong>：低流量時段的依賴行為、流量模式與團隊狀態都跟 production peak 不同，通過了不代表高峰時可承受。判讀條件：是否有 business-hours 或接近 peak 的驗證補充。行動：至少每季補一次 business-hours chaos 驗證。</li>
<li><strong>故障注入工具跟 production 不同 stack、結果不可信</strong>：staging 用不同的 broker、database 或 network 配置做 chaos，結果無法外推到 production。判讀條件：實驗環境與 production 的差異是否被記錄並納入結論限制。行動：在結論中標註環境差異、逐步推進 production chaos。</li>
<li><strong>chaos 結果沒進 runbook</strong>：值班人員不知道特定依賴失效後的預期退化行為，事故時仍靠臨場推理。判讀條件：chaos 結論是否已回寫到對應服務的 on-call runbook。行動：每次 chaos 完成後回寫 runbook 的「依賴失效預期行為」段。</li>
</ul>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR / rollback rehearsal</a>：chaos 暴露的回復路徑問題進入 DR 演練</li>
<li><a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12 idempotency / replay</a>：注入重複訊息驗證冪等能力</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 budget</a>：對依賴注入故障驗證 reliability budget</li>
<li><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety boundary</a>：chaos 的 blast radius、stop condition 與權限約束</li>
<li><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 steady state definition</a>：chaos 開始前的穩態定義</li>
<li><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 verification evidence handoff</a>：chaos 證據接到 release gate</li>
<li><a href="/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6 drills / on-call readiness</a>：chaos 結果回饋到值班訓練</li>
</ul>
]]></content:encoded></item><item><title>6.5 失敗模式預判（Pre-mortem 與 FMEA）</title><link>https://tarrragon.github.io/blog/backend/06-reliability/failure-mode-pre-mortem/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/failure-mode-pre-mortem/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>失敗模式預判是在變更上線前，主動尋找驗證覆蓋的缺口。責任是把「我們漏掉了什麼」從事後驚訝變成事前盤點。&lt;/p>
&lt;p>這一頁處理的是驗證邊界。當某個環節一旦失效就會放大事故，pre-mortem 與 FMEA 的工作是提前把那個環節標出來，讓團隊能在上線前決定是補驗證、收窄範圍還是延後變更。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>驗證缺口的核心問題是變更是否被差異化控制、回復路徑是否經過驗證。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>高風險變更是否有獨立 gate&lt;/li>
&lt;li>負載模型是否包含失敗流量特徵&lt;/li>
&lt;li>故障演練是否覆蓋 partial failure 與連鎖失效&lt;/li>
&lt;li>rollback 與 runbook 是否有時限驗證&lt;/li>
&lt;/ul>
&lt;h2 id="pre-mortem-流程">Pre-mortem 流程&lt;/h2>
&lt;p>Pre-mortem 的核心假設是「這個變更已經在 production 造成事故」，然後反向推導可能的失敗路徑。這個方法的價值在於成本極低（只需要一次結構化討論）但能暴露驗證盲區。&lt;/p>
&lt;p>流程分四步：&lt;/p>
&lt;p>&lt;strong>列出依賴與資料路徑&lt;/strong>：把變更涉及的服務依賴、資料寫入路徑與外部呼叫畫出來。重點是找出「變更直接或間接觸及的系統邊界」，包括 schema、config、依賴服務版本與流量路由。&lt;/p>
&lt;p>&lt;strong>對每條路徑問失敗影響&lt;/strong>：對每條路徑假設失敗，判斷影響範圍。問的是「如果這條路徑斷了 / 慢了 / 回傳錯誤，影響會擴散到哪裡」。影響範圍包含直接依賴方、上游呼叫者、使用者可見行為與資料一致性。&lt;/p>
&lt;p>&lt;strong>判斷現有驗證覆蓋&lt;/strong>：對每條失敗路徑，檢查現有 CI、load test、chaos experiment、contract test 是否能攔住這個失敗。重點是找出「我們認為有覆蓋但實際沒覆蓋」的路徑 — 例如 CI 有 unit test 但沒有 integration test 覆蓋跨服務呼叫，或 load test 有 throughput 驗證但沒有 retry storm 場景。&lt;/p>
&lt;p>&lt;strong>識別驗證缺口並路由&lt;/strong>：未覆蓋的失敗路徑進入兩條路由。上線前能補的缺口回寫到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review&lt;/a>，作為上線前檢查項目。上線前補不了的缺口回寫到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog&lt;/a>，作為可排序的改善項目。&lt;/p>
&lt;p>Pre-mortem 的常見失效是流程走了但結論沒路由。當缺口被列出但沒有 owner、沒有 deadline、沒有連到 readiness review 或 debt backlog，pre-mortem 就只是會議紀錄。&lt;/p>
&lt;h2 id="fmea-分類軸">FMEA 分類軸&lt;/h2>
&lt;p>Failure Mode and Effects Analysis 按失效模式分類驗證缺口。按模式分類的好處是讓團隊能判斷「缺口屬於哪一類」，然後沿對應章節的路由去補。&lt;/p>
&lt;h3 id="gate-failure">Gate failure&lt;/h3>
&lt;p>Release gate 缺少高風險變更的差異化控制。當所有變更走同一條 CI pipeline、同一套 gate 門檻，高風險變更（schema migration、payment path、config rollout）的驗證強度跟日常小改動相同，gate 實質上對高風險變更無效。&lt;/p>
&lt;p>判讀條件：高風險變更是否有獨立的 gate 流程；gate 門檻是否隨變更風險等級調整。&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">Microsoft 的變更治理實踐&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;h3 id="load-failure">Load failure&lt;/h3>
&lt;p>Workload model 沒覆蓋失敗流量特徵。壓測模型通常反映正常流量，但事故時的流量形狀完全不同：retry storm 放大請求量、cascade &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a> 佔住連線、queue backlog 堆積改變消費節奏。當壓測模型只包含正常流量，通過壓測不代表系統能承受失敗流量。&lt;/p>
&lt;p>判讀條件：workload model 是否包含 retry 放大、timeout cascade 與 queue 堆積場景。回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test&lt;/a> 補失敗流量模型。&lt;/p>
&lt;h3 id="recovery-failure">Recovery failure&lt;/h3>
&lt;p>Rollback 或 DR 路徑在事故前沒被驗證過。團隊假設 rollback 可用，但 schema 已經不向下相容；團隊假設 failover 可用，但 failover config 跟 production 已經漂移。recovery failure 的特徵是「有計畫但沒跑過」。&lt;/p>
&lt;p>判讀條件：rollback 是否在過去 90 天被 rehearsal 驗證過；DR failover config 是否跟 production 同步。回到 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR / rollback rehearsal&lt;/a> 建立定期驗證節奏。&lt;/p>
&lt;h3 id="detection-failure">Detection failure&lt;/h3>
&lt;p>告警延遲或缺失，問題被使用者先發現。當 SLO alert 覆蓋不足、dashboard 缺少關鍵路徑的訊號、或告警門檻設定過寬，團隊的 MTTD（mean time to detect）會拉長到使用者回報之後。detection failure 讓所有下游反應（止血、升級、溝通）都延遲。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>失敗模式預判是在變更上線前，主動尋找驗證覆蓋的缺口。責任是把「我們漏掉了什麼」從事後驚訝變成事前盤點。</p>
<p>這一頁處理的是驗證邊界。當某個環節一旦失效就會放大事故，pre-mortem 與 FMEA 的工作是提前把那個環節標出來，讓團隊能在上線前決定是補驗證、收窄範圍還是延後變更。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>驗證缺口的核心問題是變更是否被差異化控制、回復路徑是否經過驗證。</p>
<p>重點訊號包括：</p>
<ul>
<li>高風險變更是否有獨立 gate</li>
<li>負載模型是否包含失敗流量特徵</li>
<li>故障演練是否覆蓋 partial failure 與連鎖失效</li>
<li>rollback 與 runbook 是否有時限驗證</li>
</ul>
<h2 id="pre-mortem-流程">Pre-mortem 流程</h2>
<p>Pre-mortem 的核心假設是「這個變更已經在 production 造成事故」，然後反向推導可能的失敗路徑。這個方法的價值在於成本極低（只需要一次結構化討論）但能暴露驗證盲區。</p>
<p>流程分四步：</p>
<p><strong>列出依賴與資料路徑</strong>：把變更涉及的服務依賴、資料寫入路徑與外部呼叫畫出來。重點是找出「變更直接或間接觸及的系統邊界」，包括 schema、config、依賴服務版本與流量路由。</p>
<p><strong>對每條路徑問失敗影響</strong>：對每條路徑假設失敗，判斷影響範圍。問的是「如果這條路徑斷了 / 慢了 / 回傳錯誤，影響會擴散到哪裡」。影響範圍包含直接依賴方、上游呼叫者、使用者可見行為與資料一致性。</p>
<p><strong>判斷現有驗證覆蓋</strong>：對每條失敗路徑，檢查現有 CI、load test、chaos experiment、contract test 是否能攔住這個失敗。重點是找出「我們認為有覆蓋但實際沒覆蓋」的路徑 — 例如 CI 有 unit test 但沒有 integration test 覆蓋跨服務呼叫，或 load test 有 throughput 驗證但沒有 retry storm 場景。</p>
<p><strong>識別驗證缺口並路由</strong>：未覆蓋的失敗路徑進入兩條路由。上線前能補的缺口回寫到 <a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review</a>，作為上線前檢查項目。上線前補不了的缺口回寫到 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>，作為可排序的改善項目。</p>
<p>Pre-mortem 的常見失效是流程走了但結論沒路由。當缺口被列出但沒有 owner、沒有 deadline、沒有連到 readiness review 或 debt backlog，pre-mortem 就只是會議紀錄。</p>
<h2 id="fmea-分類軸">FMEA 分類軸</h2>
<p>Failure Mode and Effects Analysis 按失效模式分類驗證缺口。按模式分類的好處是讓團隊能判斷「缺口屬於哪一類」，然後沿對應章節的路由去補。</p>
<h3 id="gate-failure">Gate failure</h3>
<p>Release gate 缺少高風險變更的差異化控制。當所有變更走同一條 CI pipeline、同一套 gate 門檻，高風險變更（schema migration、payment path、config rollout）的驗證強度跟日常小改動相同，gate 實質上對高風險變更無效。</p>
<p>判讀條件：高風險變更是否有獨立的 gate 流程；gate 門檻是否隨變更風險等級調整。<a href="/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">Microsoft 的變更治理實踐</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>
<h3 id="load-failure">Load failure</h3>
<p>Workload model 沒覆蓋失敗流量特徵。壓測模型通常反映正常流量，但事故時的流量形狀完全不同：retry storm 放大請求量、cascade <a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 佔住連線、queue backlog 堆積改變消費節奏。當壓測模型只包含正常流量，通過壓測不代表系統能承受失敗流量。</p>
<p>判讀條件：workload model 是否包含 retry 放大、timeout cascade 與 queue 堆積場景。回到 <a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test</a> 補失敗流量模型。</p>
<h3 id="recovery-failure">Recovery failure</h3>
<p>Rollback 或 DR 路徑在事故前沒被驗證過。團隊假設 rollback 可用，但 schema 已經不向下相容；團隊假設 failover 可用，但 failover config 跟 production 已經漂移。recovery failure 的特徵是「有計畫但沒跑過」。</p>
<p>判讀條件：rollback 是否在過去 90 天被 rehearsal 驗證過；DR failover config 是否跟 production 同步。回到 <a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR / rollback rehearsal</a> 建立定期驗證節奏。</p>
<h3 id="detection-failure">Detection failure</h3>
<p>告警延遲或缺失，問題被使用者先發現。當 SLO alert 覆蓋不足、dashboard 缺少關鍵路徑的訊號、或告警門檻設定過寬，團隊的 MTTD（mean time to detect）會拉長到使用者回報之後。detection failure 讓所有下游反應（止血、升級、溝通）都延遲。</p>
<p>判讀條件：關鍵路徑的 MTTD 是否在可接受範圍；SLO alert 是否覆蓋使用者可見的服務承諾。<a href="/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">Netflix 的 chaos 實踐</a>把 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a> 定義放在驗證的第一步 — 沒有穩態定義，告警就無法判斷系統是否偏離正常，detection 變成盲目。回到 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性</a> 補訊號覆蓋。</p>
<h2 id="失敗模式嚴重度評估">失敗模式嚴重度評估</h2>
<p>FMEA 傳統用 severity × probability × detectability 三軸評估風險優先序。在可靠性驗證的語境中，這三軸可以簡化為可操作判讀：</p>
<table>
  <thead>
      <tr>
          <th>軸</th>
          <th>判讀問題</th>
          <th>量測方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Severity</td>
          <td>失效的 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a> 有多大</td>
          <td>單服務 / 跨服務 / 跨區 / 跨租戶</td>
      </tr>
      <tr>
          <td>Probability</td>
          <td>這個失效路徑多常被觸及</td>
          <td>變更頻率、歷史事故率、依賴穩定度</td>
      </tr>
      <tr>
          <td>Detectability</td>
          <td>問題被發現需要多久</td>
          <td>MTTD、alert 覆蓋率、synthetic probe 頻率</td>
      </tr>
  </tbody>
</table>
<p>三軸的交叉決定驗證投資順序：high severity + high probability + low detectability 的缺口最先處理。反過來，low severity + low probability 的缺口可以先記錄在 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt</a>，不需要立即補驗證。</p>
<p>嚴重度評估的陷阱是把評分當目標。三軸的責任是排序驗證投資，讓團隊在有限時間內先補最危險的缺口。當評分本身變成需要維護的文件，評估的維護成本會超過它帶來的判讀價值。</p>
<h2 id="服務環節問題地圖">服務環節問題地圖</h2>
<table>
  <thead>
      <tr>
          <th>環節</th>
          <th>失效分類</th>
          <th>主要問題</th>
          <th>案例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Release Gate</td>
          <td>Gate</td>
          <td>高風險變更缺少差異化 gate</td>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/supply-chain/teamcity-cve-2023-42793-ci-entrypoint/" data-link-title="7.R7.2.5 TeamCity 2023：CI 入口漏洞與交付鏈風險" data-link-desc="CI 平台入口被利用後，如何沿著建置與發佈流程擴散供應鏈風險">TeamCity 2023</a></td>
      </tr>
      <tr>
          <td>負載驗證模型</td>
          <td>Load</td>
          <td>測試流量與實際失敗節奏脫鉤</td>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/progress-wsftp-2023-file-service-breach/" data-link-title="7.R7.4.6 Progress WS_FTP 2023：檔案服務入口與資料外送" data-link-desc="對外檔案服務漏洞在企業環境常直接轉為資料外送風險">WS_FTP 2023</a></td>
      </tr>
      <tr>
          <td>失敗模式演練</td>
          <td>Recovery</td>
          <td>partial failure 與連鎖失效覆蓋不足</td>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/change-healthcare-2024-ops-impact/" data-link-title="7.R7.4.3 Change Healthcare 2024：資料事件轉為營運中斷" data-link-desc="醫療支付中樞事件如何同時衝擊資料安全與業務連續性">Change Healthcare 2024</a></td>
      </tr>
      <tr>
          <td>回復路徑驗證</td>
          <td>Recovery</td>
          <td>rollback 與 runbook 缺少時限驗證</td>
          <td><a href="/blog/backend/07-security-data-protection/red-team/cases/data-exfiltration/vmware-esxiargs-2023-ransomware-recovery-pressure/" data-link-title="7.R7.4.5 VMware ESXiArgs 2023：虛擬化平台勒索回復壓力" data-link-desc="虛擬化平台漏洞被利用後，回復策略與營運連續性會面臨同步壓力">VMware ESXiArgs 2023</a></td>
      </tr>
  </tbody>
</table>
<p>TeamCity 案例暴露的是 gate failure：CI 入口本身被繞過時，後續所有 gate 都失效。判讀條件是 CI pipeline 的存取控制是否被納入驗證範圍，而不只是 pipeline 內容。</p>
<p>Change Healthcare 案例暴露的是 recovery failure：事故影響擴散到營運層面時，技術回復完成不代表服務恢復。判讀條件是 DR plan 是否涵蓋跨系統依賴的恢復順序，而不只是單一服務的 rollback。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>失效分類</th>
          <th>判讀</th>
          <th>路由章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CI 綠燈但線上回滾率上升</td>
          <td>Gate</td>
          <td>gate 覆蓋與實際風險未對齊</td>
          <td><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a></td>
      </tr>
      <tr>
          <td>壓測通過但事故時連鎖降速</td>
          <td>Load</td>
          <td>負載模型缺少失敗流量特徵</td>
          <td><a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test</a></td>
      </tr>
      <tr>
          <td>演練記錄完整但回復時間偏長</td>
          <td>Recovery</td>
          <td>演練內容與實戰決策節奏不一致</td>
          <td><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR rehearsal</a></td>
      </tr>
      <tr>
          <td>使用者先於告警發現問題</td>
          <td>Detection</td>
          <td>訊號覆蓋不足或門檻過寬</td>
          <td><a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性</a></td>
      </tr>
  </tbody>
</table>
<p><a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">Google 的 error budget 政策</a>把 gate 門檻跟 budget 消耗綁在一起：budget 健康時走正常 gate，budget 快速消耗時提高門檻。這種做法讓 gate failure 的偵測從「事後觀察回滾率」轉成「事前看 budget 消耗趨勢」。</p>
<p><a href="/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">Shopify 的 resiliency matrix</a> 是 FMEA 的制度化形式：service × failure mode 的矩陣，每格填入防護狀態（covered / gap / in-progress），gap 欄直接成為 game day 的演練題目。這種做法讓 FMEA 從一次性盤點變成持續維護的驗證清單。</p>
<h2 id="跟其他章節的整合">跟其他章節的整合</h2>
<p>Pre-mortem 與 FMEA 的產出需要路由到三個下游：</p>
<ul>
<li><a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review</a>：上線前能補的缺口進入 readiness checklist</li>
<li><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety boundary</a>：需要驗證的失敗假設轉成 chaos / load test 的實驗設計</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>：上線前補不了的缺口進入可排序的改善 backlog</li>
</ul>
<p>路由清晰度決定 pre-mortem 的實際價值。當缺口被識別但沒有路由到具體章節的具體動作，pre-mortem 就只是風險清單。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>高風險變更走一般 gate、無差異化控制</td>
          <td>gate failure — 回到 6.8 確認是否有風險分層</td>
      </tr>
      <tr>
          <td>壓測通過但 production 事故來自 retry/queue</td>
          <td>load failure — workload model 是否涵蓋失敗流量</td>
      </tr>
      <tr>
          <td>rollback 路徑上次驗證超過 90 天</td>
          <td>recovery failure — 回到 6.7 確認 rehearsal 節奏</td>
      </tr>
      <tr>
          <td>事故 MTTD 超過 SLO window</td>
          <td>detection failure — 回到 04 確認 alert 覆蓋與門檻</td>
      </tr>
      <tr>
          <td>pre-mortem 有做但缺口無 owner</td>
          <td>流程失效 — 結論沒路由到 6.19 或 6.21</td>
      </tr>
      <tr>
          <td>FMEA 評分定期更新但驗證沒跟著動</td>
          <td>評估與行動脫鉤 — 評分的責任是排序投資，改完要回寫驗證狀態</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test</a>：補失敗流量模型（retry / timeout / queue）</li>
<li><a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 DR / rollback rehearsal</a>：補回復路徑驗證</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：補高風險變更的差異化 gate</li>
<li><a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review</a>：pre-mortem 缺口轉成上線前檢查</li>
<li><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety boundary</a>：失敗假設轉成實驗設計</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>：未修缺口進入可排序 backlog</li>
<li><a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 可觀測性</a>：detection failure 回到訊號覆蓋</li>
<li><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 verification evidence handoff</a>：FMEA 結論作為 readiness 證據</li>
<li><a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 事故處理</a>：pre-mortem 假設在事故中被驗證時回寫</li>
</ul>
]]></content:encoded></item><item><title>6.6 SLO 與 Error Budget 政策</title><link>https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>SLO 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget&lt;/a> 是把可靠性從口號變成政策的工具。SLO 定義的是服務要對哪個使用者旅程負責，error budget 定義的是這個責任在一段時間內可以承受多少退化。當這兩個條件被寫清楚，可靠性就能從「感覺上應該穩」變成「超過哪個門檻就要暫停、降風險或修復」。&lt;/p>
&lt;p>這個節點先處理目標，再處理門檻。先問服務要守住什麼體驗，再問這個體驗要用哪些訊號衡量，最後才決定 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate&lt;/a> 到多少時要 freeze。這樣寫的好處是，讀者會先理解政策責任，再理解數字本身。&lt;/p>
&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>SLI 選型：user-journey-centric vs system-metric&lt;/li>
&lt;li>SLO 目標訂定：可達性、商業意義、頻率窗&lt;/li>
&lt;li>error budget：burn rate、policy、freeze 條件&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 觀測&lt;/a> 的訊號交接&lt;/li>
&lt;li>跟 &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;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1 事故分級&lt;/a> 的門檻對齊&lt;/li>
&lt;li>反模式：cargo-cult 99.99%、SLO 無人擁有、burn rate 無 alert&lt;/li>
&lt;/ul>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>SLO 的責任是讓團隊知道自己到底在保護什麼。當讀者看到一個 SLO 時，第一個問題是這個數字是否對應使用者行為、商業風險與回復成本；數字高低要放在這個脈絡中判讀。&lt;/p>
&lt;p>error budget 的責任是把風險傳導成決策。當 burn rate 開始上升時，團隊先確認 budget 還剩多少、目前的變更是否會放大風險、freeze 條件是否已經被觸發。這裡的重點是路由清楚，數字只是路由的輸入。&lt;/p>
&lt;h2 id="sli-選型">SLI 選型&lt;/h2>
&lt;p>SLI 選型的責任是把使用者旅程轉成可量測訊號。好的 SLI 先描述使用者能否完成重要任務，再選擇最能代表該任務的 log、metric、trace 或 client-side signal。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>SLI 類型&lt;/th>
 &lt;th>適用旅程&lt;/th>
 &lt;th>常見訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Availability&lt;/td>
 &lt;td>request、checkout、login 是否成功&lt;/td>
 &lt;td>success rate、valid response&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Latency&lt;/td>
 &lt;td>使用者等待是否在可接受範圍&lt;/td>
 &lt;td>latency histogram、p95 / p99&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Freshness&lt;/td>
 &lt;td>資料是否足夠新&lt;/td>
 &lt;td>replication lag、index delay&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Correctness&lt;/td>
 &lt;td>回應是否符合業務語意&lt;/td>
 &lt;td>reconciliation error、mismatch&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Durability&lt;/td>
 &lt;td>寫入是否可保留與回復&lt;/td>
 &lt;td>write success、replay validation&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Availability 適合描述同步 API 與 user-facing request。它需要清楚定義分母與分子，例如只計算有效請求、排除客戶端取消，或把 timeout、5xx 與 business failure 分開。&lt;/p>
&lt;p>Latency 適合描述體驗壓力。平均值容易掩蓋長尾，可靠性政策通常需要 percentile 或 histogram，並且要對應使用者旅程，再用單一 process 的 handler time 作為診斷輔助。&lt;/p>
&lt;p>Freshness 適合描述資料管線、search index、cache projection 與 read model。這類服務即使 API 回應成功，資料過舊仍會破壞使用者體驗。&lt;/p>
&lt;p>Correctness 適合描述金流、帳務、庫存、資料同步與 migration。這類可靠性目標需要資料校驗與 reconciliation，而不只看 request 成功率。&lt;/p>
&lt;p>Durability 適合描述 queue、event log、object storage 與資料寫入。它關心寫入後能否找回、重播、備份與回復，常和 RPO / RTO 一起定義。&lt;/p>
&lt;h2 id="slo-政策">SLO 政策&lt;/h2>
&lt;p>SLO 政策的責任是把可靠性目標轉成團隊行為。數字本身只是門檻，政策要說明目標的 owner、時間窗、例外條件、檢視頻率與觸發後動作。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>政策欄位&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>判讀用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>User journey&lt;/td>
 &lt;td>定義受保護體驗&lt;/td>
 &lt;td>避免 SLO 停在系統資源層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>SLI formula&lt;/td>
 &lt;td>定義分母、分子與資料來源&lt;/td>
 &lt;td>保護 SLO 可重算與可解釋&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Objective&lt;/td>
 &lt;td>定義目標值與時間窗&lt;/td>
 &lt;td>連接可靠性承諾與風險預算&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Owner&lt;/td>
 &lt;td>指定維護與決策責任&lt;/td>
 &lt;td>讓 policy 能被檢視與調整&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Burn alert&lt;/td>
 &lt;td>定義消耗速度與通知條件&lt;/td>
 &lt;td>讓風險在 budget 耗盡前被看見&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Freeze action&lt;/td>
 &lt;td>定義暫停發布或限制變更的條件&lt;/td>
 &lt;td>把可靠性風險接到 release gate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Review cadence&lt;/td>
 &lt;td>定義檢視頻率與調整機制&lt;/td>
 &lt;td>避免目標跟服務現況脫節&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>User journey 是 SLO 的錨點。checkout、login、message delivery、search freshness、invoice generation 都比 CPU 或 memory 更適合承載可靠性承諾，因為它們能直接對應使用者結果。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>SLO 與 <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 是把可靠性從口號變成政策的工具。SLO 定義的是服務要對哪個使用者旅程負責，error budget 定義的是這個責任在一段時間內可以承受多少退化。當這兩個條件被寫清楚，可靠性就能從「感覺上應該穩」變成「超過哪個門檻就要暫停、降風險或修復」。</p>
<p>這個節點先處理目標，再處理門檻。先問服務要守住什麼體驗，再問這個體驗要用哪些訊號衡量，最後才決定 <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> 到多少時要 freeze。這樣寫的好處是，讀者會先理解政策責任，再理解數字本身。</p>
<h2 id="大綱">大綱</h2>
<ul>
<li>SLI 選型：user-journey-centric vs system-metric</li>
<li>SLO 目標訂定：可達性、商業意義、頻率窗</li>
<li>error budget：burn rate、policy、freeze 條件</li>
<li>跟 <a href="/blog/backend/04-observability/" data-link-title="模組四：可觀測性平台" data-link-desc="整理 log、metric、trace、dashboard 與 alert 的後端操作實務">04 觀測</a> 的訊號交接</li>
<li>跟 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a> 的凍結觸發</li>
<li>跟 <a href="/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1 事故分級</a> 的門檻對齊</li>
<li>反模式：cargo-cult 99.99%、SLO 無人擁有、burn rate 無 alert</li>
</ul>
<h2 id="核心判讀">核心判讀</h2>
<p>SLO 的責任是讓團隊知道自己到底在保護什麼。當讀者看到一個 SLO 時，第一個問題是這個數字是否對應使用者行為、商業風險與回復成本；數字高低要放在這個脈絡中判讀。</p>
<p>error budget 的責任是把風險傳導成決策。當 burn rate 開始上升時，團隊先確認 budget 還剩多少、目前的變更是否會放大風險、freeze 條件是否已經被觸發。這裡的重點是路由清楚，數字只是路由的輸入。</p>
<h2 id="sli-選型">SLI 選型</h2>
<p>SLI 選型的責任是把使用者旅程轉成可量測訊號。好的 SLI 先描述使用者能否完成重要任務，再選擇最能代表該任務的 log、metric、trace 或 client-side signal。</p>
<table>
  <thead>
      <tr>
          <th>SLI 類型</th>
          <th>適用旅程</th>
          <th>常見訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Availability</td>
          <td>request、checkout、login 是否成功</td>
          <td>success rate、valid response</td>
      </tr>
      <tr>
          <td>Latency</td>
          <td>使用者等待是否在可接受範圍</td>
          <td>latency histogram、p95 / p99</td>
      </tr>
      <tr>
          <td>Freshness</td>
          <td>資料是否足夠新</td>
          <td>replication lag、index delay</td>
      </tr>
      <tr>
          <td>Correctness</td>
          <td>回應是否符合業務語意</td>
          <td>reconciliation error、mismatch</td>
      </tr>
      <tr>
          <td>Durability</td>
          <td>寫入是否可保留與回復</td>
          <td>write success、replay validation</td>
      </tr>
  </tbody>
</table>
<p>Availability 適合描述同步 API 與 user-facing request。它需要清楚定義分母與分子，例如只計算有效請求、排除客戶端取消，或把 timeout、5xx 與 business failure 分開。</p>
<p>Latency 適合描述體驗壓力。平均值容易掩蓋長尾，可靠性政策通常需要 percentile 或 histogram，並且要對應使用者旅程，再用單一 process 的 handler time 作為診斷輔助。</p>
<p>Freshness 適合描述資料管線、search index、cache projection 與 read model。這類服務即使 API 回應成功，資料過舊仍會破壞使用者體驗。</p>
<p>Correctness 適合描述金流、帳務、庫存、資料同步與 migration。這類可靠性目標需要資料校驗與 reconciliation，而不只看 request 成功率。</p>
<p>Durability 適合描述 queue、event log、object storage 與資料寫入。它關心寫入後能否找回、重播、備份與回復，常和 RPO / RTO 一起定義。</p>
<h2 id="slo-政策">SLO 政策</h2>
<p>SLO 政策的責任是把可靠性目標轉成團隊行為。數字本身只是門檻，政策要說明目標的 owner、時間窗、例外條件、檢視頻率與觸發後動作。</p>
<table>
  <thead>
      <tr>
          <th>政策欄位</th>
          <th>責任</th>
          <th>判讀用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>User journey</td>
          <td>定義受保護體驗</td>
          <td>避免 SLO 停在系統資源層</td>
      </tr>
      <tr>
          <td>SLI formula</td>
          <td>定義分母、分子與資料來源</td>
          <td>保護 SLO 可重算與可解釋</td>
      </tr>
      <tr>
          <td>Objective</td>
          <td>定義目標值與時間窗</td>
          <td>連接可靠性承諾與風險預算</td>
      </tr>
      <tr>
          <td>Owner</td>
          <td>指定維護與決策責任</td>
          <td>讓 policy 能被檢視與調整</td>
      </tr>
      <tr>
          <td>Burn alert</td>
          <td>定義消耗速度與通知條件</td>
          <td>讓風險在 budget 耗盡前被看見</td>
      </tr>
      <tr>
          <td>Freeze action</td>
          <td>定義暫停發布或限制變更的條件</td>
          <td>把可靠性風險接到 release gate</td>
      </tr>
      <tr>
          <td>Review cadence</td>
          <td>定義檢視頻率與調整機制</td>
          <td>避免目標跟服務現況脫節</td>
      </tr>
  </tbody>
</table>
<p>User journey 是 SLO 的錨點。checkout、login、message delivery、search freshness、invoice generation 都比 CPU 或 memory 更適合承載可靠性承諾，因為它們能直接對應使用者結果。</p>
<p>SLI formula 需要可重算。分母包含哪些 request、分子如何判定成功、資料來源來自 server-side 還是 client-side、sampling 有哪些限制，都需要寫進政策。</p>
<p>Objective 需要結合商業風險與回復成本。99.9% 與 99.99% 的差異不只是小數點，而是代表可接受 downtime、工程投資、成本與變更節奏的差異。</p>
<p>Freeze action 讓 error budget 進入工程決策。當 budget 消耗過快時，團隊需要知道哪些變更暫停、哪些修復可繼續、哪些例外需要 owner 核准。</p>
<h2 id="error-budget-與-burn-rate">Error Budget 與 Burn Rate</h2>
<p>Error budget 的責任是把可靠性退化轉成可管理的風險餘額。它讓團隊在「追求穩定」與「持續變更」之間有共同語言。</p>
<table>
  <thead>
      <tr>
          <th>狀態</th>
          <th>判讀訊號</th>
          <th>常見動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Budget healthy</td>
          <td>burn rate 低於門檻</td>
          <td>維持正常發布節奏</td>
      </tr>
      <tr>
          <td>Budget warning</td>
          <td>短窗 burn rate 上升</td>
          <td>檢查近期變更與高風險發布</td>
      </tr>
      <tr>
          <td>Budget critical</td>
          <td>多窗口 burn rate 同時超門檻</td>
          <td>暫停高風險變更，優先修復可靠性</td>
      </tr>
      <tr>
          <td>Budget exhausted</td>
          <td>error budget 用盡或接近用盡</td>
          <td>啟動 freeze、復盤與可靠性改善</td>
      </tr>
      <tr>
          <td>Policy mismatch</td>
          <td>SLO 長期過鬆或過緊</td>
          <td>調整 SLI、objective 或時間窗</td>
      </tr>
  </tbody>
</table>
<p>Burn rate 要看短窗與長窗。短窗能捕捉快速事故，長窗能避免一次性尖峰造成過度反應；兩者一起使用，才適合觸發 page、ticket 或 release freeze。</p>
<p>Budget warning 適合做風險整理。團隊可以檢查近期 deploy、feature flag、migration、capacity、dependency 與 incident review action item，判斷是否需要降低變更速度。</p>
<p>Budget critical 適合觸發 release gate。此時可靠性風險已經從觀測層進入決策層，團隊需要把發布、rollback、capacity 與 incident readiness 放在同一張表中判讀。</p>
<p>Budget exhausted 適合觸發可靠性改善。改善內容可能是修 bug、補 capacity、降低 alert noise、補 runbook、重設 SLO 或清理 reliability debt。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>SLO 數字無 owner、過半年沒檢視</li>
<li>burn rate 無 alert、只有 monthly review</li>
<li>error budget 耗盡但 deployment 節奏不變</li>
<li>SLI 用 system metric（CPU / memory）、不對應 user journey</li>
<li>目標數字是抄來的（99.9 / 99.99）、無商業 anchor</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<p>Google 提供的是制度原點，因為它把 SLO、<a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">post-incident review</a> 與 toil budget 串成可管理的可靠性文化。Honeycomb 提供的是訊號層的延伸，因為 high-cardinality 與 burn rate alert 讓 SLO 可以在真實流量下被看見。Stripe 則把 SLO 風格的決策壓到交易語義上，讓 idempotency 與 migration 不會因為重試而失真。</p>
<p>當讀者把這三個案例放在一起，就會看見 SLO 不只是「填一個百分比」，而是把不同層級的風險接到同一條路由：制度、訊號與交易正確性。這也是本節章節要建立的核心能力。</p>
<h2 id="error-budget-三對齊跟-release-gating">Error Budget 三對齊跟 Release Gating</h2>
<p>Error budget 三對齊是把「SLI 範圍」「SLO 目標」「Budget gate 觸發點」分別跟「使用者價值 / 可接受承諾 / 交付節奏」綁定的設計練習。任一條未對齊、policy 就會跟團隊行為脫鉤 — SLI 不對齊使用者價值、policy 就保護錯的東西；SLO 不對齊承諾、團隊就追錯目標；Gate 不對齊交付節奏、政策就無人遵循。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">G1 Google Error Budget Policy</a>：揭露 SLO policy 設計的三個對齊 — 使用者行為對齊（哪些 journey 直接反映服務價值 → SLI 範圍）、可靠性承諾對齊（什麼水準算服務仍可接受 → SLO 目標）、交付節奏對齊（可靠性消耗到哪裡要改變發布策略 → Budget gate）。</p>
<p>三對齊完成後、release gate 可從「主觀風險判斷」轉成「政策驅動」：</p>
<ul>
<li>budget 健康：正常發版</li>
<li>budget 快速消耗：啟用變更限速、提高驗證門檻</li>
<li>budget 透支：凍結非必要變更、先修復與回補訊號</li>
</ul>
<p>把 budget gate 跟 <a href="/blog/backend/06-reliability/release-gate/#%e8%ae%8a%e6%9b%b4%e5%88%86%e5%b1%a4%e8%b7%9f-gate-%e6%94%bf%e7%ad%96" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release-gate 變更分層段</a> 綁定、讓「budget 三階段」對應「release gate 三層放行決策」。</p>
<p>Error budget 是「可靠性 vs 交付節奏」的平衡工具、不是被追求的固定分數。當 budget 被 KPI 化、SLI 範圍會被縮小、告警會被延後、例外條件會被擴張 — 三者都降低 budget 的判讀可信度。</p>
<h2 id="burn-rate-雙窗監控">Burn Rate 雙窗監控</h2>
<p>Burn rate 雙窗監控是把「budget 消耗速率」拆成短窗（急性事故）跟長窗（慢性退化）兩個 channel、各自觸發不同回應的設計。比固定閾值告警更接近使用者體感、且能區分「需立即頁」跟「需排修復節奏」。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">HC1 Honeycomb Burn Rate 驅動可靠性操作</a>：揭露 fast burn / slow burn 雙窗監控的價值 — 固定閾值告警在高變化流量下容易失真、burn rate 提供比固定閾值更接近使用者體感的判讀方式。</p>
<p>雙窗監控的設計：</p>
<ul>
<li><strong>Fast burn</strong>（短窗、高消耗率）：捕捉急性事故、觸發 page 立即響應</li>
<li><strong>Slow burn</strong>（長窗、低消耗率持續累積）：捕捉慢性退化、觸發 ticket 排入修復節奏</li>
</ul>
<p>兩窗一起用、避免單一閾值在不同流量型態下失真。Honeycomb 自家平台展示 burn rate 訊號可以跟 trace outlier path 對接 — 看到 burn rate 上升、能直接跳到具體退化 trace（這是 Honeycomb 的產品特色、tracing-first 對 burn rate 的補強）。vendor-neutral 的同類概念見 <a href="/blog/backend/04-observability/tracing-context/" data-link-title="4.3 tracing 與 context link" data-link-desc="整理 trace id、span 與跨服務 context propagation">4.3 tracing-context</a> 跟 <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.6 sli-slo-signal</a> 的訊號設計。</p>
<h2 id="控制面">控制面</h2>
<p>SLO 與 error budget 的控制面是把可靠性訊號接到發布、事故與改善流程。SLO 只有在能改變團隊行為時，才會成為政策。</p>
<ol>
<li>SLI 設計回到 <a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">4.6 SLI 量測與 SLO 訊號設計</a>。</li>
<li>資料品質限制回到 <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a>。</li>
<li>Budget warning 進入 release risk review。</li>
<li>Budget critical 進入 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate</a>。</li>
<li>事故觸發與復盤回寫進入 <a href="/blog/backend/08-incident-response/incident-severity-trigger/" data-link-title="8.1 事故分級與啟動條件" data-link-desc="建立統一分級標準與事故啟動門檻">8.1 事故分級</a> 與 <a href="/blog/backend/08-incident-response/post-incident-review/" data-link-title="8.5 復盤與改進追蹤" data-link-desc="把 RCA 與 action items 轉成可驗證閉環">8.5 復盤</a>。</li>
</ol>
<p>SLO policy 需要定期校準。服務規模、使用者旅程、依賴型態與商業風險變化後，原本的 SLI、objective 與 freeze 條件也要重新檢視。</p>
<p>SLO policy 也需要例外流程。重大資安修補、合規變更、資料修復或客戶承諾可能需要在 budget 緊張時繼續推進；例外應記錄 owner、理由、風險與回退條件。</p>
<h2 id="產業情境金融科技">產業情境：金融科技</h2>
<p>金融服務的 error budget 治理需要把合規週期納入凍結條件。交易關鍵路徑（payment / settlement / reconciliation）的 SLO 破壞可能直接觸發監管通報義務，budget 消耗到門檻時的升級路徑必須包含合規人員。</p>
<p>交易路徑的 SLI 選型需要涵蓋 correctness（reconciliation error rate），availability 和 latency 通過但對帳失敗仍然是 SLO 破壞。correctness SLI 的量測來源通常是日終或即時的 reconciliation pipeline，跟 availability SLI 的即時 request-level 量測有不同的時間粒度。</p>
<p>Budget 凍結的觸發條件除了 burn rate，還要對齊監管報告週期。若 budget 在季末報告前已消耗過多，凍結應提前啟動，因為報告期間內的可靠性退化會被放大審視。這個提前量取決於報告週期長度與修復節奏 — 月報制的提前量比季報制短。</p>
<p>Error budget 政策的升級路徑需要跟 compliance team 對齊。budget warning 階段通知工程 owner；budget critical 階段同時通知合規人員；budget exhausted 階段啟動合規審查流程。這個分層讓合規介入的時機跟工程介入同步，避免事後才發現可靠性退化已觸發通報義務。</p>
<p>金融場景的 budget 恢復比一般 SaaS 慢。恢復期間需要額外的 reconciliation 驗證（確認退化期間無交易錯漏）才能宣告 budget 回補。若 reconciliation 發現差異，budget 恢復會被延後直到差異被解決。這個約束讓金融服務的 freeze 持續時間通常比一般服務長。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>SLO 反模式通常來自把目標數字當成可靠性制度本身。數字需要對應旅程、資料、owner 與決策，才有工程意義。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Cargo-cult 99.99%</td>
          <td>目標抄自外部範例</td>
          <td>從 user journey 與商業風險回推</td>
      </tr>
      <tr>
          <td>System metric SLO</td>
          <td>SLO 看 CPU / memory</td>
          <td>改用成功率、延遲、freshness</td>
      </tr>
      <tr>
          <td>SLO 無 owner</td>
          <td>目標存在但無人調整</td>
          <td>指定 policy owner 與 review</td>
      </tr>
      <tr>
          <td>Burn rate 無 alert</td>
          <td>budget 耗盡後才開會</td>
          <td>建立短窗 / 長窗 burn alert</td>
      </tr>
      <tr>
          <td>Freeze 無路由</td>
          <td>可靠性風險不影響發布</td>
          <td>接到 release gate 與例外流程</td>
      </tr>
  </tbody>
</table>
<p>Cargo-cult 99.99% 的問題在於缺少服務脈絡。高可用目標會增加架構、成本、演練與值班負擔；低可用目標則會增加使用者與商業風險。合理目標要從服務承諾回推。</p>
<p>System metric SLO 會讓可靠性偏向基礎設施視角。CPU 健康不代表 checkout 成功，pod running 不代表資料新鮮；系統指標適合支援 diagnosis，user journey 指標適合承載 SLO。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04 訊號治理：SLI / burn rate metric 設計</li>
<li>06.8 release gate：error budget 耗盡觸發 freeze</li>
<li>06.9 capacity / cost：容量不足傳導為 SLO 風險</li>
<li>06.14 dependency budget：依賴可靠性納入 SLO 算式</li>
<li>08 事故閉環：burn rate alert 啟動條件</li>
<li>08.13 repeated / toil：error budget 撥用 toil reduction</li>
<li>06.18 reliability metrics：SLO 跟 DORA / SPACE 的指標分層</li>
</ul>
]]></content:encoded></item><item><title>6.7 DR 演練與 Rollback Rehearsal</title><link>https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/dr-rollback-rehearsal/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>DR 演練與 rollback rehearsal 是把回復能力從「有計畫」變成「經過驗證」的工具。DR 關心的是系統在災難後能不能回來，rollback rehearsal 關心的是變更失敗時能不能退回安全狀態。兩者的責任是把回復路徑變成可驗證流程。&lt;/p>
&lt;p>這個節點先處理路徑，再處理速度。先確認資料能不能回來、服務能不能切回來、回復後會不會再掉回去，然後才談 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO&lt;/a>。這樣讀，會比直接背指標更接近真實系統的恢復成本。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>DR 的責任是證明回復路徑存在，而且可實際走通。只要 backup 還沒被 restore 驗證過，它就只是備份，不是復原能力。只要 failover config 沒跟 production 對齊，它就只是文件，不是操作路由。&lt;/p>
&lt;p>rollback rehearsal 的責任是把失敗變更的退路先跑過。當 deployment 出現問題時，團隊需要知道自己是能回退、必須 roll forward，還是必須先止血再處理資料。這個判斷來自平常 rehearsal 的累積，臨場才不會陷入猜測。&lt;/p>
&lt;h2 id="rollback-vs-roll-forward-的判斷條件">Rollback vs Roll-forward 的判斷條件&lt;/h2>
&lt;p>變更失敗時的第一個決策是退回還是往前修。這個判斷取決於變更是否可逆，以及新資料是否已經依賴新版結構。&lt;/p>
&lt;p>rollback 的前提是變更可逆：schema 仍向下相容、feature flag 可關閉、routing 可切回前一版。當這些條件成立時，rollback 通常比 roll-forward 更快收斂，因為退回的行為已經被驗證過（它就是前一版的 production 狀態）。&lt;/p>
&lt;p>roll-forward 的前提是修復比退版快且安全。當新版已經寫入不可回退的資料（新欄位被使用、新格式被下游消費、交易已在新路徑完成），退版會造成資料遺失或不一致，此時 roll-forward 是被迫的選擇，不是偏好。&lt;/p>
&lt;p>兩者之間存在灰色地帶：schema migration 已執行但流量尚未切換、feature flag 已開啟但影響範圍有限。這類情境需要事前在 rehearsal 中定義判斷條件，而不是事中討論。第三種常見路徑是先 rollback 止血（降低 customer impact），確認穩定後再推出修復版 roll-forward。這個 hybrid 策略的前提是 rollback 安全且修復方案已知。&lt;/p>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe 的 expand/contract migration 模型&lt;/a> 說明交易系統的 rollback 需要同時處理 schema 相容與冪等重播。當 idempotency key 與業務操作邊界一致時，rollback 後的重試才能產生正確結果。這個案例揭露的判讀條件是：rollback 安全性不只看部署層，還要看資料語義層。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>條件&lt;/th>
 &lt;th>傾向 rollback&lt;/th>
 &lt;th>傾向 roll-forward&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Schema 相容性&lt;/td>
 &lt;td>舊版可讀新版資料、無破壞性變更&lt;/td>
 &lt;td>新欄位已被寫入、舊版無法解析&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料狀態&lt;/td>
 &lt;td>新版尚未產生不可回退的資料&lt;/td>
 &lt;td>交易、訂單或事件已在新路徑完成&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>修復時間&lt;/td>
 &lt;td>問題根因不明、修復時間不可預測&lt;/td>
 &lt;td>根因明確、修復可在分鐘內完成&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Feature flag&lt;/td>
 &lt;td>flag 可關閉且影響範圍已知&lt;/td>
 &lt;td>flag 關閉會觸發另一組問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>下游依賴&lt;/td>
 &lt;td>下游未消費新版輸出&lt;/td>
 &lt;td>下游已開始處理新格式資料&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="restore-驗證">Restore 驗證&lt;/h2>
&lt;p>備份的價值在還原時才能被證明。restore drill 的責任是證明備份能在需要時變成可用的服務狀態。&lt;/p>
&lt;p>restore 驗證分三個層次，每一層回答不同的問題。&lt;/p>
&lt;p>&lt;strong>資料完整性&lt;/strong>：還原後的資料是否完整。驗證手段包含 row count 比對、checksum 校驗、reconciliation query。這一層的失敗模式通常是 backup 時段選擇不當（跨越 batch job 執行期）或 incremental backup 鏈條斷裂。&lt;/p>
&lt;p>&lt;strong>服務可用性&lt;/strong>：還原後的系統是否能正常回應。資料完整不代表服務可用 — config、secret、schema version、connection pool 設定都可能在 restore 後失效。這一層需要在 restore 完成後跑 smoke test 與 health check，確認服務能處理請求。&lt;/p>
&lt;p>&lt;strong>恢復時間量測&lt;/strong>：實際 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO&lt;/a> 是否符合承諾。如果承諾 4 小時 RTO 但 restore 本身需要 6 小時，這個承諾就是空的。量測要包含從決策啟動到服務恢復的完整時間，不只是資料還原時間。Roblox 2021 的 73 小時 outage 說明 recovery 不是切回流量就結束 — 資料一致性重建、快取預熱與依賴服務的啟動順序都會拉長實際恢復時間。&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>tabletop&lt;/td>
 &lt;td>檢查決策路由與角色分工&lt;/td>
 &lt;td>角色清單、決策順序、通訊模板&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>partial failover&lt;/td>
 &lt;td>驗證局部區域或子系統能否切換&lt;/td>
 &lt;td>切換結果、回復時間、手動步驟&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>full region failover&lt;/td>
 &lt;td>驗證整個區域是否能從災難中回來&lt;/td>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO&lt;/a>、資料一致性檢查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>data restore drill&lt;/td>
 &lt;td>驗證備份是否能真的還原資料&lt;/td>
 &lt;td>restore log、校驗結果、缺口清單&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這些演練的共同點是：演練本身要留下證據。沒有輸出，就沒有辦法判斷回復能力到底有沒有被建立。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>DR 演練與 rollback rehearsal 是把回復能力從「有計畫」變成「經過驗證」的工具。DR 關心的是系統在災難後能不能回來，rollback rehearsal 關心的是變更失敗時能不能退回安全狀態。兩者的責任是把回復路徑變成可驗證流程。</p>
<p>這個節點先處理路徑，再處理速度。先確認資料能不能回來、服務能不能切回來、回復後會不會再掉回去，然後才談 <a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO</a> / <a href="/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO</a>。這樣讀，會比直接背指標更接近真實系統的恢復成本。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>DR 的責任是證明回復路徑存在，而且可實際走通。只要 backup 還沒被 restore 驗證過，它就只是備份，不是復原能力。只要 failover config 沒跟 production 對齊，它就只是文件，不是操作路由。</p>
<p>rollback rehearsal 的責任是把失敗變更的退路先跑過。當 deployment 出現問題時，團隊需要知道自己是能回退、必須 roll forward，還是必須先止血再處理資料。這個判斷來自平常 rehearsal 的累積，臨場才不會陷入猜測。</p>
<h2 id="rollback-vs-roll-forward-的判斷條件">Rollback vs Roll-forward 的判斷條件</h2>
<p>變更失敗時的第一個決策是退回還是往前修。這個判斷取決於變更是否可逆，以及新資料是否已經依賴新版結構。</p>
<p>rollback 的前提是變更可逆：schema 仍向下相容、feature flag 可關閉、routing 可切回前一版。當這些條件成立時，rollback 通常比 roll-forward 更快收斂，因為退回的行為已經被驗證過（它就是前一版的 production 狀態）。</p>
<p>roll-forward 的前提是修復比退版快且安全。當新版已經寫入不可回退的資料（新欄位被使用、新格式被下游消費、交易已在新路徑完成），退版會造成資料遺失或不一致，此時 roll-forward 是被迫的選擇，不是偏好。</p>
<p>兩者之間存在灰色地帶：schema migration 已執行但流量尚未切換、feature flag 已開啟但影響範圍有限。這類情境需要事前在 rehearsal 中定義判斷條件，而不是事中討論。第三種常見路徑是先 rollback 止血（降低 customer impact），確認穩定後再推出修復版 roll-forward。這個 hybrid 策略的前提是 rollback 安全且修復方案已知。</p>
<p><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe 的 expand/contract migration 模型</a> 說明交易系統的 rollback 需要同時處理 schema 相容與冪等重播。當 idempotency key 與業務操作邊界一致時，rollback 後的重試才能產生正確結果。這個案例揭露的判讀條件是：rollback 安全性不只看部署層，還要看資料語義層。</p>
<table>
  <thead>
      <tr>
          <th>條件</th>
          <th>傾向 rollback</th>
          <th>傾向 roll-forward</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema 相容性</td>
          <td>舊版可讀新版資料、無破壞性變更</td>
          <td>新欄位已被寫入、舊版無法解析</td>
      </tr>
      <tr>
          <td>資料狀態</td>
          <td>新版尚未產生不可回退的資料</td>
          <td>交易、訂單或事件已在新路徑完成</td>
      </tr>
      <tr>
          <td>修復時間</td>
          <td>問題根因不明、修復時間不可預測</td>
          <td>根因明確、修復可在分鐘內完成</td>
      </tr>
      <tr>
          <td>Feature flag</td>
          <td>flag 可關閉且影響範圍已知</td>
          <td>flag 關閉會觸發另一組問題</td>
      </tr>
      <tr>
          <td>下游依賴</td>
          <td>下游未消費新版輸出</td>
          <td>下游已開始處理新格式資料</td>
      </tr>
  </tbody>
</table>
<h2 id="restore-驗證">Restore 驗證</h2>
<p>備份的價值在還原時才能被證明。restore drill 的責任是證明備份能在需要時變成可用的服務狀態。</p>
<p>restore 驗證分三個層次，每一層回答不同的問題。</p>
<p><strong>資料完整性</strong>：還原後的資料是否完整。驗證手段包含 row count 比對、checksum 校驗、reconciliation query。這一層的失敗模式通常是 backup 時段選擇不當（跨越 batch job 執行期）或 incremental backup 鏈條斷裂。</p>
<p><strong>服務可用性</strong>：還原後的系統是否能正常回應。資料完整不代表服務可用 — config、secret、schema version、connection pool 設定都可能在 restore 後失效。這一層需要在 restore 完成後跑 smoke test 與 health check，確認服務能處理請求。</p>
<p><strong>恢復時間量測</strong>：實際 <a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO</a> 是否符合承諾。如果承諾 4 小時 RTO 但 restore 本身需要 6 小時，這個承諾就是空的。量測要包含從決策啟動到服務恢復的完整時間，不只是資料還原時間。Roblox 2021 的 73 小時 outage 說明 recovery 不是切回流量就結束 — 資料一致性重建、快取預熱與依賴服務的啟動順序都會拉長實際恢復時間。</p>
<h2 id="演練類型">演練類型</h2>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>目的</th>
          <th>典型輸出</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>tabletop</td>
          <td>檢查決策路由與角色分工</td>
          <td>角色清單、決策順序、通訊模板</td>
      </tr>
      <tr>
          <td>partial failover</td>
          <td>驗證局部區域或子系統能否切換</td>
          <td>切換結果、回復時間、手動步驟</td>
      </tr>
      <tr>
          <td>full region failover</td>
          <td>驗證整個區域是否能從災難中回來</td>
          <td><a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO</a>、<a href="/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO</a>、資料一致性檢查</td>
      </tr>
      <tr>
          <td>data restore drill</td>
          <td>驗證備份是否能真的還原資料</td>
          <td>restore log、校驗結果、缺口清單</td>
      </tr>
  </tbody>
</table>
<p>這些演練的共同點是：演練本身要留下證據。沒有輸出，就沒有辦法判斷回復能力到底有沒有被建立。</p>
<p><strong>Tabletop</strong> 的重點是決策路由清晰度。參與者在紙上走一遍事故情境，回答「誰負責決定切換」「什麼條件觸發升級」「通訊延遲多長可接受」。這個類型成本最低、頻率應最高，適合用來發現流程漏洞與角色模糊。</p>
<p><strong>Partial failover</strong> 的重點是切換腳本與監控覆蓋。選擇一個子系統或單一 availability zone 做真實切換，驗證自動化腳本是否可執行、監控是否能在切換過程中保持可見性。這個階段常暴露的問題是：腳本假設的前提條件在 production 不成立，或監控在切換過程中產生大量 false positive。</p>
<p><strong>Full region failover</strong> 的重點是資料一致性與恢復順序。<a href="/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">Meta 的 2021 年事故</a>顯示，跨區 failover 的最大風險在恢復順序 — 控制面與資料面共用路徑時，先恢復哪條路徑會直接決定整體恢復時間。當恢復動作本身依賴尚未恢復的控制面服務，恢復會陷入循環等待。</p>
<h2 id="演練節奏與升級">演練節奏與升級</h2>
<p>演練是按風險層級安排的循環流程。</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>建議節奏</th>
          <th>升級條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>tabletop</td>
          <td>季度</td>
          <td>新增關鍵依賴、組織結構變更、重大事故後</td>
      </tr>
      <tr>
          <td>partial failover</td>
          <td>半年</td>
          <td>tabletop 暴露切換路徑疑慮</td>
      </tr>
      <tr>
          <td>full region failover</td>
          <td>年度</td>
          <td>partial 驗證通過、業務需求（合規、審計）</td>
      </tr>
      <tr>
          <td>data restore drill</td>
          <td>季度</td>
          <td>備份策略變更、資料量跳升、新增資料源</td>
      </tr>
  </tbody>
</table>
<p>每輪演練產出的缺口應回寫到 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>，成為下一輪演練的驗證目標。<a href="/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">Google 的 postmortem action item closure 治理</a>說明把事故教訓轉成有 owner 與完成條件的改進項，這個機制同樣適用於演練缺口：P0 缺口應在下個 release 週期前修復，P1 缺口應排入固定追蹤。</p>
<p><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify 的 BFCM 準備流程</a>把年度高峰前的 game day 當作 DR 演練的自然觸發點。容量模型、隔離邊界與 failover 路徑在 game day 中一起驗證，每輪暴露的缺口回寫成下一輪的準備 checklist。這種做法讓演練節奏跟業務節奏對齊，不是額外負擔。</p>
<h2 id="dr-與-chaos-的邊界">DR 與 chaos 的邊界</h2>
<p>DR 演練與 <a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">chaos testing</a> 都涉及故障情境，但驗證目標不同。</p>
<p>Chaos 驗證的是系統在故障持續期間能否維持服務。它的成功條件是 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a> 不被破壞，停止條件是 steady state breach。chaos 實驗結束後，系統應該仍在運作。</p>
<p>DR 驗證的是系統在災難發生後能否回來。它的成功條件是恢復路徑可執行且符合 <a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO</a> / <a href="/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO</a> 承諾，停止條件是恢復時間超過 RTO 或資料遺失超過 RPO。DR 演練結束時，系統經歷了一次完整的失效與恢復循環。</p>
<p>兩者的交集是 failover drill：chaos 關心切換期間的服務退化程度，DR 關心切換完成後的恢復品質。在實務上，成熟團隊會把 chaos experiment 的結果作為 DR 演練的輸入 — chaos 發現的弱點變成 DR 演練的測試案例。<a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon 的 cell boundary 與 static stability 設計</a>讓恢復可分批執行，同時服務 chaos 驗證（局部故障不擴散）與 DR 驗證（分批恢復可預測）。</p>
<h2 id="產業情境醫療系統">產業情境：醫療系統</h2>
<p>醫療系統的 DR 演練受合規（HIPAA / GDPR health data）和臨床連續性的雙重約束。演練設計需要同時滿足技術恢復目標與臨床安全要求。</p>
<p>演練排程需要跟臨床作業週期對齊。手術高峰、急診高峰與夜班交接時段都應避免做 failover 演練，因為演練造成的短暫服務中斷可能直接影響臨床決策。可執行窗口通常是週末凌晨或排定的維護時段。</p>
<p>恢復順序由臨床風險決定。EMR（電子病歷）系統優先於醫囑系統、PACS（影像系統）與行政系統。這個順序跟技術依賴不完全重疊 — 技術上 PACS 可能先恢復更快，但臨床上 EMR 的中斷風險更高。恢復順序的設計需要臨床代表參與，技術團隊單獨決定會漏掉臨床優先級。</p>
<p>Restore 驗證需要額外的 audit trail 完整性檢查。HIPAA 要求能追蹤誰在什麼時間存取了哪些病患資料，恢復後的資料若 audit trail 斷裂，即使資料本身完整也不符合合規要求。restore drill 的校驗清單需要把 audit trail 連續性納入必檢項。</p>
<p>醫療紀錄的 <a href="/blog/backend/knowledge-cards/rpo/" data-link-title="RPO" data-link-desc="說明恢復點目標如何定義可接受資料損失範圍">RPO</a> 通常比一般 SaaS 更嚴格，接近零資料遺失。遺失的醫療紀錄可能直接影響用藥決策或手術判斷，RPO 設定需要對齊臨床風險而非技術方便性。</p>
<p>演練證據本身也需要合規留存。DR 演練紀錄、恢復時間量測、缺口清單與改善追蹤都是合規審計的輸入。沒有留存的演練在審計視角等同未演練。</p>
<h2 id="產業情境iot-與製造系統">產業情境：IoT 與製造系統</h2>
<p>IoT 裝置的 rollback 成本遠高於雲端服務。雲端服務的 rollback 是 deploy 前一版 container image，秒級生效；IoT 裝置的 rollback 需要 OTA（Over-the-Air）推送，受限於裝置連線狀態、頻寬、電量與儲存空間。部分裝置可能在 rollback 過程中斷線，進入新舊版本混合的不一致狀態。</p>
<p>DR 演練需要包含「裝置不在線」場景。工業場景的裝置可能在偏遠地點、離線數天到數週。DR 計畫需要回答「離線裝置重新上線後，如何安全地同步到正確版本」，以及混合版本期間的相容性處理。</p>
<p>安全關鍵系統（製造產線控制、醫療設備、車載系統）的回退約束比一般軟體更嚴格。firmware 缺陷可能造成物理傷害，rollback 後需要跑功能安全測試（IEC 61508 等級的驗證），確認回退版本在目標硬體上的行為符合安全規格。</p>
<p>A/B firmware partition 是 IoT 的 DR 基礎設計。裝置保留兩個 firmware slot（active / inactive），更新寫入 inactive slot，驗證通過後切換到新 slot。失敗時切回原 active slot，整個過程在裝置本地完成，不需要額外 OTA 推送。這個設計讓裝置的 rollback 路徑跟 <a href="/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">Amazon A2 的 static stability</a> 概念對齊 — 即使控制面（OTA server）不可用，裝置仍能用本地 slot 切換完成回退。</p>
<h2 id="案例對照">案例對照</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>DR 視角的教訓</th>
          <th>回讀章節</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Meta M1</td>
          <td>控制面與資料面共用路徑時，恢復順序決定整體恢復時間</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a>、<a href="/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14</a></td>
      </tr>
      <tr>
          <td>Amazon A1</td>
          <td>cell boundary 讓恢復可分批，不需要全域同步恢復</td>
          <td><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20</a></td>
      </tr>
      <tr>
          <td>Stripe S1</td>
          <td>交易系統 rollback 需要同時驗證 schema 相容與冪等重播</td>
          <td><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11</a>、<a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12</a></td>
      </tr>
      <tr>
          <td>Shopify H1</td>
          <td>年度高峰前的 game day 是 DR 演練的自然觸發點</td>
          <td><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9</a>、<a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>Google G2</td>
          <td>postmortem action item 轉成下一輪 DR 演練題目</td>
          <td><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21</a>、<a href="/blog/backend/08-incident-response/post-incident-review/" data-link-title="8.5 復盤與改進追蹤" data-link-desc="把 RCA 與 action items 轉成可驗證閉環">8.5</a></td>
      </tr>
      <tr>
          <td>Netflix N1</td>
          <td><a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a> 定義同時作為 DR recovery complete 的判準</td>
          <td><a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>Amazon A2</td>
          <td>static stability 讓資料面在控制面失效時仍能服務，恢復路徑不依賴已故障的控制面</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a>、<a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22</a></td>
      </tr>
      <tr>
          <td>Meta M2</td>
          <td>回復工具依賴已故障的系統（BGP / DNS / 遠端存取），恢復陷入循環等待</td>
          <td><a href="/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14</a>、<a href="/blog/backend/08-incident-response/multi-incident-coordination/" data-link-title="8.14 Multi-incident Coordination" data-link-desc="把同時多事故的優先序、資源分配與 incident command system pool 協調變成可執行流程">8.14</a></td>
      </tr>
  </tbody>
</table>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DR plan 寫在 wiki、過去 12 個月未演練</td>
          <td>回復能力不可信 — plan 與 production 可能已漂移</td>
          <td>排入下季 tabletop + partial failover 演練</td>
      </tr>
      <tr>
          <td>backup 有排程、restore 從未跑過</td>
          <td>備份完整性未知 — restore 是唯一能證明備份可用的手段</td>
          <td>安排 restore drill、量測實際 RTO</td>
      </tr>
      <tr>
          <td>failover 配置與 production 漂移</td>
          <td>failover 路徑不可靠 — 任何 infra 變更都可能讓 failover 腳本失效</td>
          <td>建 failover config diff 定期掃描</td>
      </tr>
      <tr>
          <td>RTO / RPO 是估值、不是量值</td>
          <td>恢復承諾不可信 — 未被演練量測過的數字只是猜測</td>
          <td>用 restore drill 量測實際值、更新承諾</td>
      </tr>
      <tr>
          <td>rollback 需要手動 SQL 或脫離部署流程</td>
          <td>rollback 路徑高風險 — 手動操作在壓力下容易出錯</td>
          <td>把 rollback 步驟自動化進 deploy pipeline</td>
      </tr>
      <tr>
          <td>演練缺口未回寫到 backlog</td>
          <td>演練價值流失 — 發現問題但不追蹤等同未發現</td>
          <td>每次演練產出寫入 6.21 reliability debt + owner</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台</a>：blue-green / region failover 實作</li>
<li><a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">6.4 chaos testing</a>：chaos 暴露的弱點變成 DR 演練題目</li>
<li><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety</a>：migration rollback 演練</li>
<li><a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12 idempotency / replay</a>：replay 是 DR 回復的前提</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>：演練缺口回寫</li>
<li><a href="/blog/backend/06-reliability/provider-dependency-release-gate/" data-link-title="6.25 Provider Dependency Release Gate 實作示範" data-link-desc="以 payment provider 變更示範 release gate 如何結合 evidence、stop condition 與 rollback window。">6.25 provider dependency release gate</a>：provider 變更的 rollback 實作示範</li>
<li><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 止血回復</a>：演練結果作為事中決策素材</li>
<li><a href="/blog/backend/08-incident-response/drills-and-oncall-readiness/" data-link-title="8.6 演練與值班能力建設" data-link-desc="用演練與值班訓練提升事故反應品質">8.6 演練與值班</a>：DR 結果回饋到團隊技能建設</li>
<li><a href="/blog/backend/08-incident-response/vendor-dependency-incident/" data-link-title="8.15 Vendor / 第三方依賴事故處理" data-link-desc="依賴方掛掉、自己無 control 時的決策模型">8.15 vendor 事故</a>：多 vendor / 多區 failover 路徑</li>
</ul>
]]></content:encoded></item><item><title>6.8 Release Gate 與變更節奏</title><link>https://tarrragon.github.io/blog/backend/06-reliability/release-gate/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/release-gate/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">Release gate&lt;/a> 是把放行決策從「看起來可以」變成「條件已經達成」的控制面。它的責任是把哪些變更可以進、哪些變更要等、哪些變更必須先補證據說清楚，擋住所有變更從來不是目標。當 gate 被寫成政策，團隊就能用同一套條件判斷 CI、SLO、migration、相容性與高風險時段。&lt;/p>
&lt;p>這個節點先處理節奏，再處理工具。先問變更是否應該放行，再問這次放行需要哪些訊號與檢查。當 gate 被看成節奏控制，讀者就會明白為什麼 freeze 是可靠性政策的一部分，視為例外會弱化整套節奏控制。&lt;/p>
&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>release gate 的核心責任：把放行決策從個人判斷變成可驗證條件&lt;/li>
&lt;li>gate 類別：CI 通過、SLO 健康、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget&lt;/a> 餘額、migration 可逆、相容性檢查&lt;/li>
&lt;li>變更節奏：deploy frequency、batch size、change failure rate（DORA 四指標）&lt;/li>
&lt;li>freeze 條件：error budget 耗盡、事故進行中、高風險時段&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6 SLO&lt;/a> 的耦合：error budget 是 gate 的一個條件&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署&lt;/a> 的交接：gate 通過後 rollout 策略接手&lt;/li>
&lt;li>反模式：gate 流於形式、freeze 無 owner、緊急修復繞過 gate 變常態&lt;/li>
&lt;/ul>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>gate 的責任是把放行條件具體化。CI green 只代表測試通過，不代表服務可以安全進 production；SLO 健康只代表目前風險可接受，不代表任何變更都能繼續推；migration 可逆只代表退路存在，不代表已經證明回退完全無害。這些條件要一起看，才知道 gate 有沒有真的在做事。&lt;/p>
&lt;p>資料庫 migration 的 gate 要把 evidence 放回 rollout 階段判讀。Expand、backfill、cutover 與 contract 需要不同 checks：compatibility result、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/validation-query/" data-link-title="Validation Query" data-link-desc="說明遷移、回填與修復期間如何用查詢證明資料語意是否一致">validation query&lt;/a>、mismatch rate、replication lag、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rollback-window/" data-link-title="Rollback Window" data-link-desc="說明變更進入 production 後還能用哪種方式回退或改路線的時間與條件">rollback window&lt;/a> 與 owner。完整欄位形狀可接到 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 Schema Migration Rollout 證據&lt;/a>。&lt;/p>
&lt;p>freeze 的責任是把風險攔住。當 error budget 耗盡、事故正在進行、或高風險時段已到時，freeze 不應該被視為拖延，而應該被視為維持可靠性的一種放行決策。這樣的政策，會比只看 CI 更接近真實的部署世界。&lt;/p>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;ul>
&lt;li>gate 只看 CI green、不看 SLO / error budget / migration 可逆性&lt;/li>
&lt;li>emergency bypass 從例外變週常&lt;/li>
&lt;li>freeze 條件無 owner、沒人知道誰能解凍&lt;/li>
&lt;li>change failure rate 沒量、無法評估 gate 是否有效&lt;/li>
&lt;li>migration 沒做向後相容檢查、rollback 後資料不一致&lt;/li>
&lt;/ul>
&lt;h2 id="案例對照">案例對照&lt;/h2>
&lt;p>Google 很適合用來看 gate 需要什麼政策語言，因為它把 SLO、error budget 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">post-incident review&lt;/a> 連成一套治理系統。Stripe 則適合用來看交易場景下的 gate，因為 idempotency、canary 與 migration safety 會把放行和交易正確性綁在一起。Shopify 可以補峰值節奏，因為 BFCM 前的 gate 不只是測試通過，而是要確定高峰時仍能守住容量與隔離。&lt;/p>
&lt;p>Amazon 和 Meta 則提供更偏架構層的 gate 視角。前者告訴我們隔離邊界與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a> 會直接影響哪些變更可以放行，後者則顯示 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/control-plane/" data-link-title="Control Plane" data-link-desc="負責下發策略、配置與路由決策的控制層">control plane&lt;/a> 變更如果沒有足夠的 gate，可能直接把整個區域或整個公司拖進事故。把這些案例一起看，gate 就不再只是 CI 的最後一步，而是整個變更節奏的控制面。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">Release gate</a> 是把放行決策從「看起來可以」變成「條件已經達成」的控制面。它的責任是把哪些變更可以進、哪些變更要等、哪些變更必須先補證據說清楚，擋住所有變更從來不是目標。當 gate 被寫成政策，團隊就能用同一套條件判斷 CI、SLO、migration、相容性與高風險時段。</p>
<p>這個節點先處理節奏，再處理工具。先問變更是否應該放行，再問這次放行需要哪些訊號與檢查。當 gate 被看成節奏控制，讀者就會明白為什麼 freeze 是可靠性政策的一部分，視為例外會弱化整套節奏控制。</p>
<h2 id="大綱">大綱</h2>
<ul>
<li>release gate 的核心責任：把放行決策從個人判斷變成可驗證條件</li>
<li>gate 類別：CI 通過、SLO 健康、<a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 餘額、migration 可逆、相容性檢查</li>
<li>變更節奏：deploy frequency、batch size、change failure rate（DORA 四指標）</li>
<li>freeze 條件：error budget 耗盡、事故進行中、高風險時段</li>
<li>跟 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6 SLO</a> 的耦合：error budget 是 gate 的一個條件</li>
<li>跟 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署</a> 的交接：gate 通過後 rollout 策略接手</li>
<li>反模式：gate 流於形式、freeze 無 owner、緊急修復繞過 gate 變常態</li>
</ul>
<h2 id="核心判讀">核心判讀</h2>
<p>gate 的責任是把放行條件具體化。CI green 只代表測試通過，不代表服務可以安全進 production；SLO 健康只代表目前風險可接受，不代表任何變更都能繼續推；migration 可逆只代表退路存在，不代表已經證明回退完全無害。這些條件要一起看，才知道 gate 有沒有真的在做事。</p>
<p>資料庫 migration 的 gate 要把 evidence 放回 rollout 階段判讀。Expand、backfill、cutover 與 contract 需要不同 checks：compatibility result、<a href="/blog/backend/knowledge-cards/validation-query/" data-link-title="Validation Query" data-link-desc="說明遷移、回填與修復期間如何用查詢證明資料語意是否一致">validation query</a>、mismatch rate、replication lag、<a href="/blog/backend/knowledge-cards/rollback-window/" data-link-title="Rollback Window" data-link-desc="說明變更進入 production 後還能用哪種方式回退或改路線的時間與條件">rollback window</a> 與 owner。完整欄位形狀可接到 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">1.7 Schema Migration Rollout 證據</a>。</p>
<p>freeze 的責任是把風險攔住。當 error budget 耗盡、事故正在進行、或高風險時段已到時，freeze 不應該被視為拖延，而應該被視為維持可靠性的一種放行決策。這樣的政策，會比只看 CI 更接近真實的部署世界。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>gate 只看 CI green、不看 SLO / error budget / migration 可逆性</li>
<li>emergency bypass 從例外變週常</li>
<li>freeze 條件無 owner、沒人知道誰能解凍</li>
<li>change failure rate 沒量、無法評估 gate 是否有效</li>
<li>migration 沒做向後相容檢查、rollback 後資料不一致</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<p>Google 很適合用來看 gate 需要什麼政策語言，因為它把 SLO、error budget 與 <a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">post-incident review</a> 連成一套治理系統。Stripe 則適合用來看交易場景下的 gate，因為 idempotency、canary 與 migration safety 會把放行和交易正確性綁在一起。Shopify 可以補峰值節奏，因為 BFCM 前的 gate 不只是測試通過，而是要確定高峰時仍能守住容量與隔離。</p>
<p>Amazon 和 Meta 則提供更偏架構層的 gate 視角。前者告訴我們隔離邊界與 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a> 會直接影響哪些變更可以放行，後者則顯示 <a href="/blog/backend/knowledge-cards/control-plane/" data-link-title="Control Plane" data-link-desc="負責下發策略、配置與路由決策的控制層">control plane</a> 變更如果沒有足夠的 gate，可能直接把整個區域或整個公司拖進事故。把這些案例一起看，gate 就不再只是 CI 的最後一步，而是整個變更節奏的控制面。</p>
<p><a href="/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/" data-link-title="Stripe：Canary Deploy 與 Progressive Rollout 治理" data-link-desc="金流場景如何用交易指標驅動放行節奏：延遲確認、duplicate 偵測與自動回退。">Stripe 的 canary deploy 實踐</a>把金流場景的 progressive rollout 跟交易指標綁在一起：每一批放量用 checkout success rate、duplicate charge、退款率判斷是否安全。金流的 feedback loop 比一般功能長（結帳 → 確認 → 對帳 → 退款），觀察窗必須對齊這個延遲。</p>
<h2 id="gate-類別">gate 類別</h2>
<table>
  <thead>
      <tr>
          <th>類別</th>
          <th>作用</th>
          <th>常見例子</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CI 通過</td>
          <td>確認基礎測試與 artifact 可重播</td>
          <td>unit / integration / lint</td>
      </tr>
      <tr>
          <td>SLO 健康</td>
          <td>確認服務健康仍在可接受區間</td>
          <td><a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a>、error budget</td>
      </tr>
      <tr>
          <td>Migration 可逆</td>
          <td>確認 schema / data 變更有退路</td>
          <td>forward / backward compatibility</td>
      </tr>
      <tr>
          <td>相容性檢查</td>
          <td>確認上下游協議與資料不會互相打架</td>
          <td>contract / schema checks</td>
      </tr>
      <tr>
          <td>高風險時段凍結</td>
          <td>確認人在、窗在、風險可控</td>
          <td>freeze window、<a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> presence</td>
      </tr>
  </tbody>
</table>
<p>這張表的重點是每一類都要對應 owner 與回退條件，分類只是組織方式。沒有回退條件的 gate，只是心理安慰。</p>
<h2 id="變更分層跟-gate-政策">變更分層跟 gate 政策</h2>
<p>變更分層是把變更依失敗代價跟回退成本切成不同 gate 政策的控制面。讓高風險變更承受高 gate 成本、低風險變更不被高成本拖累、是分層治理的核心責任。可重複套用的做法是先做變更分層、再對應分層 gate 政策。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/microsoft/change-management-and-reliability-governance/" data-link-title="Microsoft：變更治理與可靠性門檻" data-link-desc="透過分層變更管理與發布閘門，降低大型 SaaS 平台的系統性回歸風險。">MS1 Microsoft 變更治理與可靠性門檻</a>：揭露「變更分層 + 漸進發布 + 復盤回寫」三個機制、適用大型 SaaS 高頻變更累積回歸的場景。對應 <a href="/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">G2 Google Postmortem AI Closure</a>：揭露 P0/P1 action item 必須綁定 release gate、未完成不得放行關聯變更（這層綁定讓 gate 從 release 工具升級為事故治理工具）。詳見 <a href="/blog/backend/06-reliability/reliability-debt-backlog/#action-item-%e5%88%86%e7%b4%9a%e8%b7%9f-release-gate-%e7%b6%81%e5%ae%9a" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 Action Item 分級跟 Release Gate 綁定</a>。</p>
<p>可操作的分層方法：</p>
<ul>
<li>低風險變更（配置微調、文案、UI 細節）：CI green + SLO 健康 即可放行</li>
<li>中風險變更（新 feature、依賴升級）：加 canary + per-version SLI 偏差檢查</li>
<li>高風險變更（schema migration、payment / auth 路徑、跨 region rollout）：加 evidence package + 高風險時段 freeze + P0 action item closure 檢查</li>
</ul>
<p>高風險層的三類變更要拆開治理、彼此 gate 機制不同：schema migration 的 gate 重點是 expand/contract 階段對齊跟 rollback 路徑（詳見 <a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration-safety</a>）；跨 region rollout 的 gate 重點是 ordered failover 跟 blast radius 限制（詳見 <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>）；payment / auth 路徑的 gate 重點在交易一致性跟 idempotency（詳見後段「交易類變更的 gate 設計」跟 <a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12 idempotency-replay</a>）。三者皆屬高風險、但失敗模式跟回退路徑完全不同。</p>
<p>分層後高風險變更得到匹配的 gate 強度、低風險變更不被拖累、整體交付節奏跟可靠性同步提升。</p>
<h2 id="交易類變更的-gate-設計">交易類變更的 gate 設計</h2>
<p>交易類變更的 gate 同時承擔可用性跟正確性兩條軸。除了服務健康（一般 gate 已覆蓋）、還要守住交易結果一致性；回退條件也要多看一層：rollback 是否會觸發資料不一致。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1 Stripe Idempotency 與零停機遷移</a>：揭露 idempotency key + expand/contract migration + canary + rollback gate + transaction observability 四機制組合、適用支付類「可用性 + 正確性同時守住」的場景。</p>
<p>交易類變更的 gate 跟一般 release gate 差別在：</p>
<ul>
<li>一般 release gate 看「服務是否健康」、交易類 gate 還要看「交易結果是否一致」</li>
<li>一般 release gate 看「回退是否可行」、交易類 gate 要看「回退是否會引發資料不一致」</li>
<li>一般 release gate 看「per-version SLI 偏差」、交易類 gate 要看「duplicate request collapse ratio」「migration phase error drift」「canary transaction anomaly」這類交易專屬訊號</li>
</ul>
<p>把交易類變更的 gate 從一般 release gate 分出來、寫進獨立 checklist、由 <a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12 idempotency-replay</a> 跟 <a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration-safety</a> 提供具體欄位。</p>
<h2 id="產業情境金融科技">產業情境：金融科技</h2>
<p>金融服務的 release gate 需要把交易正確性放在跟可用性同等的位置。一般 SaaS 的 gate 主要看 error rate 和 latency；金融服務的 gate 需要加上 duplicate detection、settlement 一致性與 compliance audit trail。</p>
<p>變更風險分層跟交易路徑綁定。碰到 payment path 的變更（provider 切換、timeout 調整、retry 策略、settlement 流程）自動升級到高風險 gate，不論變更看起來多小。payment path 的變更即使只改一個 timeout 值，也可能影響交易成功率、重試行為與對帳結果。</p>
<p>Gate 通過條件需要包含交易專屬欄位。<a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> 驗證確認重試不會產生重複扣款；reconciliation 通過確認結算數字一致；audit trail 完整確認每個決策都可追溯。這三項跟一般的 CI green / SLO healthy 是不同維度的檢查，需要獨立 checklist。</p>
<p>高風險變更的 canary 觀察窗需要涵蓋結算週期。一般 feature rollout 的觀察窗是分鐘到小時級；金融變更的觀察窗需要涵蓋 T+1（隔日結算）甚至 T+2，因為交易確認延遲、退款申請與對帳差異可能在數小時到數天後才暴露。觀察窗太短會讓問題在全量放行後才被發現。</p>
<p>Rollback 決策需要考慮已完成交易的一致性。當新版已處理交易且交易已進入結算流程，rollback 可能比繼續 roll-forward 更危險 — 退回舊版的 schema / 邏輯可能無法正確處理新版產生的交易紀錄。這個判斷跟 <a href="/blog/backend/06-reliability/dr-rollback-rehearsal/" data-link-title="6.7 DR 演練與 Rollback Rehearsal" data-link-desc="把回復路徑從紙面計畫變成定期可重播、可量測的驗證流程">6.7 rollback vs roll-forward 的判斷條件</a> 對齊，但金融場景的資料不可逆性更高。</p>
<h2 id="產業情境iot-與製造系統">產業情境：IoT 與製造系統</h2>
<p>IoT 的 release gate 需要處理「一旦推出就難以全面回收」的不可逆壓力。雲端服務的 deploy 可以秒級 rollback；IoT firmware 一旦推送到裝置，回收需要每台裝置個別 OTA，受限於連線狀態與頻寬。</p>
<p>裝置碎片化要求 gate 按硬體版本分群驗證。同一產品線可能有多個硬體版本（rev A / B / C），每個版本的 firmware 相容性不同。release gate 需要按硬體版本群組各自跑 checks，通過的群組才放行推送，不能全域一次放行。</p>
<p>IoT 的 canary 是按裝置群組分批推送，而非按流量百分比分流。推送順序通常是：內部測試裝置 → beta 用戶 → 特定區域 → 全域。每批的觀察窗需要比雲端更長（天到週），因為裝置的 failure mode 可能在特定環境條件下才觸發 — 溫度、濕度、網路品質、電力穩定度都是變數。</p>
<p>OTA 推送一旦開始，中途停止意味著部分裝置已更新、部分未更新。stop condition 需要同時監控「已更新裝置的健康度」和「混合版本之間的相容性」。若新舊版本的通訊協議不相容，部分更新的裝置群可能會觸發新的 failure mode。</p>
<p>安全關鍵系統（車載、醫療設備、工業控制）的 gate 需要額外的功能安全驗證（IEC 61508 / ISO 26262 等），通過合規驗證是放行的前置條件。這類 gate 的 owner 通常跨越工程與合規兩個團隊。</p>
<p><a href="/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">Amazon A2 的 static stability</a> 跟 IoT 的離線運作需求對齊 — 裝置在控制面（OTA server）不可用時，用本地快取的配置繼續運作，回復路徑不依賴已故障的控制面。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>：CI evidence 是 release gate 的主要輸入</li>
<li>05 部署：canary / progressive delivery 實作</li>
<li>06.6 SLO：error budget 餘額查詢</li>
<li>06.10 contract testing：契約通過作為放行條件</li>
<li>06.11 migration safety：可逆性檢查</li>
<li>01.7 Schema Migration Rollout 證據：把 migration evidence 轉成 <a href="/blog/backend/knowledge-cards/gate-decision/" data-link-title="Gate Decision" data-link-desc="說明 release gate 如何把證據轉成放行、暫停、回退或補證據的決策">gate decision</a>、checks、<a href="/blog/backend/knowledge-cards/stop-condition/" data-link-title="Stop Condition" data-link-desc="說明變更、實驗或事故處理何時必須暫停、回退或改路線">stop condition</a> 與 <a href="/blog/backend/knowledge-cards/rollback-window/" data-link-title="Rollback Window" data-link-desc="說明變更進入 production 後還能用哪種方式回退或改路線的時間與條件">rollback window</a></li>
<li>06.13 perf regression gate：退化作為 freeze 條件</li>
<li>07 資安：高風險變更的權限約束</li>
<li>08 事故閉環：事故進行中 freeze 觸發</li>
<li>06.17 feature flag：rollout 的細粒度控制層</li>
<li>06.18 reliability metrics：CFR 是 gate 健康度</li>
</ul>
]]></content:encoded></item><item><title>6.9 容量與成本邊界</title><link>https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/capacity-cost/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>容量規劃的核心：peak demand × headroom × growth curve&lt;/li>
&lt;li>headroom 訂定：成本 vs 突發承載 tradeoff&lt;/li>
&lt;li>capacity test 跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test&lt;/a> 的差異：load 看 throughput、capacity 看 saturation 與 cost curve&lt;/li>
&lt;li>成本作為驗證輸入：autoscaling 上限、預算告警、queue lag 跟成本的關係&lt;/li>
&lt;li>跨層容量：DB connection、queue、cache、CDN、第三方 API rate limit&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6 SLO&lt;/a> 的耦合：SLO 達成的容量代價&lt;/li>
&lt;li>反模式：容量規劃只看 CPU、autoscaling 無上限、成本失控用降級掩蓋&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Capacity 與成本邊界是把容量規劃跟成本約束一起看，責任是讓系統能承載預期負載，同時不把成本曲線推到不可接受區域。&lt;/p>
&lt;p>這一頁處理的是規模化之後的 trade-off。容量不是越高越好，真正的目標是找到能維持 SLO、又不浪費資源的區間。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 capacity 時，先看 saturation 點，再看成本曲線是不是隨之失控。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>autoscaling 是否有清楚上限與成本門檻&lt;/li>
&lt;li>依賴層是否先於應用層成為瓶頸&lt;/li>
&lt;li>peak forecast 是否涵蓋活動、季節性與推廣事件&lt;/li>
&lt;li>降級是否被當成例外策略，而不是常態容量替代&lt;/li>
&lt;/ul>
&lt;h2 id="案例對照">案例對照&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/" data-link-title="Shopify" data-link-desc="Shopify BFCM Scaling / Pod-based Isolation / Capacity Planning">Shopify&lt;/a>：高峰型流量把容量與成本的邊界推得很清楚。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/" data-link-title="LinkedIn" data-link-desc="LinkedIn Capacity Planning 與 On-call 結構">LinkedIn&lt;/a>：互動型服務常先在某個依賴層出現瓶頸。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/" data-link-title="Amazon" data-link-desc="Amazon Cell-based Architecture / Shuffle Sharding / Blast Radius 設計">Amazon&lt;/a>：大規模系統常把成本與可靠性一起做優化。&lt;/li>
&lt;/ul>
&lt;h2 id="高峰型容量治理game-day--capacity-planning">高峰型容量治理：Game Day + Capacity Planning&lt;/h2>
&lt;p>高峰型容量治理是把「可預期的非典型流量」當獨立治理面操作的能力。涵蓋 baseline 預估、邊界隔離、game day 驗證跟 resiliency matrix 對齊四個面向。日常擴容靠 autoscaling、高峰需要的是預先驗證跟邊界控制 — 峰值期間擴容延遲跟依賴抖動會疊加放大成事故。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">H1 Shopify BFCM 容量治理與 Game Day&lt;/a>：揭露四個機制對應上述四個面向 — capacity planning baseline（高峰前可承受上限是多少）、pod/isolation boundary（故障影響如何限制在局部）、game day（高峰前如何驗證假設）、resiliency matrix（服務與失效模式如何對齊）。&lt;/p>
&lt;p>可重複套用的做法：&lt;/p>
&lt;p>三個治理面性質不同、不是同一個時間軸的三步驟：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Capacity planning&lt;/strong>（forecast + headroom 模型）：高峰前 N 週開始、整合 forecast、headroom、依賴 quota — 不只看單一 CPU 數字、要看整條依賴鏈的瓶頸層&lt;/li>
&lt;li>&lt;strong>Game day&lt;/strong>（production-like 假設驗證）：高峰前 N 天執行、把 runbook、matrix、驗證腳本、放行門檻當固定資產輸出、不是「跑完就好」&lt;/li>
&lt;li>&lt;strong>Isolation boundary&lt;/strong>（runtime 故障擴散控制）：高峰當下持續運作、cell 邊界跟 graceful degradation 把故障限制在最小可影響範圍、補強 autoscaling 來不及的延遲段&lt;/li>
&lt;/ul>
&lt;p>把每輪活動輸出的缺口回寫成固定資產（不只是「一次性專案」），下一輪準備就能從更高基準開始。&lt;/p>
&lt;h2 id="容量跟值班分層的協同">容量跟值班分層的協同&lt;/h2>
&lt;p>容量跟值班分層的綁定責任是讓「容量門檻」跟「升級路徑」在同一個 trigger 觸發：接近 headroom 限制時值班自動分層、避免事故發生才升級。這個綁定需要三件事配合：runtime 訊號（headroom 預算）、接手機制（三層值班）、模型校準（壓測驗證）。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">L1 LinkedIn Capacity Headroom 與 On-call 分層&lt;/a>：揭露三個機制對應上述三件事 — headroom 預算（何時進入風險區）、primary/secondary/SME 三層值班（何時由誰接手）、自動化壓測（模型是否貼近現況）。前兩個是 runtime 治理、後者是 model 校準、屬於不同邏輯位階。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>容量規劃的核心：peak demand × headroom × growth curve</li>
<li>headroom 訂定：成本 vs 突發承載 tradeoff</li>
<li>capacity test 跟 <a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test</a> 的差異：load 看 throughput、capacity 看 saturation 與 cost curve</li>
<li>成本作為驗證輸入：autoscaling 上限、預算告警、queue lag 跟成本的關係</li>
<li>跨層容量：DB connection、queue、cache、CDN、第三方 API rate limit</li>
<li>跟 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6 SLO</a> 的耦合：SLO 達成的容量代價</li>
<li>反模式：容量規劃只看 CPU、autoscaling 無上限、成本失控用降級掩蓋</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Capacity 與成本邊界是把容量規劃跟成本約束一起看，責任是讓系統能承載預期負載，同時不把成本曲線推到不可接受區域。</p>
<p>這一頁處理的是規模化之後的 trade-off。容量不是越高越好，真正的目標是找到能維持 SLO、又不浪費資源的區間。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 capacity 時，先看 saturation 點，再看成本曲線是不是隨之失控。</p>
<p>重點訊號包括：</p>
<ul>
<li>autoscaling 是否有清楚上限與成本門檻</li>
<li>依賴層是否先於應用層成為瓶頸</li>
<li>peak forecast 是否涵蓋活動、季節性與推廣事件</li>
<li>降級是否被當成例外策略，而不是常態容量替代</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/shopify/" data-link-title="Shopify" data-link-desc="Shopify BFCM Scaling / Pod-based Isolation / Capacity Planning">Shopify</a>：高峰型流量把容量與成本的邊界推得很清楚。</li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/" data-link-title="LinkedIn" data-link-desc="LinkedIn Capacity Planning 與 On-call 結構">LinkedIn</a>：互動型服務常先在某個依賴層出現瓶頸。</li>
<li><a href="/blog/backend/06-reliability/cases/amazon/" data-link-title="Amazon" data-link-desc="Amazon Cell-based Architecture / Shuffle Sharding / Blast Radius 設計">Amazon</a>：大規模系統常把成本與可靠性一起做優化。</li>
</ul>
<h2 id="高峰型容量治理game-day--capacity-planning">高峰型容量治理：Game Day + Capacity Planning</h2>
<p>高峰型容量治理是把「可預期的非典型流量」當獨立治理面操作的能力。涵蓋 baseline 預估、邊界隔離、game day 驗證跟 resiliency matrix 對齊四個面向。日常擴容靠 autoscaling、高峰需要的是預先驗證跟邊界控制 — 峰值期間擴容延遲跟依賴抖動會疊加放大成事故。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">H1 Shopify BFCM 容量治理與 Game Day</a>：揭露四個機制對應上述四個面向 — capacity planning baseline（高峰前可承受上限是多少）、pod/isolation boundary（故障影響如何限制在局部）、game day（高峰前如何驗證假設）、resiliency matrix（服務與失效模式如何對齊）。</p>
<p>可重複套用的做法：</p>
<p>三個治理面性質不同、不是同一個時間軸的三步驟：</p>
<ul>
<li><strong>Capacity planning</strong>（forecast + headroom 模型）：高峰前 N 週開始、整合 forecast、headroom、依賴 quota — 不只看單一 CPU 數字、要看整條依賴鏈的瓶頸層</li>
<li><strong>Game day</strong>（production-like 假設驗證）：高峰前 N 天執行、把 runbook、matrix、驗證腳本、放行門檻當固定資產輸出、不是「跑完就好」</li>
<li><strong>Isolation boundary</strong>（runtime 故障擴散控制）：高峰當下持續運作、cell 邊界跟 graceful degradation 把故障限制在最小可影響範圍、補強 autoscaling 來不及的延遲段</li>
</ul>
<p>把每輪活動輸出的缺口回寫成固定資產（不只是「一次性專案」），下一輪準備就能從更高基準開始。</p>
<h2 id="容量跟值班分層的協同">容量跟值班分層的協同</h2>
<p>容量跟值班分層的綁定責任是讓「容量門檻」跟「升級路徑」在同一個 trigger 觸發：接近 headroom 限制時值班自動分層、避免事故發生才升級。這個綁定需要三件事配合：runtime 訊號（headroom 預算）、接手機制（三層值班）、模型校準（壓測驗證）。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">L1 LinkedIn Capacity Headroom 與 On-call 分層</a>：揭露三個機制對應上述三件事 — headroom 預算（何時進入風險區）、primary/secondary/SME 三層值班（何時由誰接手）、自動化壓測（模型是否貼近現況）。前兩個是 runtime 治理、後者是 model 校準、屬於不同邏輯位階。</p>
<p>容量規劃要回答「擴容門檻是多少」、值班分層要回答「接近門檻時誰接手」。兩者綁定後、高峰期值班分層自動觸發、不需等事故發生才升級。詳見 <a href="/blog/backend/08-incident-response/ic-handoff-long-incident/" data-link-title="8.12 IC Handoff 與長事故跨班次協調" data-link-desc="把 24h&#43; / 跨 timezone 事故的接班節奏變成可重複流程">8.12 IC handoff for long incident</a>。</p>
<h2 id="快取容量的特殊性">快取容量的特殊性</h2>
<p>快取容量治理的核心責任是失溫時資料層仍可承受。headroom 不是看快取 QPS、是看命中率下滑後的回源放大係數 — 快取本身可能耐 10x 流量、資料層可能撐不到 1.5x。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">P1 Pinterest 快取可靠性與容量驚奇治理</a>：揭露三個機制 — cache headroom（命中率下滑能承受多久）、graceful degradation（快取失效時如何降級）、rewarm strategy（熱資料如何有序回填）。</p>
<p>快取容量規劃的核心問題是失溫時資料層能承受的回源放大係數。命中率從 95% 掉到 80% 意味資料層流量 4x、能否承受決定快取退化會不會升級為事故。預先設計 graceful degradation 路徑跟 rewarm 節奏、能避免快取失溫變成連鎖退化。詳見 <a href="/blog/backend/02-cache-redis/cache-migration-stampede-rollback/" data-link-title="2.9 Cache Migration 與 Stampede Rollback（實作示範）" data-link-desc="以商品詳情與價格快取示範 cache migration 如何交付 evidence package、release gate 與 incident decision log。">2.9 cache stampede rollback</a>。</p>
<h2 id="產業情境電商與零售">產業情境：電商與零售</h2>
<p>電商的容量規劃受峰值倍率與峰值持續時間的雙重約束。為年度一次的峰值預留全年容量成本過高，但峰值容量不足會直接損失營收 — 容量不足在電商是可量化的商業損失（每分鐘宕機對應可估算的 GMV 損失），技術事故與營收衝擊直接掛鉤。</p>
<p>峰值容量策略有三種模式，各自的成本與風險形狀不同。全年預留是最安全但成本最高的做法，適合峰值與日常倍率差距小（&lt; 3x）的服務。彈性擴容依賴 auto-scaling 在峰值到來時及時反應，但擴容延遲（分鐘級）加上依賴層的 warm-up 時間可能讓尖峰初期無法承接。峰值前臨時擴容需要提前 provision 並用 game day 驗證擴容路徑，是中等成本但需要較高工程投入的選項。多數大型電商混用三者：核心路徑全年預留、彈性層 auto-scale、輔助服務臨時擴容。</p>
<p>降級策略在電商有明確的不可降級邊界。推薦引擎、搜尋排序、個人化功能可以在壓力下退回簡化版或靜態結果，但結帳路徑（購物車 → 付款 → 訂單確認）不能降級 — 結帳流程中斷等於訂單流失，使用者不會等系統恢復後重新結帳。降級策略的設計需要把服務按「可降級 / 不可降級」分層，壓力下優先保護不可降級路徑的資源配額。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>06.2 load testing：capacity 輸入來自 workload model</li>
<li>06.9 reliability metrics：容量與成本要有量測口徑</li>
<li>06.13 perf regression gate：效能退化通常伴隨成本上升</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>autoscaling max 設無限大、或長期未觸碰</li>
<li>容量規劃只看 CPU、忽略 connection pool / queue / 第三方 quota</li>
<li>peak 流量 forecast 是直線外推、未考慮 promo / seasonal / 行銷事件</li>
<li>成本告警觸發後才回頭討論容量</li>
<li>降級邏輯被當成常態容量緩衝、而非例外保護</li>
</ul>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04 觀測：saturation metric、cost dashboard</li>
<li>05 部署：HPA / autoscaling policy</li>
<li>06.6 SLO：容量不足導致 SLO 風險</li>
<li>04.15 cost attribution：observability 成本作為總體成本一部分</li>
</ul>
]]></content:encoded></item><item><title>6.10 Contract Testing 與 Schema 演進</title><link>https://tarrragon.github.io/blog/backend/06-reliability/contract-testing/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/contract-testing/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Contract testing 在服務邊界上驗證 producer 與 consumer 的相容性，把跨團隊協作的隱性期待變成可執行的&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/contract/" data-link-title="Boundary Contract" data-link-desc="說明跨邊界約定如何維持相容與可驗證">契約&lt;/a>。&lt;/p>
&lt;p>這一頁處理的是服務邊界上的信任問題。當服務彼此頻繁演進，契約測試是避免變更互相踩踏的最小保護層。契約對準的是真實 consumer 的期待，而不是抽象的 spec 文件。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>好的 contract testing 會明確劃出兼容視窗，並把驗證放進 CI 或 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">release gate&lt;/a>。&lt;/p>
&lt;p>判讀時看三件事：&lt;/p>
&lt;ul>
&lt;li>契約是否對準真實 consumer，而非假想 client&lt;/li>
&lt;li>schema evolution 是否有明確 compatibility window&lt;/li>
&lt;li>失敗是否能回到責任邊界，而非只看到測試紅燈&lt;/li>
&lt;/ul>
&lt;h2 id="consumer-driven-vs-provider-driven">Consumer-driven vs Provider-driven&lt;/h2>
&lt;p>契約驗證有兩個驅動方向，適用場景不同。&lt;/p>
&lt;p>&lt;strong>Consumer-driven&lt;/strong>：consumer 先定義對 producer 回應的期望（欄位、型別、值域），producer 驗證是否能滿足。這種做法讓驗證對準真實消費需求 — consumer 只關心它用到的欄位，producer 可以自由演進不被使用的部分。缺點是 consumer 數量多時，契約管理成本上升：每個 consumer 維護自己的契約檔，producer 需要跑所有 consumer 契約才能確認相容。&lt;/p>
&lt;p>&lt;strong>Provider-driven&lt;/strong>：producer 定義 API spec（OpenAPI / gRPC schema），consumer 驗證自己能否適配。producer 主導 schema 演進節奏，consumer 接收變更通知並更新。這種做法適合公開 API 或 consumer 數量大且不可控的服務。缺點是可能漏掉 consumer 依賴的隱性行為 — spec 上合規但語意變了，consumer 仍會失敗。&lt;/p>
&lt;p>判斷依據：consumer 少且已知（內部微服務）→ consumer-driven；consumer 多或不可控（公開 API / 平台整合）→ provider-driven。兩者可混用：核心 consumer 用 consumer-driven 保護關鍵路徑，其他 consumer 靠 provider spec 覆蓋。&lt;/p>
&lt;h2 id="契約驗證的三個層次">契約驗證的三個層次&lt;/h2>
&lt;p>契約驗證按深度分三層，每一層攔截不同類型的破壞。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層次&lt;/th>
 &lt;th>驗證內容&lt;/th>
 &lt;th>常見工具&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Schema 結構&lt;/td>
 &lt;td>欄位是否存在、型別是否一致&lt;/td>
 &lt;td>JSON Schema validation / protobuf 編譯&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>語意相容&lt;/td>
 &lt;td>值域、enum 範圍、nullable 語意是否對齊&lt;/td>
 &lt;td>Pact interaction / custom assertion&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>向後相容性&lt;/td>
 &lt;td>新版輸出能否被舊版 consumer 解析&lt;/td>
 &lt;td>Avro compatibility check / Buf&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Schema 結構&lt;/strong>是最基礎的防線。欄位缺失或型別錯誤會直接導致 runtime 解析失敗。這一層成本低、回饋快，適合放在 CI fast path。&lt;/p>
&lt;p>&lt;strong>語意相容&lt;/strong>攔截的是「schema 通過但行為不同」的問題。例如某個欄位從 nullable 改成 required，或 enum 新增一個值但 consumer 的 switch 沒有 default branch。這類問題在結構層驗證不出來，需要 consumer 定義語意期望（Pact interaction 的 matcher / assertion）。&lt;/p>
&lt;p>&lt;strong>向後相容性&lt;/strong>是跨版本共存的保障。Avro 和 Protobuf 有內建 compatibility mode（backward / forward / full）；JSON Schema 需要外部工具（如 json-schema-diff）做版本比較。向後相容性驗證的成本最高，但能攔截最嚴重的破壞 — 一旦 event 寫入 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker&lt;/a>，舊版 consumer 就必須能解析它。&lt;/p>
&lt;h2 id="schema-演進規則">Schema 演進規則&lt;/h2>
&lt;p>Schema 演進按協議類型有不同的安全邊界。&lt;/p>
&lt;h3 id="api-schemaopenapi--grpc">API schema（OpenAPI / gRPC）&lt;/h3>
&lt;p>API schema 的演進判讀：新增可選欄位通常安全；移除欄位、重新命名欄位、或把可選改成必填是 breaking change；型別變更（如 int32 → int64）視 consumer 的容忍度而定。gRPC 的 field number 機制讓欄位新增與移除的相容性比 JSON 更明確 — 未知 field number 被忽略，已知 field number 被刪除會觸發 default value，兩者都有可預測行為。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Contract testing 在服務邊界上驗證 producer 與 consumer 的相容性，把跨團隊協作的隱性期待變成可執行的<a href="/blog/backend/knowledge-cards/contract/" data-link-title="Boundary Contract" data-link-desc="說明跨邊界約定如何維持相容與可驗證">契約</a>。</p>
<p>這一頁處理的是服務邊界上的信任問題。當服務彼此頻繁演進，契約測試是避免變更互相踩踏的最小保護層。契約對準的是真實 consumer 的期待，而不是抽象的 spec 文件。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>好的 contract testing 會明確劃出兼容視窗，並把驗證放進 CI 或 <a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">release gate</a>。</p>
<p>判讀時看三件事：</p>
<ul>
<li>契約是否對準真實 consumer，而非假想 client</li>
<li>schema evolution 是否有明確 compatibility window</li>
<li>失敗是否能回到責任邊界，而非只看到測試紅燈</li>
</ul>
<h2 id="consumer-driven-vs-provider-driven">Consumer-driven vs Provider-driven</h2>
<p>契約驗證有兩個驅動方向，適用場景不同。</p>
<p><strong>Consumer-driven</strong>：consumer 先定義對 producer 回應的期望（欄位、型別、值域），producer 驗證是否能滿足。這種做法讓驗證對準真實消費需求 — consumer 只關心它用到的欄位，producer 可以自由演進不被使用的部分。缺點是 consumer 數量多時，契約管理成本上升：每個 consumer 維護自己的契約檔，producer 需要跑所有 consumer 契約才能確認相容。</p>
<p><strong>Provider-driven</strong>：producer 定義 API spec（OpenAPI / gRPC schema），consumer 驗證自己能否適配。producer 主導 schema 演進節奏，consumer 接收變更通知並更新。這種做法適合公開 API 或 consumer 數量大且不可控的服務。缺點是可能漏掉 consumer 依賴的隱性行為 — spec 上合規但語意變了，consumer 仍會失敗。</p>
<p>判斷依據：consumer 少且已知（內部微服務）→ consumer-driven；consumer 多或不可控（公開 API / 平台整合）→ provider-driven。兩者可混用：核心 consumer 用 consumer-driven 保護關鍵路徑，其他 consumer 靠 provider spec 覆蓋。</p>
<h2 id="契約驗證的三個層次">契約驗證的三個層次</h2>
<p>契約驗證按深度分三層，每一層攔截不同類型的破壞。</p>
<table>
  <thead>
      <tr>
          <th>層次</th>
          <th>驗證內容</th>
          <th>常見工具</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema 結構</td>
          <td>欄位是否存在、型別是否一致</td>
          <td>JSON Schema validation / protobuf 編譯</td>
      </tr>
      <tr>
          <td>語意相容</td>
          <td>值域、enum 範圍、nullable 語意是否對齊</td>
          <td>Pact interaction / custom assertion</td>
      </tr>
      <tr>
          <td>向後相容性</td>
          <td>新版輸出能否被舊版 consumer 解析</td>
          <td>Avro compatibility check / Buf</td>
      </tr>
  </tbody>
</table>
<p><strong>Schema 結構</strong>是最基礎的防線。欄位缺失或型別錯誤會直接導致 runtime 解析失敗。這一層成本低、回饋快，適合放在 CI fast path。</p>
<p><strong>語意相容</strong>攔截的是「schema 通過但行為不同」的問題。例如某個欄位從 nullable 改成 required，或 enum 新增一個值但 consumer 的 switch 沒有 default branch。這類問題在結構層驗證不出來，需要 consumer 定義語意期望（Pact interaction 的 matcher / assertion）。</p>
<p><strong>向後相容性</strong>是跨版本共存的保障。Avro 和 Protobuf 有內建 compatibility mode（backward / forward / full）；JSON Schema 需要外部工具（如 json-schema-diff）做版本比較。向後相容性驗證的成本最高，但能攔截最嚴重的破壞 — 一旦 event 寫入 <a href="/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker</a>，舊版 consumer 就必須能解析它。</p>
<h2 id="schema-演進規則">Schema 演進規則</h2>
<p>Schema 演進按協議類型有不同的安全邊界。</p>
<h3 id="api-schemaopenapi--grpc">API schema（OpenAPI / gRPC）</h3>
<p>API schema 的演進判讀：新增可選欄位通常安全；移除欄位、重新命名欄位、或把可選改成必填是 breaking change；型別變更（如 int32 → int64）視 consumer 的容忍度而定。gRPC 的 field number 機制讓欄位新增與移除的相容性比 JSON 更明確 — 未知 field number 被忽略，已知 field number 被刪除會觸發 default value，兩者都有可預測行為。</p>
<h3 id="event-schemaavro--protobuf--json-schema">Event schema（Avro / Protobuf / JSON Schema）</h3>
<p>Event schema 的相容性要求比 API 更嚴格。API 的 breaking change 可以靠 versioning（<code>/v2/</code>）隔離，event 一旦寫入 broker 就跟所有版本的 consumer 共存。backward compatibility（新 schema 能讀舊資料）是最低要求；forward compatibility（舊 schema 能讀新資料）讓 consumer 可以延遲升級。</p>
<p>Schema registry（Confluent Schema Registry / AWS Glue Schema Registry）提供集中式的相容性 gate：producer 註冊新版 schema 前，registry 自動比對相容性規則，拒絕 breaking change。這個 gate 比 CI 更早攔截，因為它在 schema 發布時就生效。</p>
<p>DB schema 演進的契約驗證銜接到 <a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety</a> — expand/contract pattern 讓新舊版本共存，本質上跟 event schema 的 backward compatibility 是同一個問題。</p>
<h2 id="ci-整合">CI 整合</h2>
<p>Contract test 在 CI 的位置跟 unit test 不同 — 需要跨服務的契約同步。</p>
<p><strong>Fast path</strong>：producer 的 schema 變更觸發 consumer 的 contract test。實作上需要 CI 能跨 repo 觸發（webhook / pipeline trigger），或用 contract broker（如 Pact Broker）做非同步驗證。fast path 只跑受影響 consumer 的契約，保持回饋速度。</p>
<p><strong>Slow path</strong>：完整 contract matrix 驗證 — 所有 consumer × producer 組合。這個矩陣在 merge gate 或 scheduled path 跑，覆蓋 fast path 漏掉的間接影響。矩陣規模隨服務數增長，需要 selective matrix（只跑有變更的 producer 相關 consumer）控制成本。</p>
<p><strong>失敗處理</strong>：contract test 失敗時的責任分派是關鍵流程。失敗可能來自 producer 的 breaking change，也可能來自 consumer 的 expectation 過期。Pact 的 can-i-deploy 機制提供自動化判斷：比對 producer 當前版本與 consumer 上次驗證通過的版本，定位責任方。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe</a>：外部整合的 API 需要嚴格的 backward compatibility — 交易 API 的 breaking change 會直接影響商戶收入，schema 演進靠 expand/contract 逐步過渡。</li>
<li><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify</a>：跨服務 deploy 順序錯誤是高峰期常見事故源 — contract test 攔截 schema 不相容，讓 deploy 順序有驗證依據。</li>
<li><a href="/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">GitHub</a>：API 與 webhook 的契約覆蓋面廣，契約失配會直接影響整合生態。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>跨服務 deploy 順序錯誤導致 production 故障</td>
          <td>contract test 應在 CI 攔截相容性問題，deploy 順序才有驗證依據</td>
          <td>補 contract test 到 CI fast path</td>
      </tr>
      <tr>
          <td>API 文件跟實作漂移、新接入服務出意外</td>
          <td>provider-driven spec 需要自動化 diff 偵測，手動更新會漂移</td>
          <td>接 OpenAPI diff 工具到 CI、spec 變更自動 PR</td>
      </tr>
      <tr>
          <td>event schema 變更後下游 consumer 解析失敗</td>
          <td>schema registry 的 compatibility gate 應在 publish 前攔截</td>
          <td>啟用 schema registry 的 compatibility check</td>
      </tr>
      <tr>
          <td>breaking change 靠 release note 標註</td>
          <td>標註是通知、contract test 是攔截，兩者責任不同</td>
          <td>加 CI contract gate 攔截 breaking change</td>
      </tr>
      <tr>
          <td>contract 違規只在 staging 才發現</td>
          <td>contract test 應在 CI fast path 跑，staging 發現代表 CI 沒覆蓋</td>
          <td>把 contract test 從 staging 提前到 CI push 觸發</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>：contract test 作為 fast path 的跨服務驗證</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：contract 通過作為放行條件</li>
<li><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety</a>：DB schema 演進的契約驗證</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 budget</a>：依賴契約穩定性</li>
<li><a href="/blog/backend/06-reliability/environment-parity/" data-link-title="6.15 Environment Parity 與漂移控制" data-link-desc="把 staging / preprod / prod 之間的差異視為一級風險，按漂移來源分類偵測與治理">6.15 environment parity</a>：契約覆蓋的環境邊界</li>
<li><a href="/blog/backend/06-reliability/test-data-management/" data-link-title="6.16 Test Data Management" data-link-desc="把 fixture / seed / production-like data 作為跨模組共用 artifact，治理資料層次、遮罩策略與可重現性">6.16 test data</a>：fixture shape 契約</li>
<li><a href="/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17 feature flag</a>：flag 不同分支的契約覆蓋</li>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署</a>：跨服務 deploy 順序協調</li>
</ul>
]]></content:encoded></item><item><title>6.11 Migration Safety 與 DB Rollout</title><link>https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>migration 的核心約束：schema 變更必須跟程式碼版本相容&lt;/li>
&lt;li>expand / contract 模式：先擴展（雙寫 / 雙讀）、再收斂（移除舊欄位）&lt;/li>
&lt;li>雙寫驗證：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/shadow-read/" data-link-title="Shadow Read" data-link-desc="說明正式讀取仍走舊路徑時如何暗中讀新路徑比對結果">shadow read&lt;/a>、checksum 比對、流量採樣&lt;/li>
&lt;li>線上 DDL 工具：pt-online-schema-change / gh-ost / Vitess online schema change&lt;/li>
&lt;li>大表 migration 策略：批次、節流、避開 peak&lt;/li>
&lt;li>rollback 路徑設計：每階段必須可逆&lt;/li>
&lt;li>跟 &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 contract testing&lt;/a> 的整合：schema 契約驗證&lt;/li>
&lt;li>跟 &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> 的整合：migration 可逆性作為 gate 條件&lt;/li>
&lt;li>反模式：schema change 跟 code deploy 同 PR、rollback 變不可能；大表 ALTER 直接打、production 鎖表；新欄位 NOT NULL 無 default&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/schema-migration/" data-link-title="Schema Migration" data-link-desc="說明資料庫結構如何隨應用程式版本安全演進">Schema migration&lt;/a> 是把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程，責任是避免資料結構變更直接把 production 推向不可回復狀態。&lt;/p>
&lt;p>這一頁關心的是結構變更的節奏。當 code 與 schema 必須一起演進，安全做法是保留回退與相容窗口，一次到位的思路會壓縮容錯空間。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 migration 時，先看每一步是否可逆，再看它是否能在 peak 外執行。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>expand / contract 是否真的分開&lt;/li>
&lt;li>rollback 路徑是否先於 production 變更設計&lt;/li>
&lt;li>大表操作是否有節流與 dry-run&lt;/li>
&lt;li>雙寫 / shadow read 是否有一致性驗證&lt;/li>
&lt;/ul>
&lt;h2 id="案例對照">案例對照&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/" data-link-title="Pinterest" data-link-desc="Pinterest Capacity Planning 與儲存架構可靠性">Pinterest&lt;/a>：資料結構與產品演進常同步變化。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">GitHub&lt;/a>：大規模平台 migration 容易把結構風險放大。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">Stripe&lt;/a>：金流系統對 migration rollback 與一致性要求特別高。&lt;/li>
&lt;/ul>
&lt;h2 id="交易類-migration-的特殊性">交易類 migration 的特殊性&lt;/h2>
&lt;p>交易類 migration 同時承擔可用性跟正確性兩條軸。一般 schema migration 失敗的代價是停機、交易類失敗的代價額外包含結果不一致（重複扣款、訂單漏建、reconciliation 缺口）。守住兩條軸需要 idempotency + 漸進遷移 + 可回退發布 + 交易路徑可追溯四件事配合。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1 Stripe Idempotency 與零停機遷移&lt;/a>：揭露四個機制對應上述四件事 — idempotency key（同一交易重送如何得到同一結果）、expand/contract migration（資料變更如何與新舊版本共存）、canary + rollback gate（發版異常如何快速收斂）、transaction-path observability（交易路徑是否可追溯）。&lt;/p>
&lt;p>交易類 migration 的關鍵 observables：&lt;/p>
&lt;ul>
&lt;li>duplicate request collapse ratio：重試是否被正確合併&lt;/li>
&lt;li>migration phase error drift：遷移各階段錯誤是否收斂&lt;/li>
&lt;li>canary transaction anomaly：小流量交易是否出現偏差&lt;/li>
&lt;li>payment trace consistency：trace 是否完整覆蓋交易關鍵欄位&lt;/li>
&lt;/ul>
&lt;p>把這四個機制視為「交易類 migration 的安全 baseline」、跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12 idempotency-replay&lt;/a> 共用 idempotency key 設計、跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/#%e4%ba%a4%e6%98%93%e9%a1%9e%e8%ae%8a%e6%9b%b4%e7%9a%84-gate-%e8%a8%ad%e8%a8%88" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate 交易類變更段&lt;/a> 共用 canary 條件。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>migration 的核心約束：schema 變更必須跟程式碼版本相容</li>
<li>expand / contract 模式：先擴展（雙寫 / 雙讀）、再收斂（移除舊欄位）</li>
<li>雙寫驗證：<a href="/blog/backend/knowledge-cards/shadow-read/" data-link-title="Shadow Read" data-link-desc="說明正式讀取仍走舊路徑時如何暗中讀新路徑比對結果">shadow read</a>、checksum 比對、流量採樣</li>
<li>線上 DDL 工具：pt-online-schema-change / gh-ost / Vitess online schema change</li>
<li>大表 migration 策略：批次、節流、避開 peak</li>
<li>rollback 路徑設計：每階段必須可逆</li>
<li>跟 <a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10 contract testing</a> 的整合：schema 契約驗證</li>
<li>跟 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a> 的整合：migration 可逆性作為 gate 條件</li>
<li>反模式：schema change 跟 code deploy 同 PR、rollback 變不可能；大表 ALTER 直接打、production 鎖表；新欄位 NOT NULL 無 default</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/schema-migration/" data-link-title="Schema Migration" data-link-desc="說明資料庫結構如何隨應用程式版本安全演進">Schema migration</a> 是把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程，責任是避免資料結構變更直接把 production 推向不可回復狀態。</p>
<p>這一頁關心的是結構變更的節奏。當 code 與 schema 必須一起演進，安全做法是保留回退與相容窗口，一次到位的思路會壓縮容錯空間。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 migration 時，先看每一步是否可逆，再看它是否能在 peak 外執行。</p>
<p>重點訊號包括：</p>
<ul>
<li>expand / contract 是否真的分開</li>
<li>rollback 路徑是否先於 production 變更設計</li>
<li>大表操作是否有節流與 dry-run</li>
<li>雙寫 / shadow read 是否有一致性驗證</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/pinterest/" data-link-title="Pinterest" data-link-desc="Pinterest Capacity Planning 與儲存架構可靠性">Pinterest</a>：資料結構與產品演進常同步變化。</li>
<li><a href="/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">GitHub</a>：大規模平台 migration 容易把結構風險放大。</li>
<li><a href="/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">Stripe</a>：金流系統對 migration rollback 與一致性要求特別高。</li>
</ul>
<h2 id="交易類-migration-的特殊性">交易類 migration 的特殊性</h2>
<p>交易類 migration 同時承擔可用性跟正確性兩條軸。一般 schema migration 失敗的代價是停機、交易類失敗的代價額外包含結果不一致（重複扣款、訂單漏建、reconciliation 缺口）。守住兩條軸需要 idempotency + 漸進遷移 + 可回退發布 + 交易路徑可追溯四件事配合。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1 Stripe Idempotency 與零停機遷移</a>：揭露四個機制對應上述四件事 — idempotency key（同一交易重送如何得到同一結果）、expand/contract migration（資料變更如何與新舊版本共存）、canary + rollback gate（發版異常如何快速收斂）、transaction-path observability（交易路徑是否可追溯）。</p>
<p>交易類 migration 的關鍵 observables：</p>
<ul>
<li>duplicate request collapse ratio：重試是否被正確合併</li>
<li>migration phase error drift：遷移各階段錯誤是否收斂</li>
<li>canary transaction anomaly：小流量交易是否出現偏差</li>
<li>payment trace consistency：trace 是否完整覆蓋交易關鍵欄位</li>
</ul>
<p>把這四個機制視為「交易類 migration 的安全 baseline」、跟 <a href="/blog/backend/06-reliability/idempotency-replay/" data-link-title="6.12 Idempotency 與 Replay 驗證" data-link-desc="把重試、重播與冪等性從口頭約定變成可驗證屬性">6.12 idempotency-replay</a> 共用 idempotency key 設計、跟 <a href="/blog/backend/06-reliability/release-gate/#%e4%ba%a4%e6%98%93%e9%a1%9e%e8%ae%8a%e6%9b%b4%e7%9a%84-gate-%e8%a8%ad%e8%a8%88" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate 交易類變更段</a> 共用 canary 條件。</p>
<p>交易類 migration 的反模式是把 migration 當「資料庫任務」獨立執行、跟 release gate 分離。正確做法是把 migration 跟 release 綁定治理、用同一套 evidence 跟 rollback 條件判讀。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>01.6 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">資料庫轉換實作</a>：雙寫、回填、切流與回滾</li>
<li>01.7 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">Schema Migration Rollout 證據</a>：把 migration plan 落成 <a href="/blog/backend/knowledge-cards/validation-query/" data-link-title="Validation Query" data-link-desc="說明遷移、回填與修復期間如何用查詢證明資料語意是否一致">validation query</a>、evidence package、release gate 與 decision log</li>
<li>06.8 release gate：把可逆性放進放行條件</li>
<li>06.10 contract testing：先驗 schema 相容性</li>
<li>08.5 <a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">post-incident review</a>：migration 類事故通常需要結構化復盤</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>migration 失敗只能 forward-fix、無 rollback 路徑</li>
<li>大表 ALTER 在 peak 時段執行造成鎖表</li>
<li>程式碼跟 schema 必須同步部署、deploy 失敗風險高</li>
<li>雙寫期間無一致性驗證、cutover 後才發現資料漂移</li>
<li>migration 工具無 dry-run、production 才知道執行時間</li>
</ul>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>01.6 <a href="/blog/backend/01-database/database-migration-playbook/" data-link-title="1.6 資料庫轉換實作：雙寫、回填、切流與回滾" data-link-desc="同 DB 內 schema 演進與資料變更的可分段驗證流程、跟 1.12 cross-DB migration 分工">資料庫轉換實作</a>：執行層流程</li>
<li>01.7 <a href="/blog/backend/01-database/schema-migration-rollout-evidence/" data-link-title="1.7 Schema Migration Rollout 證據（Schema Migration Rollout Evidence）實作示範" data-link-desc="以訂單付款狀態欄位演進示範 schema migration 如何產出 evidence、release gate 與 incident decision log。">Schema Migration Rollout 證據</a>：production rollout evidence 與 gate 欄位</li>
<li>0.C4 <a href="/blog/backend/00-service-selection/cases/post-scale-migration-language-tool-architecture/" data-link-title="營運後技術轉換：語言、工具與架構何時該換" data-link-desc="服務營運一段時間後，如何判讀何時該轉語言、工具或架構，並用案例說明轉換動機。">營運後技術轉換</a>：決策層判讀</li>
<li>06.7 DR / rollback：migration rollback 演練</li>
<li>06.8 release gate：可逆性檢查</li>
<li>06.10 contract testing：schema 契約驗證</li>
<li>08.5 <a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">post-incident review</a>：migration 引發的事故型態</li>
</ul>
]]></content:encoded></item><item><title>6.12 Idempotency 與 Replay 驗證</title><link>https://tarrragon.github.io/blog/backend/06-reliability/idempotency-replay/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/idempotency-replay/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>為何 idempotency 是分散式系統一級屬性：retry / failover / replay 的前提&lt;/li>
&lt;li>idempotency key 的設計：來源、生命週期、儲存&lt;/li>
&lt;li>exactly-once 是幻象、at-least-once + idempotent 才實際&lt;/li>
&lt;li>replay 驗證：從 log / event store 重播能否得到相同最終狀態&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 message-queue&lt;/a> 的關係：consumer idempotency 是延伸專題&lt;/li>
&lt;li>payment / order / messaging 的 idempotency 模式差異&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">6.4 chaos&lt;/a> 的整合：注入重複訊息驗證冪等&lt;/li>
&lt;li>反模式：idempotency 只靠 DB unique constraint、無 key 設計；retry 後副作用重複；replay 路徑從未驗證&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">Idempotency&lt;/a> 與 replay 驗證是把重試、重播與副作用控制變成可驗證屬性，責任是讓 at-least-once 與 failover 不會把系統推向重複執行。&lt;/p>
&lt;p>這一頁處理的是分散式系統的重複輸入問題。只要有 retry、補償或訊息重送，冪等性就是正確性前提，把它當優化項會低估風險。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 idempotency 時，先看 key 的生命週期，再看 replay 是否能落在同一狀態。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>idempotency key 是否由 server 可控、可追蹤&lt;/li>
&lt;li>replay 路徑是否與 production 對齊&lt;/li>
&lt;li>late retry 是否會被誤視為新請求&lt;/li>
&lt;li>重複副作用是否能靠狀態機吸收&lt;/li>
&lt;/ul>
&lt;h2 id="案例對照">案例對照&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">Stripe&lt;/a>：交易流程需要嚴格控制重複請求。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">GitHub&lt;/a>：webhook / event replay 經常直接暴露冪等缺口。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/slack/" data-link-title="Slack" data-link-desc="Slack 通訊服務事故與外部狀態頁設計">Slack&lt;/a>：訊息與通知類流程特別依賴重複輸入控制。&lt;/li>
&lt;/ul>
&lt;h2 id="支付類-idempotency-的設計約束">支付類 Idempotency 的設計約束&lt;/h2>
&lt;p>支付類 idempotency 的核心約束是「key 邊界跟業務操作邊界一致」 — 同一筆支付的所有 retry 必須共用 key、跨支付 key 必須不同、key 不可被偽造、且要保留足夠重放證據。失敗代價（重複扣款、重複建單）讓這四個約束從 best practice 變成正確性前提。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1 Stripe Idempotency 與零停機遷移&lt;/a> 揭露的 idempotency key 跟 transaction-path observability 兩個機制（S1 case 直接列出）；以下實作層判讀條件屬通用工程知識展開、case 本身只給「key 跟業務邊界一致」這一條方向。&lt;/p>
&lt;p>實作層的判讀條件：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Key 邊界跟業務一致&lt;/strong>：同一筆支付的 retry 共用 idempotency key、跨支付 key 不同。Key 來源 / TTL / fallback 設計屬實作細節、跟 6.12 SSoT 描述的 server 端 key 設計呼應&lt;/li>
&lt;li>&lt;strong>保留足夠證據供重放&lt;/strong>：transaction-path observability 要覆蓋交易關鍵欄位、讓 reconciliation 跟稽核可重放判讀&lt;/li>
&lt;/ul>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/migration-safety/#%e4%ba%a4%e6%98%93%e9%a1%9e-migration-%e7%9a%84%e7%89%b9%e6%ae%8a%e6%80%a7" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration-safety 交易類段&lt;/a> 共用 transaction-path observability、避免 migration 期間 idempotency 判讀失效。支付 reconciliation 跟交易語義詳見 01 資料庫模組（具體章節依 reconciliation / transaction 主題、目前待 01 模組對應頁建立）。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>為何 idempotency 是分散式系統一級屬性：retry / failover / replay 的前提</li>
<li>idempotency key 的設計：來源、生命週期、儲存</li>
<li>exactly-once 是幻象、at-least-once + idempotent 才實際</li>
<li>replay 驗證：從 log / event store 重播能否得到相同最終狀態</li>
<li>跟 <a href="/blog/backend/03-message-queue/" data-link-title="模組三：訊息佇列與事件傳遞" data-link-desc="整理 durable queue、broker、retry、outbox 與 idempotency 的後端實務">03 message-queue</a> 的關係：consumer idempotency 是延伸專題</li>
<li>payment / order / messaging 的 idempotency 模式差異</li>
<li>跟 <a href="/blog/backend/06-reliability/chaos-testing/" data-link-title="6.4 chaos testing" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再按依賴類型設計注入、控制 blast radius 與收集證據">6.4 chaos</a> 的整合：注入重複訊息驗證冪等</li>
<li>反模式：idempotency 只靠 DB unique constraint、無 key 設計；retry 後副作用重複；replay 路徑從未驗證</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">Idempotency</a> 與 replay 驗證是把重試、重播與副作用控制變成可驗證屬性，責任是讓 at-least-once 與 failover 不會把系統推向重複執行。</p>
<p>這一頁處理的是分散式系統的重複輸入問題。只要有 retry、補償或訊息重送，冪等性就是正確性前提，把它當優化項會低估風險。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 idempotency 時，先看 key 的生命週期，再看 replay 是否能落在同一狀態。</p>
<p>重點訊號包括：</p>
<ul>
<li>idempotency key 是否由 server 可控、可追蹤</li>
<li>replay 路徑是否與 production 對齊</li>
<li>late retry 是否會被誤視為新請求</li>
<li>重複副作用是否能靠狀態機吸收</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/stripe/" data-link-title="Stripe" data-link-desc="Stripe Deploy Strategy / Game Day / Idempotency 實踐">Stripe</a>：交易流程需要嚴格控制重複請求。</li>
<li><a href="/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">GitHub</a>：webhook / event replay 經常直接暴露冪等缺口。</li>
<li><a href="/blog/backend/08-incident-response/cases/slack/" data-link-title="Slack" data-link-desc="Slack 通訊服務事故與外部狀態頁設計">Slack</a>：訊息與通知類流程特別依賴重複輸入控制。</li>
</ul>
<h2 id="支付類-idempotency-的設計約束">支付類 Idempotency 的設計約束</h2>
<p>支付類 idempotency 的核心約束是「key 邊界跟業務操作邊界一致」 — 同一筆支付的所有 retry 必須共用 key、跨支付 key 必須不同、key 不可被偽造、且要保留足夠重放證據。失敗代價（重複扣款、重複建單）讓這四個約束從 best practice 變成正確性前提。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">S1 Stripe Idempotency 與零停機遷移</a> 揭露的 idempotency key 跟 transaction-path observability 兩個機制（S1 case 直接列出）；以下實作層判讀條件屬通用工程知識展開、case 本身只給「key 跟業務邊界一致」這一條方向。</p>
<p>實作層的判讀條件：</p>
<ul>
<li><strong>Key 邊界跟業務一致</strong>：同一筆支付的 retry 共用 idempotency key、跨支付 key 不同。Key 來源 / TTL / fallback 設計屬實作細節、跟 6.12 SSoT 描述的 server 端 key 設計呼應</li>
<li><strong>保留足夠證據供重放</strong>：transaction-path observability 要覆蓋交易關鍵欄位、讓 reconciliation 跟稽核可重放判讀</li>
</ul>
<p>跟 <a href="/blog/backend/06-reliability/migration-safety/#%e4%ba%a4%e6%98%93%e9%a1%9e-migration-%e7%9a%84%e7%89%b9%e6%ae%8a%e6%80%a7" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration-safety 交易類段</a> 共用 transaction-path observability、避免 migration 期間 idempotency 判讀失效。支付 reconciliation 跟交易語義詳見 01 資料庫模組（具體章節依 reconciliation / transaction 主題、目前待 01 模組對應頁建立）。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>03 message-queue：consumer 端冪等設計</li>
<li>06.4 chaos：注入重複訊息驗證</li>
<li>06.7 DR：replay 作為回復手段的前提</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>用戶被重複扣款 / 重複建立資源、靠人工對帳發現</li>
<li>retry policy 開啟後事故變嚴重、不敢開 retry</li>
<li>replay 從 event store 跑一次、結果跟 production 不同</li>
<li>idempotency key 從 client 端帶上來、無 server 端 fallback</li>
<li>key TTL 過短、晚到的 retry 變成新請求</li>
</ul>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>03 message-queue：consumer idempotency 實作</li>
<li>06.4 chaos：注入重複訊息 / 故障 retry 場景</li>
<li>06.7 DR：replay 作為回復手段的前提</li>
<li>07 資安：idempotency key 不可被預測 / 偽造</li>
</ul>
]]></content:encoded></item><item><title>6.13 Performance Regression Gate</title><link>https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/performance-regression-gate/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Performance regression gate 守住系統的效能餘裕 — 避免看似功能正確的變更悄悄拖垮延遲、吞吐或成本。&lt;/p>
&lt;p>這一頁關心的是變更有沒有偷走系統的效能餘裕。沒有 gate，效能退化常常要等使用者感受到才會被看見。跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test&lt;/a> 的分工是：6.2 訂定 baseline 與 saturation point，6.13 確保每次變更不會讓 baseline 被偷走。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>效能 gate 的健康度取決於 baseline 是否穩定、regression 偵測是否足夠敏感。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>baseline 是否來自 production-like workload&lt;/li>
&lt;li>regression 是否能分辨 noise 與真實退化&lt;/li>
&lt;li>perf budget 是否跟 release gate 綁定&lt;/li>
&lt;li>當退化出現時，是否能快速定位到 code path 或依賴&lt;/li>
&lt;/ul>
&lt;h2 id="baseline-設定">Baseline 設定&lt;/h2>
&lt;p>Baseline 的責任是提供可比較的效能基準。沒有穩定 baseline，gate 判讀就無法區分「系統真的變慢了」跟「環境噪音」。&lt;/p>
&lt;p>Baseline 有三種來源，各自的可信度與維護成本不同。&lt;/p>
&lt;p>&lt;strong>Production percentile&lt;/strong>：從 production 的 latency / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/throughput/" data-link-title="Throughput" data-link-desc="整理系統單位時間內可處理的工作量">throughput&lt;/a> 分佈取 p50 / p95 / p99 作為基準。優點是最接近真實使用者體驗；限制是 production 流量本身有時段波動，需要選定穩定時段的統計窗口。適合作為最終判準，但不適合作為 CI 內的即時 gate（CI 環境跟 production 差異太大）。&lt;/p>
&lt;p>&lt;strong>CI benchmark history&lt;/strong>：在同一 CI 環境、同一 workload 下累積歷史趨勢。優點是環境一致，regression 可歸因到 code 變更；限制是 CI 環境本身可能有波動（runner 硬體、鄰居效應），需要 variance 控制。適合作為每次 merge 的即時 gate。&lt;/p>
&lt;p>&lt;strong>Load test 結果&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test&lt;/a> 產出的 saturation point 與 latency inflection。優點是覆蓋高負載場景；限制是執行成本高、不適合每次 push 跑。適合作為 scheduled path 的 baseline 校準來源。&lt;/p>
&lt;p>Baseline 更新頻率跟系統變更頻率對齊。高頻變更服務（每日多次 deploy）需要 rolling baseline（取最近 N 次 CI 結果的中位數）；低頻變更服務可以用固定 baseline 搭配季度校準。&lt;/p>
&lt;p>Baseline 品質的判準是自身 variance。若 baseline 的 p99 波動超過 5-10%，任何小於這個幅度的 regression 都落在噪音區間內，gate 無法可靠判讀。此時應先控制 variance（見下段），再設定 regression 門檻。&lt;/p>
&lt;h2 id="regression-判讀方法">Regression 判讀方法&lt;/h2>
&lt;p>Regression 判讀有三種方法，選擇取決於 CI 環境的穩定性與測試時間預算。&lt;/p>
&lt;h3 id="絕對門檻">絕對門檻&lt;/h3>
&lt;p>設定 p99 latency 上限（例如 200ms）或 throughput 下限（例如 1000 RPS），超過就 fail。&lt;/p>
&lt;p>這種方法實作最簡單，適合有明確 SLA 的服務。限制是容易誤報（環境噪音造成的瞬間飆高）或漏報（慢速退化每次只惡化 2-3ms，始終低於門檻，累積半年後才被注意到）。適合作為安全網而非主要判讀手段。&lt;/p>
&lt;h3 id="相對退化">相對退化&lt;/h3>
&lt;p>跟前一版 baseline 比較，退化超過 Y%（例如 latency 增加 &amp;gt; 10%）就 fail。&lt;/p>
&lt;p>這種方法能抓到漸進退化，因為每一次小幅惡化都會觸發。前提是 baseline 穩定 — 若 baseline 自身波動 8%，設定 10% 門檻幾乎沒有判讀空間。適合 variance 已被控制到 3-5% 以內的 CI 環境。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Performance regression gate 守住系統的效能餘裕 — 避免看似功能正確的變更悄悄拖垮延遲、吞吐或成本。</p>
<p>這一頁關心的是變更有沒有偷走系統的效能餘裕。沒有 gate，效能退化常常要等使用者感受到才會被看見。跟 <a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test</a> 的分工是：6.2 訂定 baseline 與 saturation point，6.13 確保每次變更不會讓 baseline 被偷走。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>效能 gate 的健康度取決於 baseline 是否穩定、regression 偵測是否足夠敏感。</p>
<p>重點訊號包括：</p>
<ul>
<li>baseline 是否來自 production-like workload</li>
<li>regression 是否能分辨 noise 與真實退化</li>
<li>perf budget 是否跟 release gate 綁定</li>
<li>當退化出現時，是否能快速定位到 code path 或依賴</li>
</ul>
<h2 id="baseline-設定">Baseline 設定</h2>
<p>Baseline 的責任是提供可比較的效能基準。沒有穩定 baseline，gate 判讀就無法區分「系統真的變慢了」跟「環境噪音」。</p>
<p>Baseline 有三種來源，各自的可信度與維護成本不同。</p>
<p><strong>Production percentile</strong>：從 production 的 latency / <a href="/blog/backend/knowledge-cards/throughput/" data-link-title="Throughput" data-link-desc="整理系統單位時間內可處理的工作量">throughput</a> 分佈取 p50 / p95 / p99 作為基準。優點是最接近真實使用者體驗；限制是 production 流量本身有時段波動，需要選定穩定時段的統計窗口。適合作為最終判準，但不適合作為 CI 內的即時 gate（CI 環境跟 production 差異太大）。</p>
<p><strong>CI benchmark history</strong>：在同一 CI 環境、同一 workload 下累積歷史趨勢。優點是環境一致，regression 可歸因到 code 變更；限制是 CI 環境本身可能有波動（runner 硬體、鄰居效應），需要 variance 控制。適合作為每次 merge 的即時 gate。</p>
<p><strong>Load test 結果</strong>：<a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test</a> 產出的 saturation point 與 latency inflection。優點是覆蓋高負載場景；限制是執行成本高、不適合每次 push 跑。適合作為 scheduled path 的 baseline 校準來源。</p>
<p>Baseline 更新頻率跟系統變更頻率對齊。高頻變更服務（每日多次 deploy）需要 rolling baseline（取最近 N 次 CI 結果的中位數）；低頻變更服務可以用固定 baseline 搭配季度校準。</p>
<p>Baseline 品質的判準是自身 variance。若 baseline 的 p99 波動超過 5-10%，任何小於這個幅度的 regression 都落在噪音區間內，gate 無法可靠判讀。此時應先控制 variance（見下段），再設定 regression 門檻。</p>
<h2 id="regression-判讀方法">Regression 判讀方法</h2>
<p>Regression 判讀有三種方法，選擇取決於 CI 環境的穩定性與測試時間預算。</p>
<h3 id="絕對門檻">絕對門檻</h3>
<p>設定 p99 latency 上限（例如 200ms）或 throughput 下限（例如 1000 RPS），超過就 fail。</p>
<p>這種方法實作最簡單，適合有明確 SLA 的服務。限制是容易誤報（環境噪音造成的瞬間飆高）或漏報（慢速退化每次只惡化 2-3ms，始終低於門檻，累積半年後才被注意到）。適合作為安全網而非主要判讀手段。</p>
<h3 id="相對退化">相對退化</h3>
<p>跟前一版 baseline 比較，退化超過 Y%（例如 latency 增加 &gt; 10%）就 fail。</p>
<p>這種方法能抓到漸進退化，因為每一次小幅惡化都會觸發。前提是 baseline 穩定 — 若 baseline 自身波動 8%，設定 10% 門檻幾乎沒有判讀空間。適合 variance 已被控制到 3-5% 以內的 CI 環境。</p>
<h3 id="統計顯著性">統計顯著性</h3>
<p>用統計檢定（t-test、Mann-Whitney U）判斷兩組測量的分佈是否有顯著差異。</p>
<p>這種方法最準確，能在高 variance 環境中篩掉噪音。限制是需要足夠樣本量 — CI 短時間測試可能只跑 10-20 次 iteration，樣本不足時統計功效低，真實退化也可能被判為不顯著。適合測試時間預算充裕的 scheduled path。</p>
<p>三種方法可以組合：fast path 用絕對門檻做安全網，slow path 用相對退化做主要判讀，scheduled path 用統計檢定做精確校準。</p>
<h2 id="variance-控制">Variance 控制</h2>
<p>CI 環境的噪音是 perf gate 最大的干擾源。噪音讓真實退化被遮蓋，也讓正常變更被誤報，兩者都會侵蝕團隊對 gate 的信任。</p>
<p>主要噪音來源與對應控制方式：</p>
<table>
  <thead>
      <tr>
          <th>噪音來源</th>
          <th>機制</th>
          <th>控制方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Shared runner 鄰居效應</td>
          <td>其他 job 搶 CPU / memory / I/O</td>
          <td>Dedicated runner 或 ephemeral instance</td>
      </tr>
      <tr>
          <td>Cold start</td>
          <td>JIT warmup、cache miss、connection 建立</td>
          <td>Warmup iteration（丟棄前 N 次結果）</td>
      </tr>
      <tr>
          <td>GC pause</td>
          <td>記憶體壓力觸發 stop-the-world GC</td>
          <td>固定 heap size、GC log 同步收集</td>
      </tr>
      <tr>
          <td>Network jitter</td>
          <td>跨服務通訊的延遲波動</td>
          <td>Local dependency（mock / sidecar）</td>
      </tr>
      <tr>
          <td>Hardware 差異</td>
          <td>不同世代 runner 的 CPU 效能不同</td>
          <td>Pinned hardware config / instance type</td>
      </tr>
  </tbody>
</table>
<p>Variance 控制的投資報酬是讓 regression 門檻可以設得更敏感。當 variance 從 15% 降到 3%，gate 就能攔住 5% 的退化；否則只能設 20% 門檻，等於放過大量漸進退化。</p>
<p>連到 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a> 的 environment 隔離段 — perf gate 需要的 runner 隔離等級通常高於一般功能測試。</p>
<h2 id="micro-benchmark-vs-end-to-end-perf-test">Micro benchmark vs End-to-end perf test</h2>
<p>兩種測試粒度服務不同的判讀需求，分工而非替代。</p>
<p><strong>Micro benchmark</strong> 對準單一函式、code path 或演算法。variance 小（不涉及 I/O、network、GC 壓力低）、回饋快（秒級）、定位精準（退化直接指向特定函式）。限制是覆蓋不到跨服務退化、serialization 成本或 middleware 堆疊的效能影響。適合跑在 CI fast path（每次 push）。</p>
<p><strong>End-to-end perf test</strong> 覆蓋真實請求路徑，從 API gateway 到 database 到 response。能抓到跨層退化（middleware 累積、serialization 成本、connection pool 競爭），但 variance 大、定位困難（退化可能來自任何一層）。適合跑在 CI slow path（merge gate）或 scheduled path。</p>
<p>分工原則：micro benchmark 負責守住 code-level baseline，end-to-end perf test 負責守住 service-level baseline。兩者都 fail 時，micro benchmark 的結果通常能直接定位 regression 來源；只有 end-to-end fail 時，需要搭配 profiling diff 做進一步歸因。</p>
<h2 id="退化定位與行動">退化定位與行動</h2>
<p>Gate 攔住 regression 後，下一步是定位來源並決定行動。</p>
<p><strong>Profiling diff</strong>：比較兩版的 flame graph 或 CPU profile，找出新增的 hot path。連到 <a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 continuous profiling</a> — 若 production 已有 continuous profiling，可以直接比較 canary 與 stable 版本的 profile 差異，定位精度高於 CI 環境的 benchmark。</p>
<p><strong>Commit bisect</strong>：在 CI benchmark history 中二分搜尋 regression 引入點。當多個 commit 合併後才觸發 gate fail，bisect 能縮小到具體 commit。前提是 CI benchmark 有逐 commit 的歷史紀錄。</p>
<p>定位後的行動有三種：</p>
<ul>
<li><strong>修復</strong>：regression 來源明確、修復成本可接受。這是預設行動。</li>
<li><strong>接受</strong>：regression 是預期的 trade-off（例如安全性改善帶來的加密成本）。此時更新 baseline，並在 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 evidence handoff</a> 記錄接受理由。</li>
<li><strong>延後</strong>：regression 來源複雜、修復需要大幅重構。記錄到 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a> 並設定修復期限。延後的風險是多次延後累積成使用者可感知的退化。</li>
</ul>
<h2 id="產業情境串流與媒體服務">產業情境：串流與媒體服務</h2>
<p>串流服務的效能 regression 量測維度跟一般 web service 不同。API latency 只是其中一層，媒體交付品質才是使用者直接感受的指標。</p>
<p>串流特有的 regression 指標包含 video start time（TTFB to first frame）、rebuffering rate（播放中斷頻率）、bitrate switches per session（畫質跳動次數）與 ABR algorithm response time（adaptive bitrate 的反應速度）。這些指標需要專門的量測管線，CI 環境的 mock player 很難完全模擬真實觀看行為，canary 階段的 real user monitoring 是更可靠的 regression 偵測來源。</p>
<p>Transcoding pipeline 的 regression 需要三維判讀。新 codec 或 encoder 版本可能改善壓縮率但增加 encoding latency，CI gate 需要同時量化 encoding speed、output quality 與 cost — 只看其中一個維度會漏掉 trade-off。例如 AV1 encoder 比 H.264 壓縮率更好，但 encoding 時間可能增加數倍，若 gate 只看 latency 就會擋住合理的品質升級。</p>
<p>CDN cache hit rate 是隱性的 regression 指標。code 變更如果改變了 cache key 策略或 content fingerprint，CDN cache hit rate 會下降，回源流量上升，間接造成 origin latency 惡化與成本跳升。這類 regression 在 staging 壓測中看不到（staging 沒有 CDN 快取層），需要 canary 階段的 CDN 層監控才能偵測。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">Google G1</a>：效能退化會加速 error budget 消耗。當 latency regression 導致 SLO breach 頻率上升，perf gate 的門檻應與 error budget 政策連動 — budget 健康時接受較寬鬆的門檻，budget 緊繃時收緊。</li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/capacity-headroom-and-oncall-tiering/" data-link-title="LinkedIn：Capacity Headroom 與 On-call 分層" data-link-desc="把容量預測與值班分層綁在一起，降低高峰時段的升級混亂與恢復延遲。">LinkedIn L1</a>：效能退化直接壓縮 capacity headroom。當 p99 latency 上升 20%，等效 headroom 下降，可能觸發 on-call 層級升級。perf gate 的門檻應考慮 headroom ratio 的安全邊界。</li>
<li><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify H1</a>：高峰前的效能退化風險比平時更高。BFCM 前收緊 perf gate 門檻，避免峰值期間 latency regression 與流量尖峰疊加。</li>
<li><a href="/blog/backend/06-reliability/cases/linkedin/automated-load-testing-and-capacity-forecasting/" data-link-title="LinkedIn：Automated Load Testing 與 Capacity Forecasting" data-link-desc="持續壓測驅動容量預測：用自動化回饋取代一次性壓測的容量規劃。">LinkedIn L2</a>：持續壓測作為 regression 偵測的輸入來源 — 自動化壓測的 saturation point 趨勢可以補充 CI benchmark 看不到的系統級退化。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>連續多版微小退化、累積後才被發現</td>
          <td>相對退化門檻未設或太寬鬆，改用 rolling baseline + 相對退化判讀</td>
          <td>設 rolling baseline + 5-10% 相對退化 threshold</td>
      </tr>
      <tr>
          <td>大版本升級 latency 漲、定位困難</td>
          <td>缺少逐 commit benchmark history，補 commit bisect 機制</td>
          <td>每個 commit 跑 micro benchmark、保留歷史</td>
      </tr>
      <tr>
          <td>Benchmark variance &gt; 退化幅度</td>
          <td>CI 環境噪音未控制，先降 variance 再設門檻</td>
          <td>改用 dedicated runner + warmup iteration</td>
      </tr>
      <tr>
          <td>Canary 只看 error rate、不看 latency</td>
          <td>perf gate 與 canary 判讀脫鉤，把 latency percentile 加入 canary</td>
          <td>補 p95/p99 latency 到 canary 判讀指標</td>
      </tr>
      <tr>
          <td>第三方依賴效能變化未納入 baseline</td>
          <td>baseline 只看本服務、漏掉依賴，補 end-to-end perf test 覆蓋</td>
          <td>加 end-to-end perf test 到 slow path</td>
      </tr>
      <tr>
          <td>Gate 頻繁誤報、團隊開始忽略</td>
          <td>門檻未對齊 variance，或測試環境不穩定，先修 variance 再調門檻</td>
          <td>先量測 variance、再設 threshold = baseline + 2σ</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/04-observability/continuous-profiling/" data-link-title="4.9 Continuous Profiling" data-link-desc="把 CPU / memory / lock profile 從一次性除錯升級為持續訊號">4.9 continuous profiling</a>：退化定位到 callstack</li>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">5 部署平台</a>：canary 階段的 perf gate</li>
<li><a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>：perf test 在 CI 分層中的位置與 runner 隔離</li>
<li><a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load test</a>：baseline 來源與 saturation point</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：退化觸發 freeze</li>
<li><a href="/blog/backend/06-reliability/feature-flag-governance/" data-link-title="6.17 Feature Flag Governance" data-link-desc="把 feature flag 從上線開關升級為有角色分類、lifecycle 管理與 debt 治理的 runtime artifact">6.17 feature flag</a>：flag 切換後的效能驗證</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt</a>：延後修復的 regression 進入 debt backlog</li>
<li><a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 evidence handoff</a>：接受 regression 時的理由留存</li>
</ul>
]]></content:encoded></item><item><title>6.14 Dependency Reliability Budget</title><link>https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>為何依賴需要 budget：自家服務的 SLO 是依賴 SLO 的乘積&lt;/li>
&lt;li>依賴類別：內部服務、第三方 API、SaaS、基礎設施（DB / cache / queue）&lt;/li>
&lt;li>依賴 SLA 對照：vendor 公布的 SLA 跟 observed reliability 的差距&lt;/li>
&lt;li>budget 計算：依賴 99.9% × 自家 99.9% = 99.8% 上限&lt;/li>
&lt;li>降級設計：依賴失效時的 fallback / cache / 隊列緩衝&lt;/li>
&lt;li>circuit breaker 與 budget 的關聯&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6 SLO&lt;/a> 的整合：依賴 budget 是 SLO 算式的一部分&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13 topology&lt;/a> 的整合：依賴拓撲提供 budget 評估資料&lt;/li>
&lt;li>反模式：SLO 訂目標時忽略依賴可靠性；vendor SLA 抄進合約但無監測；依賴掛了才發現有依賴&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Dependency reliability budget 是把外部服務與跨團隊依賴的可靠性納入設計約束，責任是避免把自己系統的目標建立在不可控前提上。&lt;/p>
&lt;p>這一頁處理的是依賴一旦變差，自己服務還能保住多少功能。當依賴不是自己能修的時候，budget 就是把不確定性明文化。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀依賴風險時，不只看 SLA，而是看依賴失效後的降級能力與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a>。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>依賴是否有明確 failure domain&lt;/li>
&lt;li>是否有 graceful degradation 或 fallback&lt;/li>
&lt;li>budget 是否會隨依賴變更而更新&lt;/li>
&lt;li>外部 outage 是否能快速路由到替代策略&lt;/li>
&lt;/ul>
&lt;h2 id="案例對照">案例對照&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/aws-s3/" data-link-title="AWS S3" data-link-desc="AWS S3 重大事故時間線與架構脈絡">AWS S3&lt;/a>：基礎儲存依賴的邊界一旦縮小，整體可靠性就會被放大影響。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/" data-link-title="Cloudflare" data-link-desc="Cloudflare 全球 edge 事故時間線與架構脈絡">Cloudflare&lt;/a>：edge / control-plane 依賴需要有明確降級路徑。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/azure-ad/" data-link-title="Azure AD / Entra ID" data-link-desc="Microsoft Identity 控制面失效與 cascading 影響">Azure AD&lt;/a>：身份依賴失效時，影響通常跨產品、跨流程。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">Amazon A2&lt;/a>：static stability 讓資料面在控制面失效時仍能服務，constant work 避免恢復放大。控制面是依賴 budget 中風險最高的項目。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">Shopify H2&lt;/a>：pod 隔離把依賴 budget 從全域帳本拆成 per-pod 結構，resiliency matrix 把依賴缺口可視化。&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/" data-link-title="Meta：BGP 事故與控制面恢復順序" data-link-desc="當回復工具依賴已故障的系統：2021-10 事故揭露控制面恢復順序與 out-of-band 存取的設計約束。">Meta M2&lt;/a>：回復工具依賴被回復的系統（BGP / DNS / 遠端存取），揭露控制面的隱性循環依賴。&lt;/li>
&lt;/ul>
&lt;h2 id="失效局部化cell-邊界跟-shuffle-sharding">失效局部化：cell 邊界跟 shuffle sharding&lt;/h2>
&lt;p>失效局部化是把單一依賴退化限制在最小可影響範圍的能力。把「依賴 budget」從統一全域帳本拆成 per-cell 可用度結構、是這層治理的核心責任。失效局部化要解四個子問題：擴散邊界、熱點重疊、控制面解耦、失敗模式工作量恆定。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">A1 Amazon Shuffle Sharding 與 Cell 邊界&lt;/a>：揭露四個機制對應上述四個子問題 — cell 邊界（擴散邊界）、shuffle sharding（熱點重疊）、static stability（控制面解耦）、constant work（失敗模式工作量恆定）。這四個機制把恢復策略從「全域搶救」轉為「分批收斂」。Cell 邊界是 6.14 SSoT；實驗時 blast radius 的邊界控制由 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment-safety-boundary&lt;/a> 處理、兩者邊界互補（前者是常態架構、後者是實驗範圍控制）。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>為何依賴需要 budget：自家服務的 SLO 是依賴 SLO 的乘積</li>
<li>依賴類別：內部服務、第三方 API、SaaS、基礎設施（DB / cache / queue）</li>
<li>依賴 SLA 對照：vendor 公布的 SLA 跟 observed reliability 的差距</li>
<li>budget 計算：依賴 99.9% × 自家 99.9% = 99.8% 上限</li>
<li>降級設計：依賴失效時的 fallback / cache / 隊列緩衝</li>
<li>circuit breaker 與 budget 的關聯</li>
<li>跟 <a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6 SLO</a> 的整合：依賴 budget 是 SLO 算式的一部分</li>
<li>跟 <a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13 topology</a> 的整合：依賴拓撲提供 budget 評估資料</li>
<li>反模式：SLO 訂目標時忽略依賴可靠性；vendor SLA 抄進合約但無監測；依賴掛了才發現有依賴</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Dependency reliability budget 是把外部服務與跨團隊依賴的可靠性納入設計約束，責任是避免把自己系統的目標建立在不可控前提上。</p>
<p>這一頁處理的是依賴一旦變差，自己服務還能保住多少功能。當依賴不是自己能修的時候，budget 就是把不確定性明文化。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀依賴風險時，不只看 SLA，而是看依賴失效後的降級能力與 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a>。</p>
<p>重點訊號包括：</p>
<ul>
<li>依賴是否有明確 failure domain</li>
<li>是否有 graceful degradation 或 fallback</li>
<li>budget 是否會隨依賴變更而更新</li>
<li>外部 outage 是否能快速路由到替代策略</li>
</ul>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/aws-s3/" data-link-title="AWS S3" data-link-desc="AWS S3 重大事故時間線與架構脈絡">AWS S3</a>：基礎儲存依賴的邊界一旦縮小，整體可靠性就會被放大影響。</li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/" data-link-title="Cloudflare" data-link-desc="Cloudflare 全球 edge 事故時間線與架構脈絡">Cloudflare</a>：edge / control-plane 依賴需要有明確降級路徑。</li>
<li><a href="/blog/backend/08-incident-response/cases/azure-ad/" data-link-title="Azure AD / Entra ID" data-link-desc="Microsoft Identity 控制面失效與 cascading 影響">Azure AD</a>：身份依賴失效時，影響通常跨產品、跨流程。</li>
<li><a href="/blog/backend/06-reliability/cases/amazon/static-stability-and-constant-work/" data-link-title="Amazon：Static Stability 與 Constant Work Pattern" data-link-desc="控制面失效時資料面如何維持服務：用快取、預計算與固定工作量避免恢復放大。">Amazon A2</a>：static stability 讓資料面在控制面失效時仍能服務，constant work 避免恢復放大。控制面是依賴 budget 中風險最高的項目。</li>
<li><a href="/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">Shopify H2</a>：pod 隔離把依賴 budget 從全域帳本拆成 per-pod 結構，resiliency matrix 把依賴缺口可視化。</li>
<li><a href="/blog/backend/06-reliability/cases/meta/bgp-control-plane-recovery-ordering/" data-link-title="Meta：BGP 事故與控制面恢復順序" data-link-desc="當回復工具依賴已故障的系統：2021-10 事故揭露控制面恢復順序與 out-of-band 存取的設計約束。">Meta M2</a>：回復工具依賴被回復的系統（BGP / DNS / 遠端存取），揭露控制面的隱性循環依賴。</li>
</ul>
<h2 id="失效局部化cell-邊界跟-shuffle-sharding">失效局部化：cell 邊界跟 shuffle sharding</h2>
<p>失效局部化是把單一依賴退化限制在最小可影響範圍的能力。把「依賴 budget」從統一全域帳本拆成 per-cell 可用度結構、是這層治理的核心責任。失效局部化要解四個子問題：擴散邊界、熱點重疊、控制面解耦、失敗模式工作量恆定。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">A1 Amazon Shuffle Sharding 與 Cell 邊界</a>：揭露四個機制對應上述四個子問題 — cell 邊界（擴散邊界）、shuffle sharding（熱點重疊）、static stability（控制面解耦）、constant work（失敗模式工作量恆定）。這四個機制把恢復策略從「全域搶救」轉為「分批收斂」。Cell 邊界是 6.14 SSoT；實驗時 blast radius 的邊界控制由 <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment-safety-boundary</a> 處理、兩者邊界互補（前者是常態架構、後者是實驗範圍控制）。</p>
<p>把 cell 邊界跟 shuffle sharding 視為依賴 budget 的前置結構：先限制擴散邊界、再談恢復策略。budget 算式裡的「依賴失效」應該對應到「最大可影響 cell」、不是「整個服務全停」。</p>
<h2 id="跨區故障跟回復順序">跨區故障跟回復順序</h2>
<p>跨區故障的核心責任是把「單區極限失效」跟「跨區連鎖退化」拆成兩個治理面。fault domain 限制單區擴散、ordered failover 控制回復節奏、dependency isolation 切斷共享路徑放大風險、三者構成跨區治理 contract。大規模平台的關鍵風險來自跨區相依引發的連鎖退化 — 單點失效只是觸發點、真正的擴散面在共享相依路徑。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/meta/region-failover-and-reliability-boundaries/" data-link-title="Meta：Region Failover 與可靠性邊界" data-link-desc="把跨區故障視為邊界治理問題，透過分區隔離與回復順序控制失效擴散。">M1 Meta Region Failover 邊界治理</a>：揭露三個機制 — region fault domain（影響面最多到哪裡）、ordered failover（先恢復哪條路徑）、dependency isolation（共享相依如何降風險）。</p>
<p>回復順序的核心是分批恢復、不同時恢復所有路徑。同時恢復多條路徑可能在剛恢復的依賴上引發回源放大或連鎖過載、把原本可控的回復變成第二次故障。實際的做法跟 ordered failover 對齊：依事故 timeline 跟團隊既定 runbook 安排回復批次、每批驗證 baseline 穩定後再進下一批。具體的批次設計跟 ordered failover 證據交給 <a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 containment-recovery-strategy</a>。</p>
<h2 id="跨團隊-reliability-契約">跨團隊 reliability 契約</h2>
<p>跨團隊 reliability 契約的核心責任是讓「依賴 budget」變成「契約欄位」：每個被依賴的服務承諾哪些 SLI、提供哪些降級路徑、failure mode 是什麼。團隊自治程度高的組織需要共同契約把跨服務的可靠性最低標準對齊、避免風險在整合時集中爆發。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">SP1 Spotify 平台工程與可靠性契約</a>：揭露三個機制 — reliability contract（每個服務最低要提供什麼）、platform self-service（標準如何降低導入成本）、cross-team evidence（證據如何跨團隊共享）。SP1 case 主場景是內部跨團隊契約、不是 vendor 軸；vendor SLA 治理請見前段「依賴類別」跟 <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">04.18 operating model</a> 的 ownership 邊界。</p>
<p>契約讓內部依賴 budget 可以基於 observed reliability（被依賴服務實際的 SLI 觀測值）、補強只靠 vendor SLA 的不足 — 後者通常是上界、不反映實際失效特性。</p>
<h2 id="產業情境saas-與-b2b-服務的依賴約束">產業情境：SaaS 與 B2B 服務的依賴約束</h2>
<p>SaaS 服務的可靠性直接綁定客戶合約，依賴 budget 的分配需要按最嚴格的 SLA 需求設計。enterprise 客戶要求 99.99%、self-serve 客戶接受 99.9% — 共享依賴的 budget 必須對齊最高 SLA，否則高階客戶的承諾無法兌現。</p>
<p>多租戶共享依賴的 budget 分配是 SaaS 特有的治理問題。所有租戶共用同一組 DB / cache / queue，但高 SLA 客戶對依賴可靠性的要求更嚴格。實務做法是把高 SLA 客戶路由到獨立依賴池（dedicated instance / priority queue），或在共享依賴上做租戶級隔離（connection pool per tenant / rate limit per tenant）。隔離策略跟 <a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon A1 的 shuffle sharding</a> 同源 — 差異在 SaaS 的隔離單位是租戶合約等級而非 cell。</p>
<p>第三方依賴的 SLA 傳遞是另一個 SaaS 常見壓力。SaaS 產品常依賴其他 SaaS（payment provider / email service / auth provider），這些依賴的 SLA 是自身 SLA 的理論上限。若 payment provider 只承諾 99.9%，自身對客戶承諾 99.99% 的結帳成功率就需要 fallback 設計（如多 provider 切換、本地排隊 + 延遲處理）。budget 計算時要把第三方依賴的 observed reliability 納入，而非照抄 vendor SLA。</p>
<p>跟 <a href="/blog/backend/06-reliability/cases/spotify/platform-engineering-and-reliability-contracts/" data-link-title="Spotify：平台工程與可靠性契約" data-link-desc="用平台契約統一服務團隊的可靠性最低標準，降低跨團隊變更造成的隱性風險。">Spotify SP1 平台工程與可靠性契約</a> 的關聯：分散團隊共用可靠性基線的契約模型，在 SaaS 組織中同時服務內部團隊對齊與外部客戶 SLA 承諾兩個面向。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>6.6 SLO / <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a>：把依賴可靠性納入目標計算</li>
<li>6.8 release gate：把依賴健康度變成放行條件</li>
<li>08.15 vendor 事故：第三方事故的事中處理</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>自家服務 SLO 高於依賴 SLA 的乘積、目標不可達</li>
<li>第三方 API 退化時無 observed metric、靠用戶投訴發現</li>
<li>vendor SLA credit 從未請領、無流程</li>
<li>新依賴接入無 reliability review</li>
<li>關鍵路徑上有「不知道掛了會怎樣」的依賴</li>
</ul>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.13 topology：依賴自動發現</li>
<li>06.6 SLO：依賴 budget 納入 SLO 算式</li>
<li>06.10 contract testing：依賴契約穩定性</li>
<li>08.15 vendor 事故：依賴方掛掉的決策模型</li>
</ul>
]]></content:encoded></item><item><title>6.15 Environment Parity 與漂移控制</title><link>https://tarrragon.github.io/blog/backend/06-reliability/environment-parity/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/environment-parity/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Staging 通過但 production 上線失敗 — 這類事故的根因常常是環境差異。Environment parity 把 staging 與 production 的差異視為一級風險，要求會影響行為的差異被識別與管理。&lt;/p>
&lt;p>三個環境完全相同既不可能也不必要，但未被追蹤的差異會讓測試結論與真實服務脫鉤。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>Parity 漂移最先暴露的訊號是差異是否可見，接著決定差異是否會改變驗證結果。&lt;/p>
&lt;p>判讀時看四件事：&lt;/p>
&lt;ul>
&lt;li>config drift 是否有清單與責任人&lt;/li>
&lt;li>data shape 是否接近 production&lt;/li>
&lt;li>infra parity 是否涵蓋 network、storage、identity&lt;/li>
&lt;li>release 前是否知道哪些差異會影響判讀&lt;/li>
&lt;/ul>
&lt;h2 id="漂移來源分類">漂移來源分類&lt;/h2>
&lt;p>Parity 漂移按來源分類，不同來源的風險特徵與偵測手段不同。&lt;/p>
&lt;h3 id="config-drift">Config drift&lt;/h3>
&lt;p>環境變數、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool&lt;/a> size、retry config、feature flag 在 staging 與 prod 不同步。這是最常見的漂移來源，因為 config 變更頻率高且通常不走完整 review 流程。&lt;/p>
&lt;p>典型暴露時機：staging 測試通過，但 prod 上線後 timeout 觸發或 pool 耗盡，根因是 staging 的 timeout 設定比 prod 寬鬆。偵測手段：定期 config snapshot diff，標註差異項目與 owner。&lt;/p>
&lt;h3 id="scale-drift">Scale drift&lt;/h3>
&lt;p>Staging 用單機或少量 replica，prod 用多區多 replica。query plan 在小資料集走 index scan、在大資料集走 table scan；&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool&lt;/a> 在低併發下不飽和、在高併發下排隊；load balancer 在少 replica 時的路由行為跟多 replica 時不同。&lt;/p>
&lt;p>典型暴露時機：壓測在 staging 通過，但 prod 出現 connection pool 耗盡或 load balancer 的 least-connection 策略在高 replica 數下行為不同。偵測手段：對照 staging 與 prod 的 replica count、resource quota、auto-scaling 設定。&lt;/p>
&lt;h3 id="data-drift">Data drift&lt;/h3>
&lt;p>Staging 資料量遠小於 prod，資料分佈也不同。index scan vs table scan 的切換點跟資料量直接相關；cache hit ratio 跟 key 分佈與資料量相關；pagination 行為在千筆與百萬筆資料下差異顯著。&lt;/p>
&lt;p>典型暴露時機：staging 的查詢 &amp;lt; 50ms，prod 同一查詢 &amp;gt; 2s，根因是 staging 資料量不足以觸發 full table scan。偵測手段：比較 staging 與 prod 的資料表 row count 與 key 分佈統計。&lt;/p>
&lt;h3 id="dependency-drift">Dependency drift&lt;/h3>
&lt;p>Staging 跟 prod 使用不同版本的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/database/" data-link-title="Database" data-link-desc="說明 database 在後端系統中如何承擔正式狀態、查詢與一致性責任">database&lt;/a> engine、cache、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker&lt;/a> 或 cloud service。版本差異的行為差異通常在 edge case 才暴露：不同版本的 SQL dialect、cache eviction policy、message ordering guarantee 可能不同。&lt;/p>
&lt;p>典型暴露時機：DB engine 小版本升級改變了 query optimizer 行為，staging 早已升級但 prod 延遲升級，兩邊 query plan 不同。偵測手段：維護 dependency version matrix，每次版本變更時檢查跨環境一致性。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Staging 通過但 production 上線失敗 — 這類事故的根因常常是環境差異。Environment parity 把 staging 與 production 的差異視為一級風險，要求會影響行為的差異被識別與管理。</p>
<p>三個環境完全相同既不可能也不必要，但未被追蹤的差異會讓測試結論與真實服務脫鉤。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Parity 漂移最先暴露的訊號是差異是否可見，接著決定差異是否會改變驗證結果。</p>
<p>判讀時看四件事：</p>
<ul>
<li>config drift 是否有清單與責任人</li>
<li>data shape 是否接近 production</li>
<li>infra parity 是否涵蓋 network、storage、identity</li>
<li>release 前是否知道哪些差異會影響判讀</li>
</ul>
<h2 id="漂移來源分類">漂移來源分類</h2>
<p>Parity 漂移按來源分類，不同來源的風險特徵與偵測手段不同。</p>
<h3 id="config-drift">Config drift</h3>
<p>環境變數、<a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a>、<a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a> size、retry config、feature flag 在 staging 與 prod 不同步。這是最常見的漂移來源，因為 config 變更頻率高且通常不走完整 review 流程。</p>
<p>典型暴露時機：staging 測試通過，但 prod 上線後 timeout 觸發或 pool 耗盡，根因是 staging 的 timeout 設定比 prod 寬鬆。偵測手段：定期 config snapshot diff，標註差異項目與 owner。</p>
<h3 id="scale-drift">Scale drift</h3>
<p>Staging 用單機或少量 replica，prod 用多區多 replica。query plan 在小資料集走 index scan、在大資料集走 table scan；<a href="/blog/backend/knowledge-cards/connection-pool/" data-link-title="Connection Pool" data-link-desc="說明連線池如何限制下游資源並影響服務容量">connection pool</a> 在低併發下不飽和、在高併發下排隊；load balancer 在少 replica 時的路由行為跟多 replica 時不同。</p>
<p>典型暴露時機：壓測在 staging 通過，但 prod 出現 connection pool 耗盡或 load balancer 的 least-connection 策略在高 replica 數下行為不同。偵測手段：對照 staging 與 prod 的 replica count、resource quota、auto-scaling 設定。</p>
<h3 id="data-drift">Data drift</h3>
<p>Staging 資料量遠小於 prod，資料分佈也不同。index scan vs table scan 的切換點跟資料量直接相關；cache hit ratio 跟 key 分佈與資料量相關；pagination 行為在千筆與百萬筆資料下差異顯著。</p>
<p>典型暴露時機：staging 的查詢 &lt; 50ms，prod 同一查詢 &gt; 2s，根因是 staging 資料量不足以觸發 full table scan。偵測手段：比較 staging 與 prod 的資料表 row count 與 key 分佈統計。</p>
<h3 id="dependency-drift">Dependency drift</h3>
<p>Staging 跟 prod 使用不同版本的 <a href="/blog/backend/knowledge-cards/database/" data-link-title="Database" data-link-desc="說明 database 在後端系統中如何承擔正式狀態、查詢與一致性責任">database</a> engine、cache、<a href="/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker</a> 或 cloud service。版本差異的行為差異通常在 edge case 才暴露：不同版本的 SQL dialect、cache eviction policy、message ordering guarantee 可能不同。</p>
<p>典型暴露時機：DB engine 小版本升級改變了 query optimizer 行為，staging 早已升級但 prod 延遲升級，兩邊 query plan 不同。偵測手段：維護 dependency version matrix，每次版本變更時檢查跨環境一致性。</p>
<h3 id="infra-drift">Infra drift</h3>
<p>Network topology、DNS 解析路徑、TLS 配置、identity provider 設定在不同環境不同。跨服務通訊路徑的差異最難偵測，因為這些差異通常在正常流量下不可見，只在跨區切換、failover 或 mTLS 驗證時才暴露。</p>
<p>典型暴露時機：staging 用同區呼叫、prod 跨區呼叫，latency 差異導致 timeout 觸發條件不同。偵測手段：infra-as-code diff 與定期 topology audit。</p>
<h2 id="漂移偵測機制">漂移偵測機制</h2>
<p>偵測環境漂移需要多種手段組合，單一手段無法覆蓋所有漂移來源。</p>
<h3 id="automated-config-diff">Automated config diff</h3>
<p>定期比較 staging 與 prod 的 config snapshot，輸出差異清單並標註 owner。diff 結果按風險等級分類：會影響行為的差異（timeout、pool size、retry policy）標為高風險；只影響標籤或描述的差異標為低風險。高風險差異在 release review 時必須被討論。</p>
<h3 id="contract--parity-test">Contract + parity test</h3>
<p><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">Contract test</a> 驗證 API 邊界（schema、欄位、狀態碼）在不同環境的一致性。Parity test 更進一步，驗證同一請求在 staging 與 prod 的行為結果是否相同。兩者互補：contract test 抓結構差異，parity test 抓行為差異。</p>
<h3 id="shadow-traffic">Shadow traffic</h3>
<p>用 prod 流量的副本打 staging，比較回應差異。shadow traffic 能偵測 data drift 和 dependency drift，因為它用真實請求觸發真實查詢路徑。限制是寫入操作需要隔離處理（shadow write 不能影響 prod 資料），且 staging 需要有足夠容量承接 prod 流量副本。跟 <a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load testing</a> 的 synthetic traffic 限制互補 — synthetic traffic 偵測不到的環境差異，shadow traffic 通常能暴露。</p>
<h3 id="canary-作為中間層">Canary 作為中間層</h3>
<p>Canary 環境處於 staging 與 prod 之間，用少量真實流量驗證變更。parity 差異在 canary 階段暴露的成本遠低於在 prod 全量暴露。canary 的偵測價值在於它跑在 prod infra 上但只承接部分流量，能暴露 scale drift 和 infra drift。</p>
<p>canary 的限制是覆蓋時間：流量比例低時，low-frequency 的 edge case 可能在 canary 期間不出現。canary 時間越長覆蓋率越高，但拉長 canary 會延遲交付。這個 trade-off 要對齊變更風險等級 — 高風險變更拉長 canary，低風險變更可以縮短。</p>
<h2 id="production-like-data-策略">Production-like data 策略</h2>
<p>Staging 需要接近 prod 的資料才能讓驗證結果可信。三種策略各有 trade-off。</p>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>真實度</th>
          <th>隱私風險</th>
          <th>維護成本</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Production sample（脫敏）</td>
          <td>高</td>
          <td>中</td>
          <td>高</td>
          <td>query plan 敏感、資料分佈關鍵</td>
      </tr>
      <tr>
          <td>Synthetic generation</td>
          <td>中</td>
          <td>低</td>
          <td>中</td>
          <td>功能驗證為主、分佈次要</td>
      </tr>
      <tr>
          <td>Schema-only + seed</td>
          <td>低</td>
          <td>低</td>
          <td>低</td>
          <td>早期開發、schema 驗證</td>
      </tr>
  </tbody>
</table>
<p>Production sample 從 prod 抽樣後做 PII masking，資料分佈最接近真實，但需要遮罩管線且每次 schema 變更後要重新抽樣。Synthetic generation 用程式生成接近 prod 分佈的假資料，安全性高但分佈模型需要維護，偏移累積後資料特徵會跟 prod 脫鉤。Schema-only + seed 只複製 schema、用 seed 填少量資料，速度最快但跟 prod 差距最大，query plan 幾乎無法對齊。</p>
<p>選擇策略的判斷條件：如果系統的風險集中在 query performance 或 data-dependent 行為，production sample 是必要的；如果風險集中在功能正確性，synthetic generation 足夠；如果還在早期開發階段，schema-only + seed 可以先用，但上線前要升級。詳見 <a href="/blog/backend/06-reliability/test-data-management/" data-link-title="6.16 Test Data Management" data-link-desc="把 fixture / seed / production-like data 作為跨模組共用 artifact，治理資料層次、遮罩策略與可重現性">6.16 test data management</a>。</p>
<h2 id="parity-治理流程">Parity 治理流程</h2>
<p>環境漂移是持續的，一次對齊不代表之後不會漂移。治理流程的責任是讓漂移保持可見且可決策。</p>
<p><strong>維護環境差異清單</strong>：記錄所有已知的環境差異，每項標注 owner、風險等級與存在理由。有些差異是刻意的（staging 用較小 instance 節省成本），有些是遺忘的（某次 prod hotfix 沒同步到 staging）。區分刻意與遺忘的差異，才能知道哪些差異需要修復、哪些需要在判讀時考慮。</p>
<p><strong>Release 前 review 差異清單</strong>：每次 release 前把差異清單跟變更內容交叉比對。如果本次變更涉及 connection pool 設定，但 staging 的 pool size 跟 prod 不同，這個差異就會影響驗證結論，必須在放行時被標記。連到 <a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review</a> 的 pre-release checklist。</p>
<p><strong>Infra 變更同步</strong>：新增 infra 變更時，同步更新 staging 或在差異清單中標記新增風險。infra-as-code 讓同步變得可自動化，但仍需要 review 確認 staging 的資源配額是否需要調整。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/heroku/" data-link-title="Heroku" data-link-desc="Heroku PaaS 事故與 router 層架構脈絡">Heroku</a>：平台抽象越高，環境行為差異越不可見，漂移偵測需要更主動的手段。</li>
<li><a href="/blog/backend/08-incident-response/cases/gcp/" data-link-title="Google Cloud Platform" data-link-desc="GCP 重大事故時間線與架構脈絡">GCP</a>：區域、網路與權限設定差異會直接影響驗證結論，infra drift 在跨區場景最先暴露。</li>
<li><a href="/blog/backend/08-incident-response/cases/github/" data-link-title="GitHub" data-link-desc="GitHub 重大事故時間線與架構脈絡">GitHub</a>：大規模部署時，環境差異通常先變成事故放大器，漂移控制是降低放大倍數的前置工作。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>staging 通過、prod 上線失敗，根因是 config / scale 差異</td>
          <td>parity 差異未被 release review 識別</td>
          <td>把失敗根因加入環境差異清單 + release checklist</td>
      </tr>
      <tr>
          <td>staging 跟 prod 用不同 DB engine 版本 / cache 配置</td>
          <td>dependency drift 未被 version matrix 追蹤</td>
          <td>建 dependency version matrix、定期 diff</td>
      </tr>
      <tr>
          <td>shadow traffic 從未啟用、staging 流量靠手動測試</td>
          <td>data drift 和 dependency drift 沒有持續偵測機制</td>
          <td>啟用 shadow traffic 或 canary 驗證</td>
      </tr>
      <tr>
          <td>prod-only bug 反覆出現、staging 無法重現</td>
          <td>環境差異是 bug 的根因，差異清單可能遺漏關鍵項目</td>
          <td>回查差異清單、補漏項 + owner</td>
      </tr>
      <tr>
          <td>環境差異無 owner、漂移無 review</td>
          <td>parity 治理流程不存在或已停止運作</td>
          <td>指定 parity owner、加入 release review 流程</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署平台</a>：環境拓撲一致性與 canary 機制</li>
<li><a href="/blog/backend/06-reliability/load-testing/" data-link-title="6.2 load test" data-link-desc="把 production 流量結構轉成可重播壓力情境，定位 saturation 轉折與容量邊界">6.2 load testing</a>：staging 壓測結果的可信度受 parity 影響</li>
<li><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10 contract testing</a>：契約覆蓋環境邊界</li>
<li><a href="/blog/backend/06-reliability/test-data-management/" data-link-title="6.16 Test Data Management" data-link-desc="把 fixture / seed / production-like data 作為跨模組共用 artifact，治理資料層次、遮罩策略與可重現性">6.16 test data management</a>：production-like data 來源與策略</li>
<li><a href="/blog/backend/06-reliability/reliability-readiness-review/" data-link-title="6.19 Reliability Readiness Review" data-link-desc="把上線前、重大變更前與高風險操作前的可靠性準備度變成可檢查門檻">6.19 reliability readiness review</a>：release 前的 parity review</li>
<li><a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 experiment safety boundary</a>：staging vs production 測試的安全邊界</li>
<li><a href="/blog/backend/08-incident-response/post-incident-review/" data-link-title="8.5 復盤與改進追蹤" data-link-desc="把 RCA 與 action items 轉成可驗證閉環">8.5 post-incident review</a>：parity 漂移作為事故根因類別</li>
</ul>
]]></content:encoded></item><item><title>6.16 Test Data Management</title><link>https://tarrragon.github.io/blog/backend/06-reliability/test-data-management/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/test-data-management/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>測試常常失敗在資料而非邏輯 — fixture 過期、seed 跟 schema 漂移、staging 資料分佈跟 production 差太遠。Test data management 把 fixture、seed 與 production-like data 當成共用資產來治理，讓測試建立在可控且可重播的資料基礎上。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>Test data 的健康度先看資料是否足夠代表真實情境，再看資料是否能安全重建與清理。&lt;/p>
&lt;p>關鍵判準：&lt;/p>
&lt;ul>
&lt;li>fixture 是否覆蓋關鍵情境，而不是只有 happy path&lt;/li>
&lt;li>seed 是否可版本化與重播&lt;/li>
&lt;li>production-like data 是否完成去識別化與權限隔離&lt;/li>
&lt;li>data lifecycle 是否和 CI / migration / contract testing 互相對齊&lt;/li>
&lt;/ul>
&lt;h2 id="資料層次">資料層次&lt;/h2>
&lt;p>測試資料按用途分四層，每層的責任、治理成本與真實度不同。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層次&lt;/th>
 &lt;th>生命週期&lt;/th>
 &lt;th>真實度&lt;/th>
 &lt;th>治理成本&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Unit fixture&lt;/td>
 &lt;td>跟 test case 綁定&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>低&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Integration seed&lt;/td>
 &lt;td>跟 test suite 綁定&lt;/td>
 &lt;td>中&lt;/td>
 &lt;td>中&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Staging dataset&lt;/td>
 &lt;td>長期存在於環境中&lt;/td>
 &lt;td>中高&lt;/td>
 &lt;td>高&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Production sample&lt;/td>
 &lt;td>定期從 prod 抽樣&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>最高&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Unit fixture&lt;/strong> 是硬編碼或 factory-generated 的資料，不碰外部系統。fixture 的責任是提供可控的輸入與預期輸出，讓 unit test 驗證邏輯正確性。fixture 覆蓋 happy path 與 edge case，但不反映 production 資料分佈 — 這是設計取捨，因為分佈驗證的責任在更高層次。&lt;/p>
&lt;p>&lt;strong>Integration seed&lt;/strong> 寫進真實 DB / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker&lt;/a> / cache，生命週期跟 test suite 綁定（setup 建立、teardown 清理）。seed 需要版本化，跟 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/schema-migration/" data-link-title="Schema Migration" data-link-desc="說明資料庫結構如何隨應用程式版本安全演進">schema migration&lt;/a> 對齊 — 見下方「可重現性與版本化」段。seed 品質的判準是：它是否能讓 integration test 驗證跨服務邊界的行為，而不是只驗證資料是否存在。&lt;/p>
&lt;p>&lt;strong>Staging dataset&lt;/strong> 長期存在於 staging 環境，模擬 production 規模與分佈。這一層的挑戰是漂移：production 的資料結構、量體與分佈持續變化，staging dataset 需要定期更新才能維持代表性。更新頻率跟 schema 變更頻率對齊 — 每次重大 schema 變更後，staging dataset 應同步重建。&lt;/p>
&lt;p>&lt;strong>Production sample（脫敏）&lt;/strong> 從 production 抽樣加 PII masking，是真實度最高的選項。它的價值在於保留真實資料的分佈、關聯與邊界條件 — 這些是 synthetic data 很難完整模擬的。代價是隱私風險與合規成本，需要遮罩管線、存取控制與定期稽核。連到 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資料保護&lt;/a>。&lt;/p>
&lt;h2 id="遮罩與合成策略">遮罩與合成策略&lt;/h2>
&lt;p>當測試需要接近 production 的資料，PII 處理策略決定了安全性與真實度的平衡。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>策略&lt;/th>
 &lt;th>原理&lt;/th>
 &lt;th>適用場景&lt;/th>
 &lt;th>限制&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Tokenization&lt;/td>
 &lt;td>PII 替換成無意義 token、保留格式&lt;/td>
 &lt;td>需要 referential integrity&lt;/td>
 &lt;td>token mapping 本身需要安全儲存&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Format-preserving encryption&lt;/td>
 &lt;td>保留原始格式但值不可逆&lt;/td>
 &lt;td>需要格式驗證（信用卡位數）&lt;/td>
 &lt;td>加密強度受格式限制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Synthetic generation&lt;/td>
 &lt;td>用規則或統計模型生成假資料&lt;/td>
 &lt;td>無 PII 風險、合規最簡單&lt;/td>
 &lt;td>資料分佈可能偏移&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Tokenization 適合需要跨表關聯的場景：同一個 user ID 在 order、payment、session 表中需要一致替換，referential integrity 才不會被破壞。format-preserving encryption 適合需要通過格式驗證的場景（信用卡號通過 Luhn check）。synthetic generation 最安全，但資料分佈偏移會讓某些測試結論失真 — &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest 的快取可靠性案例&lt;/a>說明資料分佈差異會改變 cache 命中率，進而改變瓶頸位置。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>測試常常失敗在資料而非邏輯 — fixture 過期、seed 跟 schema 漂移、staging 資料分佈跟 production 差太遠。Test data management 把 fixture、seed 與 production-like data 當成共用資產來治理，讓測試建立在可控且可重播的資料基礎上。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Test data 的健康度先看資料是否足夠代表真實情境，再看資料是否能安全重建與清理。</p>
<p>關鍵判準：</p>
<ul>
<li>fixture 是否覆蓋關鍵情境，而不是只有 happy path</li>
<li>seed 是否可版本化與重播</li>
<li>production-like data 是否完成去識別化與權限隔離</li>
<li>data lifecycle 是否和 CI / migration / contract testing 互相對齊</li>
</ul>
<h2 id="資料層次">資料層次</h2>
<p>測試資料按用途分四層，每層的責任、治理成本與真實度不同。</p>
<table>
  <thead>
      <tr>
          <th>層次</th>
          <th>生命週期</th>
          <th>真實度</th>
          <th>治理成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Unit fixture</td>
          <td>跟 test case 綁定</td>
          <td>低</td>
          <td>低</td>
      </tr>
      <tr>
          <td>Integration seed</td>
          <td>跟 test suite 綁定</td>
          <td>中</td>
          <td>中</td>
      </tr>
      <tr>
          <td>Staging dataset</td>
          <td>長期存在於環境中</td>
          <td>中高</td>
          <td>高</td>
      </tr>
      <tr>
          <td>Production sample</td>
          <td>定期從 prod 抽樣</td>
          <td>高</td>
          <td>最高</td>
      </tr>
  </tbody>
</table>
<p><strong>Unit fixture</strong> 是硬編碼或 factory-generated 的資料，不碰外部系統。fixture 的責任是提供可控的輸入與預期輸出，讓 unit test 驗證邏輯正確性。fixture 覆蓋 happy path 與 edge case，但不反映 production 資料分佈 — 這是設計取捨，因為分佈驗證的責任在更高層次。</p>
<p><strong>Integration seed</strong> 寫進真實 DB / <a href="/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker</a> / cache，生命週期跟 test suite 綁定（setup 建立、teardown 清理）。seed 需要版本化，跟 <a href="/blog/backend/knowledge-cards/schema-migration/" data-link-title="Schema Migration" data-link-desc="說明資料庫結構如何隨應用程式版本安全演進">schema migration</a> 對齊 — 見下方「可重現性與版本化」段。seed 品質的判準是：它是否能讓 integration test 驗證跨服務邊界的行為，而不是只驗證資料是否存在。</p>
<p><strong>Staging dataset</strong> 長期存在於 staging 環境，模擬 production 規模與分佈。這一層的挑戰是漂移：production 的資料結構、量體與分佈持續變化，staging dataset 需要定期更新才能維持代表性。更新頻率跟 schema 變更頻率對齊 — 每次重大 schema 變更後，staging dataset 應同步重建。</p>
<p><strong>Production sample（脫敏）</strong> 從 production 抽樣加 PII masking，是真實度最高的選項。它的價值在於保留真實資料的分佈、關聯與邊界條件 — 這些是 synthetic data 很難完整模擬的。代價是隱私風險與合規成本，需要遮罩管線、存取控制與定期稽核。連到 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資料保護</a>。</p>
<h2 id="遮罩與合成策略">遮罩與合成策略</h2>
<p>當測試需要接近 production 的資料，PII 處理策略決定了安全性與真實度的平衡。</p>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>原理</th>
          <th>適用場景</th>
          <th>限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Tokenization</td>
          <td>PII 替換成無意義 token、保留格式</td>
          <td>需要 referential integrity</td>
          <td>token mapping 本身需要安全儲存</td>
      </tr>
      <tr>
          <td>Format-preserving encryption</td>
          <td>保留原始格式但值不可逆</td>
          <td>需要格式驗證（信用卡位數）</td>
          <td>加密強度受格式限制</td>
      </tr>
      <tr>
          <td>Synthetic generation</td>
          <td>用規則或統計模型生成假資料</td>
          <td>無 PII 風險、合規最簡單</td>
          <td>資料分佈可能偏移</td>
      </tr>
  </tbody>
</table>
<p>Tokenization 適合需要跨表關聯的場景：同一個 user ID 在 order、payment、session 表中需要一致替換，referential integrity 才不會被破壞。format-preserving encryption 適合需要通過格式驗證的場景（信用卡號通過 Luhn check）。synthetic generation 最安全，但資料分佈偏移會讓某些測試結論失真 — <a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest 的快取可靠性案例</a>說明資料分佈差異會改變 cache 命中率，進而改變瓶頸位置。</p>
<p>三者的選擇取決於測試需要的真實度與隱私風險。多數團隊會混合使用：unit fixture 用 synthetic、integration seed 用 tokenization、staging dataset 用 production sample + format-preserving encryption。</p>
<h2 id="可重現性與版本化">可重現性與版本化</h2>
<p>Seed 資料需要版本化，跟 schema migration 對齊。當 DB schema 新增欄位或改型別，既有 seed 如果沒同步更新，integration test 會因資料問題失敗而非邏輯問題 — 這類 failure 的除錯成本高，因為錯誤訊息指向 schema 不符，團隊會懷疑是 migration bug 還是 seed bug。</p>
<p><strong>Seed migration</strong> 是把 seed 更新綁進 schema migration workflow 的做法：每次 DB migration 加一份對應的 seed migration。這讓 seed 狀態跟 schema 狀態同步演進，CI 跑 integration test 時永遠拿到匹配的組合。</p>
<p><strong>Fixture factory</strong> 用 factory pattern 生成測試資料，讓新增欄位自動帶 default。factory 的優勢是欄位變更只需改 factory 定義，不需要手動更新每個 fixture file — 這在高頻 schema 變更的服務中可以顯著降低 fixture 維護負擔。</p>
<p><strong>資料清理</strong> 策略決定 integration test 的隔離性。transaction rollback 最乾淨（每個 test case 跑在 transaction 內、結束後 rollback），但不適用於跨 transaction 的流程測試。truncate 較快但需要處理外鍵順序。獨立 DB per suite 隔離最強但成本最高 — 每個 test suite 用自己的 database instance。選擇時對齊 CI 的隔離需求（連到 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a> 的 environment 隔離段）。</p>
<h2 id="fixture-與-contract-testing-的整合">Fixture 與 contract testing 的整合</h2>
<p><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">Contract testing</a> 定義 schema shape，fixture factory 可以用 contract 作為資料生成的來源。當 contract 變更時（新增欄位、型別調整），fixture factory 自動更新生成邏輯，讓 test data 跟 contract 保持同步。</p>
<p>這個整合的價值是把「契約變更是否影響測試資料」從人工 review 變成自動化流程。<a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe 的交易正確性實踐</a>對此有額外要求：交易路徑的 test data 需要能重播到相同狀態，確保 <a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> 驗證的資料基礎一致。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/pinterest/cache-reliability-and-capacity-surprises/" data-link-title="Pinterest：快取可靠性與容量驚奇治理" data-link-desc="針對快取層失效與流量突增，建立容量緩衝、退化路徑與重建節奏。">Pinterest</a>：資料分佈差異改變 cache 命中率與瓶頸位置，staging dataset 若分佈偏離 production，壓測結論會失真。</li>
<li><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe</a>：交易資料需要嚴格控制可重播性，fixture 與 seed 要能產出一致的 idempotency 驗證結果。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>工程師為 debug 把 production data 拷到 local</td>
          <td>PII 暴露風險 — 需要遮罩管線而非直接複製</td>
          <td>建立遮罩 pipeline、禁止直接複製 production DB</td>
      </tr>
      <tr>
          <td>staging DB 含真實用戶 PII</td>
          <td>合規風險 — 需要用 tokenization 或 synthetic 替代</td>
          <td>導入 tokenization 工具或 synthetic generation</td>
      </tr>
      <tr>
          <td>fixture 跟 schema 漂移、測試常壞</td>
          <td>seed migration 未跟 schema migration 對齊</td>
          <td>每次 schema migration 同步更新 seed 版本</td>
      </tr>
      <tr>
          <td>新測試靠拷貼舊 fixture</td>
          <td>缺少 fixture factory — 變更範圍模糊、維護成本累積</td>
          <td>導入 factory pattern 自動帶 default</td>
      </tr>
      <tr>
          <td>production bug 重現不出</td>
          <td>staging dataset 分佈跟 production 差異太大 — 需更新或用 production sample</td>
          <td>定期用脫敏 production sample 更新 staging data</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>：test data 如何進入 fast / slow stage</li>
<li><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10 contract testing</a>：contract 定義 fixture shape</li>
<li><a href="/blog/backend/06-reliability/migration-safety/" data-link-title="6.11 Migration Safety 與 DB Rollout" data-link-desc="把 schema migration 從一次性事件變成可逆、可漸進的 rollout 流程">6.11 migration safety</a>：seed migration 跟 schema migration 對齊</li>
<li><a href="/blog/backend/06-reliability/environment-parity/" data-link-title="6.15 Environment Parity 與漂移控制" data-link-desc="把 staging / preprod / prod 之間的差異視為一級風險，按漂移來源分類偵測與治理">6.15 environment parity</a>：production-like data 是 parity 的一部分</li>
<li><a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資料保護</a>：PII 遮罩與最小揭露</li>
</ul>
]]></content:encoded></item><item><title>6.17 Feature Flag Governance</title><link>https://tarrragon.github.io/blog/backend/06-reliability/feature-flag-governance/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/feature-flag-governance/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Feature flag 在 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">release gate&lt;/a> 之後提供 runtime 層的細粒度控制。Flag governance 把這個控制從單次開關提升為有生命週期的 artifact，涵蓋灰度、實驗與緊急止血的風險管理。&lt;/p>
&lt;p>當 flag 變多，真正的風險是狀態分支不透明、技術債累積與權限混用。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>Flag governance 的健康度先看旗標角色是否分離，再看移除與審計是否有固定流程。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>release / experiment / ops / permission 是否分流&lt;/li>
&lt;li>stale flag 是否有回收機制&lt;/li>
&lt;li>progressive rollout 是否有可觀測的 cohort&lt;/li>
&lt;li>flag 變更是否可審計、可追責&lt;/li>
&lt;/ul>
&lt;h2 id="flag-角色分類">Flag 角色分類&lt;/h2>
&lt;p>Flag 按用途分離，不同角色的 lifecycle、權限與治理策略差異顯著。混用會讓審計失真、移除困難、權限控制失效。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>角色&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>Lifecycle 預期&lt;/th>
 &lt;th>Owner&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Release flag&lt;/td>
 &lt;td>控制新功能是否對使用者可見&lt;/td>
 &lt;td>天到週&lt;/td>
 &lt;td>功能團隊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Experiment flag&lt;/td>
 &lt;td>控制 A/B test 流量分配&lt;/td>
 &lt;td>週到月&lt;/td>
 &lt;td>實驗平台團隊&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ops flag&lt;/td>
 &lt;td>緊急止血、降級、流量限制&lt;/td>
 &lt;td>長期存在&lt;/td>
 &lt;td>SRE / 值班&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Permission flag&lt;/td>
 &lt;td>控制使用者 / 租戶功能存取&lt;/td>
 &lt;td>跟隨權限策略&lt;/td>
 &lt;td>產品 / IAM&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Release flag&lt;/strong> 上線後應在固定時限內收斂為 always-on 或移除。它的存在意義是灰度期間的安全網。灰度結束後，flag 的控制作用消失，只剩代碼分支 — 這段分支就是 flag debt 的來源。&lt;/p>
&lt;p>&lt;strong>Experiment flag&lt;/strong> 的 lifecycle 受實驗週期決定。實驗結束後，flag 應收斂為勝出變體的行為並移除。實驗 flag 的特殊風險是依賴統計引擎的流量分配 — 引擎異常時，flag 的行為取決於 fallback 配置。&lt;/p>
&lt;p>&lt;strong>Ops flag&lt;/strong> 是長期存在的 kill switch 與降級控制。它與其他三類 flag 的關鍵差異是觸發頻率低但影響範圍大 — 觸發時通常是事故情境，需要秒級生效與審計紀錄。ops flag 的設計需求見下方「Kill switch 設計」段。&lt;/p>
&lt;p>&lt;strong>Permission flag&lt;/strong> 本質是權限控制，應走 RBAC 或 entitlement 系統。當 permission flag 混入 feature flag 系統，功能存取權會繞過正式權限審核流程 — 修改一個 flag 值就能改變租戶的功能範圍，沒有對應的審計軌跡。判斷標準：如果 flag 的值決定「誰能用什麼功能」，它是 permission，應該從 feature flag 系統遷移到權限系統。&lt;/p>
&lt;h2 id="lifecycle-管理">Lifecycle 管理&lt;/h2>
&lt;p>Flag 的生命週期是 create → rollout → converge → remove。每個階段有明確的輸入與交付物。&lt;/p>
&lt;p>&lt;strong>Create&lt;/strong>：flag 建立時記錄 owner、用途分類（release / experiment / ops）、預計移除日期與關聯 ticket。這些 metadata 是後續治理的基礎 — 沒有 owner 的 flag 在移除階段會變成無人認領的 debt。&lt;/p>
&lt;p>&lt;strong>Rollout&lt;/strong>：progressive rollout 按 percentage、cohort 或 region 逐步放量。每一步有可觀測指標確認行為正常 — error rate、latency、business KPI。rollout 節奏跟 &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> 的放行條件對齊：gate 通過後用 flag 做細粒度控制，flag 異常時 gate 提供回退依據。&lt;/p>
&lt;p>&lt;strong>Converge&lt;/strong>：功能穩定後，flag 設定 100%（always-on）或 0%（移除功能）。此時 flag 已無控制作用，只是代碼中的條件分支。converge 階段是 flag 治理的關鍵轉折 — 很多 flag 停在這裡不再前進，持續佔用代碼路徑。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Feature flag 在 <a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">release gate</a> 之後提供 runtime 層的細粒度控制。Flag governance 把這個控制從單次開關提升為有生命週期的 artifact，涵蓋灰度、實驗與緊急止血的風險管理。</p>
<p>當 flag 變多，真正的風險是狀態分支不透明、技術債累積與權限混用。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Flag governance 的健康度先看旗標角色是否分離，再看移除與審計是否有固定流程。</p>
<p>重點訊號包括：</p>
<ul>
<li>release / experiment / ops / permission 是否分流</li>
<li>stale flag 是否有回收機制</li>
<li>progressive rollout 是否有可觀測的 cohort</li>
<li>flag 變更是否可審計、可追責</li>
</ul>
<h2 id="flag-角色分類">Flag 角色分類</h2>
<p>Flag 按用途分離，不同角色的 lifecycle、權限與治理策略差異顯著。混用會讓審計失真、移除困難、權限控制失效。</p>
<table>
  <thead>
      <tr>
          <th>角色</th>
          <th>責任</th>
          <th>Lifecycle 預期</th>
          <th>Owner</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Release flag</td>
          <td>控制新功能是否對使用者可見</td>
          <td>天到週</td>
          <td>功能團隊</td>
      </tr>
      <tr>
          <td>Experiment flag</td>
          <td>控制 A/B test 流量分配</td>
          <td>週到月</td>
          <td>實驗平台團隊</td>
      </tr>
      <tr>
          <td>Ops flag</td>
          <td>緊急止血、降級、流量限制</td>
          <td>長期存在</td>
          <td>SRE / 值班</td>
      </tr>
      <tr>
          <td>Permission flag</td>
          <td>控制使用者 / 租戶功能存取</td>
          <td>跟隨權限策略</td>
          <td>產品 / IAM</td>
      </tr>
  </tbody>
</table>
<p><strong>Release flag</strong> 上線後應在固定時限內收斂為 always-on 或移除。它的存在意義是灰度期間的安全網。灰度結束後，flag 的控制作用消失，只剩代碼分支 — 這段分支就是 flag debt 的來源。</p>
<p><strong>Experiment flag</strong> 的 lifecycle 受實驗週期決定。實驗結束後，flag 應收斂為勝出變體的行為並移除。實驗 flag 的特殊風險是依賴統計引擎的流量分配 — 引擎異常時，flag 的行為取決於 fallback 配置。</p>
<p><strong>Ops flag</strong> 是長期存在的 kill switch 與降級控制。它與其他三類 flag 的關鍵差異是觸發頻率低但影響範圍大 — 觸發時通常是事故情境，需要秒級生效與審計紀錄。ops flag 的設計需求見下方「Kill switch 設計」段。</p>
<p><strong>Permission flag</strong> 本質是權限控制，應走 RBAC 或 entitlement 系統。當 permission flag 混入 feature flag 系統，功能存取權會繞過正式權限審核流程 — 修改一個 flag 值就能改變租戶的功能範圍，沒有對應的審計軌跡。判斷標準：如果 flag 的值決定「誰能用什麼功能」，它是 permission，應該從 feature flag 系統遷移到權限系統。</p>
<h2 id="lifecycle-管理">Lifecycle 管理</h2>
<p>Flag 的生命週期是 create → rollout → converge → remove。每個階段有明確的輸入與交付物。</p>
<p><strong>Create</strong>：flag 建立時記錄 owner、用途分類（release / experiment / ops）、預計移除日期與關聯 ticket。這些 metadata 是後續治理的基礎 — 沒有 owner 的 flag 在移除階段會變成無人認領的 debt。</p>
<p><strong>Rollout</strong>：progressive rollout 按 percentage、cohort 或 region 逐步放量。每一步有可觀測指標確認行為正常 — error rate、latency、business KPI。rollout 節奏跟 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a> 的放行條件對齊：gate 通過後用 flag 做細粒度控制，flag 異常時 gate 提供回退依據。</p>
<p><strong>Converge</strong>：功能穩定後，flag 設定 100%（always-on）或 0%（移除功能）。此時 flag 已無控制作用，只是代碼中的條件分支。converge 階段是 flag 治理的關鍵轉折 — 很多 flag 停在這裡不再前進，持續佔用代碼路徑。</p>
<p><strong>Remove</strong>：移除 flag 代碼、清理條件分支、移除 flag 定義。移除動作困難的原因是 flag 可能被多處引用（server / client / config / test），每處都需要確認行為收斂到同一分支。自動化掃描（dead code detection、unused flag audit）能降低手動風險，但最終決策仍需要 flag owner 確認沒有殘留依賴。</p>
<h2 id="flag-debt-治理">Flag debt 治理</h2>
<p>每個未移除的 flag 讓測試需要覆蓋的狀態空間翻倍。10 個 stale flag 代表 1024 種潛在的狀態組合 — 實際測試覆蓋率遠低於這個數字，代碼行為的可預測性持續下降。</p>
<p><strong>TTL policy</strong>：flag 建立時設定預計移除日期。超過 TTL 且沒有活躍修改的 flag 自動標記為 debt，進入清理 backlog。TTL 按角色設定：release flag 兩週到一個月，experiment flag 與實驗週期對齊，ops flag 免 TTL 但需要年度 review。</p>
<p><strong>定期掃描</strong>：每月或每季掃描 stale flag（超過 TTL + 無活躍修改），生成清理 backlog。掃描結果對應到 flag owner，由 owner 決定是移除、延長 TTL 還是升級為 ops flag。無 owner 的 stale flag 是最高風險 — 沒有人能確認移除是否安全。</p>
<p><strong>Flag count dashboard</strong>：追蹤活躍 flag 數量趨勢。flag 數量持續上升是治理失敗的訊號 — 代表建立速度超過移除速度，debt 在累積。dashboard 按角色分類顯示，讓團隊知道 debt 集中在哪一類 flag。</p>
<h2 id="kill-switch-設計">Kill switch 設計</h2>
<p>Ops flag 作為事中止血工具，需要跟一般 feature flag 不同的設計約束。</p>
<p><strong>觸發延遲</strong>：kill switch 需要秒級生效。依賴 redeploy 才能生效的 flag 在事故中無法作為止血工具 — 部署流程本身需要數分鐘到數十分鐘。實作通常靠 flag evaluation service 的即時推送或短間隔 polling，讓 flag 值變更能在秒級傳播到所有 instance。</p>
<p><strong>權限控制</strong>：kill switch 的觸發權限應受控。值班人員與 SRE 有觸發權，一般開發者沒有。觸發記錄包含誰、什麼時間、因什麼原因觸發，接到 <a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 止血策略</a> 的決策 log。</p>
<p><strong>Fallback 行為明確</strong>：每個 kill switch 在觸發後的預期行為應事前定義。「關掉這個 flag 後會怎樣」的答案應寫在 flag 定義中，讓觸發者在壓力下可快速判斷副作用，而不是臨場推理。</p>
<h2 id="experimentation-平台可靠性">Experimentation 平台可靠性</h2>
<p>A/B test 平台本身是 feature flag 的下游消費者。平台的可用性直接影響所有走 experiment flag 的流量分配。</p>
<p>平台掛掉時，flag 的行為取決於 fallback 配置：default-on 會讓所有使用者看到實驗中的變體，default-off 會讓所有使用者回到 control group。兩者的商業影響完全不同，fallback 行為應在每個 experiment flag 建立時明確配置。</p>
<p>experimentation 平台的 SLO 應獨立定義。當平台自身的 <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 消耗過快時，影響的是所有進行中的實驗的流量分配正確性。平台故障不只是「實驗暫停」— 如果 fallback 行為配置錯誤，使用者可能被導向尚未驗證的功能路徑。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe</a>：progressive rollout 用 flag 控制 migration 的流量切換比例，每一步驗證交易正確性後再擴大，flag 的 rollout 節奏跟 migration safety 綁定。</li>
<li><a href="/blog/backend/06-reliability/cases/shopify/bfcm-capacity-and-game-day/" data-link-title="Shopify：BFCM 容量治理與 Game Day 驗證節奏" data-link-desc="把季節性流量峰值轉成年度可靠性流程，透過容量模型、演練與隔離策略提前吸收風險。">Shopify</a>：高峰流量期間 ops flag 用於細粒度降級控制 — 關閉非核心功能釋放容量給 checkout 路徑。flag 的降級策略在 game day 驗證，確認觸發後的行為符合預期。</li>
<li><a href="/blog/backend/06-reliability/cases/stripe/canary-deploy-and-progressive-rollout/" data-link-title="Stripe：Canary Deploy 與 Progressive Rollout 治理" data-link-desc="金流場景如何用交易指標驅動放行節奏：延遲確認、duplicate 偵測與自動回退。">Stripe S2</a>：progressive rollout 用 flag 控制 canary 放量比例，每一步用交易指標判斷是否繼續。flag 的 rollout 節奏跟金流風險的延遲確認窗綁定。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式碼中存在 &gt; 6 個月未切換的 flag</td>
          <td>flag 已停在 converge 階段，應進入移除流程或升級為 ops flag</td>
          <td>啟動 stale flag 掃描 + 移除 sprint</td>
      </tr>
      <tr>
          <td>flag 移除流程靠 grep 跟人工 PR</td>
          <td>缺少自動化掃描，移除成本高導致 debt 累積</td>
          <td>導入 dead code detection 工具自動標記</td>
      </tr>
      <tr>
          <td>flag 實際分支跟預期不一致</td>
          <td>flag 狀態與代碼路徑脫鉤，通常在事故時才被發現</td>
          <td>建 flag 狀態 dashboard 定期對齊</td>
      </tr>
      <tr>
          <td>experimentation 平台掛掉影響所有 A/B 流量</td>
          <td>平台 fallback 行為未配置或未驗證</td>
          <td>配置 default-on/off fallback + 定期演練</td>
      </tr>
      <tr>
          <td>ops flag 跟 release flag 混在同系統、無權限隔離</td>
          <td>止血操作的審計軌跡與一般功能開關無法區分，事後回查困難</td>
          <td>分離 flag 系統或加 RBAC 權限隔離</td>
      </tr>
      <tr>
          <td>活躍 flag 數量每季持續上升</td>
          <td>建立速度超過移除速度，測試覆蓋的狀態空間在膨脹</td>
          <td>設 flag count budget、超額暫停新 flag 建立</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：flag 是 gate 通過後的細粒度 rollout 控制</li>
<li><a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10 contract testing</a>：flag 不同分支的契約覆蓋</li>
<li><a href="/blog/backend/06-reliability/performance-regression-gate/" data-link-title="6.13 Performance Regression Gate" data-link-desc="把效能 baseline 從一次性壓測變成持續對齊的 release gate，涵蓋 baseline 設定、判讀方法、variance 控制與退化定位">6.13 perf regression gate</a>：flag 切換後的效能驗證</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>：stale flag 進入 debt 治理</li>
<li><a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安與資料保護</a>：permission flag 的權限約束</li>
<li><a href="/blog/backend/08-incident-response/containment-recovery-strategy/" data-link-title="8.3 止血、降級與回復策略" data-link-desc="把短期止血與正式回復拆成可執行步驟">8.3 止血策略</a>：ops flag 作為事中止血手段</li>
</ul>
]]></content:encoded></item><item><title>6.18 Reliability Metrics Governance</title><link>https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/reliability-metrics-governance/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Reliability metrics governance 確保團隊量測到的指標能反映真實的可靠性狀態。指標的價值在於引導討論與暴露趨勢，一旦指標被直接當成目標，治理就開始退化。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>指標是否對準使用者感受、是否能驅動工程決策 — 這兩個問題決定 metrics governance 的有效性。&lt;/p>
&lt;p>判讀的核心問題：&lt;/p>
&lt;ul>
&lt;li>SLI 是否有明確觀測窗口與採樣邊界&lt;/li>
&lt;li>SLO 是否能轉成 release / alert / incident 決策&lt;/li>
&lt;li>DORA / SPACE / CFR 是否被混用成單一成績單&lt;/li>
&lt;li>metric drift 是否被記錄與校正&lt;/li>
&lt;/ul>
&lt;h2 id="dora-四指標">DORA 四指標&lt;/h2>
&lt;p>DORA 量測的是交付與可靠性流程的效率，四個指標各自回答不同問題。&lt;/p>
&lt;p>&lt;strong>Deploy frequency&lt;/strong> 量測交付節奏 — 團隊多頻繁把變更送到 production。高頻 deploy 通常代表小批次、低風險；但判讀陷阱是拆碎 deploy 只為衝頻率。辨別方式是同時看 deploy size distribution — 若平均 deploy 的變更量持續縮小但 frequency 持續上升，gaming 的可能性高。deploy frequency 要搭配 change failure rate 一起看，頻率高但 CFR 也高代表品質沒跟上。&lt;/p>
&lt;p>&lt;strong>Lead time for changes&lt;/strong> 量測從 commit 到 production 的時間。長 lead time 通常指向 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">CI pipeline&lt;/a> bottleneck、approval queue 或 staging 排隊。判讀陷阱是把 lead time 壓短但跳過驗證步驟 — 縮短的時間可能來自移除 slow path 測試，表面效率提升但風險轉移到 production。改善 lead time 的投資方向先看 CI 分層（6.1）是否合理，再看 review queue 是否成為瓶頸。&lt;/p>
&lt;p>&lt;strong>Change failure rate (CFR)&lt;/strong> 量測 deploy 後需要 rollback 或 hotfix 的比率。CFR 是 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">release gate&lt;/a> 健康度的直接指標 — gate 有效時 CFR 應該維持穩定或下降。判讀陷阱是團隊避免標記 rollback 來壓低 CFR，或把 hotfix 歸類為「正常 deploy」。偵測方式是把 CFR 跟 customer complaint rate 做相關性分析 — 若 CFR 持續下降但客訴未減，代表量測漏洞存在。&lt;/p>
&lt;p>&lt;strong>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR&lt;/a>&lt;/strong> 量測從故障到恢復的時間。MTTR 的量測邊界需要明確定義：從 alert 觸發開始算、從 customer impact 開始算、到 recovery complete 還是到 root cause 修復。不同定義會產出完全不同的數字。判讀陷阱是延遲標記 incident 起始時間來壓低 MTTR。連到 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 incident response&lt;/a> 的事故分級與復盤流程。&lt;/p>
&lt;h2 id="space-補充維度">SPACE 補充維度&lt;/h2>
&lt;p>DORA 偏重 delivery 效率，SPACE 補人因與協作維度。五個面向各捕捉 DORA 看不到的訊號。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>量測重點&lt;/th>
 &lt;th>判讀價值&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Satisfaction&lt;/td>
 &lt;td>團隊對工具、流程、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call&lt;/a> 負擔的滿意度&lt;/td>
 &lt;td>滿意度下降常先於效能指標退化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Performance&lt;/td>
 &lt;td>code review 品質、bug escape rate&lt;/td>
 &lt;td>補 DORA 缺的品質維度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Activity&lt;/td>
 &lt;td>commit / PR / deploy 頻率&lt;/td>
 &lt;td>activity 是描述性指標，不等於 productivity&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Communication&lt;/td>
 &lt;td>跨團隊協作效率、incident communication 品質&lt;/td>
 &lt;td>協作瓶頸在 DORA 中完全看不到&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Efficiency&lt;/td>
 &lt;td>flow state time、context switch frequency&lt;/td>
 &lt;td>高 context switch 會拖慢 lead time 但原因不在 CI&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>SPACE 同樣需要 governance。Satisfaction 被 KPI 化後團隊會避免誠實回饋；Activity 被當成 productivity 量測後會鼓勵 commit 拆碎。治理原則跟 DORA 相同：指標是討論的起點，不是績效的終點。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>Reliability metrics governance 確保團隊量測到的指標能反映真實的可靠性狀態。指標的價值在於引導討論與暴露趨勢，一旦指標被直接當成目標，治理就開始退化。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>指標是否對準使用者感受、是否能驅動工程決策 — 這兩個問題決定 metrics governance 的有效性。</p>
<p>判讀的核心問題：</p>
<ul>
<li>SLI 是否有明確觀測窗口與採樣邊界</li>
<li>SLO 是否能轉成 release / alert / incident 決策</li>
<li>DORA / SPACE / CFR 是否被混用成單一成績單</li>
<li>metric drift 是否被記錄與校正</li>
</ul>
<h2 id="dora-四指標">DORA 四指標</h2>
<p>DORA 量測的是交付與可靠性流程的效率，四個指標各自回答不同問題。</p>
<p><strong>Deploy frequency</strong> 量測交付節奏 — 團隊多頻繁把變更送到 production。高頻 deploy 通常代表小批次、低風險；但判讀陷阱是拆碎 deploy 只為衝頻率。辨別方式是同時看 deploy size distribution — 若平均 deploy 的變更量持續縮小但 frequency 持續上升，gaming 的可能性高。deploy frequency 要搭配 change failure rate 一起看，頻率高但 CFR 也高代表品質沒跟上。</p>
<p><strong>Lead time for changes</strong> 量測從 commit 到 production 的時間。長 lead time 通常指向 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">CI pipeline</a> bottleneck、approval queue 或 staging 排隊。判讀陷阱是把 lead time 壓短但跳過驗證步驟 — 縮短的時間可能來自移除 slow path 測試，表面效率提升但風險轉移到 production。改善 lead time 的投資方向先看 CI 分層（6.1）是否合理，再看 review queue 是否成為瓶頸。</p>
<p><strong>Change failure rate (CFR)</strong> 量測 deploy 後需要 rollback 或 hotfix 的比率。CFR 是 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">release gate</a> 健康度的直接指標 — gate 有效時 CFR 應該維持穩定或下降。判讀陷阱是團隊避免標記 rollback 來壓低 CFR，或把 hotfix 歸類為「正常 deploy」。偵測方式是把 CFR 跟 customer complaint rate 做相關性分析 — 若 CFR 持續下降但客訴未減，代表量測漏洞存在。</p>
<p><strong><a href="/blog/backend/knowledge-cards/mttr/" data-link-title="MTTR" data-link-desc="說明平均修復時間如何作為事故處理能力指標">MTTR</a></strong> 量測從故障到恢復的時間。MTTR 的量測邊界需要明確定義：從 alert 觸發開始算、從 customer impact 開始算、到 recovery complete 還是到 root cause 修復。不同定義會產出完全不同的數字。判讀陷阱是延遲標記 incident 起始時間來壓低 MTTR。連到 <a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 incident response</a> 的事故分級與復盤流程。</p>
<h2 id="space-補充維度">SPACE 補充維度</h2>
<p>DORA 偏重 delivery 效率，SPACE 補人因與協作維度。五個面向各捕捉 DORA 看不到的訊號。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>量測重點</th>
          <th>判讀價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Satisfaction</td>
          <td>團隊對工具、流程、<a href="/blog/backend/knowledge-cards/on-call/" data-link-title="On-Call" data-link-desc="說明值班制度如何承接告警、事故分級與升級流程">on-call</a> 負擔的滿意度</td>
          <td>滿意度下降常先於效能指標退化</td>
      </tr>
      <tr>
          <td>Performance</td>
          <td>code review 品質、bug escape rate</td>
          <td>補 DORA 缺的品質維度</td>
      </tr>
      <tr>
          <td>Activity</td>
          <td>commit / PR / deploy 頻率</td>
          <td>activity 是描述性指標，不等於 productivity</td>
      </tr>
      <tr>
          <td>Communication</td>
          <td>跨團隊協作效率、incident communication 品質</td>
          <td>協作瓶頸在 DORA 中完全看不到</td>
      </tr>
      <tr>
          <td>Efficiency</td>
          <td>flow state time、context switch frequency</td>
          <td>高 context switch 會拖慢 lead time 但原因不在 CI</td>
      </tr>
  </tbody>
</table>
<p>SPACE 同樣需要 governance。Satisfaction 被 KPI 化後團隊會避免誠實回饋；Activity 被當成 productivity 量測後會鼓勵 commit 拆碎。治理原則跟 DORA 相同：指標是討論的起點，不是績效的終點。</p>
<h2 id="指標選用與團隊階段">指標選用與團隊階段</h2>
<p>指標投資的 ROI 跟團隊規模正相關。團隊小時指標治理成本高，應集中在最少的關鍵指標。</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>建議指標</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Startup（&lt; 10 人）</td>
          <td>deploy frequency + CFR</td>
          <td>兩個指標足以判讀交付節奏與品質平衡，其他指標 noise 太大</td>
      </tr>
      <tr>
          <td>Scale（10-100 人）</td>
          <td>完整 DORA</td>
          <td>加入 lead time + MTTR，開始治理跨團隊 baseline</td>
      </tr>
      <tr>
          <td>Mature（100+ 人）</td>
          <td>DORA + SPACE + trend</td>
          <td>完整框架加趨勢分析，composite metrics 需要專人維護</td>
      </tr>
  </tbody>
</table>
<p>baseline 對齊的判準是跟自己的歷史趨勢比，而非抄業界數字。DORA 報告的 elite / high / medium / low 分類提供方向參考，但直接套用會忽略產業、架構與團隊結構的差異。</p>
<h2 id="anti-gaming-與-goodharts-law">Anti-gaming 與 Goodhart&rsquo;s law</h2>
<p>當指標直接變成目標，量測的行為會改變被量測的對象。這就是 Goodhart&rsquo;s law 在工程指標上的實現。</p>
<p>常見 gaming 模式與偵測方式：</p>
<table>
  <thead>
      <tr>
          <th>Gaming 模式</th>
          <th>偵測方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>拆碎 deploy 衝 frequency</td>
          <td>deploy size distribution 出現異常小的 cluster</td>
      </tr>
      <tr>
          <td>延遲標記 incident 降 MTTR</td>
          <td>incident 起始時間 vs alert 觸發時間的 gap 分析</td>
      </tr>
      <tr>
          <td>避免 rollback 降 CFR</td>
          <td>CFR vs customer complaint rate 的相關性斷裂</td>
      </tr>
      <tr>
          <td>跳過 slow path 測試縮短 lead time</td>
          <td>lead time 下降同時 CFR 上升</td>
      </tr>
      <tr>
          <td>壓下同類 incident 不報</td>
          <td>incident recurrence rate 與 <a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">post-incident review</a> 數量不匹配</td>
      </tr>
  </tbody>
</table>
<p>治理原則：指標是診斷工具，用來發現問題方向與引導團隊討論。指標跨團隊強制排名會讓 gaming 成為理性選擇 — 團隊會優化數字而非優化系統。有效做法是把指標用在團隊自身的趨勢追蹤，跨團隊只分享經驗與改善策略。</p>
<h2 id="跟-slo-的差異">跟 SLO 的差異</h2>
<p>SLO 是面向使用者的服務承諾 — 量測的是「我的服務給使用者什麼品質」。6.18 metrics 是面向團隊的工程能力量測 — 量測的是「我的交付與可靠性流程效率如何」。</p>
<p>兩者的消費者不同：SLO 的消費者是 product / business stakeholder 與 on-call 團隊；DORA / SPACE 的消費者是工程管理與團隊自身。治理節奏也不同：SLO 跟 <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 政策綁定，burn rate 驅動即時決策；DORA 趨勢按月或按季 review。</p>
<p>混用的風險是 SLO 失去商業對齊的價值。當 SLO 被當成工程 KPI 而非使用者承諾，團隊會開始縮小 SLI 範圍或放寬目標來讓數字好看，SLO 政策的放行判讀也跟著失真。</p>
<h2 id="案例對照">案例對照</h2>
<ul>
<li><a href="/blog/backend/06-reliability/cases/google/error-budget-policy-and-release-gating/" data-link-title="Google：Error Budget 政策如何決定發布節奏" data-link-desc="把 SLO 消耗量轉成 release gate，讓可靠性與交付速度共用同一套決策語言。">Google：Error Budget 與 Release Gating</a>：SLO 與 DORA 的邊界在這個案例中最清楚 — error budget 是服務承諾的消耗量測，DORA 是交付流程的效率量測，兩者在 release gate 交會但責任不同。</li>
<li><a href="/blog/backend/06-reliability/cases/honeycomb/burn-rate-driven-reliability-operations/" data-link-title="Honeycomb：以 Burn Rate 驅動的可靠性操作" data-link-desc="把 SLO burn rate 直接連到值班決策與改善優先序，降低高噪音告警造成的判讀失真。">Honeycomb：Burn Rate 驅動可靠性</a>：用觀測資料驅動判讀，而非先設定指標再找資料。這個案例說明指標治理的起點是觀測能力，指標是觀測的摘要，觀測是指標的來源。</li>
<li><a href="/blog/backend/08-incident-response/cases/datadog/" data-link-title="Datadog" data-link-desc="Datadog 監控服務事故、客戶觀測落差">Datadog</a>：指標平台的可靠性直接影響事故判讀品質。當指標平台本身不穩定，所有基於它的 DORA / SLO 量測都會失真。</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀條件</th>
          <th>行動建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>指標數字持續改善、客戶投訴未減</td>
          <td>量測覆蓋不足或 gaming — 先檢查 CFR vs complaint 相關性</td>
          <td>把 complaint 率加入 dashboard 交叉比對</td>
      </tr>
      <tr>
          <td>跨團隊強制排名</td>
          <td>gaming 風險高 — 改為團隊自身趨勢追蹤</td>
          <td>取消排名、改為各團隊獨立看自身 trend</td>
      </tr>
      <tr>
          <td>DORA 採集靠人工、滯後超過一個月</td>
          <td>指標失去即時性 — 自動化採集連到 CI / deploy pipeline</td>
          <td>串接 CI/CD pipeline 自動產出 DORA 資料</td>
      </tr>
      <tr>
          <td>指標無 owner、半年無人 review</td>
          <td>治理已停擺 — 指定 owner 與季度 review 節奏</td>
          <td>指定 metrics owner + 排入季度 review 議程</td>
      </tr>
      <tr>
          <td>deploy frequency 上升同時 CFR 上升</td>
          <td>速度與品質失衡 — 先補 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">release gate</a> 再追 frequency</td>
          <td>暫停追 frequency、先讓 CFR 回到 baseline</td>
      </tr>
      <tr>
          <td>MTTR 定義跨團隊不一致</td>
          <td>量測不可比 — 先統一量測邊界（alert → recovery complete）</td>
          <td>發布 MTTR 量測定義文件、統一 start/end 判準</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>：lead time 的主要改善入口</li>
<li><a href="/blog/backend/06-reliability/slo-error-budget/" data-link-title="6.6 SLO 與 Error Budget 政策" data-link-desc="把可靠性目標轉成可驗證量測與凍結條件">6.6 SLO / error budget</a>：商業承諾層的指標，跟 DORA 互補但責任不同</li>
<li><a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate</a>：CFR 是 gate 健康度訊號</li>
<li><a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 reliability debt backlog</a>：指標趨勢揭露的可靠性債</li>
<li><a href="/blog/backend/04-observability/sli-slo-signal/" data-link-title="4.6 SLI 量測與 SLO 訊號設計" data-link-desc="把可靠性目標的訊號從 metric 端設計好、餵給 6.6 SLO 政策">04.6 SLI/SLO 訊號層</a>：指標的觀測來源</li>
<li><a href="/blog/backend/08-incident-response/post-incident-review/" data-link-title="8.5 復盤與改進追蹤" data-link-desc="把 RCA 與 action items 轉成可驗證閉環">08.5 post-incident review</a>：MTTR 計算的事件來源、指標漂移通常先在復盤裡被看見</li>
<li><a href="/blog/backend/08-incident-response/observability-reliability-incident-loop/" data-link-title="8.11 Observability / Reliability / Incident Response 閉環" data-link-desc="把 04 / 06 / 08 三個模組的雙向反饋串成可判讀循環，定義閉環健康度判讀訊號">08.11 觀測 / 可靠性 / 事故閉環</a>：指標治理回寫到三模組閉環</li>
</ul>
]]></content:encoded></item><item><title>6.19 Reliability Readiness Review</title><link>https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/reliability-readiness-review/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>reliability readiness 的責任：確認服務能承受預期流量、依賴失效、資料變更與回復壓力&lt;/li>
&lt;li>檢查面向：SLO、capacity、dependency、rollback、data migration、on-call、runbook&lt;/li>
&lt;li>上線前門檻：核心路徑有 SLI、load test、rollback path、owner 與 alert&lt;/li>
&lt;li>重大變更門檻：migration、feature flag、dependency change、config rollout 的風險判讀&lt;/li>
&lt;li>高風險操作門檻：手動修資料、批次任務、backfill、區域切換&lt;/li>
&lt;li>跟 04 的交接：缺少訊號時回到 observability readiness&lt;/li>
&lt;li>跟 08 的交接：缺少事故節奏時回到 drills / runbook lifecycle&lt;/li>
&lt;li>反模式：release gate 只看 CI 綠燈；沒有 rollback rehearsal；容量假設沒有驗證&lt;/li>
&lt;/ul>
&lt;p>Reliability readiness review 的核心價值是把「上線前風險」前移成可討論的工程語言。只靠測試通過不代表服務可在真實流量與依賴波動下維持穩定，readiness 讓團隊在變更前先明確回答容量、回復、資料與值班四個問題。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Reliability readiness review 是把可靠性準備度轉成可檢查門檻的流程，責任是在服務承受 production 壓力前先找出可預期失效。&lt;/p>
&lt;p>這一頁處理的是準備度。readiness 要把訊號、容量、依賴、回復、資料與值班能力放在同一張檢查表中判讀。&lt;/p>
&lt;p>readiness 的目標是提高發布品質。當缺口被提前看見，團隊可以選擇補驗證、縮小範圍、延後發布或先加保護措施，避免把不確定性直接帶進 production。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 reliability readiness 時，先看服務的核心失敗模式是否已被驗證，再看回復路徑是否可執行。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>核心 user journey 是否有 SLO、load baseline 與 alert&lt;/li>
&lt;li>主要 dependency 是否有 timeout、fallback 與 degradation plan&lt;/li>
&lt;li>rollback / failover 是否有演練紀錄&lt;/li>
&lt;li>migration / backfill 是否有停止條件與資料校驗&lt;/li>
&lt;li>on-call 是否有 runbook、owner 與 escalation policy&lt;/li>
&lt;/ul>
&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>核心旅程有 SLO 與 alert&lt;/td>
 &lt;td>只看系統資源，忽略用戶結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量邊界&lt;/td>
 &lt;td>有 load baseline 與容量餘裕&lt;/td>
 &lt;td>流量上升時才發現瓶頸&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>回復路徑&lt;/td>
 &lt;td>rollback / failover 有演練紀錄&lt;/td>
 &lt;td>事故現場才第一次走流程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料操作&lt;/td>
 &lt;td>migration 有校驗與停止條件&lt;/td>
 &lt;td>補資料操作擴大影響面&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>值班準備&lt;/td>
 &lt;td>on-call 有 runbook 與 escalation&lt;/td>
 &lt;td>事故當下才建立協作節奏&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="readiness-範圍">Readiness 範圍&lt;/h2>
&lt;p>Reliability readiness review 的範圍是服務進入 production 壓力前需要具備的最低可靠性條件。它不取代 CI、load test、release gate 或 incident drill，而是把這些控制面接成同一個放行判斷。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>範圍&lt;/th>
 &lt;th>核心問題&lt;/th>
 &lt;th>對應控制面&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>服務健康&lt;/td>
 &lt;td>核心旅程是否有可靠性目標&lt;/td>
 &lt;td>SLO、SLI、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>容量&lt;/td>
 &lt;td>預期流量與尖峰是否被驗證&lt;/td>
 &lt;td>load test、capacity model&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>依賴&lt;/td>
 &lt;td>下游失效是否有 timeout 與降級&lt;/td>
 &lt;td>dependency budget、fallback&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>資料&lt;/td>
 &lt;td>migration、backfill 是否可校驗&lt;/td>
 &lt;td>migration safety、test data&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>回復&lt;/td>
 &lt;td>rollback、failover 是否可執行&lt;/td>
 &lt;td>DR rehearsal、rollback rehearsal&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>操作&lt;/td>
 &lt;td>on-call 是否知道如何接住事故&lt;/td>
 &lt;td>runbook、escalation、drill&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>服務健康是 readiness 的第一層。核心 user journey 需要有 SLO、dashboard、alert 與 owner，讓團隊知道「服務是否仍在承諾範圍內」。&lt;/p>
&lt;p>容量是 readiness 的第二層。load baseline、throughput ceiling、queue lag、dependency saturation 與 cost threshold 都需要在上線前被看見，避免第一個尖峰才揭露瓶頸。&lt;/p>
&lt;p>依賴是 readiness 的第三層。每個關鍵 downstream 都需要 timeout、deadline、retry、fallback、circuit breaker 或 degradation plan，讓局部失效維持在可控範圍。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>reliability readiness 的責任：確認服務能承受預期流量、依賴失效、資料變更與回復壓力</li>
<li>檢查面向：SLO、capacity、dependency、rollback、data migration、on-call、runbook</li>
<li>上線前門檻：核心路徑有 SLI、load test、rollback path、owner 與 alert</li>
<li>重大變更門檻：migration、feature flag、dependency change、config rollout 的風險判讀</li>
<li>高風險操作門檻：手動修資料、批次任務、backfill、區域切換</li>
<li>跟 04 的交接：缺少訊號時回到 observability readiness</li>
<li>跟 08 的交接：缺少事故節奏時回到 drills / runbook lifecycle</li>
<li>反模式：release gate 只看 CI 綠燈；沒有 rollback rehearsal；容量假設沒有驗證</li>
</ul>
<p>Reliability readiness review 的核心價值是把「上線前風險」前移成可討論的工程語言。只靠測試通過不代表服務可在真實流量與依賴波動下維持穩定，readiness 讓團隊在變更前先明確回答容量、回復、資料與值班四個問題。</p>
<h2 id="概念定位">概念定位</h2>
<p>Reliability readiness review 是把可靠性準備度轉成可檢查門檻的流程，責任是在服務承受 production 壓力前先找出可預期失效。</p>
<p>這一頁處理的是準備度。readiness 要把訊號、容量、依賴、回復、資料與值班能力放在同一張檢查表中判讀。</p>
<p>readiness 的目標是提高發布品質。當缺口被提前看見，團隊可以選擇補驗證、縮小範圍、延後發布或先加保護措施，避免把不確定性直接帶進 production。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 reliability readiness 時，先看服務的核心失敗模式是否已被驗證，再看回復路徑是否可執行。</p>
<p>重點訊號包括：</p>
<ul>
<li>核心 user journey 是否有 SLO、load baseline 與 alert</li>
<li>主要 dependency 是否有 timeout、fallback 與 degradation plan</li>
<li>rollback / failover 是否有演練紀錄</li>
<li>migration / backfill 是否有停止條件與資料校驗</li>
<li>on-call 是否有 runbook、owner 與 escalation policy</li>
</ul>
<table>
  <thead>
      <tr>
          <th>檢查面向</th>
          <th>最小可用判準</th>
          <th>常見風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>服務健康</td>
          <td>核心旅程有 SLO 與 alert</td>
          <td>只看系統資源，忽略用戶結果</td>
      </tr>
      <tr>
          <td>容量邊界</td>
          <td>有 load baseline 與容量餘裕</td>
          <td>流量上升時才發現瓶頸</td>
      </tr>
      <tr>
          <td>回復路徑</td>
          <td>rollback / failover 有演練紀錄</td>
          <td>事故現場才第一次走流程</td>
      </tr>
      <tr>
          <td>資料操作</td>
          <td>migration 有校驗與停止條件</td>
          <td>補資料操作擴大影響面</td>
      </tr>
      <tr>
          <td>值班準備</td>
          <td>on-call 有 runbook 與 escalation</td>
          <td>事故當下才建立協作節奏</td>
      </tr>
  </tbody>
</table>
<h2 id="readiness-範圍">Readiness 範圍</h2>
<p>Reliability readiness review 的範圍是服務進入 production 壓力前需要具備的最低可靠性條件。它不取代 CI、load test、release gate 或 incident drill，而是把這些控制面接成同一個放行判斷。</p>
<table>
  <thead>
      <tr>
          <th>範圍</th>
          <th>核心問題</th>
          <th>對應控制面</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>服務健康</td>
          <td>核心旅程是否有可靠性目標</td>
          <td>SLO、SLI、<a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a></td>
      </tr>
      <tr>
          <td>容量</td>
          <td>預期流量與尖峰是否被驗證</td>
          <td>load test、capacity model</td>
      </tr>
      <tr>
          <td>依賴</td>
          <td>下游失效是否有 timeout 與降級</td>
          <td>dependency budget、fallback</td>
      </tr>
      <tr>
          <td>資料</td>
          <td>migration、backfill 是否可校驗</td>
          <td>migration safety、test data</td>
      </tr>
      <tr>
          <td>回復</td>
          <td>rollback、failover 是否可執行</td>
          <td>DR rehearsal、rollback rehearsal</td>
      </tr>
      <tr>
          <td>操作</td>
          <td>on-call 是否知道如何接住事故</td>
          <td>runbook、escalation、drill</td>
      </tr>
  </tbody>
</table>
<p>服務健康是 readiness 的第一層。核心 user journey 需要有 SLO、dashboard、alert 與 owner，讓團隊知道「服務是否仍在承諾範圍內」。</p>
<p>容量是 readiness 的第二層。load baseline、throughput ceiling、queue lag、dependency saturation 與 cost threshold 都需要在上線前被看見，避免第一個尖峰才揭露瓶頸。</p>
<p>依賴是 readiness 的第三層。每個關鍵 downstream 都需要 timeout、deadline、retry、fallback、circuit breaker 或 degradation plan，讓局部失效維持在可控範圍。</p>
<p>資料是 readiness 的第四層。schema migration、backfill、online migration 與資料修復需要校驗、停止條件、rollback 或補償流程，讓資料風險能被事前判讀。</p>
<p>操作是 readiness 的最後一層。runbook、owner、escalation policy、incident intake 與 <a href="/blog/backend/knowledge-cards/incident-decision-log/" data-link-title="Incident Decision Log" data-link-desc="說明事故期間如何保留決策、證據、owner 與回退條件">decision log</a> 讓服務在失效時能被團隊接住。</p>
<h2 id="review-流程">Review 流程</h2>
<p>Reliability readiness review 的流程是從風險清單走向放行判斷。每個缺口都要被分類為阻擋、降級接受或後續改善，讓發布決策有清楚路由。</p>
<ol>
<li>定義本次上線或變更的服務承諾。</li>
<li>列出核心 failure mode、dependency、資料操作與回復路徑。</li>
<li>檢查 04 訊號是否足以支援判讀。</li>
<li>檢查 06 驗證是否足以支援放行。</li>
<li>檢查 08 值班與事故流程是否能接住失效。</li>
<li>對每個缺口指定 owner、處理路由與重新評估條件。</li>
</ol>
<p>服務承諾是 readiness review 的錨點。若本次變更影響 checkout、payment、message delivery 或 tenant migration，review 就要圍繞這些旅程的可靠性承諾，並把程式碼合併狀態視為其中一個輸入。</p>
<p>Failure mode 清單需要具體。依賴 timeout、queue lag、cache stampede、migration lock、feature flag misrouting、region failover 與 data reconciliation 都是不同失效模式，對應不同驗證與回復路由。</p>
<p>04 訊號是 readiness 的前提。若缺少 SLI、trace、log correlation 或 telemetry data quality，可靠性 review 只能停在推測；這類缺口應先回到 04.16 與 04.17。</p>
<p>08 流程是 readiness 的接手面。若 on-call 沒有 runbook、incident commander 不清楚啟動條件、status update 沒有節奏，可靠性缺口會在事故時轉成協作壓力。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>上線前只看 unit / integration test，沒有容量與回復判準</li>
<li>依賴失效時只能現場討論 fallback</li>
<li>migration 執行前沒有 rollback rehearsal</li>
<li>服務 owner 需要臨場補 RTO / RPO 或核心 SLO</li>
<li>on-call 第一次接觸 runbook 是事故當下</li>
</ul>
<p>典型情境是服務通過 CI 與 integration test 就上線，結果在流量尖峰時 dependency timeout 連鎖放大。若前一輪 readiness 已要求 load baseline、fallback 驗證與 rollback rehearsal，這類事故通常會降級成可控風險，維持在局部範圍。</p>
<h2 id="放行判斷">放行判斷</h2>
<p>Reliability readiness 的放行判斷需要區分「阻擋上線」與「帶限制上線」。這個區分讓團隊既能控制風險，也能在低風險缺口存在時保持交付節奏。</p>
<table>
  <thead>
      <tr>
          <th>結果</th>
          <th>判斷條件</th>
          <th>常見動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pass</td>
          <td>核心路徑、容量、回復與值班皆達標</td>
          <td>正常進入 release gate</td>
      </tr>
      <tr>
          <td>Conditional pass</td>
          <td>缺口可被降級、人工查證或短期 runbook 承接</td>
          <td>記錄限制、owner 與補齊期限</td>
      </tr>
      <tr>
          <td>Block</td>
          <td>核心旅程、資料或回復路徑缺少判讀</td>
          <td>暫停發布，補驗證或縮小範圍</td>
      </tr>
      <tr>
          <td>Defer</td>
          <td>需求價值低於可靠性風險</td>
          <td>延後變更，先處理 reliability debt</td>
      </tr>
  </tbody>
</table>
<p>Pass 代表核心風險已有證據支撐。這不代表系統完美，而是代表本次發布或操作有足夠訊號、驗證與回復路由。</p>
<p>Conditional pass 適合處理可控缺口。例如某個低風險 batch job 缺少完整 trace，但已有 log query、manual replay 與 on-call owner，可以帶著明確限制上線。</p>
<p>Block 適合處理核心旅程與資料風險。payment migration 缺少 rollback rehearsal、tenant backfill 缺少校驗、核心 API 缺少 SLO alert，這些缺口會讓事故處理沒有可靠入口。</p>
<p>Defer 適合處理價值與風險不對稱的變更。若本次變更只是次要優化，但會暴露高風險 migration 或 dependency change，延後是合理的 reliability decision。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Reliability readiness 的反模式通常來自把測試通過視為 production 準備度。測試通過證明某些功能路徑可執行，readiness 則要證明服務能在真實壓力、依賴波動與事故流程下被接住。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CI 綠燈即上線</td>
          <td>只看 test pass</td>
          <td>加入 SLO、capacity、rollback 判準</td>
      </tr>
      <tr>
          <td>容量假設無驗證</td>
          <td>靠估算決定尖峰承載</td>
          <td>補 load baseline 與容量餘裕</td>
      </tr>
      <tr>
          <td>Rollback 只寫文件</td>
          <td>回復流程沒有演練紀錄</td>
          <td>補 rollback rehearsal</td>
      </tr>
      <tr>
          <td>Migration 缺停止條件</td>
          <td>執行中才判斷是否暫停</td>
          <td>事前定義校驗、pause、fallback</td>
      </tr>
      <tr>
          <td>On-call 臨場接手</td>
          <td>事故時才找 owner 與 runbook</td>
          <td>補 drill 與 escalation route</td>
      </tr>
  </tbody>
</table>
<p>CI 綠燈即上線會讓可靠性停在程式正確性層。production 可靠性還包含容量、依賴、資料、回復與協作，這些條件需要各自的證據。</p>
<p>Rollback 只寫文件會在事故現場暴露落差。回復流程需要在類 production 條件下演練過，才能知道權限、資料、流量、相容性與通訊是否接得上。</p>
<h2 id="產業情境醫療系統">產業情境：醫療系統</h2>
<p>醫療系統上線前的 readiness review 需要額外的合規維度。可靠性準備度跟醫療法規準備度是同一個放行判斷的兩個面向，缺任何一個都應 block。</p>
<p>Readiness checklist 需要包含合規項目：PHI（受保護健康資訊）加密狀態、存取控制驗證、audit trail 完整性、backup encryption 驗證。這些項目跟可靠性項目（SLO、load baseline、rollback path）平行檢查，合規缺口的阻擋權重跟核心旅程缺口相同。</p>
<p>合規驗證跟可靠性驗證有時存在張力。為了 HIPAA compliance 加密所有 backup 會增加 restore 時間，<a href="/blog/backend/knowledge-cards/rto/" data-link-title="RTO" data-link-desc="說明恢復時間目標如何約束事故回復策略">RTO</a> 可能不符合臨床需求。為了最小資料揭露限制 staging 資料量會降低環境 parity。這類 trade-off 需要在 readiness review 中明確記錄，包含選擇理由與風險接受者。</p>
<p>醫療系統的 readiness review 需要臨床代表參與。技術 readiness 回答的是「系統能否穩定運作」，臨床 readiness 回答的是「臨床 workflow 能否安全繼續」。EMR 升級後的畫面配置變更、醫囑流程的步驟調整、報告格式的差異，這些在技術指標上可能正常，但在臨床操作上可能造成用藥錯誤或判讀延遲。</p>
<p>高風險變更（EMR 升級、PACS 遷移、醫囑系統切換）需要 go-live support window。變更後的前 24-72 小時維持加強值班，因為臨床問題的反饋延遲通常比技術指標長 — 護理站的操作異常可能在換班時才被回報，藥局的處方錯誤可能在調劑時才被發現。support window 的長度由臨床回饋延遲決定，技術團隊單獨設定容易低估。</p>
<h2 id="與-release-gate-的關係">與 Release Gate 的關係</h2>
<p>Reliability readiness review 是 release gate 的上游資料。readiness 負責整理風險與證據，release gate 負責根據政策做放行、暫停、縮小範圍或例外核准。</p>
<p>Readiness 結果應包含三種資訊：已驗證條件、已接受限制與阻擋缺口。Release gate 只看「通過 / 失敗」會遺失判讀脈絡；保留這三類資訊才能讓發布決策可復盤。</p>
<p>Readiness 也應回寫 reliability debt。每次 conditional pass 都代表團隊暫時接受一個缺口；若缺口反覆被接受，就應進入 <a href="/blog/backend/06-reliability/reliability-debt-backlog/" data-link-title="6.21 Reliability Debt Backlog" data-link-desc="把反覆事故、演練缺口與手動修復累積成可排序、可關閉的 reliability debt">6.21 Reliability Debt Backlog</a>。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.16 observability readiness：確認訊號可支援 readiness 判讀</li>
<li>06.2 load test：補容量與吞吐驗證</li>
<li>06.7 DR / rollback rehearsal：補回復路徑演練</li>
<li>06.8 release gate：把 readiness 結果變成放行條件</li>
<li>08.6 drills / on-call readiness：補值班與事故演練</li>
</ul>
]]></content:encoded></item><item><title>6.20 Experiment Safety Boundary</title><link>https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/experiment-safety-boundary/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>experiment safety boundary 的責任：讓可靠性實驗可控、可停、可回復&lt;/li>
&lt;li>實驗類型：chaos test、load test、failover drill、rollback rehearsal、DR drill&lt;/li>
&lt;li>blast radius：服務、tenant、region、dependency、資料範圍&lt;/li>
&lt;li>停止條件：SLO burn、error rate、latency、queue lag、customer impact、cost threshold&lt;/li>
&lt;li>權限約束：誰能啟動、誰能停止、誰能擴大範圍&lt;/li>
&lt;li>evidence 要求：假設、步驟、觀測訊號、結果、回復時間、action item&lt;/li>
&lt;li>跟 07 的交接：高風險演練需要權限與稽核約束&lt;/li>
&lt;li>反模式：直接在 production 打 chaos；缺停止條件；實驗 owner 與 incident commander 不清楚&lt;/li>
&lt;/ul>
&lt;p>Experiment safety boundary 的價值在於讓失敗驗證可重播、可停止、可回復。實驗越接近真實失效，對團隊越有學習價值；同時也越需要清楚邊界，避免「為了驗證韌性」而產生額外事故。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Experiment safety boundary 是定義可靠性實驗安全範圍的控制面，責任是讓團隊能主動驗證失敗，同時控制實驗造成的實際風險。&lt;/p>
&lt;p>這一頁處理的是實驗邊界。可靠性實驗的價值來自接近真實失效，但越接近真實，越需要明確 blast radius、停止條件與回復路徑。&lt;/p>
&lt;p>安全邊界是一組事前契約：誰能啟動、誰有停止權、觸發什麼門檻必須終止、終止後怎麼回復。契約存在時，團隊才能在實驗中保持速度，同時控制風險成本。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 experiment safety 時，先看實驗假設是否明確，再看實驗失控時是否能立刻停止與回復。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>experiment hypothesis 是否連到具體 failure mode&lt;/li>
&lt;li>blast radius 是否限制 service、tenant、region 或 traffic percentage&lt;/li>
&lt;li>stop condition 是否連到 SLO / customer impact / cost&lt;/li>
&lt;li>rollback / failover 是否在實驗前準備好&lt;/li>
&lt;li>observer、executor、approver 是否分工清楚&lt;/li>
&lt;/ul>
&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>blast radius 限在服務 / 區域 / 流量百分比&lt;/td>
 &lt;td>影響擴散到非目標服務&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>停止條件&lt;/td>
 &lt;td>stop condition 連到 SLO / impact / cost&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>rollback / failover 已預演&lt;/td>
 &lt;td>終止後回復時間失控&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>證據留存&lt;/td>
 &lt;td>hypothesis 與結果可回放&lt;/td>
 &lt;td>成功與失敗都不可重現&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="實驗類型">實驗類型&lt;/h2>
&lt;p>Experiment safety boundary 需要依實驗類型調整邊界。不同實驗打到的系統層不同，學習價值與實際風險也不同。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>實驗類型&lt;/th>
 &lt;th>驗證問題&lt;/th>
 &lt;th>主要邊界&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Chaos test&lt;/td>
 &lt;td>依賴、節點、網路失效是否可承受&lt;/td>
 &lt;td>service、region、dependency&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Load test&lt;/td>
 &lt;td>流量與資料量是否超過容量模型&lt;/td>
 &lt;td>traffic percentage、cost、quota&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Failover drill&lt;/td>
 &lt;td>切換流程是否可執行&lt;/td>
 &lt;td>region、data replication、routing&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rollback rehearsal&lt;/td>
 &lt;td>回復到前一版本是否安全&lt;/td>
 &lt;td>version、migration、feature flag&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>DR drill&lt;/td>
 &lt;td>災難恢復是否符合 RTO / RPO&lt;/td>
 &lt;td>data scope、region、access&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Chaos test 的風險在於故障注入接近真實失效。它需要明確 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state&lt;/a>、觀測訊號與停止條件，讓團隊知道實驗如何驗證韌性。&lt;/p>
&lt;p>Load test 的風險在於放大共享依賴。測試流量可能壓到 database、cache、broker、third-party API 或 observability pipeline，因此邊界要包含共享資源與成本上限。&lt;/p>
&lt;p>Failover drill 的風險在於切換後的長尾狀態。流量切過去只是第一步，團隊還需要看資料同步、cache warmup、queue drain、DNS / routing propagation 與客戶端行為。&lt;/p>
&lt;p>Rollback rehearsal 的風險在於資料與版本相容性。程式可回滾不代表 schema、message、cache、feature flag 與 client contract 都能同步回到安全狀態。&lt;/p>
&lt;p>DR drill 的風險在於權限、資料與外部通訊。災難恢復通常涉及高權限操作、備份還原與跨團隊協作，因此需要額外 audit trail 與 incident communication 準備。&lt;/p>
&lt;h2 id="boundary-契約">Boundary 契約&lt;/h2>
&lt;p>Experiment boundary 契約的責任是讓實驗在開始前就具備可停止、可回復與可復盤條件。契約應被寫成實驗 artifact，並納入可回查的操作紀錄。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>契約欄位&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>判讀用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Hypothesis&lt;/td>
 &lt;td>說明要驗證的 failure mode&lt;/td>
 &lt;td>避免實驗變成任意故障注入&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Blast radius&lt;/td>
 &lt;td>限制服務、tenant、region 範圍&lt;/td>
 &lt;td>控制實際影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Steady state&lt;/td>
 &lt;td>定義實驗期間應維持的狀態&lt;/td>
 &lt;td>判斷實驗是否成功&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Stop condition&lt;/td>
 &lt;td>定義終止門檻&lt;/td>
 &lt;td>讓失控時能立刻停手&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rollback path&lt;/td>
 &lt;td>定義回復步驟&lt;/td>
 &lt;td>降低終止後的恢復成本&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Authority&lt;/td>
 &lt;td>定義啟動、停止與擴大權限&lt;/td>
 &lt;td>避免事中權責不清&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence&lt;/td>
 &lt;td>定義要收集的觀測與決策紀錄&lt;/td>
 &lt;td>支援復盤與可重播&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Hypothesis 是實驗的錨點。好的假設會說明「當 dependency timeout 發生時，checkout 應進入 degraded mode，SLO &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate&lt;/a> 應維持在門檻內」，而不只是「關掉某個服務」。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>experiment safety boundary 的責任：讓可靠性實驗可控、可停、可回復</li>
<li>實驗類型：chaos test、load test、failover drill、rollback rehearsal、DR drill</li>
<li>blast radius：服務、tenant、region、dependency、資料範圍</li>
<li>停止條件：SLO burn、error rate、latency、queue lag、customer impact、cost threshold</li>
<li>權限約束：誰能啟動、誰能停止、誰能擴大範圍</li>
<li>evidence 要求：假設、步驟、觀測訊號、結果、回復時間、action item</li>
<li>跟 07 的交接：高風險演練需要權限與稽核約束</li>
<li>反模式：直接在 production 打 chaos；缺停止條件；實驗 owner 與 incident commander 不清楚</li>
</ul>
<p>Experiment safety boundary 的價值在於讓失敗驗證可重播、可停止、可回復。實驗越接近真實失效，對團隊越有學習價值；同時也越需要清楚邊界，避免「為了驗證韌性」而產生額外事故。</p>
<h2 id="概念定位">概念定位</h2>
<p>Experiment safety boundary 是定義可靠性實驗安全範圍的控制面，責任是讓團隊能主動驗證失敗，同時控制實驗造成的實際風險。</p>
<p>這一頁處理的是實驗邊界。可靠性實驗的價值來自接近真實失效，但越接近真實，越需要明確 blast radius、停止條件與回復路徑。</p>
<p>安全邊界是一組事前契約：誰能啟動、誰有停止權、觸發什麼門檻必須終止、終止後怎麼回復。契約存在時，團隊才能在實驗中保持速度，同時控制風險成本。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 experiment safety 時，先看實驗假設是否明確，再看實驗失控時是否能立刻停止與回復。</p>
<p>重點訊號包括：</p>
<ul>
<li>experiment hypothesis 是否連到具體 failure mode</li>
<li>blast radius 是否限制 service、tenant、region 或 traffic percentage</li>
<li>stop condition 是否連到 SLO / customer impact / cost</li>
<li>rollback / failover 是否在實驗前準備好</li>
<li>observer、executor、approver 是否分工清楚</li>
</ul>
<table>
  <thead>
      <tr>
          <th>控制面</th>
          <th>最小可用判準</th>
          <th>失控信號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>範圍控制</td>
          <td>blast radius 限在服務 / 區域 / 流量百分比</td>
          <td>影響擴散到非目標服務</td>
      </tr>
      <tr>
          <td>停止條件</td>
          <td>stop condition 連到 SLO / impact / cost</td>
          <td>超門檻仍持續實驗</td>
      </tr>
      <tr>
          <td>權限治理</td>
          <td>啟動者、停止者、核准者分離</td>
          <td>需要額外查證誰在操作</td>
      </tr>
      <tr>
          <td>回復能力</td>
          <td>rollback / failover 已預演</td>
          <td>終止後回復時間失控</td>
      </tr>
      <tr>
          <td>證據留存</td>
          <td>hypothesis 與結果可回放</td>
          <td>成功與失敗都不可重現</td>
      </tr>
  </tbody>
</table>
<h2 id="實驗類型">實驗類型</h2>
<p>Experiment safety boundary 需要依實驗類型調整邊界。不同實驗打到的系統層不同，學習價值與實際風險也不同。</p>
<table>
  <thead>
      <tr>
          <th>實驗類型</th>
          <th>驗證問題</th>
          <th>主要邊界</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Chaos test</td>
          <td>依賴、節點、網路失效是否可承受</td>
          <td>service、region、dependency</td>
      </tr>
      <tr>
          <td>Load test</td>
          <td>流量與資料量是否超過容量模型</td>
          <td>traffic percentage、cost、quota</td>
      </tr>
      <tr>
          <td>Failover drill</td>
          <td>切換流程是否可執行</td>
          <td>region、data replication、routing</td>
      </tr>
      <tr>
          <td>Rollback rehearsal</td>
          <td>回復到前一版本是否安全</td>
          <td>version、migration、feature flag</td>
      </tr>
      <tr>
          <td>DR drill</td>
          <td>災難恢復是否符合 RTO / RPO</td>
          <td>data scope、region、access</td>
      </tr>
  </tbody>
</table>
<p>Chaos test 的風險在於故障注入接近真實失效。它需要明確 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a>、觀測訊號與停止條件，讓團隊知道實驗如何驗證韌性。</p>
<p>Load test 的風險在於放大共享依賴。測試流量可能壓到 database、cache、broker、third-party API 或 observability pipeline，因此邊界要包含共享資源與成本上限。</p>
<p>Failover drill 的風險在於切換後的長尾狀態。流量切過去只是第一步，團隊還需要看資料同步、cache warmup、queue drain、DNS / routing propagation 與客戶端行為。</p>
<p>Rollback rehearsal 的風險在於資料與版本相容性。程式可回滾不代表 schema、message、cache、feature flag 與 client contract 都能同步回到安全狀態。</p>
<p>DR drill 的風險在於權限、資料與外部通訊。災難恢復通常涉及高權限操作、備份還原與跨團隊協作，因此需要額外 audit trail 與 incident communication 準備。</p>
<h2 id="boundary-契約">Boundary 契約</h2>
<p>Experiment boundary 契約的責任是讓實驗在開始前就具備可停止、可回復與可復盤條件。契約應被寫成實驗 artifact，並納入可回查的操作紀錄。</p>
<table>
  <thead>
      <tr>
          <th>契約欄位</th>
          <th>責任</th>
          <th>判讀用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Hypothesis</td>
          <td>說明要驗證的 failure mode</td>
          <td>避免實驗變成任意故障注入</td>
      </tr>
      <tr>
          <td>Blast radius</td>
          <td>限制服務、tenant、region 範圍</td>
          <td>控制實際影響</td>
      </tr>
      <tr>
          <td>Steady state</td>
          <td>定義實驗期間應維持的狀態</td>
          <td>判斷實驗是否成功</td>
      </tr>
      <tr>
          <td>Stop condition</td>
          <td>定義終止門檻</td>
          <td>讓失控時能立刻停手</td>
      </tr>
      <tr>
          <td>Rollback path</td>
          <td>定義回復步驟</td>
          <td>降低終止後的恢復成本</td>
      </tr>
      <tr>
          <td>Authority</td>
          <td>定義啟動、停止與擴大權限</td>
          <td>避免事中權責不清</td>
      </tr>
      <tr>
          <td>Evidence</td>
          <td>定義要收集的觀測與決策紀錄</td>
          <td>支援復盤與可重播</td>
      </tr>
  </tbody>
</table>
<p>Hypothesis 是實驗的錨點。好的假設會說明「當 dependency timeout 發生時，checkout 應進入 degraded mode，SLO <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> 應維持在門檻內」，而不只是「關掉某個服務」。</p>
<p>Blast radius 需要同時包含技術範圍與客戶範圍。技術範圍是 service、region、cluster、dependency；客戶範圍是 tenant、plan、traffic percentage 或 internal-only cohort。</p>
<p>Stop condition 需要對應使用者影響。CPU 上升可以作為輔助訊號，但停止條件更應包含 SLO burn、error rate、latency、queue lag、customer ticket、成本與安全事件。</p>
<p>Authority 需要事前分清。executor 可以啟動實驗，observer 可以判讀訊號，incident commander 或 designated stop owner 必須有權直接終止實驗。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>chaos 實驗描述只有「打掉節點」，沒有 steady state 與停止條件</li>
<li>load test 影響共享 dependency，其他服務被連帶拖垮</li>
<li>DR drill 的停止擴大條件需要臨場討論</li>
<li>實驗成功但沒有 evidence，可重播性不足</li>
<li>實驗權限過寬，值班人員不知道誰在操作</li>
</ul>
<p>常見事故型場景是 load test 誤傷共享依賴，導致無關服務一起退化。若實驗前有 boundary 契約，至少會先限制流量比例、設定跨服務告警與 stop condition，讓問題停留在演練範圍內。</p>
<h2 id="stop-condition-設計">Stop Condition 設計</h2>
<p>Stop condition 的責任是把「什麼時候停」變成可觀測門檻。實驗期間不應靠臨場感覺判斷是否繼續，應根據預先同意的訊號停止或縮小範圍。</p>
<table>
  <thead>
      <tr>
          <th>停止條件</th>
          <th>常見門檻</th>
          <th>路由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SLO burn</td>
          <td>短窗 burn rate 超過 policy</td>
          <td>終止實驗，進 incident intake</td>
      </tr>
      <tr>
          <td>Customer impact</td>
          <td>ticket、RUM、synthetic probe 異常</td>
          <td>終止或降到 internal cohort</td>
      </tr>
      <tr>
          <td>Queue lag</td>
          <td>lag 超過 drain 能力</td>
          <td>暫停流量，啟動 drain plan</td>
      </tr>
      <tr>
          <td>Error rate</td>
          <td>目標服務或相鄰服務錯誤率上升</td>
          <td>縮小 blast radius</td>
      </tr>
      <tr>
          <td>Cost threshold</td>
          <td>cloud cost 或 observability cost 暴增</td>
          <td>終止 load / trace 擴張</td>
      </tr>
      <tr>
          <td>Security signal</td>
          <td>audit、WAF、IAM 異常</td>
          <td>停止實驗，轉 07 / 08 分流</td>
      </tr>
  </tbody>
</table>
<p>SLO burn 是最適合作為 stop condition 的可靠性訊號。它能把多個低層訊號聚合成使用者影響，並且直接接到 <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 與 release policy。</p>
<p>Customer impact 是停止條件的高優先訊號。即使 backend 指標尚未超標，只要 RUM、synthetic probe、support ticket 或 status page evidence 顯示客戶受影響，實驗就應縮小或終止。</p>
<p>Security signal 需要獨立路由。若實驗觸發異常權限、audit log gap、WAF event 或資料外送風險，應停止 reliability experiment，改由 security / incident response 流程判讀。</p>
<h2 id="evidence-與復盤">Evidence 與復盤</h2>
<p>Experiment evidence 的責任是讓實驗結果可被重播、比較與回寫。一次實驗不論成功或失敗，都應產出可被後續 readiness、release gate 與 incident drill 使用的證據。</p>
<table>
  <thead>
      <tr>
          <th>Evidence 欄位</th>
          <th>責任</th>
          <th>後續用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Hypothesis</td>
          <td>保留原始假設</td>
          <td>判斷成功或失敗</td>
      </tr>
      <tr>
          <td>Timeline</td>
          <td>記錄開始、注入、停止、回復</td>
          <td>產生 incident / drill 時間線</td>
      </tr>
      <tr>
          <td>Signal set</td>
          <td>保存 dashboard、query、alert</td>
          <td>回寫 04 observability readiness</td>
      </tr>
      <tr>
          <td>Decision log</td>
          <td>保存停止、擴大、回復決策</td>
          <td>支援 08 <a href="/blog/backend/knowledge-cards/incident-decision-log/" data-link-title="Incident Decision Log" data-link-desc="說明事故期間如何保留決策、證據、owner 與回退條件">incident decision log</a></td>
      </tr>
      <tr>
          <td>Action items</td>
          <td>保存缺口與 owner</td>
          <td>進入 reliability debt backlog</td>
      </tr>
  </tbody>
</table>
<p>成功實驗也需要 evidence。成功代表某個假設在某個範圍內成立，未必代表所有流量、region、tenant 或依賴都安全；evidence 能保留適用範圍。</p>
<p>失敗實驗需要分清系統缺口與實驗缺口。系統缺口可能是 fallback 沒生效；實驗缺口可能是 stop condition 不清、dashboard 缺訊號或 owner 權限不足。兩者回寫路由不同。</p>
<h2 id="案例對照chaos--fit-的安全邊界設計">案例對照：Chaos / FIT 的安全邊界設計</h2>
<p>本章的 boundary 跟 stop condition 框架在 Netflix 三個 case 中各對應不同子問題：N1 給出單輪 chaos 的四元素、N2 給出時段選擇 guardrails、N3 給出實驗輸出的結構化欄位。三者連起來、安全邊界從「實驗執行階段」延伸到「證據交接階段」。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/netflix/steady-state-chaos-and-fit/" data-link-title="Netflix：Steady State、Chaos 與 FIT 的驗證路徑" data-link-desc="把故障注入從工具操作升級成可驗證流程：先定義穩態，再設計注入與回復條件。">N1 Netflix Steady State、Chaos 與 FIT</a>：揭露一輪有效 chaos 驗證的四元素 — Steady state（服務正常時應維持什麼行為）、Hypothesis（失效發生後仍應維持什麼）、Blast radius（實驗範圍怎麼限制）、Abort condition（何時立即停止）。</p>
<p>四元素中 Blast radius + Abort condition 直接對應本章的 boundary 契約跟 stop condition。Steady state 對應 <a href="/blog/backend/06-reliability/steady-state-definition/" data-link-title="6.22 Steady State Definition" data-link-desc="在 chaos 與 failover 前先定義系統應維持的穩定狀態與可接受退化">6.22 steady-state-definition</a>、Hypothesis 對應實驗設計層。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/netflix/chaos-monkey-business-hours-guardrails/" data-link-title="Netflix：Business-Hours Chaos 與 Guardrails" data-link-desc="Chaos Monkey 為何刻意在 business hours 執行：把即時應變能力納入驗證，並用 guardrails 限制實驗風險。">N2 Netflix Business-Hours Chaos 與 Guardrails</a>：揭露「business-hours chaos 跟 off-hours chaos 的選擇」— 工作時間執行能驗證即時應變能力跟通訊鏈條、但要在 guardrails 內（時段限制、實驗範圍限制、明確 abort trigger、事後回寫）。</p>
<p>Business-hours chaos 的核心價值是在 guardrails 內接近真實情境：人員在線可即時應變、依賴流量真實、通訊鏈條被測到。Off-hours 雖然短期風險低、但測到的多是「工具可執行」、不等於「服務可承受」。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/netflix/fit-failure-injection-evidence-handoff/" data-link-title="Netflix：FIT 證據交接與 Release Gate 回寫" data-link-desc="用 Failure Injection Testing 產出的證據直接驅動 release gate：把實驗結果轉成可放行、可凍結、可回退的決策欄位。">N3 Netflix FIT 證據交接</a>：揭露實驗輸出要結構化成四個決策欄位。四欄位分屬不同 release gate 階段 — rollout 決策類（steady-state impact、dependency drift）回答「能否繼續 rollout / blast radius 是否可接受」、事故處置類（abort trigger record、fallback result）回答「是否進入凍結與回退 / 事故時能否安全止血」。這四欄位讓 FIT 結果直接對應 release gate 的具體決策 — 不再倚賴主觀討論回到放行 / 凍結判斷。詳見 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 verification-evidence-handoff</a> 跟 <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 rule-rollout-safety-gate</a>。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Experiment safety 的反模式通常來自把可靠性實驗當成勇敢行為。可靠性實驗的價值在設計、控制與學習，風險承受只是需要被管理的成本。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>直接打 production chaos</td>
          <td>真實但邊界不清</td>
          <td>先定義 cohort、stop condition</td>
      </tr>
      <tr>
          <td>無 steady state</td>
          <td>只知道打壞了什麼</td>
          <td>補 6.22 穩態定義</td>
      </tr>
      <tr>
          <td>無 stop owner</td>
          <td>超門檻後仍等會議決定</td>
          <td>指定有權停止的人</td>
      </tr>
      <tr>
          <td>缺 evidence</td>
          <td>實驗做過但缺少重播材料</td>
          <td>保存 hypothesis、timeline、signal</td>
      </tr>
      <tr>
          <td>權限過寬</td>
          <td>任意工程師可擴大 blast radius</td>
          <td>啟動、停止、擴大權限分離</td>
      </tr>
  </tbody>
</table>
<p>直接打 production chaos 的問題是風險與學習常被混在一起。production 實驗可以有價值，但需要從小 cohort、清楚 stop condition 與完整 rollback path 開始。</p>
<p>缺 evidence 會讓實驗只留下口頭記憶。可靠性能力需要累積，實驗結果應能回寫到 readiness、release gate、runbook 與 incident drill。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.16 observability readiness：確認實驗可被觀測</li>
<li>06.4 chaos testing：定義故障注入場景</li>
<li>06.7 DR / rollback rehearsal：定義回復路徑</li>
<li>06.22 steady state definition：定義實驗前 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a></li>
<li>07.23 shared controls：接 containment、rollback、degradation 共用控制面</li>
<li>08.6 drills / on-call readiness：把實驗轉成值班演練</li>
</ul>
]]></content:encoded></item><item><title>6.21 Reliability Debt Backlog</title><link>https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/reliability-debt-backlog/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>reliability debt 的責任：把可靠性缺口從口頭風險變成可管理 backlog&lt;/li>
&lt;li>來源：post-incident review、game day、load test、chaos、on-call toil、customer ticket&lt;/li>
&lt;li>debt 類型：missing automation、weak rollback、manual recovery、fragile dependency、observability gap&lt;/li>
&lt;li>欄位：impact、frequency、owner、evidence、mitigation、target state、closure signal&lt;/li>
&lt;li>排序方式：SLO 影響、事故重複率、toil 成本、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius&lt;/a>、修復成本&lt;/li>
&lt;li>關閉條件：測試、演練、runbook 更新、alert 改善、manual step 移除&lt;/li>
&lt;li>跟 08 的交接：PIR action item 進 reliability debt，集中成可追蹤工作&lt;/li>
&lt;li>反模式：每次復盤都列改善，三個月後仍 open；toil 沒有量化；debt 無 owner&lt;/li>
&lt;/ul>
&lt;p>Reliability debt backlog 的重點是把「事故教訓」轉成「可交付工作」。沒有 backlog，團隊每次復盤都會得到相似結論；有 backlog，才有辦法把缺口排序、分派、驗收並逐步關閉。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Reliability debt backlog 是管理可靠性缺口的工作佇列，責任是把反覆事故、演練缺口與手動修復轉成可排序、可驗證、可關閉的工程工作。&lt;/p>
&lt;p>這一頁處理的是債務治理。可靠性問題常以事故、值班疲勞與手動操作出現；backlog 讓這些訊號進入產品與工程排程。&lt;/p>
&lt;p>debt backlog 也提供跨團隊溝通語言。平台、服務、SRE 與產品可以用同一組欄位討論優先序，讓決策建立在同一批證據與欄位定義上。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 reliability debt 時，先看缺口是否有 evidence，再看關閉條件是否可驗證。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>debt 是否連到事故、演練或 toil 證據&lt;/li>
&lt;li>owner 是否能決定修復方案與排程&lt;/li>
&lt;li>impact 是否能對應 SLO、customer impact 或 on-call cost&lt;/li>
&lt;li>mitigation 是否只降低風險，或真正移除根因&lt;/li>
&lt;li>closure signal 是否能由測試、演練或監控證明&lt;/li>
&lt;/ul>
&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>Impact / Frequency&lt;/td>
 &lt;td>定義業務與技術代價&lt;/td>
 &lt;td>是否可量化到 SLO / toil / 客訴&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Owner / Due&lt;/td>
 &lt;td>明確責任與時程&lt;/td>
 &lt;td>是否有人可決策與執行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence&lt;/td>
 &lt;td>連回事故或演練證據&lt;/td>
 &lt;td>是否能追溯原始問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Mitigation / Target&lt;/td>
 &lt;td>區分短期止血與長期修法&lt;/td>
 &lt;td>是否避免只補 workaround&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Closure Signal&lt;/td>
 &lt;td>定義完成條件&lt;/td>
 &lt;td>是否可由測試或演練驗證&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;ul>
&lt;li>同類事故重複發生，但每次 action item 都重新命名&lt;/li>
&lt;li>on-call 反覆手動修同一個問題&lt;/li>
&lt;li>runbook 記錄 workaround，但沒有工程化任務&lt;/li>
&lt;li>debt backlog 只有優先級，缺少 impact / evidence / closure&lt;/li>
&lt;li>reliability 工作永遠輸給 feature，但事故成本持續上升&lt;/li>
&lt;/ul>
&lt;p>實務上最常見的失敗模式是 action item 全留在會議筆記。三個月後同類事故再發生，團隊才重新開同一張單。把 PIR 直接轉進 debt backlog，才能讓「是否真的改善」變成可驗證事實。&lt;/p>
&lt;h2 id="action-item-分級跟-release-gate-綁定">Action Item 分級跟 Release Gate 綁定&lt;/h2>
&lt;p>Action item 分級的核心責任是給每個改進項匹配的強制力：高風險者進 release gate 綁定、中風險者進 backlog 落地節點、低風險者保留追蹤節點。三類風險（重複事故、影響面放大、診斷效率）各需不同強制力、沒有分級時所有改進項並列競爭資源、強制力被攤平。&lt;/p>
&lt;p>對應 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">G2 Google Postmortem Action Item Closure 治理&lt;/a>：揭露三層機制對應上述三類風險 — action item 分級（P0/P1/P2）、可驗證完成條件（不是「優化」「強化」抽象字）、closure 進固定 review cadence。&lt;/p>
&lt;p>P0/P1/P2 分級的核心價值是「給高風險 action item 強制力」：&lt;/p>
&lt;ul>
&lt;li>P0 重複事故高機率再發生：下個 release 週期前完成並驗證&lt;/li>
&lt;li>P1 會放大事故影響面：要有落地日期跟 gate 條件&lt;/li>
&lt;li>P2 提升診斷或操作效率：可排 backlog、但保留追蹤節點&lt;/li>
&lt;/ul>
&lt;p>最關鍵的綁定是 &lt;strong>P0/P1 直接掛到 release gate&lt;/strong>：未完成時不得放行關聯變更。這層綁定才讓分級從「backlog 優先序」升級為「工程強制力」 — P0/P1 直接決定 release 是否放行、未完成的 action item 直接是放行條件缺口。詳見 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/release-gate/#%e8%ae%8a%e6%9b%b4%e5%88%86%e5%b1%a4%e8%b7%9f-gate-%e6%94%bf%e7%ad%96" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate 變更分層&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>reliability debt 的責任：把可靠性缺口從口頭風險變成可管理 backlog</li>
<li>來源：post-incident review、game day、load test、chaos、on-call toil、customer ticket</li>
<li>debt 類型：missing automation、weak rollback、manual recovery、fragile dependency、observability gap</li>
<li>欄位：impact、frequency、owner、evidence、mitigation、target state、closure signal</li>
<li>排序方式：SLO 影響、事故重複率、toil 成本、<a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a>、修復成本</li>
<li>關閉條件：測試、演練、runbook 更新、alert 改善、manual step 移除</li>
<li>跟 08 的交接：PIR action item 進 reliability debt，集中成可追蹤工作</li>
<li>反模式：每次復盤都列改善，三個月後仍 open；toil 沒有量化；debt 無 owner</li>
</ul>
<p>Reliability debt backlog 的重點是把「事故教訓」轉成「可交付工作」。沒有 backlog，團隊每次復盤都會得到相似結論；有 backlog，才有辦法把缺口排序、分派、驗收並逐步關閉。</p>
<h2 id="概念定位">概念定位</h2>
<p>Reliability debt backlog 是管理可靠性缺口的工作佇列，責任是把反覆事故、演練缺口與手動修復轉成可排序、可驗證、可關閉的工程工作。</p>
<p>這一頁處理的是債務治理。可靠性問題常以事故、值班疲勞與手動操作出現；backlog 讓這些訊號進入產品與工程排程。</p>
<p>debt backlog 也提供跨團隊溝通語言。平台、服務、SRE 與產品可以用同一組欄位討論優先序，讓決策建立在同一批證據與欄位定義上。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 reliability debt 時，先看缺口是否有 evidence，再看關閉條件是否可驗證。</p>
<p>重點訊號包括：</p>
<ul>
<li>debt 是否連到事故、演練或 toil 證據</li>
<li>owner 是否能決定修復方案與排程</li>
<li>impact 是否能對應 SLO、customer impact 或 on-call cost</li>
<li>mitigation 是否只降低風險，或真正移除根因</li>
<li>closure signal 是否能由測試、演練或監控證明</li>
</ul>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>目的</th>
          <th>驗收重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Impact / Frequency</td>
          <td>定義業務與技術代價</td>
          <td>是否可量化到 SLO / toil / 客訴</td>
      </tr>
      <tr>
          <td>Owner / Due</td>
          <td>明確責任與時程</td>
          <td>是否有人可決策與執行</td>
      </tr>
      <tr>
          <td>Evidence</td>
          <td>連回事故或演練證據</td>
          <td>是否能追溯原始問題</td>
      </tr>
      <tr>
          <td>Mitigation / Target</td>
          <td>區分短期止血與長期修法</td>
          <td>是否避免只補 workaround</td>
      </tr>
      <tr>
          <td>Closure Signal</td>
          <td>定義完成條件</td>
          <td>是否可由測試或演練驗證</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>同類事故重複發生，但每次 action item 都重新命名</li>
<li>on-call 反覆手動修同一個問題</li>
<li>runbook 記錄 workaround，但沒有工程化任務</li>
<li>debt backlog 只有優先級，缺少 impact / evidence / closure</li>
<li>reliability 工作永遠輸給 feature，但事故成本持續上升</li>
</ul>
<p>實務上最常見的失敗模式是 action item 全留在會議筆記。三個月後同類事故再發生，團隊才重新開同一張單。把 PIR 直接轉進 debt backlog，才能讓「是否真的改善」變成可驗證事實。</p>
<h2 id="action-item-分級跟-release-gate-綁定">Action Item 分級跟 Release Gate 綁定</h2>
<p>Action item 分級的核心責任是給每個改進項匹配的強制力：高風險者進 release gate 綁定、中風險者進 backlog 落地節點、低風險者保留追蹤節點。三類風險（重複事故、影響面放大、診斷效率）各需不同強制力、沒有分級時所有改進項並列競爭資源、強制力被攤平。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/google/postmortem-action-item-closure-governance/" data-link-title="Google：Postmortem Action Item Closure 治理" data-link-desc="把 blameless postmortem 從會議文件變成可追蹤的可靠性治理機制：action item 分級、完成條件與回寫節奏。">G2 Google Postmortem Action Item Closure 治理</a>：揭露三層機制對應上述三類風險 — action item 分級（P0/P1/P2）、可驗證完成條件（不是「優化」「強化」抽象字）、closure 進固定 review cadence。</p>
<p>P0/P1/P2 分級的核心價值是「給高風險 action item 強制力」：</p>
<ul>
<li>P0 重複事故高機率再發生：下個 release 週期前完成並驗證</li>
<li>P1 會放大事故影響面：要有落地日期跟 gate 條件</li>
<li>P2 提升診斷或操作效率：可排 backlog、但保留追蹤節點</li>
</ul>
<p>最關鍵的綁定是 <strong>P0/P1 直接掛到 release gate</strong>：未完成時不得放行關聯變更。這層綁定才讓分級從「backlog 優先序」升級為「工程強制力」 — P0/P1 直接決定 release 是否放行、未完成的 action item 直接是放行條件缺口。詳見 <a href="/blog/backend/06-reliability/release-gate/#%e8%ae%8a%e6%9b%b4%e5%88%86%e5%b1%a4%e8%b7%9f-gate-%e6%94%bf%e7%ad%96" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 release gate 變更分層</a>。</p>
<p>整體 reliability 訊號量化（含 toil ratio、closure rate、debt 趨勢）由 <a href="/blog/backend/06-reliability/reliability-metrics-governance/" data-link-title="6.18 Reliability Metrics Governance" data-link-desc="DORA / SPACE 指標的選用、量測陷阱、anti-gaming 與團隊階段適配">6.18 reliability-metrics-governance</a> 處理。</p>
<h2 id="toil-budget把重複手動工作變成預算問題">Toil Budget：把重複手動工作變成預算問題</h2>
<p>Toil budget 是把重複手動工作量化成預算、用 closure 機制強制超標部分轉投自動化的治理工具。Toil 沒被當預算治理時、會吸收 SRE 時間、把可靠性改進工作擠掉。</p>
<p>對應 <a href="/blog/backend/06-reliability/cases/google/toil-budget-and-automation-investment-policy/" data-link-title="Google：Toil Budget 與 Automation 投資政策" data-link-desc="把 toil 從感受問題轉成預算問題：用時間配比與自動化回報機制，避免 on-call 壓力長期侵蝕可靠性工程。">G3 Google Toil Budget 與 Automation 投資政策</a>：揭露四個機制 — toil 分類（哪些屬可自動化）、時間配比（Google SRE 經驗值 50%、組織應依自身 toil 性質校準、不是普世門檻）、超標處理（凍結部分 feature、轉投自動化）、改善驗證（closure 指標跟 evidence）。前兩項屬「測量設計」（toil 是什麼 + 多少算超標）、後兩項屬「治理動作」（超標後做什麼 + 改善如何驗證）。</p>
<p>Toil budget 跟 reliability debt backlog 是兩個面向：</p>
<ul>
<li>Reliability debt backlog：管「失效缺口」（事故 / 演練揭露的工程化任務）</li>
<li>Toil budget：管「日常壓力」（on-call 反覆手動工作的時間成本）</li>
</ul>
<p>兩者要共用同一個 closure 機制：toil 超標時、超標部分強制轉投自動化、進 debt backlog 排序、按完成條件驗收。這層綁定讓 toil 超標自動觸發改善排程：超標 ratio 進日常輸入信號、相關 feature 凍結、自動化工作進 debt backlog 排序、按完成條件驗收。把 toil ratio 當日常治理輸入、而非 on-call burnout 後的事後指標。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.8 signal governance loop：把觀測缺口變成 debt</li>
<li>06.8 release gate：高風險 debt 可成為 freeze 條件</li>
<li>06.18 reliability metrics governance：量化 debt 趨勢</li>
<li>08.5 post-incident review：PIR action items 的上游來源</li>
<li>08.13 repeated incident / toil：反覆事故與 toil 的事故端入口</li>
</ul>
]]></content:encoded></item><item><title>6.22 Steady State Definition</title><link>https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/steady-state-definition/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>steady state 的責任：定義實驗期間系統應維持的可接受狀態&lt;/li>
&lt;li>穩態來源：SLO、business KPI、queue lag、error rate、latency、throughput、customer impact&lt;/li>
&lt;li>可接受退化：degradation mode、fallback、load shedding、partial outage&lt;/li>
&lt;li>實驗假設：故障注入後哪些訊號應保持穩定，哪些訊號可暫時退化&lt;/li>
&lt;li>觀測要求：dashboard、alert、trace、synthetic probe、client-side signal&lt;/li>
&lt;li>跟 chaos 的關係：沒有 steady state，chaos 只能證明系統被打壞&lt;/li>
&lt;li>跟 incident response 的關係：steady state 也定義事故恢復完成條件&lt;/li>
&lt;li>反模式：只定義故障動作，不定義成功條件；只看 server 指標，不看使用者影響&lt;/li>
&lt;/ul>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">Steady state&lt;/a> definition 的價值是讓實驗與事故有共同終點。穩態定義建立後，團隊可以同時回答「壞到什麼程度可接受」與「什麼時候算恢復」。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Steady state definition 是可靠性實驗的成功條件，責任是讓團隊知道故障發生後系統應該維持什麼服務能力。&lt;/p>
&lt;p>這一頁處理的是穩態定義。Chaos、failover 與 DR drill 都需要先定義系統的可接受狀態，才能判斷實驗是在驗證韌性，還是在製造混亂。&lt;/p>
&lt;p>穩態是一組服務承諾，通常同時包含成功率、延遲、資料正確性與使用者影響，並對應不同故障情境下的可接受退化範圍。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 steady state 時，先看穩態是否貼近使用者，再看退化是否有明確邊界。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>steady state 是否包含 success rate、latency、queue lag 與 user impact&lt;/li>
&lt;li>degraded mode 是否說明哪些功能保留、哪些功能暫停&lt;/li>
&lt;li>stop condition 是否連到 steady state breach&lt;/li>
&lt;li>dashboard 是否能同時呈現系統指標與使用者旅程&lt;/li>
&lt;li>recovery complete 是否有可量測門檻&lt;/li>
&lt;/ul>
&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>success rate / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget&lt;/a> 在可接受範圍&lt;/td>
 &lt;td>判斷是否需要升級事故&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>體驗延遲&lt;/td>
 &lt;td>latency 與 queue lag 在門檻內&lt;/td>
 &lt;td>判斷是否進入 degraded mode&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>recovery complete 有量測閾值&lt;/td>
 &lt;td>判斷事故何時可關閉&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="穩態來源">穩態來源&lt;/h2>
&lt;p>Steady state 的來源是服務承諾與操作訊號。它需要把 SLO、business KPI、系統指標與客戶感知訊號放在同一個判讀模型中。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>來源&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>常見訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>SLO / SLI&lt;/td>
 &lt;td>定義可靠性承諾&lt;/td>
 &lt;td>success rate、latency、freshness&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Business KPI&lt;/td>
 &lt;td>定義業務結果是否維持&lt;/td>
 &lt;td>checkout success、order volume&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Queue / async&lt;/td>
 &lt;td>定義背景流程是否可追上&lt;/td>
 &lt;td>queue lag、DLQ、retry rate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Client signal&lt;/td>
 &lt;td>定義使用者感知是否正常&lt;/td>
 &lt;td>RUM、synthetic probe、mobile error&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Data signal&lt;/td>
 &lt;td>定義資料是否正確且可回復&lt;/td>
 &lt;td>reconciliation、replication lag&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>SLO / SLI 是 steady state 的主要來源。它們讓實驗與事故判讀有共同基準，避免每次演練都重新討論什麼算可接受狀態。&lt;/p>
&lt;p>Business KPI 能補足純技術指標的盲區。checkout success、payment authorization、message delivery、document publish 與 invoice generation 這些業務結果，能直接反映使用者旅程是否維持。&lt;/p>
&lt;p>Queue / async 訊號能保護延遲性風險。同步 API 可能恢復，但 queue lag、DLQ、retry storm 或 backfill backlog 仍在累積；steady state 應包含這些後段壓力。&lt;/p>
&lt;p>Client signal 能補 server-side 盲區。CDN、mobile network、browser runtime、third-party script 與 regional routing 可能讓 server 看起來健康，但使用者仍感知到失敗。&lt;/p>
&lt;p>Data signal 能保護正確性。failover、migration、replay 與 DR drill 都需要確認資料沒有遺失，或至少有明確補償與 reconciliation 路徑。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>steady state 的責任：定義實驗期間系統應維持的可接受狀態</li>
<li>穩態來源：SLO、business KPI、queue lag、error rate、latency、throughput、customer impact</li>
<li>可接受退化：degradation mode、fallback、load shedding、partial outage</li>
<li>實驗假設：故障注入後哪些訊號應保持穩定，哪些訊號可暫時退化</li>
<li>觀測要求：dashboard、alert、trace、synthetic probe、client-side signal</li>
<li>跟 chaos 的關係：沒有 steady state，chaos 只能證明系統被打壞</li>
<li>跟 incident response 的關係：steady state 也定義事故恢復完成條件</li>
<li>反模式：只定義故障動作，不定義成功條件；只看 server 指標，不看使用者影響</li>
</ul>
<p><a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">Steady state</a> definition 的價值是讓實驗與事故有共同終點。穩態定義建立後，團隊可以同時回答「壞到什麼程度可接受」與「什麼時候算恢復」。</p>
<h2 id="概念定位">概念定位</h2>
<p>Steady state definition 是可靠性實驗的成功條件，責任是讓團隊知道故障發生後系統應該維持什麼服務能力。</p>
<p>這一頁處理的是穩態定義。Chaos、failover 與 DR drill 都需要先定義系統的可接受狀態，才能判斷實驗是在驗證韌性，還是在製造混亂。</p>
<p>穩態是一組服務承諾，通常同時包含成功率、延遲、資料正確性與使用者影響，並對應不同故障情境下的可接受退化範圍。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 steady state 時，先看穩態是否貼近使用者，再看退化是否有明確邊界。</p>
<p>重點訊號包括：</p>
<ul>
<li>steady state 是否包含 success rate、latency、queue lag 與 user impact</li>
<li>degraded mode 是否說明哪些功能保留、哪些功能暫停</li>
<li>stop condition 是否連到 steady state breach</li>
<li>dashboard 是否能同時呈現系統指標與使用者旅程</li>
<li>recovery complete 是否有可量測門檻</li>
</ul>
<table>
  <thead>
      <tr>
          <th>穩態元素</th>
          <th>最小可用判準</th>
          <th>判讀價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>服務成功</td>
          <td>success rate / <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 在可接受範圍</td>
          <td>判斷是否需要升級事故</td>
      </tr>
      <tr>
          <td>體驗延遲</td>
          <td>latency 與 queue lag 在門檻內</td>
          <td>判斷是否進入 degraded mode</td>
      </tr>
      <tr>
          <td>資料正確</td>
          <td>無資料遺失或可接受補償策略</td>
          <td>判斷是否可宣告恢復</td>
      </tr>
      <tr>
          <td>恢復條件</td>
          <td>recovery complete 有量測閾值</td>
          <td>判斷事故何時可關閉</td>
      </tr>
  </tbody>
</table>
<h2 id="穩態來源">穩態來源</h2>
<p>Steady state 的來源是服務承諾與操作訊號。它需要把 SLO、business KPI、系統指標與客戶感知訊號放在同一個判讀模型中。</p>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>責任</th>
          <th>常見訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SLO / SLI</td>
          <td>定義可靠性承諾</td>
          <td>success rate、latency、freshness</td>
      </tr>
      <tr>
          <td>Business KPI</td>
          <td>定義業務結果是否維持</td>
          <td>checkout success、order volume</td>
      </tr>
      <tr>
          <td>Queue / async</td>
          <td>定義背景流程是否可追上</td>
          <td>queue lag、DLQ、retry rate</td>
      </tr>
      <tr>
          <td>Client signal</td>
          <td>定義使用者感知是否正常</td>
          <td>RUM、synthetic probe、mobile error</td>
      </tr>
      <tr>
          <td>Data signal</td>
          <td>定義資料是否正確且可回復</td>
          <td>reconciliation、replication lag</td>
      </tr>
  </tbody>
</table>
<p>SLO / SLI 是 steady state 的主要來源。它們讓實驗與事故判讀有共同基準，避免每次演練都重新討論什麼算可接受狀態。</p>
<p>Business KPI 能補足純技術指標的盲區。checkout success、payment authorization、message delivery、document publish 與 invoice generation 這些業務結果，能直接反映使用者旅程是否維持。</p>
<p>Queue / async 訊號能保護延遲性風險。同步 API 可能恢復，但 queue lag、DLQ、retry storm 或 backfill backlog 仍在累積；steady state 應包含這些後段壓力。</p>
<p>Client signal 能補 server-side 盲區。CDN、mobile network、browser runtime、third-party script 與 regional routing 可能讓 server 看起來健康，但使用者仍感知到失敗。</p>
<p>Data signal 能保護正確性。failover、migration、replay 與 DR drill 都需要確認資料沒有遺失，或至少有明確補償與 reconciliation 路徑。</p>
<h2 id="產業情境遊戲服務的穩態定義">產業情境：遊戲服務的穩態定義</h2>
<p>遊戲伺服器的穩態指標跟一般 web service 有結構性差異。即時互動遊戲的關鍵衡量是 tick rate stability（伺服器每秒處理的遊戲邏輯循環數）和 player session continuity（玩家連線不中斷），HTTP success rate 只能反映 API 層健康，無法代表 gameplay 品質。</p>
<p>穩態訊號需要覆蓋四個面向：tick rate 維持在目標頻率（如 64 tick/s 的射擊遊戲降到 32 就會被感知）、matchmaking latency 在可接受範圍、session persistence 不因後端變更掉線、state synchronization lag 不讓玩家看到不一致的遊戲狀態。</p>
<p>遊戲的高峰型態跟電商不同。峰值可能是新版本上線首日、季賽開始或限時活動開放，持續時間通常以天計（而非 BFCM 的數小時），流量曲線有明顯的日週期（每日晚間尖峰）。workload model 需要反映這種「多日高原 + 日內尖峰」的形狀，而非單一爆量。</p>
<p>Degraded mode 的定義需要區分核心 gameplay loop 與周邊系統。排行榜、成就系統、社交功能、商城可以暫停或降級，但核心對戰邏輯必須維持。玩家對 gameplay 中斷的容忍度遠低於周邊功能 — 排行榜延遲更新是可接受的退化，比賽中角色動作不同步則直接導致玩家離開。</p>
<p>穩態 breach 的判準對應兩個升級門檻：tick rate 低於感知門檻時，遊戲體驗開始劣化，需要啟動 load shedding 或關閉新 match 入口；session drop rate 超過門檻時，代表大量玩家掉線，需要升級事故等級並啟動 rollback。</p>
<h2 id="產業情境saas-與-b2b-服務的穩態定義">產業情境：SaaS 與 B2B 服務的穩態定義</h2>
<p>SaaS 服務的穩態需要按租戶層級定義。全域指標健康但特定租戶劣化的情境在多租戶系統很常見 — 全域 error rate 正常但某個大客戶的 latency 已超出其 SLA 承諾，只用全域穩態定義會讓這類局部退化被平均值隱藏。</p>
<p>租戶級 SLI 是 SaaS 穩態定義的核心擴充。按 tenant_id label 拆分 SLI（success rate / latency / queue lag），讓穩態判讀能對齊個別客戶的 SLA 承諾。enterprise 客戶的穩態門檻通常比 self-serve 更嚴格，拆分後才能分別判讀。拆分的成本是 cardinality 上升（每個 SLI × 租戶數），需要搭配 recording rule 或 rollup 控制 Prometheus / metrics backend 的壓力。</p>
<p>Noisy neighbor 是 SaaS 穩態的特有威脅。一個租戶的流量爆增或異常 query pattern 會拖垮共享資源（DB connection pool / cache throughput / queue depth），其他租戶的穩態被連帶破壞。穩態定義需要包含「單租戶資源消耗不超過共享資源配額的 X%」的條件，X 的值取決於隔離策略的強度 — <a href="/blog/backend/06-reliability/cases/amazon/shuffle-sharding-and-cell-boundary/" data-link-title="Amazon：Shuffle Sharding 與 Cell 邊界的失效局部化" data-link-desc="用 cell 與 shuffle sharding 將多租戶故障限制在局部，讓恢復策略可分批執行。">Amazon A1 的 shuffle sharding</a> 讓租戶間擴散受限於 shard 重疊機率，<a href="/blog/backend/06-reliability/cases/shopify/pod-architecture-and-resiliency-matrix/" data-link-title="Shopify：Pod Architecture 與 Resiliency Matrix" data-link-desc="多租戶隔離與系統化失敗模式盤點：pod 邊界控制擴散、resiliency matrix 驅動演練。">Shopify H2 的 pod 隔離</a> 讓租戶群組有獨立 pod 的穩態邊界。</p>
<p>Chaos 實驗在 SaaS 場景需要同時驗證全域穩態與租戶穩態。注入 DB latency 後，全域 success rate 可能只掉 0.1%（被其他健康租戶稀釋），但受影響的租戶群組可能已經 breach SLA。實驗的 steady state probe 需要同時查詢全域 SLI 和 top-N 租戶 SLI，才能判斷退化是否在可接受範圍。</p>
<h2 id="可接受退化">可接受退化</h2>
<p>可接受退化的責任是定義故障期間哪些能力要維持、哪些能力可以暫停、哪些能力需要補償。它讓團隊在壓力下有一致的降級語言。</p>
<table>
  <thead>
      <tr>
          <th>退化模式</th>
          <th>適用情境</th>
          <th>穩態判準</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Read-only mode</td>
          <td>寫入風險高、讀取仍可服務</td>
          <td>讀取成功率維持，寫入明確暫停</td>
      </tr>
      <tr>
          <td>Fallback</td>
          <td>下游依賴失效</td>
          <td>使用替代資料，標示 freshness 限制</td>
      </tr>
      <tr>
          <td>Load shedding</td>
          <td>流量超過容量</td>
          <td>保核心旅程，拒絕低優先請求</td>
      </tr>
      <tr>
          <td>Partial outage</td>
          <td>區域、tenant 或功能局部受影響</td>
          <td>影響範圍可界定且持續收斂</td>
      </tr>
      <tr>
          <td>Manual recovery</td>
          <td>自動回復不足</td>
          <td>人工步驟有 owner、timeline、證據</td>
      </tr>
  </tbody>
</table>
<p>Read-only mode 適合保護資料正確性。若寫入路徑風險高，暫停寫入但保留查詢，可以讓服務維持部分價值，同時避免資料修復成本擴大。</p>
<p>Fallback 適合吸收下游失效。fallback 需要明確資料新鮮度、適用功能與使用者提示，讓服務承諾暫時降到可接受範圍。</p>
<p>Load shedding 適合處理容量壓力。它需要先定義核心旅程與低優先請求，讓系統在高壓下保住最重要的使用者結果。</p>
<p>Partial outage 適合處理 <a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a> 已被限制的事故。穩態定義應說明受影響 region、tenant、功能與預期恢復路徑，避免把局部可控誤讀成全域恢復。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>chaos 實驗只記錄「節點被關掉」，沒有記錄服務是否維持</li>
<li>failover 後 server healthy，但用戶核心流程仍失敗</li>
<li>degraded mode 啟動後，團隊不知道何時能解除</li>
<li>recovery 宣告依賴人工感覺，而非 SLO / synthetic probe / queue drain</li>
<li>事故與演練使用不同的恢復完成定義</li>
</ul>
<p>典型場景是 failover 後基礎 health check 全綠，但核心交易成功率仍低於承諾。若 steady state 只看系統健康，團隊會過早宣告恢復；若 steady state 包含 user journey，則會持續修復直到服務承諾回線。</p>
<h2 id="實驗假設">實驗假設</h2>
<p>Steady state 是 experiment hypothesis 的成功條件。故障注入前，團隊要先寫清楚哪些訊號應維持、哪些訊號可退化、退化多久仍可接受。</p>
<table>
  <thead>
      <tr>
          <th>假設欄位</th>
          <th>責任</th>
          <th>範例</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Injected failure</td>
          <td>說明要注入的失效</td>
          <td>關閉一個 cache node</td>
      </tr>
      <tr>
          <td>Expected behavior</td>
          <td>說明系統應如何吸收</td>
          <td>request latency 短暫上升</td>
      </tr>
      <tr>
          <td>Stable signals</td>
          <td>說明應維持穩定的訊號</td>
          <td>checkout success rate 維持門檻</td>
      </tr>
      <tr>
          <td>Allowed degradation</td>
          <td>說明可接受退化</td>
          <td>p99 latency 10 分鐘內回線</td>
      </tr>
      <tr>
          <td>Stop condition</td>
          <td>說明何時終止</td>
          <td>error budget burn 超門檻</td>
      </tr>
      <tr>
          <td>Recovery complete</td>
          <td>說明何時算恢復</td>
          <td>queue lag drain 到基準線</td>
      </tr>
  </tbody>
</table>
<p>Injected failure 只是實驗輸入。可靠性實驗真正要驗證的是 expected behavior，也就是系統面對失效時是否維持約定服務能力。</p>
<p>Stable signals 需要同時包含 server-side 與 user-facing 訊號。pod healthy、CPU 正常、database 可連線都很有用，但最後仍要回到核心旅程是否成功。</p>
<p>Allowed degradation 能避免過度反應。某些實驗預期會造成短暫 latency 上升或 fallback 啟動，只要在可接受時間窗內回線，就代表系統符合預期。</p>
<p>Recovery complete 應該可量測。queue lag drain、error rate 回到 baseline、synthetic probe 連續通過、reconciliation 完成，都比「看起來好了」更適合作為關閉條件。</p>
<h2 id="事故恢復">事故恢復</h2>
<p>Steady state 也是事故恢復宣告的共同基準。事故處理需要知道服務何時從 containment 進入 recovery，何時可以對內外部宣告恢復，何時進入 post-incident review。</p>
<table>
  <thead>
      <tr>
          <th>階段</th>
          <th>Steady state 責任</th>
          <th>事故決策</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Triage</td>
          <td>判斷是否已偏離穩態</td>
          <td>啟動或升級 incident</td>
      </tr>
      <tr>
          <td>Containment</td>
          <td>判斷退化是否維持在可接受範圍</td>
          <td>降級、限流、切換</td>
      </tr>
      <tr>
          <td>Recovery</td>
          <td>判斷核心旅程是否回到門檻</td>
          <td>宣告服務恢復</td>
      </tr>
      <tr>
          <td>Review</td>
          <td>判斷穩態定義是否足以支援判讀</td>
          <td>回寫 SLO、dashboard、runbook</td>
      </tr>
  </tbody>
</table>
<p>Triage 階段，steady state 幫助團隊把異常轉成事故門檻。若 success rate、latency、queue lag 或 customer impact 偏離穩態，就有足夠理由啟動分級。</p>
<p>Containment 階段，steady state 幫助團隊判斷退化策略是否有效。fallback、load shedding 或 read-only mode 啟動後，團隊要看核心旅程是否回到可接受範圍。</p>
<p>Recovery 階段，steady state 幫助團隊避免過早關閉事故。基礎 health check 回綠只是其中一個訊號，核心旅程、資料正確性與長尾 backlog 都要回到門檻。</p>
<p>Review 階段，steady state 會回寫到 04 與 06。若事故期間發現穩態指標缺失、門檻過鬆或 dashboard 不支援判讀，就要回到 SLO、observability readiness 或 reliability readiness。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Steady state 的反模式通常來自只定義故障動作，缺少成功條件。成功條件能讓 chaos、failover 與 DR drill 證明系統如何承受失效，而不只證明系統被打壞。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只定義故障動作</td>
          <td>實驗說明只有關機、斷線、切流量</td>
          <td>補 stable signals 與成功條件</td>
      </tr>
      <tr>
          <td>只看 server 指標</td>
          <td>health check 綠燈就宣告恢復</td>
          <td>加入 user journey 與 client signal</td>
      </tr>
      <tr>
          <td>退化模式無邊界</td>
          <td>fallback 啟動後無時間窗與限制</td>
          <td>定義 allowed degradation</td>
      </tr>
      <tr>
          <td>恢復完成靠感覺</td>
          <td>IC 以主觀判斷關閉事故</td>
          <td>定義 recovery complete metric</td>
      </tr>
      <tr>
          <td>實驗與事故標準不同</td>
          <td>drill 通過但事故時用另一套門檻</td>
          <td>共用 steady state 與 runbook</td>
      </tr>
  </tbody>
</table>
<p>只看 server 指標會讓恢復宣告偏早。服務健康需要同時看基礎設施、後端旅程、client-side signal 與資料正確性，才能支援對外通訊。</p>
<p>退化模式無邊界會讓 fallback 變成隱性事故。fallback 可用時，團隊仍需要知道資料新鮮度、功能限制、時間窗與客戶影響。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.6 SLI/SLO signal：把穩態轉成可量測訊號</li>
<li>04.10 client-side / synthetic / RUM：補使用者感知訊號</li>
<li>06.4 chaos testing：把 steady state 作為實驗前提</li>
<li>06.7 DR / rollback rehearsal：把 steady state 作為恢復完成條件</li>
<li>08.3 containment / recovery：事故恢復宣告使用同一組穩態門檻</li>
</ul>
]]></content:encoded></item><item><title>6.23 Verification Evidence Handoff</title><link>https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>verification evidence handoff 的責任：把可靠性驗證結果交給 release gate、runbook 與 incident response&lt;/li>
&lt;li>來源：SLO policy、load test、chaos experiment、DR drill、rollback rehearsal、readiness review&lt;/li>
&lt;li>欄位：hypothesis、steady state、result、scope、evidence package、decision、owner、next route&lt;/li>
&lt;li>跟 4.20 的關係：使用同一 evidence package 格式承接 observability 證據&lt;/li>
&lt;li>跟 8.22 的關係：事故復盤會回寫新的驗證題目與證據缺口&lt;/li>
&lt;li>反模式：驗證做完只留結論；load test 圖表沒有 workload；chaos 成功但沒有 runbook 回寫&lt;/li>
&lt;/ul>
&lt;p>Verification evidence handoff 的核心是把可靠性驗證從「做過測試」升級成「留下可用證據」。驗證結果需要能進 release gate、runbook、incident drill 與 post-incident review，才會形成跨模組閉環。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Verification evidence handoff 是可靠性模組交給發布與事故流程的證據交接，責任是讓 SLO、load、chaos、DR 與 readiness 結果能被決策使用。&lt;/p>
&lt;p>這一頁處理的是驗證結果的交付格式。6.20 定義實驗安全邊界，6.22 定義 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state&lt;/a>；本章把這些驗證輸出整理成可以被 05 release、08 incident response 與 04 observability 回寫使用的 artifact。&lt;/p>
&lt;p>驗證證據的價值在於支援未來決策。一次 load test 的圖表、一次 chaos 成功、一次 DR drill 通過，如果沒有 hypothesis、scope、steady state、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/evidence-package/" data-link-title="Evidence Package" data-link-desc="說明觀測、驗證與事故流程如何把證據包成可交接、可回放的 artifact">evidence package&lt;/a> 與 action item，後續團隊很難知道它證明了什麼。&lt;/p>
&lt;h2 id="handoff-欄位">Handoff 欄位&lt;/h2>
&lt;p>Verification evidence handoff 的欄位要同時保存驗證前提、觀測證據與決策結果。欄位的目標是讓下游能判斷「這個驗證能支持哪個決策」。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>欄位&lt;/th>
 &lt;th>責任&lt;/th>
 &lt;th>判讀用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Hypothesis&lt;/td>
 &lt;td>說明要驗證的 failure mode&lt;/td>
 &lt;td>判斷實驗是否回答原問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Scope&lt;/td>
 &lt;td>標示服務、tenant、region 範圍&lt;/td>
 &lt;td>防止把局部結果外推&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Steady state&lt;/td>
 &lt;td>定義成功條件&lt;/td>
 &lt;td>判斷是否通過&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Workload / Fault&lt;/td>
 &lt;td>記錄流量模型或故障注入&lt;/td>
 &lt;td>支援重播&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence package&lt;/td>
 &lt;td>連到 log、metric、trace&lt;/td>
 &lt;td>支援查證與 handoff&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Result&lt;/td>
 &lt;td>pass、conditional、fail&lt;/td>
 &lt;td>接 release gate 與 readiness&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Decision&lt;/td>
 &lt;td>放行、阻擋、補驗證、補 runbook&lt;/td>
 &lt;td>把結果轉成動作&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Owner&lt;/td>
 &lt;td>指定後續責任人&lt;/td>
 &lt;td>支援 action item closure&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Hypothesis 欄位讓驗證聚焦。&lt;code>打掉 node&lt;/code> 只是操作；&lt;code>打掉一個 worker 後 queue lag 應在 10 分鐘內回到 baseline&lt;/code> 才是可判讀假設。&lt;/p>
&lt;p>Scope 欄位保護結論邊界。internal traffic、single tenant、one region、10% production traffic 與 full production 都是不同證據強度，handoff 需要明確標示。&lt;/p>
&lt;p>Evidence package 欄位接 4.20。驗證結果應保存 dashboard、query、trace、log、client-side signal、time range 與 data quality 限制，讓 release gate 或 incident response 可以回放。&lt;/p>
&lt;p>Result 欄位需要分層。Pass 代表在指定 scope 內符合 steady state；conditional 代表可接受但有缺口；fail 代表需要補設計、補訊號、補 runbook 或阻擋 release。&lt;/p>
&lt;h2 id="驗證來源">驗證來源&lt;/h2>
&lt;p>Verification evidence 的來源分成政策、容量、故障、回復與準備度。不同來源回答的決策問題不同。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>verification evidence handoff 的責任：把可靠性驗證結果交給 release gate、runbook 與 incident response</li>
<li>來源：SLO policy、load test、chaos experiment、DR drill、rollback rehearsal、readiness review</li>
<li>欄位：hypothesis、steady state、result、scope、evidence package、decision、owner、next route</li>
<li>跟 4.20 的關係：使用同一 evidence package 格式承接 observability 證據</li>
<li>跟 8.22 的關係：事故復盤會回寫新的驗證題目與證據缺口</li>
<li>反模式：驗證做完只留結論；load test 圖表沒有 workload；chaos 成功但沒有 runbook 回寫</li>
</ul>
<p>Verification evidence handoff 的核心是把可靠性驗證從「做過測試」升級成「留下可用證據」。驗證結果需要能進 release gate、runbook、incident drill 與 post-incident review，才會形成跨模組閉環。</p>
<h2 id="概念定位">概念定位</h2>
<p>Verification evidence handoff 是可靠性模組交給發布與事故流程的證據交接，責任是讓 SLO、load、chaos、DR 與 readiness 結果能被決策使用。</p>
<p>這一頁處理的是驗證結果的交付格式。6.20 定義實驗安全邊界，6.22 定義 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a>；本章把這些驗證輸出整理成可以被 05 release、08 incident response 與 04 observability 回寫使用的 artifact。</p>
<p>驗證證據的價值在於支援未來決策。一次 load test 的圖表、一次 chaos 成功、一次 DR drill 通過，如果沒有 hypothesis、scope、steady state、<a href="/blog/backend/knowledge-cards/evidence-package/" data-link-title="Evidence Package" data-link-desc="說明觀測、驗證與事故流程如何把證據包成可交接、可回放的 artifact">evidence package</a> 與 action item，後續團隊很難知道它證明了什麼。</p>
<h2 id="handoff-欄位">Handoff 欄位</h2>
<p>Verification evidence handoff 的欄位要同時保存驗證前提、觀測證據與決策結果。欄位的目標是讓下游能判斷「這個驗證能支持哪個決策」。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>責任</th>
          <th>判讀用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Hypothesis</td>
          <td>說明要驗證的 failure mode</td>
          <td>判斷實驗是否回答原問題</td>
      </tr>
      <tr>
          <td>Scope</td>
          <td>標示服務、tenant、region 範圍</td>
          <td>防止把局部結果外推</td>
      </tr>
      <tr>
          <td>Steady state</td>
          <td>定義成功條件</td>
          <td>判斷是否通過</td>
      </tr>
      <tr>
          <td>Workload / Fault</td>
          <td>記錄流量模型或故障注入</td>
          <td>支援重播</td>
      </tr>
      <tr>
          <td>Evidence package</td>
          <td>連到 log、metric、trace</td>
          <td>支援查證與 handoff</td>
      </tr>
      <tr>
          <td>Result</td>
          <td>pass、conditional、fail</td>
          <td>接 release gate 與 readiness</td>
      </tr>
      <tr>
          <td>Decision</td>
          <td>放行、阻擋、補驗證、補 runbook</td>
          <td>把結果轉成動作</td>
      </tr>
      <tr>
          <td>Owner</td>
          <td>指定後續責任人</td>
          <td>支援 action item closure</td>
      </tr>
  </tbody>
</table>
<p>Hypothesis 欄位讓驗證聚焦。<code>打掉 node</code> 只是操作；<code>打掉一個 worker 後 queue lag 應在 10 分鐘內回到 baseline</code> 才是可判讀假設。</p>
<p>Scope 欄位保護結論邊界。internal traffic、single tenant、one region、10% production traffic 與 full production 都是不同證據強度，handoff 需要明確標示。</p>
<p>Evidence package 欄位接 4.20。驗證結果應保存 dashboard、query、trace、log、client-side signal、time range 與 data quality 限制，讓 release gate 或 incident response 可以回放。</p>
<p>Result 欄位需要分層。Pass 代表在指定 scope 內符合 steady state；conditional 代表可接受但有缺口；fail 代表需要補設計、補訊號、補 runbook 或阻擋 release。</p>
<h2 id="驗證來源">驗證來源</h2>
<p>Verification evidence 的來源分成政策、容量、故障、回復與準備度。不同來源回答的決策問題不同。</p>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>回答問題</th>
          <th>交接對象</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SLO / Error Budget</td>
          <td>可靠性目標是否仍有風險餘額</td>
          <td>release gate、severity trigger</td>
      </tr>
      <tr>
          <td>Load test</td>
          <td>workload 是否覆蓋容量與成本壓力</td>
          <td>capacity plan、release gate</td>
      </tr>
      <tr>
          <td>Chaos experiment</td>
          <td>failure mode 是否可被吸收</td>
          <td>runbook、incident drill</td>
      </tr>
      <tr>
          <td>DR drill</td>
          <td>RTO / RPO 是否可達</td>
          <td>business continuity、IR</td>
      </tr>
      <tr>
          <td>Rollback rehearsal</td>
          <td>版本或資料回復是否可執行</td>
          <td>deployment platform、incident</td>
      </tr>
      <tr>
          <td>Readiness review</td>
          <td>上線前風險是否已被判讀</td>
          <td>release gate、service owner</td>
      </tr>
  </tbody>
</table>
<p>SLO evidence 適合支援變更節奏。當 <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> 上升或 <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 緊張，release gate 需要知道哪些 user journey 受影響、資料品質是否可信、freeze 是否觸發。</p>
<p>Load test evidence 適合支援容量與成本決策。它要保留 workload model、traffic shape、data volume、dependency saturation、cost threshold 與觀測限制。</p>
<p>Chaos evidence 適合支援 incident drill。它要保留 injected failure、steady state、stop condition、<a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a>、<a href="/blog/backend/knowledge-cards/incident-decision-log/" data-link-title="Incident Decision Log" data-link-desc="說明事故期間如何保留決策、證據、owner 與回退條件">decision log</a> 與 action item。</p>
<p>DR evidence 適合支援恢復承諾。它要保留切換步驟、資料同步、RTO / RPO、權限、通訊節奏與回復完成條件。</p>
<p>Rollback evidence 適合支援事故止血。它要保留版本、migration、feature flag、client compatibility、cache 與資料相容性。</p>
<h2 id="交接流程">交接流程</h2>
<p>Verification handoff 的流程是從驗證結果走向下游決策。每個結果都要明確路由，讓測試報告轉成 release、runbook 或 incident drill 的輸入。</p>
<ol>
<li>把驗證結果整理成 handoff 欄位。</li>
<li>附上 4.20 evidence package 與 data quality 限制。</li>
<li>判斷 result：pass、conditional、fail。</li>
<li>把 pass 送入 release gate 或 runbook。</li>
<li>把 conditional 送入 reliability debt 或 follow-up。</li>
<li>把 fail 送入 block、補驗證、補 observability 或 incident drill。</li>
</ol>
<p>Pass 的責任是支持後續放行。Pass 需要同時保留 scope，避免「小範圍通過」被誤用成「全域安全」。</p>
<p>Conditional 的責任是保留風險借款。若驗證結果可接受但缺 trace、runbook、owner 或資料校驗，應進入 reliability debt backlog，並設定 closure signal。</p>
<p>Fail 的責任是阻止風險下流。Fail 不只代表測試失敗，也可能代表 steady state 定義錯誤、evidence 不足、blast radius 過大或 stop condition 不清。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Verification evidence handoff 的反模式通常來自把驗證結果寫成結論，而沒有保留判讀過程。下游需要知道結論成立的條件。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只寫 pass / fail</td>
          <td>release gate 看不到證據</td>
          <td>補 hypothesis、scope、evidence</td>
      </tr>
      <tr>
          <td>Load 圖表缺 workload</td>
          <td>圖表存在但缺少重播條件</td>
          <td>保存 traffic shape 與 data volume</td>
      </tr>
      <tr>
          <td>Chaos 成功無 runbook</td>
          <td>實驗有效但事故時用不上</td>
          <td>回寫 runbook 與 drill</td>
      </tr>
      <tr>
          <td>DR 通過缺 RTO / RPO</td>
          <td>切換完成但缺少承諾對齊</td>
          <td>保存 recovery timeline</td>
      </tr>
      <tr>
          <td>Conditional 無關閉條件</td>
          <td>風險借款長期存在</td>
          <td>設定 owner 與 closure signal</td>
      </tr>
  </tbody>
</table>
<p>只寫 pass / fail 會讓驗證證據失去工程價值。Pass 要說明在什麼範圍、什麼假設、什麼資料品質下成立；fail 要說明哪個控制面失效。</p>
<p>Conditional 無關閉條件會讓可靠性債累積。每個 conditional handoff 都需要 owner、期限、closure signal 與重新評估條件。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>4.20 observability evidence package：承接 log、metric、trace 與 data quality</li>
<li>6.8 release gate：把驗證結果轉成放行、阻擋或例外</li>
<li>6.20 experiment safety：保存 blast radius、stop condition 與權限</li>
<li>6.21 reliability debt backlog：承接 conditional 與 follow-up</li>
<li>8.6 drills / on-call readiness：把驗證結果轉成值班演練</li>
<li>8.22 incident evidence write-back：承接事故後新增的驗證題目</li>
</ul>
]]></content:encoded></item><item><title>6.24 規則推送安全閘門</title><link>https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/rule-rollout-safety-gate/</guid><description>&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>規則推送安全閘門（rule rollout safety gate）的核心責任是防止控制面錯誤快速擴散到資料面。這個閘門是補上「規則與配置類變更」特有風險，跟既有 release gate 互補而非取代：變更體積小、覆蓋範圍大、擴散速度快。&lt;/p>
&lt;p>當變更屬於 WAF rule、routing policy、token/policy、或 Addressing API 相關設定時，判讀重點從程式碼正確性轉為擴散控制。這類變更即使 diff 很短，也可能在數十秒內影響跨區域流量與多產品控制面。&lt;/p>
&lt;h2 id="適用場景">適用場景&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>典型風險&lt;/th>
 &lt;th>為何需要獨立 gate&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>WAF / regex 規則更新&lt;/td>
 &lt;td>高計算成本規則拖垮 edge runtime&lt;/td>
 &lt;td>CI 綠燈無法代表 runtime 成本安全&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Routing / BYOIP 相關設定變更&lt;/td>
 &lt;td>prefix withdrawal 造成服務不可達&lt;/td>
 &lt;td>單一 API 查詢語意錯誤可全網擴散&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Token / policy 控制面變更&lt;/td>
 &lt;td>多產品授權連鎖失效&lt;/td>
 &lt;td>shared dependency 失效會誤導排障路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>共享控制面批次清理任務&lt;/td>
 &lt;td>批量誤刪或批量撤告&lt;/td>
 &lt;td>需要數量閘門與緊急停機機制&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="產業情境遊戲服務的規則推送安全">產業情境：遊戲服務的規則推送安全&lt;/h2>
&lt;p>遊戲的規則推送（平衡性調整、反作弊規則、賽季設定、經濟系統參數）有特殊的擴散特性：影響面是全體玩家、生效時機即時、且玩家行為會立刻適應規則變更。&lt;/p>
&lt;p>規則推送的 blast radius 預設是全體在線玩家。一次平衡性調整會立刻改變所有正在進行的比賽的角色強度、裝備數值或技能效果。跟一般 feature flag 的 percentage rollout 不同，遊戲平衡性需要所有玩家看到相同規則，否則同場比賽的公平性會被破壞。&lt;/p>
&lt;p>推送時機需要跟 match lifecycle 對齊。在進行中的比賽套用新規則會造成不公平 — 某隊在舊規則下建立的優勢可能在新規則下消失。安全做法是在 match boundary（比賽開始或結束時）切換，讓新規則只套用到新開始的比賽。這要求規則推送系統能區分「已開始的 match」和「即將開始的 match」。&lt;/p>
&lt;p>回退條件需要綁定遊戲特有的 KPI。active player count 下降超過門檻、match completion rate 異常降低（玩家中途離開）、player report rate 上升（玩家回報異常體驗）、in-game economy anomaly（虛擬貨幣或道具流通量異常）都是規則推送出問題的訊號。這些 KPI 的 feedback loop 比一般服務長 — 玩家行為的適應需要數小時到數天才會穩定，短窗觀察可能漏掉延遲暴露的問題。&lt;/p>
&lt;p>反作弊規則的推送有額外約束：規則內容本身是機密的，推送失敗後不能在 log 或 alert 中暴露規則細節，回退也必須在不洩漏偵測邏輯的前提下進行。&lt;/p>
&lt;h2 id="gate-檢查層">Gate 檢查層&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層級&lt;/th>
 &lt;th>Gate 問題&lt;/th>
 &lt;th>不通過訊號&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Query / API 語意&lt;/td>
 &lt;td>查詢參數有沒有安全預設與錯誤拒絕策略&lt;/td>
 &lt;td>空參數回傳全量、模糊布林語意&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rule 計算成本&lt;/td>
 &lt;td>規則是否通過代表性 payload 成本檢查&lt;/td>
 &lt;td>單一規則可造成 CPU 熱點&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>推送策略&lt;/td>
 &lt;td>是否採分群 rollout 並設即時回退條件&lt;/td>
 &lt;td>一次推全域、無分區觀測閘門&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Withdrawal 限速&lt;/td>
 &lt;td>批次撤告 / 刪除是否有數量與速率限制&lt;/td>
 &lt;td>單次操作可影響大量 prefixes 或 bindings&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Shared dependency&lt;/td>
 &lt;td>是否識別跨產品共享控制點&lt;/td>
 &lt;td>多產品同時異常卻無 shared root 判讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Evidence 與回寫&lt;/td>
 &lt;td>事故後是否可回放決策、查證恢復路徑與狀態差異&lt;/td>
 &lt;td>決策只留結論，缺假設與驗證證據&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;ul>
&lt;li>控制面變更後，多區域同時出現 5xx / timeout / auth 失敗&lt;/li>
&lt;li>指標在秒級惡化，且與最新規則或 policy 變更高度同時&lt;/li>
&lt;li>回退後短時間明顯回穩，顯示變更與故障高度關聯&lt;/li>
&lt;li>部分服務可自助恢復、部分服務需人工修復，代表狀態損壞分層&lt;/li>
&lt;li>事故中出現「每個產品都在修自己的症狀」，代表 shared dependency 沒被先識別&lt;/li>
&lt;/ul>
&lt;h2 id="最低可執行-gate">最低可執行 Gate&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>變更分類&lt;/strong>：將規則/配置/控制面 API 變更標為 &lt;code>high-blast-radius change&lt;/code>。&lt;/li>
&lt;li>&lt;strong>語意檢查&lt;/strong>：對 query 參數、空值與預設行為做拒絕式驗證。&lt;/li>
&lt;li>&lt;strong>成本檢查&lt;/strong>：用代表性 payload 跑 rule-level CPU / latency 測試。&lt;/li>
&lt;li>&lt;strong>分批推送&lt;/strong>：至少分成小流量群組與全量兩階段，且每階段有回退條件。&lt;/li>
&lt;li>&lt;strong>撤告保護&lt;/strong>：對 withdrawal / delete 設速率與數量上限，超限自動中止。&lt;/li>
&lt;li>&lt;strong>決策紀錄&lt;/strong>：事故期間保留假設、證據、回退門檻、owner 與狀態差異。&lt;/li>
&lt;/ol>
&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>強制走高風險變更 gate&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>只看 CI / lint&lt;/td>
 &lt;td>無法捕捉 runtime 計算成本風險&lt;/td>
 &lt;td>補 rule replay 與成本基線&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>全域一次推送&lt;/td>
 &lt;td>擴散太快，回退窗口太短&lt;/td>
 &lt;td>改 staged rollout + 即時回退閘門&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事故後只寫事後摘要&lt;/td>
 &lt;td>無法復盤決策與恢復路徑&lt;/td>
 &lt;td>補 decision log + evidence package&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>未分離 desired/actual state&lt;/td>
 &lt;td>壞狀態難以回到已知穩定點&lt;/td>
 &lt;td>引入 snapshot 與 staged state restore&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/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">Cloudflare 2019 Regex CPU Outage&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">Cloudflare 2023 Control Plane Token Incident&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">Cloudflare 2026 BYOIP BGP Withdrawal&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>這三個案例對應同一個上位問題：控制面小變更如何在短時間擴散成全網事故。不同事故只是擴散路徑不同，gate 核心都是「先限制擴散、再修復功能」。&lt;/p></description><content:encoded><![CDATA[<h2 id="概念定位">概念定位</h2>
<p>規則推送安全閘門（rule rollout safety gate）的核心責任是防止控制面錯誤快速擴散到資料面。這個閘門是補上「規則與配置類變更」特有風險，跟既有 release gate 互補而非取代：變更體積小、覆蓋範圍大、擴散速度快。</p>
<p>當變更屬於 WAF rule、routing policy、token/policy、或 Addressing API 相關設定時，判讀重點從程式碼正確性轉為擴散控制。這類變更即使 diff 很短，也可能在數十秒內影響跨區域流量與多產品控制面。</p>
<h2 id="適用場景">適用場景</h2>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>典型風險</th>
          <th>為何需要獨立 gate</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>WAF / regex 規則更新</td>
          <td>高計算成本規則拖垮 edge runtime</td>
          <td>CI 綠燈無法代表 runtime 成本安全</td>
      </tr>
      <tr>
          <td>Routing / BYOIP 相關設定變更</td>
          <td>prefix withdrawal 造成服務不可達</td>
          <td>單一 API 查詢語意錯誤可全網擴散</td>
      </tr>
      <tr>
          <td>Token / policy 控制面變更</td>
          <td>多產品授權連鎖失效</td>
          <td>shared dependency 失效會誤導排障路徑</td>
      </tr>
      <tr>
          <td>共享控制面批次清理任務</td>
          <td>批量誤刪或批量撤告</td>
          <td>需要數量閘門與緊急停機機制</td>
      </tr>
  </tbody>
</table>
<h2 id="產業情境遊戲服務的規則推送安全">產業情境：遊戲服務的規則推送安全</h2>
<p>遊戲的規則推送（平衡性調整、反作弊規則、賽季設定、經濟系統參數）有特殊的擴散特性：影響面是全體玩家、生效時機即時、且玩家行為會立刻適應規則變更。</p>
<p>規則推送的 blast radius 預設是全體在線玩家。一次平衡性調整會立刻改變所有正在進行的比賽的角色強度、裝備數值或技能效果。跟一般 feature flag 的 percentage rollout 不同，遊戲平衡性需要所有玩家看到相同規則，否則同場比賽的公平性會被破壞。</p>
<p>推送時機需要跟 match lifecycle 對齊。在進行中的比賽套用新規則會造成不公平 — 某隊在舊規則下建立的優勢可能在新規則下消失。安全做法是在 match boundary（比賽開始或結束時）切換，讓新規則只套用到新開始的比賽。這要求規則推送系統能區分「已開始的 match」和「即將開始的 match」。</p>
<p>回退條件需要綁定遊戲特有的 KPI。active player count 下降超過門檻、match completion rate 異常降低（玩家中途離開）、player report rate 上升（玩家回報異常體驗）、in-game economy anomaly（虛擬貨幣或道具流通量異常）都是規則推送出問題的訊號。這些 KPI 的 feedback loop 比一般服務長 — 玩家行為的適應需要數小時到數天才會穩定，短窗觀察可能漏掉延遲暴露的問題。</p>
<p>反作弊規則的推送有額外約束：規則內容本身是機密的，推送失敗後不能在 log 或 alert 中暴露規則細節，回退也必須在不洩漏偵測邏輯的前提下進行。</p>
<h2 id="gate-檢查層">Gate 檢查層</h2>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>Gate 問題</th>
          <th>不通過訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Query / API 語意</td>
          <td>查詢參數有沒有安全預設與錯誤拒絕策略</td>
          <td>空參數回傳全量、模糊布林語意</td>
      </tr>
      <tr>
          <td>Rule 計算成本</td>
          <td>規則是否通過代表性 payload 成本檢查</td>
          <td>單一規則可造成 CPU 熱點</td>
      </tr>
      <tr>
          <td>推送策略</td>
          <td>是否採分群 rollout 並設即時回退條件</td>
          <td>一次推全域、無分區觀測閘門</td>
      </tr>
      <tr>
          <td>Withdrawal 限速</td>
          <td>批次撤告 / 刪除是否有數量與速率限制</td>
          <td>單次操作可影響大量 prefixes 或 bindings</td>
      </tr>
      <tr>
          <td>Shared dependency</td>
          <td>是否識別跨產品共享控制點</td>
          <td>多產品同時異常卻無 shared root 判讀</td>
      </tr>
      <tr>
          <td>Evidence 與回寫</td>
          <td>事故後是否可回放決策、查證恢復路徑與狀態差異</td>
          <td>決策只留結論，缺假設與驗證證據</td>
      </tr>
  </tbody>
</table>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>控制面變更後，多區域同時出現 5xx / timeout / auth 失敗</li>
<li>指標在秒級惡化，且與最新規則或 policy 變更高度同時</li>
<li>回退後短時間明顯回穩，顯示變更與故障高度關聯</li>
<li>部分服務可自助恢復、部分服務需人工修復，代表狀態損壞分層</li>
<li>事故中出現「每個產品都在修自己的症狀」，代表 shared dependency 沒被先識別</li>
</ul>
<h2 id="最低可執行-gate">最低可執行 Gate</h2>
<ol>
<li><strong>變更分類</strong>：將規則/配置/控制面 API 變更標為 <code>high-blast-radius change</code>。</li>
<li><strong>語意檢查</strong>：對 query 參數、空值與預設行為做拒絕式驗證。</li>
<li><strong>成本檢查</strong>：用代表性 payload 跑 rule-level CPU / latency 測試。</li>
<li><strong>分批推送</strong>：至少分成小流量群組與全量兩階段，且每階段有回退條件。</li>
<li><strong>撤告保護</strong>：對 withdrawal / delete 設速率與數量上限，超限自動中止。</li>
<li><strong>決策紀錄</strong>：事故期間保留假設、證據、回退門檻、owner 與狀態差異。</li>
</ol>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>風險結果</th>
          <th>修法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>把規則推送當一般配置</td>
          <td>低估擴散速度與影響面</td>
          <td>強制走高風險變更 gate</td>
      </tr>
      <tr>
          <td>只看 CI / lint</td>
          <td>無法捕捉 runtime 計算成本風險</td>
          <td>補 rule replay 與成本基線</td>
      </tr>
      <tr>
          <td>全域一次推送</td>
          <td>擴散太快，回退窗口太短</td>
          <td>改 staged rollout + 即時回退閘門</td>
      </tr>
      <tr>
          <td>事故後只寫事後摘要</td>
          <td>無法復盤決策與恢復路徑</td>
          <td>補 decision log + evidence package</td>
      </tr>
      <tr>
          <td>未分離 desired/actual state</td>
          <td>壞狀態難以回到已知穩定點</td>
          <td>引入 snapshot 與 staged state restore</td>
      </tr>
  </tbody>
</table>
<h2 id="案例回扣">案例回扣</h2>
<ul>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2019-regex-cpu-outage/" data-link-title="Cloudflare 2019 Regex CPU Outage" data-link-desc="2019-07-02 Cloudflare WAF 規則更新導致全球 CPU 飆升的事故解析：觸發條件、擴散機制、止血決策與可回寫控制面。">Cloudflare 2019 Regex CPU Outage</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2023-control-plane-token-incident/" data-link-title="Cloudflare 2023 Control Plane Token Incident" data-link-desc="2023-01-24 Cloudflare service token 錯誤變更導致多產品連鎖影響的事故解析：信任邊界、擴散機制、止血策略與流程回寫。">Cloudflare 2023 Control Plane Token Incident</a></li>
<li><a href="/blog/backend/08-incident-response/cases/cloudflare/2026-byoip-bgp-withdrawal/" data-link-title="Cloudflare 2026 BYOIP BGP Withdrawal" data-link-desc="2026-02-20 Cloudflare BYOIP prefixes 被非預期撤告的事故解析：Addressing API bug、BGP withdrawal、狀態恢復與控制面回寫。">Cloudflare 2026 BYOIP BGP Withdrawal</a></li>
</ul>
<p>這三個案例對應同一個上位問題：控制面小變更如何在短時間擴散成全網事故。不同事故只是擴散路徑不同，gate 核心都是「先限制擴散、再修復功能」。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li><code>04</code>： <a href="/blog/backend/04-observability/telemetry-data-quality/" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Telemetry Data Quality</a></li>
<li><code>06</code>： <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate</a></li>
<li><code>06</code>： <a href="/blog/backend/06-reliability/experiment-safety-boundary/" data-link-title="6.20 Experiment Safety Boundary" data-link-desc="定義 chaos、load test、DR drill 的 [blast radius](/backend/knowledge-cards/blast-radius/)、停止條件與權限約束">6.20 Experiment Safety Boundary</a></li>
<li><code>06</code>： <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 Verification Evidence Handoff</a></li>
<li><code>08</code>： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a></li>
<li><code>08</code>： <a href="/blog/backend/08-incident-response/incident-evidence-write-back/" data-link-title="8.22 Incident Evidence Write-back" data-link-desc="把事故證據、決策與復盤結論回寫到 observability、reliability 與 runbook">8.22 Incident Evidence Write-back</a></li>
</ul>
]]></content:encoded></item><item><title>6.25 Provider Dependency Release Gate 實作示範</title><link>https://tarrragon.github.io/blog/backend/06-reliability/provider-dependency-release-gate/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/provider-dependency-release-gate/</guid><description>&lt;p>Provider dependency release gate 的核心責任是把第三方依賴風險轉成可驗證放行條件，避免變更在高不確定性下直接擴散。&lt;/p>
&lt;h2 id="服務路徑與風險模型">服務路徑與風險模型&lt;/h2>
&lt;p>示範路徑是 checkout API 切換 payment provider timeout/retry 設定。這類變更看起來只改 config，但會直接影響交易成功率、延遲與重試風暴。&lt;/p>
&lt;p>gate 應固定五欄：&lt;code>Gate decision&lt;/code>、&lt;code>Checks&lt;/code>、&lt;code>Stop condition&lt;/code>、&lt;code>Rollback window&lt;/code>、&lt;code>Owner&lt;/code>。欄位先成立，再討論工具落地。&lt;/p>
&lt;p>以 payment provider timeout 調整為例，五欄的具體內容：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>欄位&lt;/th>
 &lt;th>範例值&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Gate decision&lt;/td>
 &lt;td>proceed / hold / rollback — 每批 canary 結束時做一次判定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Checks&lt;/td>
 &lt;td>checkout success rate &amp;gt; 99.5%、provider timeout ratio &amp;lt; 2%、duplicate charge = 0、error budget remaining &amp;gt; 30%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Stop condition&lt;/td>
 &lt;td>error rate 超門檻、latency p99 超過基線 2 倍、provider timeout ratio &amp;gt; 5%，任一觸發即停止擴批&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rollback window&lt;/td>
 &lt;td>15 min — config-only 變更無 schema 衝突，超過 15 min 後交易資料可能依賴新設定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Owner&lt;/td>
 &lt;td>checkout team lead，負責每批 go/no-go 與 rollback 決策&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Checks 欄位的數值來自歷史 baseline，每次變更前從 production 最近 7 天取值。baseline 偏移超過 10% 時，先校準再啟動 canary。&lt;/p>
&lt;h2 id="實作步驟">實作步驟&lt;/h2>
&lt;ol>
&lt;li>定義放行前檢查：checkout 成功率、provider timeout 比率、duplicate charge 監控、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget&lt;/a> 餘量。&lt;/li>
&lt;li>設定 canary 節奏：1% -&amp;gt; 5% -&amp;gt; 25% -&amp;gt; 100%，每批觀察固定時間窗。&lt;/li>
&lt;li>為每批設定 stop condition：error rate、latency、provider timeout 任一超門檻即停止擴大。&lt;/li>
&lt;li>設定 rollback window：例如 15 分鐘內可無資料格式衝突地回退設定。&lt;/li>
&lt;li>把每批結果寫入 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 Verification Evidence Handoff&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log&lt;/a>。&lt;/li>
&lt;/ol>
&lt;h3 id="canary-節奏與觀察窗">Canary 節奏與觀察窗&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>批次&lt;/th>
 &lt;th>流量比例&lt;/th>
 &lt;th>觀察窗&lt;/th>
 &lt;th>Go/no-go 判斷依據&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>B1&lt;/td>
 &lt;td>1%&lt;/td>
 &lt;td>30 min&lt;/td>
 &lt;td>checks 全過、stop condition 未觸發&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B2&lt;/td>
 &lt;td>5%&lt;/td>
 &lt;td>1 h&lt;/td>
 &lt;td>B1 指標持平、無 duplicate charge、無客訴&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B3&lt;/td>
 &lt;td>25%&lt;/td>
 &lt;td>2 h&lt;/td>
 &lt;td>B2 指標持平、error budget 消耗速度未加快&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>B4&lt;/td>
 &lt;td>100%&lt;/td>
 &lt;td>持續觀測&lt;/td>
 &lt;td>B3 指標持平、跨區結果一致，進入持續觀測而非一次性放行&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Payment 類變更的觀察窗比一般 config 變更長，原因有兩個。第一，交易確認有延遲 — provider 回傳 settlement 結果可能在數分鐘到數小時後，短觀察窗無法看到完整的交易結果分佈。第二，退款與爭議申請通常在交易後數小時甚至數天才出現，B3 階段需要持續追蹤退款率趨勢，確認新設定沒有引發 provider 層的異常判定。&lt;/p></description><content:encoded><![CDATA[<p>Provider dependency release gate 的核心責任是把第三方依賴風險轉成可驗證放行條件，避免變更在高不確定性下直接擴散。</p>
<h2 id="服務路徑與風險模型">服務路徑與風險模型</h2>
<p>示範路徑是 checkout API 切換 payment provider timeout/retry 設定。這類變更看起來只改 config，但會直接影響交易成功率、延遲與重試風暴。</p>
<p>gate 應固定五欄：<code>Gate decision</code>、<code>Checks</code>、<code>Stop condition</code>、<code>Rollback window</code>、<code>Owner</code>。欄位先成立，再討論工具落地。</p>
<p>以 payment provider timeout 調整為例，五欄的具體內容：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>範例值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Gate decision</td>
          <td>proceed / hold / rollback — 每批 canary 結束時做一次判定</td>
      </tr>
      <tr>
          <td>Checks</td>
          <td>checkout success rate &gt; 99.5%、provider timeout ratio &lt; 2%、duplicate charge = 0、error budget remaining &gt; 30%</td>
      </tr>
      <tr>
          <td>Stop condition</td>
          <td>error rate 超門檻、latency p99 超過基線 2 倍、provider timeout ratio &gt; 5%，任一觸發即停止擴批</td>
      </tr>
      <tr>
          <td>Rollback window</td>
          <td>15 min — config-only 變更無 schema 衝突，超過 15 min 後交易資料可能依賴新設定</td>
      </tr>
      <tr>
          <td>Owner</td>
          <td>checkout team lead，負責每批 go/no-go 與 rollback 決策</td>
      </tr>
  </tbody>
</table>
<p>Checks 欄位的數值來自歷史 baseline，每次變更前從 production 最近 7 天取值。baseline 偏移超過 10% 時，先校準再啟動 canary。</p>
<h2 id="實作步驟">實作步驟</h2>
<ol>
<li>定義放行前檢查：checkout 成功率、provider timeout 比率、duplicate charge 監控、<a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a> 餘量。</li>
<li>設定 canary 節奏：1% -&gt; 5% -&gt; 25% -&gt; 100%，每批觀察固定時間窗。</li>
<li>為每批設定 stop condition：error rate、latency、provider timeout 任一超門檻即停止擴大。</li>
<li>設定 rollback window：例如 15 分鐘內可無資料格式衝突地回退設定。</li>
<li>把每批結果寫入 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23 Verification Evidence Handoff</a> 與 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a>。</li>
</ol>
<h3 id="canary-節奏與觀察窗">Canary 節奏與觀察窗</h3>
<table>
  <thead>
      <tr>
          <th>批次</th>
          <th>流量比例</th>
          <th>觀察窗</th>
          <th>Go/no-go 判斷依據</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>B1</td>
          <td>1%</td>
          <td>30 min</td>
          <td>checks 全過、stop condition 未觸發</td>
      </tr>
      <tr>
          <td>B2</td>
          <td>5%</td>
          <td>1 h</td>
          <td>B1 指標持平、無 duplicate charge、無客訴</td>
      </tr>
      <tr>
          <td>B3</td>
          <td>25%</td>
          <td>2 h</td>
          <td>B2 指標持平、error budget 消耗速度未加快</td>
      </tr>
      <tr>
          <td>B4</td>
          <td>100%</td>
          <td>持續觀測</td>
          <td>B3 指標持平、跨區結果一致，進入持續觀測而非一次性放行</td>
      </tr>
  </tbody>
</table>
<p>Payment 類變更的觀察窗比一般 config 變更長，原因有兩個。第一，交易確認有延遲 — provider 回傳 settlement 結果可能在數分鐘到數小時後，短觀察窗無法看到完整的交易結果分佈。第二，退款與爭議申請通常在交易後數小時甚至數天才出現，B3 階段需要持續追蹤退款率趨勢，確認新設定沒有引發 provider 層的異常判定。</p>
<h3 id="證據留存格式">證據留存格式</h3>
<p>每批 canary 結束時留存一筆結構化證據，供 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">6.23</a> 與 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19</a> 調用。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>內容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>batch</td>
          <td>B1 / B2 / B3 / B4</td>
      </tr>
      <tr>
          <td>timestamp</td>
          <td>批次開始與結束時間</td>
      </tr>
      <tr>
          <td>traffic %</td>
          <td>該批實際流量比例</td>
      </tr>
      <tr>
          <td>metrics snapshot</td>
          <td>checkout success rate、latency p99、provider timeout ratio</td>
      </tr>
      <tr>
          <td>decision</td>
          <td>proceed / hold / rollback</td>
      </tr>
      <tr>
          <td>decider</td>
          <td>做出該決策的人與角色</td>
      </tr>
  </tbody>
</table>
<p>這個格式讓事故發生時，<a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">8.19 Incident Decision Log</a> 可以直接調用每批的 metrics 與決策紀錄，不需要回溯 dashboard 截圖。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>canary 成功率正常但 timeout 升高</td>
          <td>交易完成但成本與延遲風險在累積</td>
          <td>暫停擴批，先調 provider timeout 策略</td>
      </tr>
      <tr>
          <td>error budget 快速消耗</td>
          <td>變更風險超過目前可承受範圍</td>
          <td>觸發 freeze，回到上一批設定</td>
      </tr>
      <tr>
          <td>rollback 成功但客訴仍上升</td>
          <td>影響可能來自非同步補償或下游延遲</td>
          <td>補 replay/對帳證據，再決定是否二次回退</td>
      </tr>
      <tr>
          <td>不同區域結果分歧</td>
          <td>provider 區域品質差異或路由策略不一致</td>
          <td>分區 gate，禁止全域同批放行</td>
      </tr>
      <tr>
          <td>告警只反映症狀無法定位變更關聯</td>
          <td>evidence 與 deploy event 沒對位</td>
          <td>補 deployment event link 與 owner 欄位</td>
      </tr>
  </tbody>
</table>
<h2 id="常見誤區">常見誤區</h2>
<p>把 gate 當成 CI 綠燈會漏掉依賴風險。依賴類變更需要觀測窗與停損條件，單靠測試通過不足以放行。</p>
<p>把 rollback window 寫成「可回退」但沒有時限也會失效。沒有時間邊界的回退通常意味著資料與行為已經漂移。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>這條路徑可用 <a href="/blog/backend/06-reliability/cases/stripe/idempotency-and-zero-downtime-migration/" data-link-title="Stripe：Idempotency 與零停機遷移的交易安全設計" data-link-desc="把 API 重試與資料遷移放在同一套安全模型，維持支付交易的一致結果。">Stripe Idempotency and Zero-downtime Migration</a> 回寫。先看交易正確性與變更節奏如何綁定，再回到本章對齊 gate 欄位與停損邏輯。</p>
<p>這個案例主要支撐的是「交易依賴變更放行節奏」判讀，不直接支撐 incident 通訊節奏；對外更新要回到 8.4。</p>
<h2 id="跨模組路由">跨模組路由</h2>
<ol>
<li>與 4.22 的交接：證據來源使用 <a href="/blog/backend/04-observability/checkout-api-evidence-package/" data-link-title="4.22 Checkout API Evidence Package 實作示範" data-link-desc="用 checkout 路徑示範 evidence package 如何交接給 release gate 與 incident decision。">Checkout API Evidence Package</a>。</li>
<li>與 6.8 的交接：策略與制度回到 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">Release Gate 與變更節奏</a>。</li>
<li>與 6.23 的交接：每批驗證證據進 <a href="/blog/backend/06-reliability/verification-evidence-handoff/" data-link-title="6.23 Verification Evidence Handoff" data-link-desc="把 SLO、load、chaos、DR 與 readiness 結果包成 release / incident 可用證據">Verification Evidence Handoff</a>。</li>
<li>與 8.19 的交接：停損與回退決策同步到 incident decision log。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>要看控制面事故如何用 decision log 與 write-back 關閉迴圈，接著讀 <a href="/blog/backend/08-incident-response/control-plane-decision-log-write-back/" data-link-title="8.23 Control Plane Decision Log and Write-back 實作示範" data-link-desc="以 rule/config rollout 事故示範 decision log 與 write-back 如何形成可回放閉環。">8.23 Control Plane Decision Log and Write-back 實作示範</a>。</p>
]]></content:encoded></item></channel></rss>