<?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/04-observability/cases/</link><description>Recent content in 可觀測性案例正文 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 07 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/04-observability/cases/index.xml" rel="self" type="application/rss+xml"/><item><title>FinTech：審計證據鏈的可觀測性設計</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/fintech-audit-evidence-observability/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/fintech-audit-evidence-observability/</guid><description>&lt;p>本案例的核心責任是讓審計證據與運維訊號共用同一套資料邊界。FinTech 場景下，觀測資料不只是除錯用途，也是合規證據基礎。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>一家處理線上支付的金融科技公司，每日交易量約 200 萬筆，涵蓋信用卡收單、轉帳與退款。每季有外部稽核查核交易處理的完整性與存取控制，事故發生時法務需要在 48 小時內提供特定交易的完整處理鏈證據。&lt;/p>
&lt;p>初期系統把所有 log 寫到同一個 log group — application debug、request trace、交易狀態變更與使用者存取紀錄全混在一起。稽核人員要從數 TB 的 log 中撈出特定交易的完整軌跡，每次查詢耗時數小時。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="operational-log-與-audit-log-混合">Operational log 與 audit log 混合&lt;/h3>
&lt;p>Application log 記錄 debug 資訊（SQL timing、cache hit/miss、retry），audit log 記錄業務事件（交易建立、狀態變更、存取紀錄）。兩者混在同一個 pipeline 時，retention 策略互相衝突 — debug log 留 14 天夠用，但 audit log 法規要求保留 5 年。統一設成 5 年讓儲存成本暴增，統一設成 14 天則遺失合規證據。&lt;/p>
&lt;h3 id="pii-暴露在-log-中">PII 暴露在 log 中&lt;/h3>
&lt;p>早期 log 直接印出 request body，信用卡號跟身分證字號散落在各種 log entry。稽核指出 PII 在 log 系統中的暴露面超過業務需要，但 log 已經寫入後無法回溯修改。&lt;/p>
&lt;h3 id="event-correlation-斷裂">Event correlation 斷裂&lt;/h3>
&lt;p>交易從建立到完成經過多個服務（checkout-api → payment-gateway → settlement → notification），但各服務的 log 使用不同的 correlation key。Checkout 用 &lt;code>order_id&lt;/code>，payment-gateway 用 &lt;code>payment_ref&lt;/code>，settlement 用自己的 &lt;code>batch_id&lt;/code>。稽核要求「給我交易 X 的完整處理鏈」時，工程師需要手動在三個系統各自查詢再人工拼接。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="audit-log-分離">Audit log 分離&lt;/h3>
&lt;p>把 audit event 獨立到專屬 pipeline：交易狀態變更、使用者存取、權限變動、退款操作各自產生結構化 audit event，寫入 immutable storage（append-only、禁止刪除與修改）。Operational log 維持 14 天 retention，audit log 走 5 年 retention + cold archive。&lt;/p>
&lt;p>分離的判準是「這筆紀錄是否可能被稽核或法務要求提供」。是 → audit pipeline；否 → operational pipeline。灰色地帶（例如認證失敗 log）歸入 audit pipeline — 寧可多留不可少留。&lt;/p>
&lt;h3 id="pii-redaction-pipeline">PII redaction pipeline&lt;/h3>
&lt;p>在 log ingestion 階段加入 redaction processor：信用卡號遮罩為末四碼、身分證字號完全移除、email 保留 domain 遮罩使用者名稱。Redaction 發生在寫入儲存之前，原始資料不落地。&lt;/p>
&lt;p>需要完整 PII 的場景（如詐欺調查）走另一條授權存取管道，跟觀測 pipeline 分離。&lt;/p>
&lt;h3 id="統一-correlation-key">統一 correlation key&lt;/h3>
&lt;p>所有服務在交易入口處產生 &lt;code>trace_id&lt;/code> 和 &lt;code>transaction_id&lt;/code>，兩個 key 同時寫入每一筆 audit event 和 operational log。稽核查詢用 &lt;code>transaction_id&lt;/code> 就能撈出跨服務的完整處理鏈，不需要手動拼接。&lt;/p>
&lt;h2 id="取捨">取捨&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>混合 pipeline&lt;/th>
 &lt;th>分離 pipeline&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>建置成本&lt;/td>
 &lt;td>低（一套 pipeline）&lt;/td>
 &lt;td>中（兩套 pipeline + routing 邏輯）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>儲存成本&lt;/td>
 &lt;td>高（全部用最長 retention）&lt;/td>
 &lt;td>可控（各自 retention）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>查詢效率&lt;/td>
 &lt;td>低（audit event 淹沒在 debug log 中）&lt;/td>
 &lt;td>高（audit 獨立查詢）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>合規風險&lt;/td>
 &lt;td>高（PII 暴露面大、retention 可能不足）&lt;/td>
 &lt;td>低（PII redacted、retention 對齊法規）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維運複雜度&lt;/td>
 &lt;td>低&lt;/td>
 &lt;td>中（需維護 routing 規則與 redaction 規則）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>分離 pipeline 的最大成本在 routing 規則的維護 — 新服務上線時要確認 audit event 走對 pipeline。解法是在 SDK 層提供 &lt;code>emit_audit_event()&lt;/code> 函式，讓 routing 在 producer 端決定，不依賴下游 pipeline 的內容判斷。&lt;/p></description><content:encoded><![CDATA[<p>本案例的核心責任是讓審計證據與運維訊號共用同一套資料邊界。FinTech 場景下，觀測資料不只是除錯用途，也是合規證據基礎。</p>
<h2 id="業務背景">業務背景</h2>
<p>一家處理線上支付的金融科技公司，每日交易量約 200 萬筆，涵蓋信用卡收單、轉帳與退款。每季有外部稽核查核交易處理的完整性與存取控制，事故發生時法務需要在 48 小時內提供特定交易的完整處理鏈證據。</p>
<p>初期系統把所有 log 寫到同一個 log group — application debug、request trace、交易狀態變更與使用者存取紀錄全混在一起。稽核人員要從數 TB 的 log 中撈出特定交易的完整軌跡，每次查詢耗時數小時。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="operational-log-與-audit-log-混合">Operational log 與 audit log 混合</h3>
<p>Application log 記錄 debug 資訊（SQL timing、cache hit/miss、retry），audit log 記錄業務事件（交易建立、狀態變更、存取紀錄）。兩者混在同一個 pipeline 時，retention 策略互相衝突 — debug log 留 14 天夠用，但 audit log 法規要求保留 5 年。統一設成 5 年讓儲存成本暴增，統一設成 14 天則遺失合規證據。</p>
<h3 id="pii-暴露在-log-中">PII 暴露在 log 中</h3>
<p>早期 log 直接印出 request body，信用卡號跟身分證字號散落在各種 log entry。稽核指出 PII 在 log 系統中的暴露面超過業務需要，但 log 已經寫入後無法回溯修改。</p>
<h3 id="event-correlation-斷裂">Event correlation 斷裂</h3>
<p>交易從建立到完成經過多個服務（checkout-api → payment-gateway → settlement → notification），但各服務的 log 使用不同的 correlation key。Checkout 用 <code>order_id</code>，payment-gateway 用 <code>payment_ref</code>，settlement 用自己的 <code>batch_id</code>。稽核要求「給我交易 X 的完整處理鏈」時，工程師需要手動在三個系統各自查詢再人工拼接。</p>
<h2 id="解法">解法</h2>
<h3 id="audit-log-分離">Audit log 分離</h3>
<p>把 audit event 獨立到專屬 pipeline：交易狀態變更、使用者存取、權限變動、退款操作各自產生結構化 audit event，寫入 immutable storage（append-only、禁止刪除與修改）。Operational log 維持 14 天 retention，audit log 走 5 年 retention + cold archive。</p>
<p>分離的判準是「這筆紀錄是否可能被稽核或法務要求提供」。是 → audit pipeline；否 → operational pipeline。灰色地帶（例如認證失敗 log）歸入 audit pipeline — 寧可多留不可少留。</p>
<h3 id="pii-redaction-pipeline">PII redaction pipeline</h3>
<p>在 log ingestion 階段加入 redaction processor：信用卡號遮罩為末四碼、身分證字號完全移除、email 保留 domain 遮罩使用者名稱。Redaction 發生在寫入儲存之前，原始資料不落地。</p>
<p>需要完整 PII 的場景（如詐欺調查）走另一條授權存取管道，跟觀測 pipeline 分離。</p>
<h3 id="統一-correlation-key">統一 correlation key</h3>
<p>所有服務在交易入口處產生 <code>trace_id</code> 和 <code>transaction_id</code>，兩個 key 同時寫入每一筆 audit event 和 operational log。稽核查詢用 <code>transaction_id</code> 就能撈出跨服務的完整處理鏈，不需要手動拼接。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>混合 pipeline</th>
          <th>分離 pipeline</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>建置成本</td>
          <td>低（一套 pipeline）</td>
          <td>中（兩套 pipeline + routing 邏輯）</td>
      </tr>
      <tr>
          <td>儲存成本</td>
          <td>高（全部用最長 retention）</td>
          <td>可控（各自 retention）</td>
      </tr>
      <tr>
          <td>查詢效率</td>
          <td>低（audit event 淹沒在 debug log 中）</td>
          <td>高（audit 獨立查詢）</td>
      </tr>
      <tr>
          <td>合規風險</td>
          <td>高（PII 暴露面大、retention 可能不足）</td>
          <td>低（PII redacted、retention 對齊法規）</td>
      </tr>
      <tr>
          <td>維運複雜度</td>
          <td>低</td>
          <td>中（需維護 routing 規則與 redaction 規則）</td>
      </tr>
  </tbody>
</table>
<p>分離 pipeline 的最大成本在 routing 規則的維護 — 新服務上線時要確認 audit event 走對 pipeline。解法是在 SDK 層提供 <code>emit_audit_event()</code> 函式，讓 routing 在 producer 端決定，不依賴下游 pipeline 的內容判斷。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log Governance</a>：audit log 分離的設計原則與 PII 治理。</li>
<li><a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">4.20 Observability Evidence Package</a>：把 audit trail 包成可交接的 evidence package。</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a>：audit pipeline 的 ownership 歸 platform team 還是 compliance team。</li>
<li><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>：跨服務 correlation key 的 propagation 設計。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>稽核或法務要求提供某筆交易的完整處理鏈，工程師需要超過 1 小時才能拼出來</li>
<li>Log retention 設定跟法規要求不一致，但沒人確切知道差多少</li>
<li>PII 出現在 log search 結果中，但沒有系統性的遮罩機制</li>
<li>Application log 跟 audit log 用同一套 retention policy，儲存成本持續上升但沒人敢縮短</li>
<li>事故後法務要證據，發現關鍵時段的 log 已經因為 retention 過期而被刪除</li>
</ul>
]]></content:encoded></item><item><title>Gaming：高峰流量下的訊號新鮮度與 Cardinality</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/gaming-peak-signal-freshness-and-cardinality/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/gaming-peak-signal-freshness-and-cardinality/</guid><description>&lt;p>本案例的核心責任是避免高峰流量讓觀測系統本身失真。若訊號延遲與 cardinality 膨脹失控，值班決策會落在過期資料上。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>一個線上多人遊戲平台，日活躍使用者約 50 萬人。每逢賽季開跑或限時活動，同時在線人數在 30 分鐘內從平日基線暴增 8-10 倍，matchmaking 服務的 request rate 從 5k/s 衝到 50k/s，遊戲伺服器同時運行的 match instance 從數千增到數萬。&lt;/p>
&lt;p>觀測系統在平日運作良好 — Prometheus 單機 scrape 500 萬 active series、Grafana dashboard 查詢秒級回應、告警在 1 分鐘內觸發。但每次活動開跑時，觀測系統本身開始劣化：dashboard 查詢從秒級變成分鐘級、告警延遲 5 分鐘以上才送到、部分 metric 直接消失。值班工程師在最需要觀測的時刻失去了可信訊號。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="cardinality-爆炸">Cardinality 爆炸&lt;/h3>
&lt;p>平日的 metric label 設計包含 &lt;code>match_id&lt;/code>、&lt;code>player_id&lt;/code> 跟 &lt;code>server_instance&lt;/code>。平日 active series 約 500 萬，活動開跑後 match 跟 player 數量暴增，active series 在 30 分鐘內衝到 2000 萬。Prometheus 的 head block 記憶體從 20 GB 暴增到 80 GB，超過機器 64 GB 上限，觸發 OOM kill。&lt;/p>
&lt;p>OOM 後 Prometheus 重啟需要 replay WAL，這段時間（5-15 分鐘）完全沒有 metric。活動最需要觀測的前 30 分鐘，觀測系統反而停擺。&lt;/p>
&lt;h3 id="scrape-freshness-延遲">Scrape freshness 延遲&lt;/h3>
&lt;p>即使 Prometheus 沒 OOM，大量 target 的 scrape 時間也會拉長。平日每輪 scrape 15 秒完成，活動期間拉長到 60-90 秒。Scrape interval 設定 30 秒時，下一輪 scrape 在上一輪還沒結束時就啟動，造成 sample 丟失跟時間錯位。Dashboard 上看到的數字可能延遲 2-3 分鐘，值班人員基於過期數據做判斷。&lt;/p>
&lt;h3 id="alert-閾值失真">Alert 閾值失真&lt;/h3>
&lt;p>告警規則基於平日 baseline 設定 — 例如 &lt;code>error_rate &amp;gt; 1%&lt;/code> 觸發。活動期間的 error rate 波動更大（matchmaking 短暫排隊造成的 timeout 增加是預期行為），平日閾值在活動期間持續觸發 false positive。值班人員開始 ignore alert，真正的問題（伺服器記憶體洩漏）被淹沒在噪音中。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="cardinality-guardrail">Cardinality guardrail&lt;/h3>
&lt;p>把高 cardinality label 從 real-time metric 移除。&lt;code>match_id&lt;/code> 和 &lt;code>player_id&lt;/code> 不再作為 Prometheus label，改為 log 和 trace 的欄位。Real-time metric 只保留 &lt;code>region&lt;/code>、&lt;code>server_pool&lt;/code>、&lt;code>game_mode&lt;/code> 等低 cardinality 維度。&lt;/p>
&lt;p>需要 per-match 或 per-player 分析時，走 log analytics pipeline（非 real-time，延遲 5-10 分鐘可接受）。這讓 Prometheus 的 active series 在活動期間從 2000 萬降到 800 萬，留在單機可承受範圍。&lt;/p>
&lt;h3 id="pre-aggregation-recording-rules">Pre-aggregation recording rules&lt;/h3>
&lt;p>為活動期間最常查的 pattern（per-region error rate、matchmaking queue depth、server utilization）建立 recording rules。Recording rules 在 Prometheus server 端預先計算，dashboard 查詢直接讀預計算結果，避免 heavy aggregation query 在活動期間拖慢 Prometheus。&lt;/p></description><content:encoded><![CDATA[<p>本案例的核心責任是避免高峰流量讓觀測系統本身失真。若訊號延遲與 cardinality 膨脹失控，值班決策會落在過期資料上。</p>
<h2 id="業務背景">業務背景</h2>
<p>一個線上多人遊戲平台，日活躍使用者約 50 萬人。每逢賽季開跑或限時活動，同時在線人數在 30 分鐘內從平日基線暴增 8-10 倍，matchmaking 服務的 request rate 從 5k/s 衝到 50k/s，遊戲伺服器同時運行的 match instance 從數千增到數萬。</p>
<p>觀測系統在平日運作良好 — Prometheus 單機 scrape 500 萬 active series、Grafana dashboard 查詢秒級回應、告警在 1 分鐘內觸發。但每次活動開跑時，觀測系統本身開始劣化：dashboard 查詢從秒級變成分鐘級、告警延遲 5 分鐘以上才送到、部分 metric 直接消失。值班工程師在最需要觀測的時刻失去了可信訊號。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="cardinality-爆炸">Cardinality 爆炸</h3>
<p>平日的 metric label 設計包含 <code>match_id</code>、<code>player_id</code> 跟 <code>server_instance</code>。平日 active series 約 500 萬，活動開跑後 match 跟 player 數量暴增，active series 在 30 分鐘內衝到 2000 萬。Prometheus 的 head block 記憶體從 20 GB 暴增到 80 GB，超過機器 64 GB 上限，觸發 OOM kill。</p>
<p>OOM 後 Prometheus 重啟需要 replay WAL，這段時間（5-15 分鐘）完全沒有 metric。活動最需要觀測的前 30 分鐘，觀測系統反而停擺。</p>
<h3 id="scrape-freshness-延遲">Scrape freshness 延遲</h3>
<p>即使 Prometheus 沒 OOM，大量 target 的 scrape 時間也會拉長。平日每輪 scrape 15 秒完成，活動期間拉長到 60-90 秒。Scrape interval 設定 30 秒時，下一輪 scrape 在上一輪還沒結束時就啟動，造成 sample 丟失跟時間錯位。Dashboard 上看到的數字可能延遲 2-3 分鐘，值班人員基於過期數據做判斷。</p>
<h3 id="alert-閾值失真">Alert 閾值失真</h3>
<p>告警規則基於平日 baseline 設定 — 例如 <code>error_rate &gt; 1%</code> 觸發。活動期間的 error rate 波動更大（matchmaking 短暫排隊造成的 timeout 增加是預期行為），平日閾值在活動期間持續觸發 false positive。值班人員開始 ignore alert，真正的問題（伺服器記憶體洩漏）被淹沒在噪音中。</p>
<h2 id="解法">解法</h2>
<h3 id="cardinality-guardrail">Cardinality guardrail</h3>
<p>把高 cardinality label 從 real-time metric 移除。<code>match_id</code> 和 <code>player_id</code> 不再作為 Prometheus label，改為 log 和 trace 的欄位。Real-time metric 只保留 <code>region</code>、<code>server_pool</code>、<code>game_mode</code> 等低 cardinality 維度。</p>
<p>需要 per-match 或 per-player 分析時，走 log analytics pipeline（非 real-time，延遲 5-10 分鐘可接受）。這讓 Prometheus 的 active series 在活動期間從 2000 萬降到 800 萬，留在單機可承受範圍。</p>
<h3 id="pre-aggregation-recording-rules">Pre-aggregation recording rules</h3>
<p>為活動期間最常查的 pattern（per-region error rate、matchmaking queue depth、server utilization）建立 recording rules。Recording rules 在 Prometheus server 端預先計算，dashboard 查詢直接讀預計算結果，避免 heavy aggregation query 在活動期間拖慢 Prometheus。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="c"># recording rule 示例</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="nt">groups</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">peak_precompute</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">15s</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">      </span>- <span class="nt">record</span><span class="p">:</span><span class="w"> </span><span class="l">region:matchmaking_errors:rate5m</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">        </span><span class="nt">expr</span><span class="p">:</span><span class="w"> </span><span class="l">sum(rate(matchmaking_errors_total[5m])) by (region)</span></span></span></code></pre></div><h3 id="signal-tiering">Signal tiering</h3>
<p>把觀測訊號分成兩層：</p>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>訊號類型</th>
          <th>Pipeline</th>
          <th>Freshness</th>
          <th>Cardinality 限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Tier 1</td>
          <td>Golden signals（latency、error rate、throughput、saturation）</td>
          <td>Prometheus real-time</td>
          <td>&lt; 30s</td>
          <td>嚴格（低 cardinality label only）</td>
      </tr>
      <tr>
          <td>Tier 2</td>
          <td>Debug signals（per-match、per-player、per-request）</td>
          <td>Log + trace analytics</td>
          <td>5-10 min</td>
          <td>無限制</td>
      </tr>
  </tbody>
</table>
<p>Tier 1 支撐告警跟即時 dashboard，保證活動期間不劣化。Tier 2 支撐事後分析跟 root cause investigation，接受延遲。</p>
<h3 id="dynamic-alert-threshold">Dynamic alert threshold</h3>
<p>活動期間啟用「高峰模式」alert profile — 調高 error rate 閾值（1% → 5%）、加長 <code>for:</code> duration（1m → 5m）、停用已知在活動期間會 false positive 的告警。高峰模式由活動排程系統自動觸發，活動結束後自動切回平日 profile。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>高 cardinality real-time</th>
          <th>分層治理</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Debug 即時性</td>
          <td>高（per-match real-time）</td>
          <td>低到中（per-match 延遲 5-10 min）</td>
      </tr>
      <tr>
          <td>Prometheus 穩定性</td>
          <td>低（活動期間 OOM 風險）</td>
          <td>高（active series 可控）</td>
      </tr>
      <tr>
          <td>Dashboard 回應速度</td>
          <td>活動期間劣化</td>
          <td>穩定（recording rules 預計算）</td>
      </tr>
      <tr>
          <td>告警可信度</td>
          <td>低（false positive 淹沒真問題）</td>
          <td>中到高（dynamic threshold 降噪）</td>
      </tr>
      <tr>
          <td>維護複雜度</td>
          <td>低（一套 pipeline）</td>
          <td>中（兩套 pipeline + 高峰模式切換）</td>
      </tr>
  </tbody>
</table>
<p>分層治理的核心取捨是犧牲 per-match real-time debug 能力，換取觀測系統在高峰期間的穩定。這個取捨在活動場景成立 — 活動期間最需要的是「整體是否健康」的判斷，per-match debug 在事後分析夠用。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality Cost Governance</a>：cardinality guardrail 的設計原則與偵測機制。</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>：scrape freshness、sampling bias 與 signal tiering。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：real-time vs batch analytics pipeline 的分層設計。</li>
<li><a href="/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">4.4 Dashboard Alert</a>：dynamic alert threshold 與高峰模式切換。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>流量高峰期間 Prometheus 記憶體使用異常增長或觸發 OOM</li>
<li>Dashboard 在尖峰時段查詢變慢或 timeout，正好是最需要看的時候</li>
<li>Alert 在活動期間大量觸發但多數是 false positive，值班人員開始 ignore</li>
<li><code>prometheus_tsdb_head_series</code> 在特定時段突然暴增，結束後回落</li>
<li>Metric label 中包含高 cardinality identifier（user_id、session_id、request_id）</li>
</ul>
]]></content:encoded></item><item><title>Healthcare：存取可追溯性與保留邊界</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/</guid><description>&lt;p>本案例的核心責任是讓資料主權場景下的觀測仍可追溯。Healthcare 系統常同時面臨最小存取原則、資料留存規範與跨團隊協作需求。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>一個遠距醫療平台，服務多家醫療機構（multi-tenant），處理病歷查閱、處方開立、檢驗報告與預約排程。平台受 HIPAA 跟當地個資法規範，稽核單位要求能回答「哪個使用者在什麼時間查看了哪個病患的哪份紀錄」。&lt;/p>
&lt;p>初期系統的存取紀錄散落在各服務的 application log 中 — 病歷服務記了一筆 &lt;code>GET /patient/123/records&lt;/code>，處方服務記了一筆 &lt;code>POST /prescription&lt;/code>，但兩者沒有共同的 correlation key。稽核問「護理師 A 在 3 月 15 日存取了哪些病歷」時，工程師需要在四個服務各自 grep，再用 timestamp 近似對齊，整個流程耗時半天且結果不可靠。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="存取-log-與-application-log-混合">存取 log 與 application log 混合&lt;/h3>
&lt;p>存取紀錄（誰看了什麼）跟 operational log（request timing、error、retry）寫在同一個 pipeline。Application log 的 retention 設定 30 天（除錯夠用），但法規要求存取紀錄保留 6 年。等到稽核來查詢時，超過 30 天的存取紀錄已經被刪。&lt;/p>
&lt;h3 id="跨服務存取鏈斷裂">跨服務存取鏈斷裂&lt;/h3>
&lt;p>一次病歷查閱可能經過 API gateway → auth service → patient service → record service → audit service 五個服務。每個服務各自記 log，但沒有統一的 access event correlation。Auth service 知道「誰」，patient service 知道「看了哪個病患」，record service 知道「看了哪份紀錄」— 三段資訊散落在三個服務的 log 中，無法自動關聯。&lt;/p>
&lt;h3 id="multi-tenant-retention-差異">Multi-tenant retention 差異&lt;/h3>
&lt;p>不同醫療機構受不同法規管轄 — 機構 A 在美國需要 HIPAA 6 年 retention，機構 B 在歐盟需要 GDPR 的「目的限縮」原則（保留期限隨用途而定），機構 C 在台灣需要醫療法規定的 7 年。統一 retention policy 要嘛過度保留（增加成本與 PII 暴露面），要嘛保留不足（法規風險）。&lt;/p>
&lt;h2 id="解法">解法&lt;/h2>
&lt;h3 id="data-access-audit-log-獨立-pipeline">Data access audit log 獨立 pipeline&lt;/h3>
&lt;p>把存取事件從 application log 分離出來。每當使用者查閱、修改或匯出 PHI（Protected Health Information）時，產生結構化 access event：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;event_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;phi_access&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;actor&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;nurse-a@hospital-x.com&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;patient_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;P-2048&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;resource&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;medical_record/lab_result/2026-03-15&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;view&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;trace_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;abc123&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;access_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;acc-789&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;tenant&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;hospital-x&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="nt">&amp;#34;timestamp&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2026-03-15T14:22:05Z&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Access event 寫入獨立的 immutable storage（append-only log），跟 application log 分開的 pipeline 與 retention。&lt;/p>
&lt;h3 id="cross-service-access-chain">Cross-service access chain&lt;/h3>
&lt;p>在 API gateway 入口產生 &lt;code>access_id&lt;/code>，跟 &lt;code>trace_id&lt;/code> 一起透過 context propagation 傳遞到所有下游服務。每個服務在產生 access event 時帶上這兩個 key。查詢時用 &lt;code>access_id&lt;/code> 就能撈出一次存取操作在所有服務的完整軌跡，不需要手動拼接。&lt;/p>
&lt;p>&lt;code>trace_id&lt;/code> 用於關聯 operational 訊號（latency、error），&lt;code>access_id&lt;/code> 用於關聯合規稽核。兩者可以相同也可以不同 — 關鍵是 access event 要同時帶兩個 key。&lt;/p>
&lt;h3 id="分層-retention-與-tenant-level-policy">分層 retention 與 tenant-level policy&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>層級&lt;/th>
 &lt;th>儲存&lt;/th>
 &lt;th>Retention&lt;/th>
 &lt;th>用途&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Hot&lt;/td>
 &lt;td>搜尋引擎（Elasticsearch / Cloud Logging）&lt;/td>
 &lt;td>90 天&lt;/td>
 &lt;td>即時查詢、事故調查&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Warm&lt;/td>
 &lt;td>Object storage（壓縮）&lt;/td>
 &lt;td>2 年&lt;/td>
 &lt;td>定期稽核、合規查詢&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cold&lt;/td>
 &lt;td>Archive storage（冰凍）&lt;/td>
 &lt;td>6-7 年（依 tenant 法規）&lt;/td>
 &lt;td>法規保留、法務調查&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>每個 tenant 在平台建立時設定法規要求的 retention 期限。Pipeline 根據 tenant tag 自動把 access event 路由到對應的 retention tier。Tenant A 的紀錄到第 6 年自動歸檔到 cold，tenant B 在 GDPR 目的屆滿時觸發刪除審核。&lt;/p></description><content:encoded><![CDATA[<p>本案例的核心責任是讓資料主權場景下的觀測仍可追溯。Healthcare 系統常同時面臨最小存取原則、資料留存規範與跨團隊協作需求。</p>
<h2 id="業務背景">業務背景</h2>
<p>一個遠距醫療平台，服務多家醫療機構（multi-tenant），處理病歷查閱、處方開立、檢驗報告與預約排程。平台受 HIPAA 跟當地個資法規範，稽核單位要求能回答「哪個使用者在什麼時間查看了哪個病患的哪份紀錄」。</p>
<p>初期系統的存取紀錄散落在各服務的 application log 中 — 病歷服務記了一筆 <code>GET /patient/123/records</code>，處方服務記了一筆 <code>POST /prescription</code>，但兩者沒有共同的 correlation key。稽核問「護理師 A 在 3 月 15 日存取了哪些病歷」時，工程師需要在四個服務各自 grep，再用 timestamp 近似對齊，整個流程耗時半天且結果不可靠。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="存取-log-與-application-log-混合">存取 log 與 application log 混合</h3>
<p>存取紀錄（誰看了什麼）跟 operational log（request timing、error、retry）寫在同一個 pipeline。Application log 的 retention 設定 30 天（除錯夠用），但法規要求存取紀錄保留 6 年。等到稽核來查詢時，超過 30 天的存取紀錄已經被刪。</p>
<h3 id="跨服務存取鏈斷裂">跨服務存取鏈斷裂</h3>
<p>一次病歷查閱可能經過 API gateway → auth service → patient service → record service → audit service 五個服務。每個服務各自記 log，但沒有統一的 access event correlation。Auth service 知道「誰」，patient service 知道「看了哪個病患」，record service 知道「看了哪份紀錄」— 三段資訊散落在三個服務的 log 中，無法自動關聯。</p>
<h3 id="multi-tenant-retention-差異">Multi-tenant retention 差異</h3>
<p>不同醫療機構受不同法規管轄 — 機構 A 在美國需要 HIPAA 6 年 retention，機構 B 在歐盟需要 GDPR 的「目的限縮」原則（保留期限隨用途而定），機構 C 在台灣需要醫療法規定的 7 年。統一 retention policy 要嘛過度保留（增加成本與 PII 暴露面），要嘛保留不足（法規風險）。</p>
<h2 id="解法">解法</h2>
<h3 id="data-access-audit-log-獨立-pipeline">Data access audit log 獨立 pipeline</h3>
<p>把存取事件從 application log 分離出來。每當使用者查閱、修改或匯出 PHI（Protected Health Information）時，產生結構化 access event：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  <span class="nt">&#34;event_type&#34;</span><span class="p">:</span> <span class="s2">&#34;phi_access&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="nt">&#34;actor&#34;</span><span class="p">:</span> <span class="s2">&#34;nurse-a@hospital-x.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nt">&#34;patient_id&#34;</span><span class="p">:</span> <span class="s2">&#34;P-2048&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="nt">&#34;resource&#34;</span><span class="p">:</span> <span class="s2">&#34;medical_record/lab_result/2026-03-15&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;view&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  <span class="nt">&#34;trace_id&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="nt">&#34;access_id&#34;</span><span class="p">:</span> <span class="s2">&#34;acc-789&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="nt">&#34;tenant&#34;</span><span class="p">:</span> <span class="s2">&#34;hospital-x&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="nt">&#34;timestamp&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-03-15T14:22:05Z&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Access event 寫入獨立的 immutable storage（append-only log），跟 application log 分開的 pipeline 與 retention。</p>
<h3 id="cross-service-access-chain">Cross-service access chain</h3>
<p>在 API gateway 入口產生 <code>access_id</code>，跟 <code>trace_id</code> 一起透過 context propagation 傳遞到所有下游服務。每個服務在產生 access event 時帶上這兩個 key。查詢時用 <code>access_id</code> 就能撈出一次存取操作在所有服務的完整軌跡，不需要手動拼接。</p>
<p><code>trace_id</code> 用於關聯 operational 訊號（latency、error），<code>access_id</code> 用於關聯合規稽核。兩者可以相同也可以不同 — 關鍵是 access event 要同時帶兩個 key。</p>
<h3 id="分層-retention-與-tenant-level-policy">分層 retention 與 tenant-level policy</h3>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>儲存</th>
          <th>Retention</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Hot</td>
          <td>搜尋引擎（Elasticsearch / Cloud Logging）</td>
          <td>90 天</td>
          <td>即時查詢、事故調查</td>
      </tr>
      <tr>
          <td>Warm</td>
          <td>Object storage（壓縮）</td>
          <td>2 年</td>
          <td>定期稽核、合規查詢</td>
      </tr>
      <tr>
          <td>Cold</td>
          <td>Archive storage（冰凍）</td>
          <td>6-7 年（依 tenant 法規）</td>
          <td>法規保留、法務調查</td>
      </tr>
  </tbody>
</table>
<p>每個 tenant 在平台建立時設定法規要求的 retention 期限。Pipeline 根據 tenant tag 自動把 access event 路由到對應的 retention tier。Tenant A 的紀錄到第 6 年自動歸檔到 cold，tenant B 在 GDPR 目的屆滿時觸發刪除審核。</p>
<h3 id="存取-log-中的-pii-處理">存取 log 中的 PII 處理</h3>
<p>Access event 本身包含 <code>patient_id</code> 跟 <code>actor</code>，這些在存取紀錄中是必要資訊（「誰看了什麼」需要這兩個欄位）。處理方式是存取控制而非遮罩 — access event storage 的讀取權限限縮到 compliance team 跟 audit 角色，engineering team 的一般查詢權限無法看到這些欄位。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>統一 retention</th>
          <th>分層 + tenant-level</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>實作複雜度</td>
          <td>低</td>
          <td>高（routing 邏輯、多層 storage）</td>
      </tr>
      <tr>
          <td>儲存成本</td>
          <td>高（全部留最長）</td>
          <td>可控（各層各自成本）</td>
      </tr>
      <tr>
          <td>合規精確度</td>
          <td>低（過度保留或保留不足）</td>
          <td>高（對齊各 tenant 法規要求）</td>
      </tr>
      <tr>
          <td>刪除能力</td>
          <td>無法按 tenant 刪</td>
          <td>可（GDPR right to erasure）</td>
      </tr>
      <tr>
          <td>查詢效率</td>
          <td>全量搜尋</td>
          <td>Hot tier 秒級、Cold tier 分鐘到小時級</td>
      </tr>
  </tbody>
</table>
<p>分層架構的最大風險是跨層查詢的延遲 — 稽核要求「給我 3 年前的存取紀錄」時，cold tier 的解凍時間可能是小時級。解法是在稽核週期前預先解凍相關 tenant 的 cold archive 到 warm tier。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">4.12 Audit Log Governance</a>：audit log 分離與 PII 治理。</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a>：access log pipeline 的 ownership 與 review cadence。</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>：timestamp integrity 跟跨服務時序校正。</li>
<li><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>：access_id 跟 trace_id 的 propagation 設計。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>稽核問「使用者 X 在某段時間存取了什麼」，回答需要超過數小時的手動拼接</li>
<li>存取紀錄的 retention 跟法規要求不一致，但沒人確切量化差距</li>
<li>Multi-tenant 環境中所有 tenant 共用同一個 retention policy，無法按法規區分</li>
<li>跨服務的存取事件無法自動關聯，需要靠 timestamp 近似比對</li>
<li>PHI 相關的 log 跟一般 application log 存在同一個 storage，存取控制無法區隔</li>
</ul>
]]></content:encoded></item><item><title>4.C4 AWS：X-Ray 到 OpenTelemetry 轉換</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/xray-to-opentelemetry-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/xray-to-opentelemetry-migration/</guid><description>&lt;p>這個案例的核心責任是把觀測遷移從工具替換，提升為標準化策略。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>AWS 已明確提出 X-Ray SDK/Daemon 的維護時程，並提供遷移到 OpenTelemetry 的路徑。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當 observability agent 與 SDK 受限於單一供應商，轉向 OTel 可以降低未來轉移成本，但需要治理採集、匯出與語意對齊。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先盤點現有 instrumentation 與依賴 SDK。&lt;/li>
&lt;li>先換 collector/agent，再逐步改應用端 instrumentation。&lt;/li>
&lt;li>把 trace/metric 的等價驗證納入 release gate。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a> 與 &lt;a href="https://tarrragon.github.io/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&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-migration.html">X-Ray to OpenTelemetry migration guide&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把觀測遷移從工具替換，提升為標準化策略。</p>
<h2 id="觀察">觀察</h2>
<p>AWS 已明確提出 X-Ray SDK/Daemon 的維護時程，並提供遷移到 OpenTelemetry 的路徑。</p>
<h2 id="判讀">判讀</h2>
<p>當 observability agent 與 SDK 受限於單一供應商，轉向 OTel 可以降低未來轉移成本，但需要治理採集、匯出與語意對齊。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先盤點現有 instrumentation 與依賴 SDK。</li>
<li>先換 collector/agent，再逐步改應用端 instrumentation。</li>
<li>把 trace/metric 的等價驗證納入 release gate。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a> 與 <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>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-migration.html">X-Ray to OpenTelemetry migration guide</a></li>
</ul>
]]></content:encoded></item><item><title>4.C5 Google Cloud：Cloud Trace 導入 OTLP 入口</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/cloud-trace-otlp-adoption/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/cloud-trace-otlp-adoption/</guid><description>&lt;p>這個案例的核心責任是說明 observability 平台轉換常來自資料通道標準化需求。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Google Cloud 在 Cloud Trace 提供 OTLP 支援，降低應用程式對特定傳輸介面的綁定。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>當團隊要跨多環境與多工具，標準化傳輸協定能減少重複 instrumentation 與遷移摩擦。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>將 collector 與 in-process exporter 對齊 OTLP。&lt;/li>
&lt;li>把 trace schema 與 sampling 規則集中治理。&lt;/li>
&lt;li>在遷移期保留舊通道與新通道比對。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://cloud.google.com/blog/products/management-tools/opentelemetry-now-in-google-cloud-observability">OTLP in Google Cloud Observability&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是說明 observability 平台轉換常來自資料通道標準化需求。</p>
<h2 id="觀察">觀察</h2>
<p>Google Cloud 在 Cloud Trace 提供 OTLP 支援，降低應用程式對特定傳輸介面的綁定。</p>
<h2 id="判讀">判讀</h2>
<p>當團隊要跨多環境與多工具，標準化傳輸協定能減少重複 instrumentation 與遷移摩擦。</p>
<h2 id="策略">策略</h2>
<ol>
<li>將 collector 與 in-process exporter 對齊 OTLP。</li>
<li>把 trace schema 與 sampling 規則集中治理。</li>
<li>在遷移期保留舊通道與新通道比對。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a> 與 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://cloud.google.com/blog/products/management-tools/opentelemetry-now-in-google-cloud-observability">OTLP in Google Cloud Observability</a></li>
</ul>
]]></content:encoded></item><item><title>4.C6 AWS：ADOT on EKS 管線遷移</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/adot-eks-observability-pipeline-migration/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/adot-eks-observability-pipeline-migration/</guid><description>&lt;p>這個案例的核心責任是把 observability 遷移做成管線治理，而不是單點 agent 替換。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>AWS ADOT on EKS 的實務把 metrics、traces 採集策略整合到可管理的 collector pipeline。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>多代理混用雖然能運作，但在規模化時會放大配置漂移與維運成本。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先統一 collector 部署模式。&lt;/li>
&lt;li>將 exporter 與 sampling 規則集中管理。&lt;/li>
&lt;li>以資料品質指標驗證遷移成效。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws-otel.github.io/docs/getting-started/adot-eks-add-on/">AWS Distro for OpenTelemetry on EKS&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 observability 遷移做成管線治理，而不是單點 agent 替換。</p>
<h2 id="觀察">觀察</h2>
<p>AWS ADOT on EKS 的實務把 metrics、traces 採集策略整合到可管理的 collector pipeline。</p>
<h2 id="判讀">判讀</h2>
<p>多代理混用雖然能運作，但在規模化時會放大配置漂移與維運成本。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先統一 collector 部署模式。</li>
<li>將 exporter 與 sampling 規則集中管理。</li>
<li>以資料品質指標驗證遷移成效。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 telemetry pipeline</a> 與 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 observability operating model</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://aws-otel.github.io/docs/getting-started/adot-eks-add-on/">AWS Distro for OpenTelemetry on EKS</a></li>
</ul>
]]></content:encoded></item><item><title>4.C7 Datadog：OTel 相容遷移實務</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/datadog-otel-migration-practice/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/datadog-otel-migration-practice/</guid><description>&lt;p>這個案例的核心責任是把 observability 遷移做成可逐步替換的技術路線。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Datadog 與 OTel 生態整合的做法，顯示團隊可在不一次重寫下逐步切換採集管線。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>觀測遷移的主要風險是資料語意漂移與管線雙軌期成本，而非單一 agent 安裝。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>先建立雙軌採集的對照驗證。&lt;/li>
&lt;li>把 schema 與 sampling 政策版本化。&lt;/li>
&lt;li>用品質指標決定何時關閉舊管線。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11&lt;/a> 與 &lt;a href="https://tarrragon.github.io/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&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.datadoghq.com/blog/instrument-python-apps-with-datadog-and-opentelemetry/">Datadog and OpenTelemetry&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把 observability 遷移做成可逐步替換的技術路線。</p>
<h2 id="觀察">觀察</h2>
<p>Datadog 與 OTel 生態整合的做法，顯示團隊可在不一次重寫下逐步切換採集管線。</p>
<h2 id="判讀">判讀</h2>
<p>觀測遷移的主要風險是資料語意漂移與管線雙軌期成本，而非單一 agent 安裝。</p>
<h2 id="策略">策略</h2>
<ol>
<li>先建立雙軌採集的對照驗證。</li>
<li>把 schema 與 sampling 政策版本化。</li>
<li>用品質指標決定何時關閉舊管線。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11</a> 與 <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</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.datadoghq.com/blog/instrument-python-apps-with-datadog-and-opentelemetry/">Datadog and OpenTelemetry</a></li>
</ul>
]]></content:encoded></item><item><title>4.C8 Airbnb：Kubernetes 規模化下的觀測訊號治理</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/airbnb-observability-k8s-scale-signals/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/airbnb-observability-k8s-scale-signals/</guid><description>&lt;p>這個案例的核心責任是把平台擴縮行為轉成可觀測治理問題。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>Airbnb 在 Kubernetes 規模化過程強調動態擴縮，代表觀測系統需要追上容量與拓撲變化。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;p>若訊號模型無法反映動態叢集，告警與容量判讀容易失真。&lt;/p>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>將叢集層指標與服務層指標分開治理。&lt;/li>
&lt;li>在擴縮流程中保留關鍵健康訊號。&lt;/li>
&lt;li>用回溯報表驗證擴縮與事故關聯。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18&lt;/a>。&lt;/p>
&lt;h2 id="引用源">引用源&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://airbnb.tech/infrastructure/dynamic-kubernetes-cluster-scaling-at-airbnb/">Dynamic Kubernetes Cluster Scaling at Airbnb&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>這個案例的核心責任是把平台擴縮行為轉成可觀測治理問題。</p>
<h2 id="觀察">觀察</h2>
<p>Airbnb 在 Kubernetes 規模化過程強調動態擴縮，代表觀測系統需要追上容量與拓撲變化。</p>
<h2 id="判讀">判讀</h2>
<p>若訊號模型無法反映動態叢集，告警與容量判讀容易失真。</p>
<h2 id="策略">策略</h2>
<ol>
<li>將叢集層指標與服務層指標分開治理。</li>
<li>在擴縮流程中保留關鍵健康訊號。</li>
<li>用回溯報表驗證擴縮與事故關聯。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13</a> 與 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18</a>。</p>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://airbnb.tech/infrastructure/dynamic-kubernetes-cluster-scaling-at-airbnb/">Dynamic Kubernetes Cluster Scaling at Airbnb</a></li>
</ul>
]]></content:encoded></item><item><title>4.C9 反例：OTel 遷移後訊號漂移</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/</guid><description>&lt;p>這個反例的核心責任是說明 observability 遷移失敗常以語意漂移形式出現，資料丟失反而少見。&lt;/p>
&lt;h2 id="事故長相">事故長相&lt;/h2>
&lt;p>OTel 切換後，儀表板看起來都有資料，但 on-call 開始收到不同告警，SLO burn rate 與舊系統長期對不上。同一個事故在新舊管線裡被歸到不同 service、不同 label 或不同 latency bucket。&lt;/p>
&lt;h2 id="為什麼會擴大">為什麼會擴大&lt;/h2>
&lt;p>觀測資料是事故判讀的入口。若 metric 名稱、label、sampling、aggregation 不一致，團隊會對同一個現象做出不同判斷，甚至在錯誤訊號上回退服務。&lt;/p>
&lt;h2 id="回退判讀">回退判讀&lt;/h2>
&lt;p>觀測遷移的回退不一定是回到舊 agent。更重要的是保留新舊訊號對照，先停止讓新管線主導告警與 SLO 判定，再修正語意對齊。若直接關掉新管線，反而會失去分析漂移原因的證據。&lt;/p>
&lt;h2 id="觀測專屬告警條件">觀測專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>新舊管線對同一服務的 error rate 長期偏離&lt;/li>
&lt;li>missing span 或 missing metric 比例持續上升&lt;/li>
&lt;li>alert 噪音增加，但事故量沒有對應增加&lt;/li>
&lt;/ul>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>回 &lt;a href="https://tarrragon.github.io/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&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這個反例的核心責任是說明 observability 遷移失敗常以語意漂移形式出現，資料丟失反而少見。</p>
<h2 id="事故長相">事故長相</h2>
<p>OTel 切換後，儀表板看起來都有資料，但 on-call 開始收到不同告警，SLO burn rate 與舊系統長期對不上。同一個事故在新舊管線裡被歸到不同 service、不同 label 或不同 latency bucket。</p>
<h2 id="為什麼會擴大">為什麼會擴大</h2>
<p>觀測資料是事故判讀的入口。若 metric 名稱、label、sampling、aggregation 不一致，團隊會對同一個現象做出不同判斷，甚至在錯誤訊號上回退服務。</p>
<h2 id="回退判讀">回退判讀</h2>
<p>觀測遷移的回退不一定是回到舊 agent。更重要的是保留新舊訊號對照，先停止讓新管線主導告警與 SLO 判定，再修正語意對齊。若直接關掉新管線，反而會失去分析漂移原因的證據。</p>
<h2 id="觀測專屬告警條件">觀測專屬告警條件</h2>
<ul>
<li>新舊管線對同一服務的 error rate 長期偏離</li>
<li>missing span 或 missing metric 比例持續上升</li>
<li>alert 噪音增加，但事故量沒有對應增加</li>
</ul>
<h2 id="下一步路由">下一步路由</h2>
<p>回 <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</a> 與 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11</a>。</p>
]]></content:encoded></item><item><title>4.C10 對照：規模差異下的觀測遷移</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/</guid><description>&lt;p>這篇對照的核心責任是提醒觀測遷移是治理能力轉換，工具替換只是表面動作。&lt;/p>
&lt;h2 id="小型團隊常見判讀">小型團隊常見判讀&lt;/h2>
&lt;p>小型團隊最怕雙軌過久。若同時維護兩套儀表，通常會先耗盡人力。小團隊更需要短期對照、快速收斂，而不是一次拉滿所有治理流程。&lt;/p>
&lt;h2 id="中型團隊常見判讀">中型團隊常見判讀&lt;/h2>
&lt;p>中型團隊會碰到 schema 漂移與標籤膨脹。這個階段的失敗常見於「看得到數據，但看不懂是否同一語意」，導致告警與容量判讀彼此矛盾。&lt;/p>
&lt;h2 id="大型團隊常見判讀">大型團隊常見判讀&lt;/h2>
&lt;p>大型團隊的觀測遷移會牽涉成本分攤、採樣策略、collector 拓撲。若只追求功能對齊，往往在遷移後才出現成本暴增與告警漂移。&lt;/p>
&lt;h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件&lt;/h2>
&lt;ul>
&lt;li>新舊管線 &lt;code>error rate&lt;/code> 或 &lt;code>burn rate&lt;/code> 偏差長期超標&lt;/li>
&lt;li>missing signal 比例持續上升&lt;/li>
&lt;li>同一事件在兩套儀表板得到相反結論&lt;/li>
&lt;/ul>
&lt;p>觸發條件時應停止切換，先修資料語意與採樣策略，再決定是否繼續遷移。&lt;/p>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;p>判讀重點是「兩套觀測是否仍在描述同一個系統狀態」。當 error rate、burn rate、trace coverage 三者任一長期偏離，就代表遷移證據不可信，應先停切換再修資料品質。&lt;/p>
&lt;h2 id="邊界判讀">邊界判讀&lt;/h2>
&lt;p>這篇對照只處理觀測遷移的判讀邊界，不處理各 vendor 的實作細節。主要風險是把資料語意不一致當成短暫噪音，導致團隊在錯誤證據上推進切換。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>先回到 &lt;a href="https://tarrragon.github.io/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&lt;/a> 修正語意與採樣，再到 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline&lt;/a> 校正雙軌管線。若已影響事故判讀，交接到 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp;amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>這篇對照的核心責任是提醒觀測遷移是治理能力轉換，工具替換只是表面動作。</p>
<h2 id="小型團隊常見判讀">小型團隊常見判讀</h2>
<p>小型團隊最怕雙軌過久。若同時維護兩套儀表，通常會先耗盡人力。小團隊更需要短期對照、快速收斂，而不是一次拉滿所有治理流程。</p>
<h2 id="中型團隊常見判讀">中型團隊常見判讀</h2>
<p>中型團隊會碰到 schema 漂移與標籤膨脹。這個階段的失敗常見於「看得到數據，但看不懂是否同一語意」，導致告警與容量判讀彼此矛盾。</p>
<h2 id="大型團隊常見判讀">大型團隊常見判讀</h2>
<p>大型團隊的觀測遷移會牽涉成本分攤、採樣策略、collector 拓撲。若只追求功能對齊，往往在遷移後才出現成本暴增與告警漂移。</p>
<h2 id="這個情境的專屬告警條件">這個情境的專屬告警條件</h2>
<ul>
<li>新舊管線 <code>error rate</code> 或 <code>burn rate</code> 偏差長期超標</li>
<li>missing signal 比例持續上升</li>
<li>同一事件在兩套儀表板得到相反結論</li>
</ul>
<p>觸發條件時應停止切換，先修資料語意與採樣策略，再決定是否繼續遷移。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<p>判讀重點是「兩套觀測是否仍在描述同一個系統狀態」。當 error rate、burn rate、trace coverage 三者任一長期偏離，就代表遷移證據不可信，應先停切換再修資料品質。</p>
<h2 id="邊界判讀">邊界判讀</h2>
<p>這篇對照只處理觀測遷移的判讀邊界，不處理各 vendor 的實作細節。主要風險是把資料語意不一致當成短暫噪音，導致團隊在錯誤證據上推進切換。</p>
<h2 id="下一步路由">下一步路由</h2>
<p>先回到 <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> 修正語意與採樣，再到 <a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a> 校正雙軌管線。若已影響事故判讀，交接到 <a href="/blog/backend/08-incident-response/incident-intake-evidence-triage/" data-link-title="8.18 Incident Intake &amp; Evidence Triage" data-link-desc="把告警、客訴、支援回報與第三方狀態轉成同一個 intake / evidence 判讀流程">8.18 Incident Intake</a>。</p>
]]></content:encoded></item><item><title>4.C11 Uber：M3 大規模 Metrics 平台</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/uber-m3-metrics-platform-scale/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/uber-m3-metrics-platform-scale/</guid><description>&lt;p>Uber 的 M3 案例揭露了 metrics 系統從「每個團隊各跑一套 Prometheus」到「全公司共用的 metrics 平台」的轉折點。轉折的核心判斷是：當 active series 總量超過單機 Prometheus 的記憶體上限、且多個團隊需要跨叢集查詢時，自建平台層的成本低於持續橫向複製 Prometheus 實例的成本。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Uber 的服務觀測涵蓋行程追蹤、即時定價、ETA 計算、司機定位、支付結算與推播通知。每個微服務都暴露 Prometheus-compatible metrics，隨著服務數量成長到數千個，寫入速率達到每秒數十億 data points。&lt;/p>
&lt;p>早期每個團隊各自部署 Prometheus，各管自己的 retention、scrape config 與 alerting rules。規模小時這個模式運作良好 — 每個 Prometheus 實例只需要處理自己團隊的幾萬到幾十萬 series。但當組織成長到數百個團隊、數千個服務時，散落的 Prometheus 實例帶來三個問題。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="單機記憶體天花板">單機記憶體天花板&lt;/h3>
&lt;p>Prometheus 的 TSDB 把 active series 放在記憶體的 head block，每個 series 消耗約 3-4 KB（詳見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/prometheus/capacity-failure-modes/" data-link-title="Prometheus 容量規劃與故障模式" data-link-desc="說明 Prometheus 單機容量邊界、cardinality 與 retention 的資源模型、常見故障模式與判讀方式">Prometheus 容量規劃&lt;/a>）。當單一 Prometheus 實例需要 scrape 的 series 超過 1000 萬時，head block 就需要 40+ GB 記憶體。加上 query execution 跟 WAL replay 的暫時開銷，單機很容易 OOM。&lt;/p>
&lt;p>團隊的第一反應是按服務拆分多個 Prometheus 實例，但這讓跨服務查詢變得困難 — 要看一條 request 從 gateway 到 payment 的 latency 分布，需要分別查三個 Prometheus 再手動關聯。&lt;/p>
&lt;h3 id="retention-與長期趨勢">Retention 與長期趨勢&lt;/h3>
&lt;p>Prometheus 預設 retention 15 天。容量規劃與季度趨勢分析需要 90 天甚至 1 年的歷史資料。把 Prometheus retention 拉長到 90 天，disk 跟 memory 需求同步上升，而且 compaction 效率在資料量大時會下降。&lt;/p>
&lt;p>團隊需要的是分層 retention — 近期資料保留全精度、歷史資料做 downsampling 後保留更久。Prometheus 原生不支援 downsampling。&lt;/p>
&lt;h3 id="高可用與跨叢集查詢">高可用與跨叢集查詢&lt;/h3>
&lt;p>Prometheus 沒有原生 HA — 標準做法是跑兩個 instance scrape 同一批 target，靠下游去重。但兩個 instance 各自獨立儲存，查詢只打一個；instance 故障切換時會有短暫資料缺口。&lt;/p>
&lt;p>跨叢集查詢更困難。Prometheus federation 可以做簡單的 metric 聚合，但 federation 本身是 pull-based scrape — federation target 太多或 series 太大時，federation Prometheus 自己也會 OOM。&lt;/p>
&lt;h2 id="解法m3-平台">解法：M3 平台&lt;/h2>
&lt;p>Uber 開發了 M3 — 一個 Prometheus-compatible 的分散式 metrics 平台，由三個核心元件組成。&lt;/p>
&lt;h3 id="m3db分散式-time-series-storage">M3DB：分散式 time series storage&lt;/h3>
&lt;p>M3DB 是分散式 TSDB，資料按 namespace 和 shard 分布在多個節點。每個 namespace 可以有不同的 retention 和 resolution — 例如 &lt;code>realtime&lt;/code> namespace 保留 2 天全精度，&lt;code>aggregated_1m&lt;/code> namespace 保留 90 天 1 分鐘精度。這解決了 retention tiering 的問題。&lt;/p></description><content:encoded><![CDATA[<p>Uber 的 M3 案例揭露了 metrics 系統從「每個團隊各跑一套 Prometheus」到「全公司共用的 metrics 平台」的轉折點。轉折的核心判斷是：當 active series 總量超過單機 Prometheus 的記憶體上限、且多個團隊需要跨叢集查詢時，自建平台層的成本低於持續橫向複製 Prometheus 實例的成本。</p>
<h2 id="業務背景">業務背景</h2>
<p>Uber 的服務觀測涵蓋行程追蹤、即時定價、ETA 計算、司機定位、支付結算與推播通知。每個微服務都暴露 Prometheus-compatible metrics，隨著服務數量成長到數千個，寫入速率達到每秒數十億 data points。</p>
<p>早期每個團隊各自部署 Prometheus，各管自己的 retention、scrape config 與 alerting rules。規模小時這個模式運作良好 — 每個 Prometheus 實例只需要處理自己團隊的幾萬到幾十萬 series。但當組織成長到數百個團隊、數千個服務時，散落的 Prometheus 實例帶來三個問題。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="單機記憶體天花板">單機記憶體天花板</h3>
<p>Prometheus 的 TSDB 把 active series 放在記憶體的 head block，每個 series 消耗約 3-4 KB（詳見 <a href="/blog/backend/04-observability/vendors/prometheus/capacity-failure-modes/" data-link-title="Prometheus 容量規劃與故障模式" data-link-desc="說明 Prometheus 單機容量邊界、cardinality 與 retention 的資源模型、常見故障模式與判讀方式">Prometheus 容量規劃</a>）。當單一 Prometheus 實例需要 scrape 的 series 超過 1000 萬時，head block 就需要 40+ GB 記憶體。加上 query execution 跟 WAL replay 的暫時開銷，單機很容易 OOM。</p>
<p>團隊的第一反應是按服務拆分多個 Prometheus 實例，但這讓跨服務查詢變得困難 — 要看一條 request 從 gateway 到 payment 的 latency 分布，需要分別查三個 Prometheus 再手動關聯。</p>
<h3 id="retention-與長期趨勢">Retention 與長期趨勢</h3>
<p>Prometheus 預設 retention 15 天。容量規劃與季度趨勢分析需要 90 天甚至 1 年的歷史資料。把 Prometheus retention 拉長到 90 天，disk 跟 memory 需求同步上升，而且 compaction 效率在資料量大時會下降。</p>
<p>團隊需要的是分層 retention — 近期資料保留全精度、歷史資料做 downsampling 後保留更久。Prometheus 原生不支援 downsampling。</p>
<h3 id="高可用與跨叢集查詢">高可用與跨叢集查詢</h3>
<p>Prometheus 沒有原生 HA — 標準做法是跑兩個 instance scrape 同一批 target，靠下游去重。但兩個 instance 各自獨立儲存，查詢只打一個；instance 故障切換時會有短暫資料缺口。</p>
<p>跨叢集查詢更困難。Prometheus federation 可以做簡單的 metric 聚合，但 federation 本身是 pull-based scrape — federation target 太多或 series 太大時，federation Prometheus 自己也會 OOM。</p>
<h2 id="解法m3-平台">解法：M3 平台</h2>
<p>Uber 開發了 M3 — 一個 Prometheus-compatible 的分散式 metrics 平台，由三個核心元件組成。</p>
<h3 id="m3db分散式-time-series-storage">M3DB：分散式 time series storage</h3>
<p>M3DB 是分散式 TSDB，資料按 namespace 和 shard 分布在多個節點。每個 namespace 可以有不同的 retention 和 resolution — 例如 <code>realtime</code> namespace 保留 2 天全精度，<code>aggregated_1m</code> namespace 保留 90 天 1 分鐘精度。這解決了 retention tiering 的問題。</p>
<p>M3DB 的記憶體模型跟 Prometheus 不同 — 近期資料在記憶體，冷資料在 disk，不像 Prometheus 把所有 active series 都放 head block。這讓它能處理遠超單機 Prometheus 的 series 數量。</p>
<h3 id="m3-coordinator統一查詢入口">M3 Coordinator：統一查詢入口</h3>
<p>M3 Coordinator 接收 PromQL 查詢，轉譯後分發到 M3DB 節點，聚合結果後返回。對 Grafana 和 alerting rules 來說，M3 Coordinator 的 API 跟 Prometheus 完全相容 — 不需要改 dashboard 或 alert config。</p>
<h3 id="m3-aggregator寫入路徑聚合">M3 Aggregator：寫入路徑聚合</h3>
<p>高 cardinality 的原始 series 在寫入 M3DB 前先經過 M3 Aggregator 做 pre-aggregation — 例如把每秒的 request count 聚合成每分鐘，再寫入長期 namespace。這控制了長期儲存的資料量跟成本。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Prometheus standalone</th>
          <th>M3 平台</th>
          <th>Mimir / Thanos（替代）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>部署複雜度</td>
          <td>低（單一 binary）</td>
          <td>高（M3DB + Coordinator + Aggregator）</td>
          <td>中到高</td>
      </tr>
      <tr>
          <td>單機 series 上限</td>
          <td>~500 萬-1000 萬</td>
          <td>不適用（分散式）</td>
          <td>不適用</td>
      </tr>
      <tr>
          <td>Retention tiering</td>
          <td>無</td>
          <td>原生支援</td>
          <td>Thanos compactor / Mimir 支援</td>
      </tr>
      <tr>
          <td>PromQL 相容</td>
          <td>原生</td>
          <td>相容</td>
          <td>相容</td>
      </tr>
      <tr>
          <td>社群活躍度</td>
          <td>高（CNCF）</td>
          <td>低（Uber 主導、2023 後維護縮減）</td>
          <td>高（Grafana Labs / 社群）</td>
      </tr>
      <tr>
          <td>適用規模</td>
          <td>單團隊到中型組織</td>
          <td>大型組織（數十億 series）</td>
          <td>中型到大型</td>
      </tr>
  </tbody>
</table>
<p>M3 的最大風險是社群活躍度 — Uber 自 2023 年後縮減了 M3 的開發投入，Grafana Mimir 成為更活躍的替代。新專案選型時，Mimir 跟 Thanos 的社群支援度跟 Grafana 生態整合度都優於 M3。M3 的價值在於它驗證了「分散式 TSDB + 寫入路徑聚合 + retention tiering」這組設計模式，這組模式在 Mimir 跟 Thanos 裡以不同形式被採用。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/metrics-basics/" data-link-title="4.2 metrics 與 SLI/SLO" data-link-desc="整理 counter、gauge、histogram 與服務健康指標">4.2 Metrics Basics</a>：active series、cardinality 與 recording rules 的基礎模型，M3 的 pre-aggregation 對應 recording rules 的平台化版本。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：M3 的 Aggregator 是 pipeline 中 processing 層的實例。</li>
<li><a href="/blog/backend/04-observability/vendors/prometheus/remote-write-long-term-storage/" data-link-title="Remote Write 與長期儲存整合" data-link-desc="說明 Prometheus remote write 的配置、三家長期儲存後端比較（Mimir / Thanos / Cortex）、故障模式與容量規劃">Prometheus Remote Write 與長期儲存</a>：M3 是 remote write 目標之一，跟 Mimir / Thanos / Cortex 的比較在該文。</li>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理</a>：M3 的 per-namespace cardinality limit 是治理機制的生產實例。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>單一 Prometheus 實例 memory 接近機器上限，開始 OOM restart</li>
<li>多個 Prometheus 實例各自 scrape，跨服務查詢需要手動關聯</li>
<li>Retention 15 天不夠做季度趨勢分析，但拉長 retention 資源撐不住</li>
<li>團隊開始問「我們的 metrics 總共有多少 series、誰佔最多」但沒有統一的 cardinality 觀測</li>
<li>Grafana federation dashboard 查詢越來越慢或經常 timeout</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://www.uber.com/en-GB/blog/m3/">M3: Uber&rsquo;s Open Source, Large-scale Metrics Platform for Prometheus</a></li>
</ul>
]]></content:encoded></item><item><title>4.C12 Cloudflare：內部觀測平台的三層能力</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/cloudflare-internal-observability-architecture/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/cloudflare-internal-observability-architecture/</guid><description>&lt;p>Cloudflare 的觀測架構把 monitoring、analytics 和 forensics 拆成三層 pipeline，三層各自承擔不同的 resolution、retention 和查詢模式。規模到達每秒數十億 request、300+ edge location 時，用同一套 pipeline 處理三種能力會同時在成本跟查詢延遲上碰壁。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Cloudflare 的服務涵蓋 CDN、DNS、DDoS 防護、Workers 邊緣運算與 Zero Trust 安全。每秒處理數十億 HTTP request，分布在全球 300+ 資料中心。觀測資料量極大 — 僅 HTTP request log 每秒就產生數百 GB 未壓縮的結構化日誌。&lt;/p>
&lt;p>早期觀測用單一 pipeline 處理所有資料，隨著資料量成長，pipeline 面臨三個壓力：monitoring 需要秒級即時性但不需要全量資料；analytics 需要完整資料但可以延遲分鐘級；forensics（鑑識）需要保留原始事件但查詢頻率極低。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="資料量與成本">資料量與成本&lt;/h3>
&lt;p>每秒數十億 request 的全量日誌，即使壓縮後仍是 PB 級月儲存量。把全量資料送到集中式 log backend（無論是自建 Elasticsearch 或 SaaS Datadog）的 ingestion 成本本身就是天文數字。&lt;/p>
&lt;p>Cloudflare 公開表示過去曾用過 Kafka + Elasticsearch + Grafana 的組合，但隨著 edge 節點增加，centralized ingestion 的頻寬跟儲存成本持續超線性成長。&lt;/p>
&lt;h3 id="edge-到-core-的延遲">Edge 到 Core 的延遲&lt;/h3>
&lt;p>觀測資料從 300+ edge 節點匯聚到中心叢集，網路延遲跟 bandwidth 是物理限制。monitoring 需要秒級判斷（alert 要快觸發），但全量日誌的傳輸延遲可能是分鐘級。&lt;/p>
&lt;h3 id="查詢模式衝突">查詢模式衝突&lt;/h3>
&lt;p>on-call 值班需要的是 dashboard 上的 aggregated metrics（error rate、latency percentile、traffic volume），查詢要快、資料要即時。analytics 團隊需要的是全量日誌做 ad-hoc 查詢（某個 IP 在過去 24 小時的 request pattern），查詢可以慢、但資料要完整。forensics 需要的是單一事件的原始內容（某筆 request 的完整 header 跟 body），查詢極少但需要保留數月。&lt;/p>
&lt;p>三種查詢模式在 resolution、freshness 跟 retention 上的需求完全不同，用同一套 backend 處理會讓所有人的體驗都變差。&lt;/p>
&lt;h2 id="解法三層觀測能力">解法：三層觀測能力&lt;/h2>
&lt;h3 id="monitoringpre-aggregated-metrics--alerting">Monitoring：pre-aggregated metrics + alerting&lt;/h3>
&lt;p>edge 節點在本地做 pre-aggregation — 把每秒的 request count、error count、latency histogram 聚合成每 10 秒的 metric batch，push 到中心的 metrics backend。資料量從 PB/月壓縮到 TB/月。&lt;/p>
&lt;p>Alerting 跟 dashboard 只看聚合後的 metrics，查詢延遲在毫秒級。metrics backend 用 Prometheus-compatible 儲存，Grafana 作為查詢入口。&lt;/p>
&lt;h3 id="analyticssampled--full-fidelity-log-pipeline">Analytics：sampled + full-fidelity log pipeline&lt;/h3>
&lt;p>analytics 層接收全量日誌但做分層處理：高流量 endpoint 的日誌做 adaptive sampling（保留 1%-10%），低流量跟異常 request 保留全量。日誌送到自建的 columnar store（Cloudflare 用 ClickHouse 類的 OLAP 引擎），支援 ad-hoc 查詢。&lt;/p>
&lt;p>Retention 30-90 天，查詢延遲在秒到分鐘級。成本比 monitoring 層高但仍可控 — sampling 是關鍵的成本旋鈕。&lt;/p>
&lt;h3 id="forensics原始事件歸檔">Forensics：原始事件歸檔&lt;/h3>
&lt;p>需要完整保留的事件（安全事件、DDoS 攻擊、客戶投訴關聯的 request）寫入冷儲存（object storage）。查詢走 batch 模式（scan-based），延遲在分鐘到小時級。&lt;/p>
&lt;p>Retention 按合規需求保留 6 個月到數年。成本主要是儲存（object storage 便宜），ingestion 跟 query 成本極低。&lt;/p>
&lt;h2 id="取捨">取捨&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>單一 pipeline&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>差（全量資料走同一條路，成本隨 traffic 線性成長）&lt;/td>
 &lt;td>好（每層各自有成本旋鈕）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>查詢一致性&lt;/td>
 &lt;td>高（同一個 backend 查）&lt;/td>
 &lt;td>低（三個 backend，查詢語言可能不同）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Freshness&lt;/td>
 &lt;td>被最慢的一段拖住&lt;/td>
 &lt;td>每層獨立（monitoring 秒級、analytics 分鐘級、forensics 小時級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Debugging 路徑&lt;/td>
 &lt;td>短（一個入口）&lt;/td>
 &lt;td>長（先看 monitoring 判斷層級、再決定進 analytics 或 forensics）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>三層拆分的最大風險是 debugging 路徑變長 — on-call 先看 dashboard 發現異常，再到 analytics 查 sampled log 找 pattern，最後到 forensics 查原始事件確認細節。如果三層之間的 correlation ID（trace ID、request ID）沒有對齊，跨層查詢會斷掉。&lt;/p></description><content:encoded><![CDATA[<p>Cloudflare 的觀測架構把 monitoring、analytics 和 forensics 拆成三層 pipeline，三層各自承擔不同的 resolution、retention 和查詢模式。規模到達每秒數十億 request、300+ edge location 時，用同一套 pipeline 處理三種能力會同時在成本跟查詢延遲上碰壁。</p>
<h2 id="業務背景">業務背景</h2>
<p>Cloudflare 的服務涵蓋 CDN、DNS、DDoS 防護、Workers 邊緣運算與 Zero Trust 安全。每秒處理數十億 HTTP request，分布在全球 300+ 資料中心。觀測資料量極大 — 僅 HTTP request log 每秒就產生數百 GB 未壓縮的結構化日誌。</p>
<p>早期觀測用單一 pipeline 處理所有資料，隨著資料量成長，pipeline 面臨三個壓力：monitoring 需要秒級即時性但不需要全量資料；analytics 需要完整資料但可以延遲分鐘級；forensics（鑑識）需要保留原始事件但查詢頻率極低。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="資料量與成本">資料量與成本</h3>
<p>每秒數十億 request 的全量日誌，即使壓縮後仍是 PB 級月儲存量。把全量資料送到集中式 log backend（無論是自建 Elasticsearch 或 SaaS Datadog）的 ingestion 成本本身就是天文數字。</p>
<p>Cloudflare 公開表示過去曾用過 Kafka + Elasticsearch + Grafana 的組合，但隨著 edge 節點增加，centralized ingestion 的頻寬跟儲存成本持續超線性成長。</p>
<h3 id="edge-到-core-的延遲">Edge 到 Core 的延遲</h3>
<p>觀測資料從 300+ edge 節點匯聚到中心叢集，網路延遲跟 bandwidth 是物理限制。monitoring 需要秒級判斷（alert 要快觸發），但全量日誌的傳輸延遲可能是分鐘級。</p>
<h3 id="查詢模式衝突">查詢模式衝突</h3>
<p>on-call 值班需要的是 dashboard 上的 aggregated metrics（error rate、latency percentile、traffic volume），查詢要快、資料要即時。analytics 團隊需要的是全量日誌做 ad-hoc 查詢（某個 IP 在過去 24 小時的 request pattern），查詢可以慢、但資料要完整。forensics 需要的是單一事件的原始內容（某筆 request 的完整 header 跟 body），查詢極少但需要保留數月。</p>
<p>三種查詢模式在 resolution、freshness 跟 retention 上的需求完全不同，用同一套 backend 處理會讓所有人的體驗都變差。</p>
<h2 id="解法三層觀測能力">解法：三層觀測能力</h2>
<h3 id="monitoringpre-aggregated-metrics--alerting">Monitoring：pre-aggregated metrics + alerting</h3>
<p>edge 節點在本地做 pre-aggregation — 把每秒的 request count、error count、latency histogram 聚合成每 10 秒的 metric batch，push 到中心的 metrics backend。資料量從 PB/月壓縮到 TB/月。</p>
<p>Alerting 跟 dashboard 只看聚合後的 metrics，查詢延遲在毫秒級。metrics backend 用 Prometheus-compatible 儲存，Grafana 作為查詢入口。</p>
<h3 id="analyticssampled--full-fidelity-log-pipeline">Analytics：sampled + full-fidelity log pipeline</h3>
<p>analytics 層接收全量日誌但做分層處理：高流量 endpoint 的日誌做 adaptive sampling（保留 1%-10%），低流量跟異常 request 保留全量。日誌送到自建的 columnar store（Cloudflare 用 ClickHouse 類的 OLAP 引擎），支援 ad-hoc 查詢。</p>
<p>Retention 30-90 天，查詢延遲在秒到分鐘級。成本比 monitoring 層高但仍可控 — sampling 是關鍵的成本旋鈕。</p>
<h3 id="forensics原始事件歸檔">Forensics：原始事件歸檔</h3>
<p>需要完整保留的事件（安全事件、DDoS 攻擊、客戶投訴關聯的 request）寫入冷儲存（object storage）。查詢走 batch 模式（scan-based），延遲在分鐘到小時級。</p>
<p>Retention 按合規需求保留 6 個月到數年。成本主要是儲存（object storage 便宜），ingestion 跟 query 成本極低。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>單一 pipeline</th>
          <th>三層拆分</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>架構複雜度</td>
          <td>低（一條路走完）</td>
          <td>高（三條路各自維護）</td>
      </tr>
      <tr>
          <td>成本可控度</td>
          <td>差（全量資料走同一條路，成本隨 traffic 線性成長）</td>
          <td>好（每層各自有成本旋鈕）</td>
      </tr>
      <tr>
          <td>查詢一致性</td>
          <td>高（同一個 backend 查）</td>
          <td>低（三個 backend，查詢語言可能不同）</td>
      </tr>
      <tr>
          <td>Freshness</td>
          <td>被最慢的一段拖住</td>
          <td>每層獨立（monitoring 秒級、analytics 分鐘級、forensics 小時級）</td>
      </tr>
      <tr>
          <td>Debugging 路徑</td>
          <td>短（一個入口）</td>
          <td>長（先看 monitoring 判斷層級、再決定進 analytics 或 forensics）</td>
      </tr>
  </tbody>
</table>
<p>三層拆分的最大風險是 debugging 路徑變長 — on-call 先看 dashboard 發現異常，再到 analytics 查 sampled log 找 pattern，最後到 forensics 查原始事件確認細節。如果三層之間的 correlation ID（trace ID、request ID）沒有對齊，跨層查詢會斷掉。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">4.1 Log Schema</a>：三層共用的欄位設計（correlation ID、timestamp、service tag）是 log schema 的規模化實例。</li>
<li><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>：跨層 correlation 依賴 trace context propagation，edge → core 的 context 傳遞是挑戰。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：三層拆分就是 pipeline 的 routing 跟 processing 層設計。</li>
<li><a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 Cost Attribution</a>：三層各自的成本旋鈕（sampling rate、retention、storage tier）是成本歸因的實作入口。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>觀測平台帳單主要被全量日誌 ingestion 佔據，但 90% 的日誌沒人查過</li>
<li>Dashboard 查詢越來越慢，因為查詢打的是存了全量資料的同一個 backend</li>
<li>on-call 跟 analytics 團隊對觀測 backend 的需求衝突（一個要快、一個要全）</li>
<li>edge / CDN / 多 region 架構下，central pipeline 的 ingestion bandwidth 成為瓶頸</li>
<li>安全團隊要求保留原始事件 6 個月以上，但 hot tier 儲存成本撐不住</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://blog.cloudflare.com/vision-for-observability/">Our Vision for Observability at Cloudflare</a></li>
<li><a href="https://blog.cloudflare.com/building-cloudflare-on-cloudflare/">Building Cloudflare on Cloudflare</a></li>
</ul>
]]></content:encoded></item><item><title>4.C13 Discord：從儲存問題回推觀測缺口</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/discord-storage-growth-observability-gap/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/discord-storage-growth-observability-gap/</guid><description>&lt;p>Discord 的儲存演進案例從觀測角度回推一個教訓：儲存成長問題通常先表現為觀測缺口。不是資料庫變慢了才去看 metric，是該有的 metric 從一開始就沒設計。每一次儲存遷移（MongoDB → Cassandra → ScyllaDB）都揭露了上一階段缺少的訊號。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>Discord 處理 trillions of messages。訊息是核心 user journey — 文字、圖片、附件、thread、搜尋全部依賴訊息儲存層。從 2015 年到 2023 年，Discord 的訊息儲存經歷三代架構。&lt;/p>
&lt;p>每一代遷移都由 production 問題觸發 — 追查後發現儲存層已經撐不住，才啟動下一代架構。追查過程中反覆出現的盲區是：觀測訊號不夠早、不夠細或不夠可信。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="mongodb-階段latency-tail-不可見">MongoDB 階段：latency tail 不可見&lt;/h3>
&lt;p>早期用 MongoDB 儲存訊息。隨著使用者成長，部分大型 server（Discord 的群組概念）的訊息量遠超平均值。這些 server 的查詢 latency 偶爾飆升到秒級，但 aggregated latency metric（p50、p95）看起來正常 — 因為大型 server 的 request 數量在整體中佔比極低。&lt;/p>
&lt;p>缺少的訊號：per-server latency breakdown。aggregated metric 遮蔽了局部惡化。&lt;/p>
&lt;h3 id="cassandra-階段hot-partition-沒有早期訊號">Cassandra 階段：hot partition 沒有早期訊號&lt;/h3>
&lt;p>遷移到 Cassandra 後，partition key 設計（channel ID）讓某些高流量 channel 成為 hot partition。Cassandra 的 compaction 在 hot partition 上延遲，讀取 latency 上升。&lt;/p>
&lt;p>問題由使用者回報「訊息載入很慢」才被發現，alert 沒有提前攔截。事後回看，Cassandra 的 read latency per partition 跟 compaction pending bytes per table 這兩個 metric 都有異常，但沒有人在 dashboard 上設 alert — 因為這兩個 metric 在 Cassandra 的預設 monitoring 裡不是 first-class 告警對象。&lt;/p>
&lt;p>缺少的訊號：hot partition 識別跟 compaction health 的主動告警。&lt;/p>
&lt;h3 id="scylladb-遷移階段dual-read-沒有比對-metric">ScyllaDB 遷移階段：dual-read 沒有比對 metric&lt;/h3>
&lt;p>從 Cassandra 遷移到 ScyllaDB 的過程中，Discord 做了 dual-read（同時讀舊資料庫跟新資料庫、比對結果）。dual-read 的正確性比對有做，但 latency 跟 error rate 的比對 metric 設計不完整 — 知道結果一致，但不知道 ScyllaDB 在特定 query pattern 下是否比 Cassandra 慢。&lt;/p>
&lt;p>遷移後才發現某些 query pattern 在 ScyllaDB 上的 tail latency 比 Cassandra 高，需要額外的 schema 調整。如果 dual-read 階段就有 per-query-pattern latency comparison metric，這個問題可以在 cutover 前發現。&lt;/p>
&lt;p>缺少的訊號：migration 期間的 per-pattern latency comparison。&lt;/p>
&lt;h2 id="教訓">教訓&lt;/h2>
&lt;p>三次遷移暴露的觀測缺口有共同結構：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>缺口類型&lt;/th>
 &lt;th>MongoDB 階段&lt;/th>
 &lt;th>Cassandra 階段&lt;/th>
 &lt;th>ScyllaDB 遷移&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>維度不夠細&lt;/td>
 &lt;td>aggregated latency 遮蔽局部惡化&lt;/td>
 &lt;td>table-level metric 遮蔽 partition-level 問題&lt;/td>
 &lt;td>整體 dual-read match rate 遮蔽 per-pattern 差異&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>告警設計缺失&lt;/td>
 &lt;td>沒有 per-entity latency alert&lt;/td>
 &lt;td>沒有 hot partition alert&lt;/td>
 &lt;td>沒有 latency comparison alert&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>發現方式&lt;/td>
 &lt;td>使用者回報&lt;/td>
 &lt;td>使用者回報&lt;/td>
 &lt;td>遷移後才發現&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>共同模式：觀測訊號的粒度不夠、或告警只設在 aggregated 層 — 局部惡化被平均值淹沒，直到使用者感受到影響才被發現。&lt;/p></description><content:encoded><![CDATA[<p>Discord 的儲存演進案例從觀測角度回推一個教訓：儲存成長問題通常先表現為觀測缺口。不是資料庫變慢了才去看 metric，是該有的 metric 從一開始就沒設計。每一次儲存遷移（MongoDB → Cassandra → ScyllaDB）都揭露了上一階段缺少的訊號。</p>
<h2 id="業務背景">業務背景</h2>
<p>Discord 處理 trillions of messages。訊息是核心 user journey — 文字、圖片、附件、thread、搜尋全部依賴訊息儲存層。從 2015 年到 2023 年，Discord 的訊息儲存經歷三代架構。</p>
<p>每一代遷移都由 production 問題觸發 — 追查後發現儲存層已經撐不住，才啟動下一代架構。追查過程中反覆出現的盲區是：觀測訊號不夠早、不夠細或不夠可信。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="mongodb-階段latency-tail-不可見">MongoDB 階段：latency tail 不可見</h3>
<p>早期用 MongoDB 儲存訊息。隨著使用者成長，部分大型 server（Discord 的群組概念）的訊息量遠超平均值。這些 server 的查詢 latency 偶爾飆升到秒級，但 aggregated latency metric（p50、p95）看起來正常 — 因為大型 server 的 request 數量在整體中佔比極低。</p>
<p>缺少的訊號：per-server latency breakdown。aggregated metric 遮蔽了局部惡化。</p>
<h3 id="cassandra-階段hot-partition-沒有早期訊號">Cassandra 階段：hot partition 沒有早期訊號</h3>
<p>遷移到 Cassandra 後，partition key 設計（channel ID）讓某些高流量 channel 成為 hot partition。Cassandra 的 compaction 在 hot partition 上延遲，讀取 latency 上升。</p>
<p>問題由使用者回報「訊息載入很慢」才被發現，alert 沒有提前攔截。事後回看，Cassandra 的 read latency per partition 跟 compaction pending bytes per table 這兩個 metric 都有異常，但沒有人在 dashboard 上設 alert — 因為這兩個 metric 在 Cassandra 的預設 monitoring 裡不是 first-class 告警對象。</p>
<p>缺少的訊號：hot partition 識別跟 compaction health 的主動告警。</p>
<h3 id="scylladb-遷移階段dual-read-沒有比對-metric">ScyllaDB 遷移階段：dual-read 沒有比對 metric</h3>
<p>從 Cassandra 遷移到 ScyllaDB 的過程中，Discord 做了 dual-read（同時讀舊資料庫跟新資料庫、比對結果）。dual-read 的正確性比對有做，但 latency 跟 error rate 的比對 metric 設計不完整 — 知道結果一致，但不知道 ScyllaDB 在特定 query pattern 下是否比 Cassandra 慢。</p>
<p>遷移後才發現某些 query pattern 在 ScyllaDB 上的 tail latency 比 Cassandra 高，需要額外的 schema 調整。如果 dual-read 階段就有 per-query-pattern latency comparison metric，這個問題可以在 cutover 前發現。</p>
<p>缺少的訊號：migration 期間的 per-pattern latency comparison。</p>
<h2 id="教訓">教訓</h2>
<p>三次遷移暴露的觀測缺口有共同結構：</p>
<table>
  <thead>
      <tr>
          <th>缺口類型</th>
          <th>MongoDB 階段</th>
          <th>Cassandra 階段</th>
          <th>ScyllaDB 遷移</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>維度不夠細</td>
          <td>aggregated latency 遮蔽局部惡化</td>
          <td>table-level metric 遮蔽 partition-level 問題</td>
          <td>整體 dual-read match rate 遮蔽 per-pattern 差異</td>
      </tr>
      <tr>
          <td>告警設計缺失</td>
          <td>沒有 per-entity latency alert</td>
          <td>沒有 hot partition alert</td>
          <td>沒有 latency comparison alert</td>
      </tr>
      <tr>
          <td>發現方式</td>
          <td>使用者回報</td>
          <td>使用者回報</td>
          <td>遷移後才發現</td>
      </tr>
  </tbody>
</table>
<p>共同模式：觀測訊號的粒度不夠、或告警只設在 aggregated 層 — 局部惡化被平均值淹沒，直到使用者感受到影響才被發現。</p>
<p>三個缺口的修正方向也一致：</p>
<ol>
<li>把 entity-level metric（per-server、per-partition、per-query-pattern）從 debug-only 提升為 first-class 觀測訊號</li>
<li>在 aggregated alert 之外加 percentile 跟 tail latency alert（p99.9 而非只看 p95）</li>
<li>Migration 期間把 latency comparison 做成 per-pattern 的 real-time dashboard，不只看 overall match rate</li>
</ol>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<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>：aggregated metric 遮蔽局部惡化是 data quality 問題 — 訊號存在但粒度不足以判讀。</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 Observability Operating Model</a>：觀測缺口反覆出現代表 operating model 缺少「新服務上線 / 遷移時強制檢查觀測覆蓋」的 gate。</li>
<li><a href="/blog/backend/04-observability/debuggability-by-design/" data-link-title="4.19 Debuggability by Design" data-link-desc="把可診斷性前移到 API、async workflow、dependency call 與錯誤模型設計">4.19 Debuggability by Design</a>：per-entity latency breakdown 跟 migration comparison metric 應該在系統設計時就規劃，不是事故後補。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>使用者回報問題但 dashboard 看起來正常 — aggregated metric 可能遮蔽局部惡化</li>
<li>資料庫或儲存層偶爾變慢但找不到原因 — 可能缺少 per-entity 或 per-partition metric</li>
<li>Migration 做了 dual-read 但只比對正確性、沒比對 latency — 遷移後才發現效能回歸</li>
<li>告警設計只有 error rate 跟 aggregated latency — 缺少 tail latency 跟 entity-level alert</li>
</ul>
<h2 id="引用源">引用源</h2>
<ul>
<li><a href="https://discord.com/blog/how-discord-stores-billions-of-messages">How Discord Stores Billions of Messages</a>（MongoDB → Cassandra 階段）</li>
<li><a href="https://discord.com/blog/how-discord-stores-trillions-of-messages">How Discord Stores Trillions of Messages</a>（Cassandra → ScyllaDB 階段）</li>
</ul>
]]></content:encoded></item><item><title>4.C14 觀測平台成本治理：從帳單驚嚇到可預測成本</title><link>https://tarrragon.github.io/blog/backend/04-observability/cases/observability-cost-governance-at-scale/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cases/observability-cost-governance-at-scale/</guid><description>&lt;p>觀測成本治理案例來自多家企業的共同經驗：觀測平台帳單每季成長 30%，管理層問「為什麼監控這麼貴」但沒人能歸因。問題的核心不是「花太多」而是「花在哪不知道」— 沒有 per-team cost attribution 的觀測平台，成本優化只能靠全域砍 retention 或降 sampling，兩者都會傷害觀測品質。&lt;/p>
&lt;h2 id="業務背景">業務背景&lt;/h2>
&lt;p>這個案例綜合三個組織的經驗模式：&lt;/p>
&lt;p>一家中型 SaaS 公司用 Datadog 做全端觀測（APM + logs + metrics + RUM）。月帳單從 $15K 成長到 $60K，兩年內四倍。CFO 問 CTO「這筆錢買到什麼」，CTO 轉問 platform team，platform team 說不出哪些團隊佔多少。&lt;/p>
&lt;p>一家金融科技公司自建 Grafana Stack（Prometheus + Loki + Tempo + Mimir）。自建沒有 SaaS 帳單，但 Kubernetes 節點跟 storage 的成本持續增加。infra team 知道 Mimir 的 storage 在成長，但不知道是哪些 metric label 造成的 cardinality 爆炸。&lt;/p>
&lt;p>一家遊戲公司用 CloudWatch 做 AWS 原生觀測。Logs 的 ingestion 費用佔帳單 70%，但追查後發現 90% 是 debug-level log，只在排錯時用到，平常沒人查。&lt;/p>
&lt;h2 id="技術挑戰">技術挑戰&lt;/h2>
&lt;h3 id="沒有-cost-attribution">沒有 cost attribution&lt;/h3>
&lt;p>觀測帳單通常是 organization-level 的一筆支出。SaaS 帳單按 hosts、custom metrics、log volume、APM spans 計費；自建平台按 compute 跟 storage 計費。兩種模式都缺少「這些費用是哪個 team / service 造成的」的歸因。&lt;/p>
&lt;p>沒有 attribution 的後果是所有優化都是全域操作 — 砍 retention 從 30 天到 7 天影響所有人，降 sampling 從 100% 到 10% 影響所有服務。需要觀測資料的團隊被平均到成本節省裡，不需要的團隊搭便車。&lt;/p>
&lt;h3 id="cardinality-爆炸">Cardinality 爆炸&lt;/h3>
&lt;p>Metrics 成本的主要 driver 是 cardinality — unique label combination 的數量。常見的 cardinality 爆炸來源：&lt;/p>
&lt;ul>
&lt;li>把 user ID 或 request ID 放進 metric label（每個 unique user 產生一組 series）&lt;/li>
&lt;li>動態的 endpoint path（&lt;code>/api/users/123&lt;/code> 每個 user ID 是一個 label value）&lt;/li>
&lt;li>多租戶 label 過細（tenant × region × service × endpoint 的笛卡兒積）&lt;/li>
&lt;/ul>
&lt;p>一個失控的 label 可以讓 series 數量從 10 萬跳到 1000 萬。SaaS 的計費是 per custom metric，自建的代價是 Prometheus / Mimir 的 memory 跟 storage。&lt;/p>
&lt;h3 id="log-volume-失控">Log volume 失控&lt;/h3>
&lt;p>Debug-level log 在開發階段有用，但 production 環境裡通常只在排錯時被查。全量 debug log 送進 hot tier（Elasticsearch、Loki、CloudWatch Logs）的 ingestion 跟 storage 成本是最大的 log 成本來源。&lt;/p>
&lt;p>問題是沒人敢降 debug log — 「萬一出事需要 debug log 怎麼辦」。恐懼驅動的 log level 設定讓 log volume 只升不降。&lt;/p></description><content:encoded><![CDATA[<p>觀測成本治理案例來自多家企業的共同經驗：觀測平台帳單每季成長 30%，管理層問「為什麼監控這麼貴」但沒人能歸因。問題的核心不是「花太多」而是「花在哪不知道」— 沒有 per-team cost attribution 的觀測平台，成本優化只能靠全域砍 retention 或降 sampling，兩者都會傷害觀測品質。</p>
<h2 id="業務背景">業務背景</h2>
<p>這個案例綜合三個組織的經驗模式：</p>
<p>一家中型 SaaS 公司用 Datadog 做全端觀測（APM + logs + metrics + RUM）。月帳單從 $15K 成長到 $60K，兩年內四倍。CFO 問 CTO「這筆錢買到什麼」，CTO 轉問 platform team，platform team 說不出哪些團隊佔多少。</p>
<p>一家金融科技公司自建 Grafana Stack（Prometheus + Loki + Tempo + Mimir）。自建沒有 SaaS 帳單，但 Kubernetes 節點跟 storage 的成本持續增加。infra team 知道 Mimir 的 storage 在成長，但不知道是哪些 metric label 造成的 cardinality 爆炸。</p>
<p>一家遊戲公司用 CloudWatch 做 AWS 原生觀測。Logs 的 ingestion 費用佔帳單 70%，但追查後發現 90% 是 debug-level log，只在排錯時用到，平常沒人查。</p>
<h2 id="技術挑戰">技術挑戰</h2>
<h3 id="沒有-cost-attribution">沒有 cost attribution</h3>
<p>觀測帳單通常是 organization-level 的一筆支出。SaaS 帳單按 hosts、custom metrics、log volume、APM spans 計費；自建平台按 compute 跟 storage 計費。兩種模式都缺少「這些費用是哪個 team / service 造成的」的歸因。</p>
<p>沒有 attribution 的後果是所有優化都是全域操作 — 砍 retention 從 30 天到 7 天影響所有人，降 sampling 從 100% 到 10% 影響所有服務。需要觀測資料的團隊被平均到成本節省裡，不需要的團隊搭便車。</p>
<h3 id="cardinality-爆炸">Cardinality 爆炸</h3>
<p>Metrics 成本的主要 driver 是 cardinality — unique label combination 的數量。常見的 cardinality 爆炸來源：</p>
<ul>
<li>把 user ID 或 request ID 放進 metric label（每個 unique user 產生一組 series）</li>
<li>動態的 endpoint path（<code>/api/users/123</code> 每個 user ID 是一個 label value）</li>
<li>多租戶 label 過細（tenant × region × service × endpoint 的笛卡兒積）</li>
</ul>
<p>一個失控的 label 可以讓 series 數量從 10 萬跳到 1000 萬。SaaS 的計費是 per custom metric，自建的代價是 Prometheus / Mimir 的 memory 跟 storage。</p>
<h3 id="log-volume-失控">Log volume 失控</h3>
<p>Debug-level log 在開發階段有用，但 production 環境裡通常只在排錯時被查。全量 debug log 送進 hot tier（Elasticsearch、Loki、CloudWatch Logs）的 ingestion 跟 storage 成本是最大的 log 成本來源。</p>
<p>問題是沒人敢降 debug log — 「萬一出事需要 debug log 怎麼辦」。恐懼驅動的 log level 設定讓 log volume 只升不降。</p>
<h3 id="trace-sampling-恐懼">Trace sampling 恐懼</h3>
<p>類似的恐懼存在於 trace sampling — 「如果剛好那筆有問題的 request 被 sample 掉怎麼辦」。100% tracing 的成本在中等規模（每秒數萬 request）就開始顯著。</p>
<h2 id="解法">解法</h2>
<h3 id="cost-attribution-by-team--service">Cost attribution by team / service</h3>
<p>第一步是讓成本可見，歸因先於優化。</p>
<p>SaaS 平台：用 Datadog 的 usage attribution 或 Grafana Cloud 的 usage reporting 把 ingestion 按 service tag / team tag 拆分。每個 team 看到自己的 metric series、log volume 跟 span 數量。</p>
<p>自建平台：在 Mimir / Loki 的 tenant 維度或 Prometheus 的 namespace 維度拆分 storage 跟 query cost。用 <a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 Cost Attribution</a> 的框架把 infra cost 按 service ownership 分配。</p>
<p>Attribution 本身就能驅動行為改變 — 當團隊看到自己佔了 40% 的 log volume、而且 95% 是 debug level 時，他們會主動調 log level。</p>
<h3 id="cardinality-budget-per-team">Cardinality budget per team</h3>
<p>Attribution 之後，為每個 team / service 設定 cardinality budget（active series 上限）。超出 budget 的 series 進入 review 流程 — team 決定哪些 label 可以 aggregate 或移除，而非由 platform 單方面 drop。</p>
<p>Budget 的設定依據是 baseline measurement + growth rate，不是拍腦袋。先觀察 3 個月的 cardinality 趨勢，把 budget 設在 baseline 的 1.5 倍，每季 review。</p>
<h3 id="log-tiering">Log tiering</h3>
<p>把 log 從「全部進 hot tier」改成分層：</p>
<table>
  <thead>
      <tr>
          <th>Log level</th>
          <th>目的地</th>
          <th>Retention</th>
          <th>查詢延遲</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Error / Warn</td>
          <td>Hot tier（Loki / Elasticsearch）</td>
          <td>30 天</td>
          <td>即時</td>
      </tr>
      <tr>
          <td>Info</td>
          <td>Warm tier（壓縮 + 延遲查詢）</td>
          <td>14 天</td>
          <td>秒到分鐘</td>
      </tr>
      <tr>
          <td>Debug</td>
          <td>Cold archive（object storage）</td>
          <td>7 天</td>
          <td>分鐘到小時</td>
      </tr>
  </tbody>
</table>
<p>Debug log 仍然保留，但不進昂貴的 hot tier。需要排錯時從 cold archive 拉回 — 多等幾分鐘的代價遠低於全量 hot tier 的持續成本。</p>
<h3 id="adaptive-sampling">Adaptive sampling</h3>
<p>Trace sampling 從 uniform 改成 adaptive：</p>
<ul>
<li>錯誤 request 100% 保留</li>
<li>高 latency request（&gt; p99）100% 保留</li>
<li>正常 request 依 traffic volume adaptive sampling（高流量 endpoint 低 sample rate、低流量 endpoint 高 sample rate）</li>
</ul>
<p>Adaptive sampling 保留了排錯最需要的 trace（error 跟 outlier），砍的是正常 request 的重複 trace。</p>
<h2 id="取捨">取捨</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>不治理</th>
          <th>治理後</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>成本趨勢</td>
          <td>隨 traffic 超線性成長</td>
          <td>跟 traffic 線性成長或低於線性</td>
      </tr>
      <tr>
          <td>觀測覆蓋</td>
          <td>全量（但可能是低品質的全量）</td>
          <td>分層（high-value 資料保留全量、low-value 降級）</td>
      </tr>
      <tr>
          <td>Debug 體驗</td>
          <td>所有資料都在 hot tier、查得快</td>
          <td>部分資料要從 cold archive 拉、多等幾分鐘</td>
      </tr>
      <tr>
          <td>團隊自主性</td>
          <td>無限制（cardinality 跟 log level 隨意）</td>
          <td>有 budget 跟 policy 約束</td>
      </tr>
      <tr>
          <td>治理人力</td>
          <td>零（直到帳單爆炸才開始）</td>
          <td>需要 platform team 持續維護 attribution + budget + policy</td>
      </tr>
  </tbody>
</table>
<p>治理的最大風險是「砍過頭」— 在事故期間發現 debug log 被移到 cold archive 查不到、或 trace 被 sample 掉找不到問題 request。Adaptive sampling 跟 error retention 100% 是安全網，但安全網的設計本身需要定期 review（例如 error 的定義是否涵蓋了所有異常模式）。</p>
<h2 id="回寫教材的連結">回寫教材的連結</h2>
<ul>
<li><a href="/blog/backend/04-observability/cost-attribution/" data-link-title="4.15 Cost Attribution / Chargeback" data-link-desc="把 observability 成本拆到團隊、產品、環境維度">4.15 Cost Attribution</a>：per-team cost visibility 是治理的起點。</li>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Cardinality 治理</a>：cardinality budget 跟 label review 的操作流程。</li>
<li><a href="/blog/backend/04-observability/telemetry-pipeline/" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 Telemetry Pipeline</a>：log tiering 跟 adaptive sampling 是 pipeline 的 routing 跟 processing 層配置。</li>
</ul>
<h2 id="判讀徵兆">判讀徵兆</h2>
<p>讀者在自己的系統看到以下訊號時，應該回讀本案例：</p>
<ul>
<li>觀測帳單每季成長 &gt; 20%，但服務的 request volume 成長遠小於此 — cardinality 或 log volume 可能在失控成長</li>
<li>管理層問「監控花多少錢、誰在用」但沒人能回答</li>
<li>曾經做過「全域降 retention」或「全域降 sampling」的成本優化，但幾個月後成本回升</li>
<li>Platform team 花大量時間處理「Prometheus OOM」或「Elasticsearch disk full」而非改善觀測品質</li>
<li>團隊的 debug log level 在 production 預設開著，理由是「不知道什麼時候需要」</li>
</ul>
]]></content:encoded></item></channel></rss>