<?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/</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>Wed, 22 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/backend/04-observability/index.xml" rel="self" type="application/rss+xml"/><item><title>4.1 log schema 與搜尋規劃</title><link>https://tarrragon.github.io/blog/backend/04-observability/log-schema/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/log-schema/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>structured &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log-schema/" data-link-title="Log Schema" data-link-desc="說明結構化 log 欄位如何支援搜尋、關聯與事故排查">log schema&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/correlation-id/" data-link-title="Correlation ID" data-link-desc="說明跨事件或跨服務的關聯識別碼如何支援排障">correlation id&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id&lt;/a> fields&lt;/li>
&lt;li>index 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention&lt;/a>&lt;/li>
&lt;li>query pattern&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log-schema/" data-link-title="Log Schema" data-link-desc="說明結構化 log 欄位如何支援搜尋、關聯與事故排查">log schema&lt;/a> 是把事件紀錄從文字輸出變成可查詢資料的契約，責任是讓不同服務在事故時能用同一組欄位還原脈絡。&lt;/p>
&lt;p>這一頁處理的是欄位與搜尋路徑。log 的價值在於事故時能用穩定欄位找到同一個 request、同一個 tenant、同一個 dependency call 與同一段錯誤鏈，寫得多本身沒有幫助。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 log schema 時，先看 correlation fields 是否穩定，再看 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/search-index/" data-link-title="Search Index" data-link-desc="說明搜尋索引如何承擔全文檢索、排序與查詢體驗">search index&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention&lt;/a> 是否對齊查詢需求。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tenant-boundary/" data-link-title="Tenant Boundary" data-link-desc="說明多租戶系統如何隔離不同客戶或組織的資料與資源">tenant boundary&lt;/a> 與 service name 是否跨服務一致&lt;/li>
&lt;li>high-cardinality 欄位是否被放進可控索引，並受查詢價值與成本預算約束&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention&lt;/a> 是否依 operational debug、&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">audit&lt;/a>、compliance 分層&lt;/li>
&lt;li>query pattern 是否能支援 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 還原&lt;/li>
&lt;/ul>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;ul>
&lt;li>log 欄位 schema 漂移、跨服務 correlation id 對不上&lt;/li>
&lt;li>事故時靠 grep 拼湊事件、無結構化查詢入口&lt;/li>
&lt;li>log 索引爆量、查詢退化但無清理流程&lt;/li>
&lt;li>log 含大量 free-form text、無一致關鍵欄位&lt;/li>
&lt;li>retention 策略全平、舊事件查不到 / 不該留的還在留&lt;/li>
&lt;/ul>
&lt;h2 id="查詢模式設計">查詢模式設計&lt;/h2>
&lt;p>Log 的寫入格式跟讀取需求是兩個不同的設計問題。寫入追求 schema 穩定與吞吐效率；讀取要在不同時間壓力下，用不同的查詢形狀取回不同精度的資料。同一份 structured log 至少被三種查詢模式讀取，每種模式對索引、延遲與結果形狀的要求不同。&lt;/p>
&lt;h3 id="即席診斷查詢">即席診斷查詢&lt;/h3>
&lt;p>事故中的查詢要在秒級內定位問題。典型操作是拿到一個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id&lt;/a> 或 error code，加上 time window，撈出相關事件鏈。&lt;/p>
&lt;p>即席查詢的索引策略是把高頻過濾欄位放進結構化索引：service name、log level、error code、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tenant-boundary/" data-link-title="Tenant Boundary" data-link-desc="說明多租戶系統如何隔離不同客戶或組織的資料與資源">tenant boundary&lt;/a>。這些欄位的共同特徵是有界或半有界（error code 有限、request id 雖然無界但查詢時一定帶精確值），查詢時用等值匹配或短範圍掃描。&lt;/p>
&lt;p>即席查詢的反模式是對 free-text 欄位做全文搜尋當作主要診斷入口。全文搜尋適合探索性調查（「最近有沒有出現某個未預期的 exception message」），但事故中的時間壓力下，結構化欄位的精確查詢比全文搜尋快一到兩個數量級。&lt;/p>
&lt;h3 id="聚合趨勢查詢">聚合趨勢查詢&lt;/h3>
&lt;p>Dashboard 跟告警的查詢是定期的聚合計算：過去 5 分鐘的 error count by service、過去 1 小時的 log volume by level、某個 tenant 的 warning 趨勢。這類查詢不需要看單筆 log 的內容，而是需要 count / rate / group by 的聚合結果。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>structured <a href="/blog/backend/knowledge-cards/log-schema/" data-link-title="Log Schema" data-link-desc="說明結構化 log 欄位如何支援搜尋、關聯與事故排查">log schema</a></li>
<li><a href="/blog/backend/knowledge-cards/correlation-id/" data-link-title="Correlation ID" data-link-desc="說明跨事件或跨服務的關聯識別碼如何支援排障">correlation id</a> / <a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id</a> fields</li>
<li>index 與 <a href="/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention</a></li>
<li>query pattern</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/log-schema/" data-link-title="Log Schema" data-link-desc="說明結構化 log 欄位如何支援搜尋、關聯與事故排查">log schema</a> 是把事件紀錄從文字輸出變成可查詢資料的契約，責任是讓不同服務在事故時能用同一組欄位還原脈絡。</p>
<p>這一頁處理的是欄位與搜尋路徑。log 的價值在於事故時能用穩定欄位找到同一個 request、同一個 tenant、同一個 dependency call 與同一段錯誤鏈，寫得多本身沒有幫助。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 log schema 時，先看 correlation fields 是否穩定，再看 <a href="/blog/backend/knowledge-cards/search-index/" data-link-title="Search Index" data-link-desc="說明搜尋索引如何承擔全文檢索、排序與查詢體驗">search index</a> 與 <a href="/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention</a> 是否對齊查詢需求。</p>
<p>重點訊號包括：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id</a>、<a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id</a>、<a href="/blog/backend/knowledge-cards/tenant-boundary/" data-link-title="Tenant Boundary" data-link-desc="說明多租戶系統如何隔離不同客戶或組織的資料與資源">tenant boundary</a> 與 service name 是否跨服務一致</li>
<li>high-cardinality 欄位是否被放進可控索引，並受查詢價值與成本預算約束</li>
<li><a href="/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention</a> 是否依 operational debug、<a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">audit</a>、compliance 分層</li>
<li>query pattern 是否能支援 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 還原</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>log 欄位 schema 漂移、跨服務 correlation id 對不上</li>
<li>事故時靠 grep 拼湊事件、無結構化查詢入口</li>
<li>log 索引爆量、查詢退化但無清理流程</li>
<li>log 含大量 free-form text、無一致關鍵欄位</li>
<li>retention 策略全平、舊事件查不到 / 不該留的還在留</li>
</ul>
<h2 id="查詢模式設計">查詢模式設計</h2>
<p>Log 的寫入格式跟讀取需求是兩個不同的設計問題。寫入追求 schema 穩定與吞吐效率；讀取要在不同時間壓力下，用不同的查詢形狀取回不同精度的資料。同一份 structured log 至少被三種查詢模式讀取，每種模式對索引、延遲與結果形狀的要求不同。</p>
<h3 id="即席診斷查詢">即席診斷查詢</h3>
<p>事故中的查詢要在秒級內定位問題。典型操作是拿到一個 <a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id</a> 或 error code，加上 time window，撈出相關事件鏈。</p>
<p>即席查詢的索引策略是把高頻過濾欄位放進結構化索引：service name、log level、error code、<a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id</a>、<a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id</a>、<a href="/blog/backend/knowledge-cards/tenant-boundary/" data-link-title="Tenant Boundary" data-link-desc="說明多租戶系統如何隔離不同客戶或組織的資料與資源">tenant boundary</a>。這些欄位的共同特徵是有界或半有界（error code 有限、request id 雖然無界但查詢時一定帶精確值），查詢時用等值匹配或短範圍掃描。</p>
<p>即席查詢的反模式是對 free-text 欄位做全文搜尋當作主要診斷入口。全文搜尋適合探索性調查（「最近有沒有出現某個未預期的 exception message」），但事故中的時間壓力下，結構化欄位的精確查詢比全文搜尋快一到兩個數量級。</p>
<h3 id="聚合趨勢查詢">聚合趨勢查詢</h3>
<p>Dashboard 跟告警的查詢是定期的聚合計算：過去 5 分鐘的 error count by service、過去 1 小時的 log volume by level、某個 tenant 的 warning 趨勢。這類查詢不需要看單筆 log 的內容，而是需要 count / rate / group by 的聚合結果。</p>
<p>聚合查詢的負載特性跟即席查詢不同。即席查詢讀少量資料、要求低延遲；聚合查詢掃大量資料、容忍較高延遲但執行頻率高（dashboard 每 30 秒刷新一次 = 每分鐘 2 次相同的重聚合）。當 log volume 成長，重複計算聚合的成本會推高 query engine 負擔。</p>
<p>應對策略有兩種。一是在 log pipeline 把常用聚合轉成 <a href="/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics</a> — collector 端做 log-to-metric 轉換（例：把 <code>level=error</code> 的 log 計數轉成 error_log_total counter），dashboard 讀 metric 而非重掃 log。二是在查詢層設定 <a href="/blog/backend/knowledge-cards/materialized-view/" data-link-title="Materialized View" data-link-desc="說明預先計算並儲存查詢結果以加速讀取的資料結構">materialized view</a> 或快取，讓重複查詢直接取用預計算結果。</p>
<h3 id="鑑識回溯查詢">鑑識回溯查詢</h3>
<p>事後分析與合規稽核的查詢範圍大（跨天、跨週甚至跨月）、對完整性要求高、但延遲容忍也高（分鐘級回應可接受）。鑑識查詢常見的形狀是「某個 tenant 在過去 30 天內所有 authentication failure」或「某個 API 的 error 分布演變」。</p>
<p>鑑識查詢的儲存設計跟 <a href="/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">storage tiering</a> 直接相關。Hot tier 保留最近數天的 full-index log，warm tier 保留數週的部分索引或壓縮 log，cold tier 保留數月到數年的歸檔 log。鑑識查詢命中 cold tier 時，系統可能需要 rehydrate（把歸檔資料暫時載回可查詢狀態），這個操作本身需要時間和臨時儲存空間。</p>
<p>鑑識場景的關鍵設計決策是「哪些欄位在 cold tier 仍可查詢」。全部欄位都保留索引成本太高；只保留 timestamp + service name + tenant 的最小索引，能支援基本的範圍掃描，細節再用 rehydrate 後的全文搜尋補。</p>
<h3 id="三種模式的資源隔離">三種模式的資源隔離</h3>
<p>三種查詢模式搶同一個 query engine 時，聚合查詢的持續負載會擠壓即席查詢的回應速度。事故中團隊最需要即席查詢的低延遲，但此時 dashboard 也在高頻刷新聚合查詢，兩者競爭 query 資源。</p>
<p>可操作的隔離方式是讓即席查詢跟聚合查詢走不同的 query priority 或 query queue。Elasticsearch 的 search thread pool、Loki 的 query-frontend queue、Datadog 的 query quota 都提供某種程度的查詢隔離。設計時要把即席查詢的延遲 SLA 當作硬性約束，聚合查詢的延遲可以被彈性排程。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.7 <a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">metric cardinality</a> / cost：label 預算與保留階梯</li>
<li>04.8 訊號治理閉環：log-based alert 的生命週期</li>
<li>04.12 <a href="/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">audit log</a>：稽核訊號跟 operational log 的邊界</li>
<li>04.23 <a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">觀測查詢設計</a>：跨訊號類型的讀取路徑系統設計</li>
</ul>
]]></content:encoded></item><item><title>4.2 metrics 與 SLI/SLO</title><link>https://tarrragon.github.io/blog/backend/04-observability/metrics-basics/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/metrics-basics/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics&lt;/a> 基本型別&lt;/li>
&lt;li>latency &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram&lt;/a>&lt;/li>
&lt;li>error rate / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/throughput/" data-link-title="Throughput" data-link-desc="整理系統單位時間內可處理的工作量">throughput&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI / SLO&lt;/a> / &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;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics&lt;/a> 是把服務狀態壓縮成可聚合、可比較、可告警的時間序列，責任是讓團隊看見趨勢、容量與服務健康。&lt;/p>
&lt;p>這一頁處理的是 metric 型別與計算語意。counter、gauge 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram&lt;/a> 各自回答不同問題；選錯型別會讓後面的 SLI、dashboard 與 alert 都建立在錯誤訊號上。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 metrics 時，先看指標型別是否對應問題，再看分母、bucket 與 label 是否穩定。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>latency 是否用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/percentile/" data-link-title="Percentile" data-link-desc="說明 p95 與 p99 如何描述長尾延遲與使用者體驗">percentile&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram&lt;/a> 補足 average 的盲點&lt;/li>
&lt;li>error rate 的分母是否能代表真實請求量&lt;/li>
&lt;li>bucket 是否覆蓋實際尾端延遲&lt;/li>
&lt;li>label 是否能切出必要維度，同時不讓 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">metric cardinality&lt;/a> 失控&lt;/li>
&lt;/ul>
&lt;h2 id="判讀訊號">判讀訊號&lt;/h2>
&lt;ul>
&lt;li>用 average 而非 percentile 追 latency、p99 失真&lt;/li>
&lt;li>counter / gauge 混用、計算公式錯&lt;/li>
&lt;li>histogram bucket 沒對齊實際分佈、tail latency 被截斷&lt;/li>
&lt;li>error rate 分母不穩（流量低時誤觸發、高時稀釋）&lt;/li>
&lt;li>商業 SLI 跟 metric 對不上、靠人解釋&lt;/li>
&lt;/ul>
&lt;h2 id="聚合查詢與-recording-rule">聚合查詢與 recording rule&lt;/h2>
&lt;p>Metrics 的讀取面跟寫入面是兩個不同的效能瓶頸。寫入面的壓力來自 series 數量（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">cardinality&lt;/a>）；讀取面的壓力來自查詢時的聚合計算量。兩者可以獨立失控 — series 數量合理但每次 dashboard 刷新都重算複雜表達式，query engine 一樣會過載。&lt;/p>
&lt;h3 id="query-time-aggregation-的成本">Query-time aggregation 的成本&lt;/h3>
&lt;p>Dashboard panel 或 alert rule 每次觸發時，TSDB 對 raw series 執行聚合表達式（rate、sum、histogram_quantile）。當 raw series 數量大、查詢時間範圍長、dashboard 刷新頻率高，同一個計算會被反覆執行。&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> panel 可能涉及：先算 rate、再除以 total、再跟 threshold 比較、最後乘以 window。每次刷新把整條運算鏈走一遍。當這類 panel 有十幾個、每 30 秒刷新一次，query engine 的 CPU 會被 dashboard 佔滿，留給事故即席查詢的餘量不夠。&lt;/p>
&lt;h3 id="recording-rule-把計算推到寫入時">Recording rule 把計算推到寫入時&lt;/h3>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">Recording rule&lt;/a> 是 Prometheus 生態（包括 Thanos、Mimir、VictoriaMetrics）的標準應對方式：在 TSDB 內定期執行聚合表達式，把結果寫成新的 time series。Dashboard 跟 alert rule 讀 recording rule 的輸出而非重算 raw series。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics</a> 基本型別</li>
<li>latency <a href="/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram</a></li>
<li>error rate / <a href="/blog/backend/knowledge-cards/throughput/" data-link-title="Throughput" data-link-desc="整理系統單位時間內可處理的工作量">throughput</a></li>
<li><a href="/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI / SLO</a> / <a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a></li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics</a> 是把服務狀態壓縮成可聚合、可比較、可告警的時間序列，責任是讓團隊看見趨勢、容量與服務健康。</p>
<p>這一頁處理的是 metric 型別與計算語意。counter、gauge 與 <a href="/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram</a> 各自回答不同問題；選錯型別會讓後面的 SLI、dashboard 與 alert 都建立在錯誤訊號上。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 metrics 時，先看指標型別是否對應問題，再看分母、bucket 與 label 是否穩定。</p>
<p>重點訊號包括：</p>
<ul>
<li>latency 是否用 <a href="/blog/backend/knowledge-cards/percentile/" data-link-title="Percentile" data-link-desc="說明 p95 與 p99 如何描述長尾延遲與使用者體驗">percentile</a> / <a href="/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram</a> 補足 average 的盲點</li>
<li>error rate 的分母是否能代表真實請求量</li>
<li>bucket 是否覆蓋實際尾端延遲</li>
<li>label 是否能切出必要維度，同時不讓 <a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">metric cardinality</a> 失控</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>用 average 而非 percentile 追 latency、p99 失真</li>
<li>counter / gauge 混用、計算公式錯</li>
<li>histogram bucket 沒對齊實際分佈、tail latency 被截斷</li>
<li>error rate 分母不穩（流量低時誤觸發、高時稀釋）</li>
<li>商業 SLI 跟 metric 對不上、靠人解釋</li>
</ul>
<h2 id="聚合查詢與-recording-rule">聚合查詢與 recording rule</h2>
<p>Metrics 的讀取面跟寫入面是兩個不同的效能瓶頸。寫入面的壓力來自 series 數量（<a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">cardinality</a>）；讀取面的壓力來自查詢時的聚合計算量。兩者可以獨立失控 — series 數量合理但每次 dashboard 刷新都重算複雜表達式，query engine 一樣會過載。</p>
<h3 id="query-time-aggregation-的成本">Query-time aggregation 的成本</h3>
<p>Dashboard panel 或 alert rule 每次觸發時，TSDB 對 raw series 執行聚合表達式（rate、sum、histogram_quantile）。當 raw series 數量大、查詢時間範圍長、dashboard 刷新頻率高，同一個計算會被反覆執行。</p>
<p>一個典型的 SLO <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> panel 可能涉及：先算 rate、再除以 total、再跟 threshold 比較、最後乘以 window。每次刷新把整條運算鏈走一遍。當這類 panel 有十幾個、每 30 秒刷新一次，query engine 的 CPU 會被 dashboard 佔滿，留給事故即席查詢的餘量不夠。</p>
<h3 id="recording-rule-把計算推到寫入時">Recording rule 把計算推到寫入時</h3>
<p><a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">Recording rule</a> 是 Prometheus 生態（包括 Thanos、Mimir、VictoriaMetrics）的標準應對方式：在 TSDB 內定期執行聚合表達式，把結果寫成新的 time series。Dashboard 跟 alert rule 讀 recording rule 的輸出而非重算 raw series。</p>
<p>Recording rule 的設計判準是查詢頻率跟計算成本的乘積。高頻讀取（dashboard auto-refresh、每分鐘 evaluate 的 alert rule）加上高計算成本（多維度 rate + ratio + quantile）的組合最值得做 recording rule。低頻即席查詢（事故時的 ad-hoc 切片）直接查 raw series，保留完整維度。</p>
<p>Recording rule 的命名慣例用 <code>level:metric:operations</code> 格式（如 <code>job:http_requests_total:rate5m</code>），讓讀者從名稱直接判斷來源粒度跟計算方式。沒有命名慣例時，recording rule 增長到數百條後會難以維護跟除錯。</p>
<h3 id="rollup-與-downsampling">Rollup 與 downsampling</h3>
<p><a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">Rollup</a> 解決的是時間維度的讀取成本。原始資料以 15 秒間隔採集，查詢「過去 90 天的 error rate 趨勢」時需要掃描數百萬個資料點；rollup 把舊資料聚合成 5 分鐘或 1 小時粒度，查詢時只讀取聚合後的少量資料點。</p>
<p>Rollup 的聚合函數選擇影響查詢語意。Counter 用 sum 合理、gauge 用 average 合理、histogram 用 average 會失去分布資訊（p99 被壓平）。設計 rollup 時要按 metric type 指定對應的聚合函數，混用會讓長時間範圍的 dashboard 產生誤導性數值。</p>
<p>查詢路由的透明度也是設計重點。使用者把 dashboard 時間範圍從 1 小時拉到 7 天時，系統自動從 raw series 切到 rollup series，精度從 15 秒變成 5 分鐘。如果這個切換對使用者不透明，事故中觀察到的數值變化可能是精度切換的假象而非真實服務變化。</p>
<h3 id="metrics-讀取面的資源隔離">Metrics 讀取面的資源隔離</h3>
<p>Metrics 的 query engine 跟 log 一樣面臨多種查詢模式競爭資源的問題。Dashboard 定期刷新是穩定的背景負載；alert rule evaluation 是系統關鍵的定期負載；事故即席查詢是偶發的突增負載。三者搶同一個 query engine 時，dashboard 跟 alert 的穩定負載會壓縮即席查詢的可用資源。</p>
<p>Prometheus 原生的資源隔離有限，但 Thanos Query Frontend、Mimir Query Frontend、Grafana Cloud 的 query scheduler 都支援 query priority 或 query queue 分離。設計時把 alert evaluation 設為最高優先（告警不能因 query 排隊而延遲），dashboard 次之，即席查詢的延遲容忍最高但不能被完全餓死。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.6 SLI/SLO 訊號設計：把 metric 升級為 user-journey SLI</li>
<li>04.7 <a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">metric cardinality</a> / cost：label 治理與成本邊界</li>
<li>04.9 continuous profiling：metrics 之外的第四角觀測訊號</li>
<li>04.23 <a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">觀測查詢設計</a>：跨訊號類型的讀取路徑系統設計</li>
<li><a href="/blog/backend/04-observability/cases/uber-m3-metrics-platform-scale/" data-link-title="4.C11 Uber：M3 大規模 Metrics 平台" data-link-desc="從散落的 Prometheus 實例到統一 metrics 平台，處理 cardinality 爆炸、長期 retention 與跨叢集查詢的規模化挑戰。">4.C11 Uber M3</a>：單機 Prometheus 到平台級 metrics 系統的演進</li>
</ul>
]]></content:encoded></item><item><title>4.3 tracing 與 context link</title><link>https://tarrragon.github.io/blog/backend/04-observability/tracing-context/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/tracing-context/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace&lt;/a> / &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/span/" data-link-title="Span" data-link-desc="說明 trace 中一段工作如何記錄耗時、狀態與關聯">span&lt;/a> 模型&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context&lt;/a> propagation&lt;/li>
&lt;li>context 斷鏈的常見邊界與修復&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">sampling&lt;/a> 策略的 tracing 面（SSoT 在 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/#sampling-%e7%ad%96%e7%95%a5" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7&lt;/a>）&lt;/li>
&lt;li>service graph 與依賴發現&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">Trace&lt;/a> 是把一次 request 在多個服務、queue 與背景任務中的路徑串起來的診斷訊號，責任是讓團隊從症狀追到跨服務等待點。&lt;/p>
&lt;p>Log 回答「某個服務發生了什麼」；metric 回答「某個服務的健康趨勢」；trace 回答「一次 request 跨多個服務時，時間花在哪、錯誤發生在哪一段」。三者互補，trace 的獨特價值在於它串起跨服務的因果鏈 — 沒有 trace，事故定位只能靠人工比對不同服務的 log timestamp。&lt;/p>
&lt;p>本章處理的是 context propagation — 怎麼讓 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context&lt;/a> 在 HTTP call、queue 投遞、背景任務啟動等邊界上正確傳遞。Context 斷掉時，trace 從「完整路徑」退化成幾段需要人工拼接的局部紀錄，跨服務診斷的時間成本會從秒級回退到分鐘甚至小時級。&lt;/p>
&lt;h2 id="trace-與-span-的結構">Trace 與 Span 的結構&lt;/h2>
&lt;h3 id="span-是-trace-的基本單位">Span 是 trace 的基本單位&lt;/h3>
&lt;p>一個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/span/" data-link-title="Span" data-link-desc="說明 trace 中一段工作如何記錄耗時、狀態與關聯">span&lt;/a> 代表一段有起止時間的工作。每個 span 記錄：操作名稱（&lt;code>POST /api/orders&lt;/code>）、開始與結束時間、狀態（OK / Error）、屬性（service name、http.status_code、db.statement）與事件（exception、log message）。&lt;/p>
&lt;p>Span 之間透過 parent-child 關係組成 tree。一個 HTTP request 進入 API gateway 時建立 root span，gateway 呼叫 order service 時建立 child span，order service 查 DB 時建立另一個 child span。整棵 tree 共享同一個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id&lt;/a>，讓所有 span 可以被聚合成一次 request 的完整路徑。&lt;/p>
&lt;h3 id="trace-是-span-tree">Trace 是 span tree&lt;/h3>
&lt;p>一個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace&lt;/a> 是所有共享同一個 trace id 的 span 的集合。在 waterfall view 中，trace 呈現為時間軸上的巢狀條狀圖 — root span 在最上面，child span 依序往下排列，每段的長度代表耗時。&lt;/p>
&lt;p>Waterfall view 的診斷價值是「一眼看到時間花在哪」。如果 checkout API 的 total latency 是 800ms，waterfall 會顯示 payment service 佔了 600ms — 問題定位從「整個 checkout 慢」縮小到「payment service 慢」，後續 debug 只需要看 payment service 的 log 跟 metric。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace</a> / <a href="/blog/backend/knowledge-cards/span/" data-link-title="Span" data-link-desc="說明 trace 中一段工作如何記錄耗時、狀態與關聯">span</a> 模型</li>
<li><a href="/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context</a> propagation</li>
<li>context 斷鏈的常見邊界與修復</li>
<li><a href="/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">sampling</a> 策略的 tracing 面（SSoT 在 <a href="/blog/backend/04-observability/cardinality-cost-governance/#sampling-%e7%ad%96%e7%95%a5" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7</a>）</li>
<li>service graph 與依賴發現</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">Trace</a> 是把一次 request 在多個服務、queue 與背景任務中的路徑串起來的診斷訊號，責任是讓團隊從症狀追到跨服務等待點。</p>
<p>Log 回答「某個服務發生了什麼」；metric 回答「某個服務的健康趨勢」；trace 回答「一次 request 跨多個服務時，時間花在哪、錯誤發生在哪一段」。三者互補，trace 的獨特價值在於它串起跨服務的因果鏈 — 沒有 trace，事故定位只能靠人工比對不同服務的 log timestamp。</p>
<p>本章處理的是 context propagation — 怎麼讓 <a href="/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context</a> 在 HTTP call、queue 投遞、背景任務啟動等邊界上正確傳遞。Context 斷掉時，trace 從「完整路徑」退化成幾段需要人工拼接的局部紀錄，跨服務診斷的時間成本會從秒級回退到分鐘甚至小時級。</p>
<h2 id="trace-與-span-的結構">Trace 與 Span 的結構</h2>
<h3 id="span-是-trace-的基本單位">Span 是 trace 的基本單位</h3>
<p>一個 <a href="/blog/backend/knowledge-cards/span/" data-link-title="Span" data-link-desc="說明 trace 中一段工作如何記錄耗時、狀態與關聯">span</a> 代表一段有起止時間的工作。每個 span 記錄：操作名稱（<code>POST /api/orders</code>）、開始與結束時間、狀態（OK / Error）、屬性（service name、http.status_code、db.statement）與事件（exception、log message）。</p>
<p>Span 之間透過 parent-child 關係組成 tree。一個 HTTP request 進入 API gateway 時建立 root span，gateway 呼叫 order service 時建立 child span，order service 查 DB 時建立另一個 child span。整棵 tree 共享同一個 <a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id</a>，讓所有 span 可以被聚合成一次 request 的完整路徑。</p>
<h3 id="trace-是-span-tree">Trace 是 span tree</h3>
<p>一個 <a href="/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace</a> 是所有共享同一個 trace id 的 span 的集合。在 waterfall view 中，trace 呈現為時間軸上的巢狀條狀圖 — root span 在最上面，child span 依序往下排列，每段的長度代表耗時。</p>
<p>Waterfall view 的診斷價值是「一眼看到時間花在哪」。如果 checkout API 的 total latency 是 800ms，waterfall 會顯示 payment service 佔了 600ms — 問題定位從「整個 checkout 慢」縮小到「payment service 慢」，後續 debug 只需要看 payment service 的 log 跟 metric。</p>
<h2 id="context-propagation">Context Propagation</h2>
<h3 id="什麼是-trace-context">什麼是 trace context</h3>
<p><a href="/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">Trace context</a> 是跨服務傳遞 trace 身份的資料。最小的 trace context 包含 trace id（標識整條 trace）跟 parent span id（標識上游 span）。下游服務收到 trace context 後，建立新的 child span 並繼承 trace id，讓兩端的 span 歸屬同一條 trace。</p>
<p>W3C Trace Context 標準定義了 HTTP header 的傳遞格式：<code>traceparent</code> header 帶 trace id + parent span id + trace flags，<code>tracestate</code> header 帶 vendor-specific 的附加資訊。OpenTelemetry SDK 預設使用 W3C 格式；部分 vendor 有自己的 header 格式（Datadog 用 <code>x-datadog-trace-id</code>、AWS X-Ray 用 <code>X-Amzn-Trace-Id</code>），需要在 collector 或 SDK 層做格式轉換。</p>
<h3 id="propagation-的傳遞機制">Propagation 的傳遞機制</h3>
<p>HTTP call 是最常見的 propagation 路徑 — SDK 的 HTTP client middleware 自動把 trace context 注入 request header，下游 SDK 的 HTTP server middleware 自動從 header 提取 context。大部分 OpenTelemetry SDK 的 auto-instrumentation 會自動處理這一層，開發者不需要手動注入。</p>
<p>gRPC 用 metadata（等同 HTTP header）傳遞，機制類似。</p>
<p>Message queue 的 propagation 需要把 trace context 放進 message 的 header 或 metadata。Kafka 用 record header、RabbitMQ 用 message properties、NATS 用 message header。Producer 端注入、consumer 端提取。Queue 的 propagation 比 HTTP 複雜的原因是 consumer 可能在 producer 之後很久才消費 — context 的時間跨度可能從毫秒擴大到分鐘或小時。</p>
<h3 id="context-斷鏈的常見邊界">Context 斷鏈的常見邊界</h3>
<p>Context propagation 在以下邊界容易斷裂：</p>
<p><strong>Thread / goroutine / task 邊界</strong>：同步 runtime 通常用 thread-local 存放 context，新開 thread 不會自動繼承。Go 用 <code>context.Context</code> 顯式傳遞，相對不容易遺漏；Java 用 ThreadLocal，啟動新 thread 或提交到 thread pool 時 context 需要手動傳遞或用 agent auto-instrumentation。Async runtime（Node.js 的 AsyncLocalStorage、Python 的 contextvars）各有自己的 context 傳播機制。</p>
<p><strong>Queue / event 邊界</strong>：producer 把 trace context 注入 message header，consumer 提取並建立新 span。如果 producer 端的 SDK 沒有自動注入（例如用了原生 Kafka client 而非 instrumented client），context 就斷了。跨 queue 的 trace 在 waterfall view 中會出現時間斷層 — producer span 結束到 consumer span 開始之間可能有秒級到分鐘級的等待。</p>
<p><strong>Background job / cron 邊界</strong>：cron job 或 scheduled task 沒有上游 request，沒有 trace context 可繼承。這類工作需要在啟動時建立 root span，並把 job name、schedule、trigger reason 作為 span 屬性，讓 trace 至少可以追蹤 job 內部的行為。</p>
<p><strong>跨語言 / 跨 vendor 邊界</strong>：不同語言的 SDK 或不同 vendor 的 instrumentation 可能用不同的 header 格式。W3C Trace Context 標準解決了格式問題，但混用 vendor-specific SDK 時（例如一個服務用 Datadog agent、另一個用 OTel SDK），需要在 collector 層做 context format 轉換。</p>
<h3 id="斷鏈的修復策略">斷鏈的修復策略</h3>
<p>修復斷鏈的目標是讓 trace 在邊界處重新接上，不需要人工拼接。</p>
<p><strong>Queue 邊界</strong>：確保 producer 跟 consumer 都使用 instrumented client（OTel SDK 的 messaging instrumentation），而非原生 client。Instrumented client 自動處理 header 注入跟提取。Consumer 端建立的 span 用 <code>CONSUMER</code> kind 標記，waterfall view 會顯示 queue 等待時間。</p>
<p><strong>Thread pool 邊界</strong>：Java 生態用 <code>Context.wrap()</code> 包裝提交到 thread pool 的 Runnable/Callable；Go 生態用 <code>context.Context</code> 作為第一個函數參數傳遞（這是 Go 的慣例，不需要額外處理）。Auto-instrumentation agent 可以自動處理常見 thread pool（Java 的 ExecutorService、Node.js 的 worker_threads）。</p>
<p><strong>跨 vendor 邊界</strong>：在 collector 層（OTel Collector）統一轉換 header 格式。Collector 的 receiver 支援多種格式輸入，exporter 統一輸出 W3C 格式。這層轉換在 <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> 的 collector 中介段處理。</p>
<h2 id="trace-與-log--metric-的關聯">Trace 與 Log / Metric 的關聯</h2>
<h3 id="correlation-id-統一">Correlation id 統一</h3>
<p><a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">Trace id</a> 應該同時出現在 log 的結構化欄位中。當 log 的 <code>trace_id</code> 欄位帶著跟 trace 相同的值，debug 工作流就能從 trace waterfall 跳到某個 span 對應的 log，或從 log 跳到完整的 trace view。</p>
<p>實作方式是在 logger 初始化時，把當前 span 的 trace id 注入 log 的 context fields。OTel SDK 的 log bridge 可以自動做這件事；沒有自動橋接的框架需要手動把 <code>span.SpanContext().TraceID()</code> 寫進 log 的 <a href="/blog/backend/knowledge-cards/correlation-id/" data-link-title="Correlation ID" data-link-desc="說明跨事件或跨服務的關聯識別碼如何支援排障">correlation id</a> 欄位。</p>
<h3 id="exemplarmetric-到-trace-的跳板">Exemplar：metric 到 trace 的跳板</h3>
<p>Metric 是聚合訊號，本身不帶單一 request 的 trace id。Exemplar 是附加在 metric 資料點上的代表性 trace id — 當某個 histogram bucket 收到一個資料點時，附帶記錄產生這個資料點的 trace id。</p>
<p>Dashboard 上看到 latency p99 升高時，可以從 exemplar 跳到一個具體的高延遲 trace，看 waterfall 定位慢在哪。Exemplar 是 metric 到 trace 的橋樑，讓聚合訊號（metric）跟個別案例（trace）連接起來。</p>
<h2 id="service-graph-與依賴發現">Service Graph 與依賴發現</h2>
<p>Trace 資料聚合後可以自動生成 service graph — 哪些服務在呼叫哪些服務、call 的頻率、延遲分布、錯誤率。這個 graph 跟手動維護的 architecture diagram 不同：它來自實際流量，反映的是「現在真的在發生什麼」而非「設計時預期會發生什麼」。</p>
<p>Service graph 的價值在於依賴發現。新服務加入後，如果有 trace instrumentation，它會自動出現在 graph 上。舊服務之間新增的依賴（例如 A 開始直接呼叫 C、繞過 B）也會被 graph 反映。手動維護的 wiki 通常落後實際狀況數週到數月。</p>
<p>Service graph 的完整性取決於 trace 的覆蓋率。如果某些服務沒有 instrumentation 或 sampling 率太低，graph 上會出現斷點或邊權不準。把 service graph 的完整性（「有多少比例的服務有 trace」）作為觀測覆蓋率的一個指標，能推動 instrumentation 的漸進覆蓋。</p>
<p>詳見 <a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13 service topology</a>。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 tracing 時，先看 propagation 是否完整，再看 sampling 是否保留可除錯樣本。</p>
<p>重點訊號包括：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id</a> 是否能和 log、metric 共享 <a href="/blog/backend/knowledge-cards/correlation-id/" data-link-title="Correlation ID" data-link-desc="說明跨事件或跨服務的關聯識別碼如何支援排障">correlation id</a></li>
<li>async / queue / background job 是否能保留 parent-child 關係</li>
<li>sampling 是否能在高流量下保留錯誤與高延遲樣本（策略矩陣見 <a href="/blog/backend/04-observability/cardinality-cost-governance/#sampling-%e7%ad%96%e7%95%a5" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7</a>）</li>
<li>service graph 是否能由 trace 聚合而來，並降低 wiki 手動維護成本</li>
<li>trace context 在跨語言 / 跨 vendor 邊界是否用 W3C 標準統一</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>Request 跨服務後 trace 斷鏈、靠人重組</li>
<li>Async / queue 邊界 context 沒傳遞</li>
<li>採樣率太低、production debug 找不到對應 trace</li>
<li>Trace id 跟 log / metric 對不上、無共同 correlation key</li>
<li>Service graph 不存在或半年沒人看</li>
<li>多個 vendor SDK 混用、header 格式不一致</li>
<li>Background job / cron 沒有 root span、trace 無法追蹤</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只 instrument HTTP、忽略 queue</td>
          <td>Queue 消費後的 span 都是孤兒</td>
          <td>Producer / consumer 都用 instrumented client</td>
      </tr>
      <tr>
          <td>Thread pool 不傳 context</td>
          <td>平行處理的 span 不歸屬任何 trace</td>
          <td>用 Context.wrap() 或語言慣例傳遞 context</td>
      </tr>
      <tr>
          <td>Trace id 沒寫進 log</td>
          <td>從 log 找不到對應 trace、反向也找不到</td>
          <td>Logger context 注入 trace id</td>
      </tr>
      <tr>
          <td>混用 vendor header 無轉換</td>
          <td>部分服務的 span 串不進同一條 trace</td>
          <td>Collector 層統一轉換成 W3C 格式</td>
      </tr>
      <tr>
          <td>所有 span 都是 root span</td>
          <td>Trace 只有一層、沒有 parent-child 結構</td>
          <td>確認 SDK 的 context extraction 有正確從 header 繼承</td>
      </tr>
      <tr>
          <td>Background job 無 instrumentation</td>
          <td>Job 內的 DB / HTTP call 沒有 trace 可追蹤</td>
          <td>Job 啟動時建立 root span、內部操作作為 child span</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<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>：trace 資料在 dashboard 的呈現跟 alert 設計</li>
<li><a href="/blog/backend/04-observability/cardinality-cost-governance/#sampling-%e7%ad%96%e7%95%a5" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 cardinality / cost</a>：sampling 策略矩陣（Head / Tail / Adaptive / Exemplar）與保留決策</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>：sampling 在 collector 的集中治理、跨 vendor header 轉換</li>
<li><a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13 service topology</a>：trace 訊號聚合成依賴圖</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>：sampling bias 跟 trace 完整性的資料品質</li>
<li><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：trace 查詢作為即席診斷的一種模式</li>
</ul>
]]></content:encoded></item><item><title>4.4 dashboard 與 alert 設計</title><link>https://tarrragon.github.io/blog/backend/04-observability/dashboard-alert/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/dashboard-alert/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">Dashboard&lt;/a> 設計原則：SLI 導向 vs 指標堆疊&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert/" data-link-title="Alert" data-link-desc="說明 alert 如何把需要處理的服務症狀轉成可行動通知">Alert&lt;/a> 設計：symptom-based vs cause-based&lt;/li>
&lt;li>Alert noise control 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">Runbook&lt;/a> linkage&lt;/li>
&lt;li>Dashboard / alert 的生命週期與 ownership&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">Dashboard&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert/" data-link-title="Alert" data-link-desc="說明 alert 如何把需要處理的服務症狀轉成可行動通知">alert&lt;/a> 是把觀測訊號轉成操作入口的控制面，責任是讓團隊在正常巡檢與事故響應時看到同一組事實。&lt;/p>
&lt;p>Dashboard 讓人理解狀態，alert 讓人採取行動。兩者的設計問題不同：dashboard 的問題是「資訊太多、焦點不明」；alert 的問題是「通知太多、行動不明」。兩者都需要 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/ownership/" data-link-title="Ownership" data-link-desc="說明 ownership 如何把問題、決策與交接責任固定到可執行角色">ownership&lt;/a>、生命週期管理與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 連結。&lt;/p>
&lt;h2 id="dashboard-設計">Dashboard 設計&lt;/h2>
&lt;h3 id="sli-導向-vs-指標堆疊">SLI 導向 vs 指標堆疊&lt;/h3>
&lt;p>Dashboard 的常見失敗模式是「把所有能拿到的指標都放上去」。二十個 panel、五十條曲線、無法在 3 秒內回答「服務現在健康嗎」。&lt;/p>
&lt;p>SLI 導向的 dashboard 從使用者體驗出發：第一排 panel 回答「使用者感受到的健康狀態」（availability、latency percentile、error ratio），第二排回答「健康狀態的原因」（dependency latency、queue depth、resource utilization），第三排回答「趨勢與容量」（traffic growth、storage usage、capacity headroom）。&lt;/p>
&lt;p>每個 panel 都應該能回答一個具體問題。如果團隊看了某個 panel 後的反應是「所以呢？」，這個 panel 不是放錯位置就是不該存在。&lt;/p>
&lt;h3 id="dashboard-層級">Dashboard 層級&lt;/h3>
&lt;p>不同使用者看不同層級的 dashboard。把所有資訊擠在同一個 dashboard 會讓每個角色都找不到自己要的。&lt;/p>
&lt;p>&lt;strong>Service overview&lt;/strong>：on-call 工程師的第一個入口。5-8 個 panel，回答「這個服務現在有沒有問題」。SLI 指標（error rate、latency p99、availability）、最近的 alert、dependency 健康。&lt;/p>
&lt;p>&lt;strong>Debug dashboard&lt;/strong>：事故中的深入診斷入口。按 dependency 分組（database panel group、cache panel group、downstream API panel group），每組顯示延遲、錯誤率、連線數。Panel 數量多但按需展開。&lt;/p>
&lt;p>&lt;strong>Capacity dashboard&lt;/strong>：容量規劃用。週到月級的趨勢圖 — traffic growth、storage usage、connection pool saturation、cost trends。刷新頻率低（每小時或每天），panel 讀 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule&lt;/a> 或 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup&lt;/a> 資料。&lt;/p>
&lt;p>&lt;strong>Business dashboard&lt;/strong>：給非工程角色看。轉換率、使用者活躍度、營收指標。資料來源可能不只是觀測訊號，還包括 analytics 跟 business metrics。&lt;/p>
&lt;h3 id="dashboard-的查詢效能">Dashboard 的查詢效能&lt;/h3>
&lt;p>Dashboard 是觀測查詢設計中「聚合趨勢」模式的主要消費者（見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23&lt;/a>）。每個 panel 每 30 秒刷新一次，十個團隊各自有 dashboard 就是每分鐘數百個背景查詢。&lt;/p>
&lt;p>Panel 設計時要注意查詢成本：時間範圍越長、raw series 越多、聚合越複雜，query-time cost 越高。長時間趨勢 panel 應該讀 recording rule 或 rollup series，而非每次刷新都掃描 raw data。&lt;/p>
&lt;h2 id="alert-設計">Alert 設計&lt;/h2>
&lt;h3 id="symptom-based-vs-cause-based">Symptom-based vs cause-based&lt;/h3>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">Symptom-based alert&lt;/a> 觸發在使用者可感知的症狀上 — error rate 升高、latency p99 超過閾值、availability 下降。Cause-based alert 觸發在內部原因上 — CPU &amp;gt; 90%、disk usage &amp;gt; 85%、connection pool exhausted。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">Dashboard</a> 設計原則：SLI 導向 vs 指標堆疊</li>
<li><a href="/blog/backend/knowledge-cards/alert/" data-link-title="Alert" data-link-desc="說明 alert 如何把需要處理的服務症狀轉成可行動通知">Alert</a> 設計：symptom-based vs cause-based</li>
<li>Alert noise control 與 <a href="/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue</a></li>
<li><a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">Runbook</a> linkage</li>
<li>Dashboard / alert 的生命週期與 ownership</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">Dashboard</a> 與 <a href="/blog/backend/knowledge-cards/alert/" data-link-title="Alert" data-link-desc="說明 alert 如何把需要處理的服務症狀轉成可行動通知">alert</a> 是把觀測訊號轉成操作入口的控制面，責任是讓團隊在正常巡檢與事故響應時看到同一組事實。</p>
<p>Dashboard 讓人理解狀態，alert 讓人採取行動。兩者的設計問題不同：dashboard 的問題是「資訊太多、焦點不明」；alert 的問題是「通知太多、行動不明」。兩者都需要 <a href="/blog/backend/knowledge-cards/ownership/" data-link-title="Ownership" data-link-desc="說明 ownership 如何把問題、決策與交接責任固定到可執行角色">ownership</a>、生命週期管理與 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 連結。</p>
<h2 id="dashboard-設計">Dashboard 設計</h2>
<h3 id="sli-導向-vs-指標堆疊">SLI 導向 vs 指標堆疊</h3>
<p>Dashboard 的常見失敗模式是「把所有能拿到的指標都放上去」。二十個 panel、五十條曲線、無法在 3 秒內回答「服務現在健康嗎」。</p>
<p>SLI 導向的 dashboard 從使用者體驗出發：第一排 panel 回答「使用者感受到的健康狀態」（availability、latency percentile、error ratio），第二排回答「健康狀態的原因」（dependency latency、queue depth、resource utilization），第三排回答「趨勢與容量」（traffic growth、storage usage、capacity headroom）。</p>
<p>每個 panel 都應該能回答一個具體問題。如果團隊看了某個 panel 後的反應是「所以呢？」，這個 panel 不是放錯位置就是不該存在。</p>
<h3 id="dashboard-層級">Dashboard 層級</h3>
<p>不同使用者看不同層級的 dashboard。把所有資訊擠在同一個 dashboard 會讓每個角色都找不到自己要的。</p>
<p><strong>Service overview</strong>：on-call 工程師的第一個入口。5-8 個 panel，回答「這個服務現在有沒有問題」。SLI 指標（error rate、latency p99、availability）、最近的 alert、dependency 健康。</p>
<p><strong>Debug dashboard</strong>：事故中的深入診斷入口。按 dependency 分組（database panel group、cache panel group、downstream API panel group），每組顯示延遲、錯誤率、連線數。Panel 數量多但按需展開。</p>
<p><strong>Capacity dashboard</strong>：容量規劃用。週到月級的趨勢圖 — traffic growth、storage usage、connection pool saturation、cost trends。刷新頻率低（每小時或每天），panel 讀 <a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule</a> 或 <a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup</a> 資料。</p>
<p><strong>Business dashboard</strong>：給非工程角色看。轉換率、使用者活躍度、營收指標。資料來源可能不只是觀測訊號，還包括 analytics 跟 business metrics。</p>
<h3 id="dashboard-的查詢效能">Dashboard 的查詢效能</h3>
<p>Dashboard 是觀測查詢設計中「聚合趨勢」模式的主要消費者（見 <a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23</a>）。每個 panel 每 30 秒刷新一次，十個團隊各自有 dashboard 就是每分鐘數百個背景查詢。</p>
<p>Panel 設計時要注意查詢成本：時間範圍越長、raw series 越多、聚合越複雜，query-time cost 越高。長時間趨勢 panel 應該讀 recording rule 或 rollup series，而非每次刷新都掃描 raw data。</p>
<h2 id="alert-設計">Alert 設計</h2>
<h3 id="symptom-based-vs-cause-based">Symptom-based vs cause-based</h3>
<p><a href="/blog/backend/knowledge-cards/symptom-based-alert/" data-link-title="Symptom-Based Alert" data-link-desc="說明告警應優先偵測使用者可感知症狀">Symptom-based alert</a> 觸發在使用者可感知的症狀上 — error rate 升高、latency p99 超過閾值、availability 下降。Cause-based alert 觸發在內部原因上 — CPU &gt; 90%、disk usage &gt; 85%、connection pool exhausted。</p>
<p>Symptom-based 是 alert 設計的起點。原因是：cause-based alert 容易產生大量「系統在忙但使用者沒受影響」的 false alarm。CPU 短暫衝到 95% 然後回落，如果 latency 跟 error rate 都正常，這個 alert 不需要人類介入。</p>
<p>Cause-based alert 的價值是預防性告警 — disk usage 趨勢在兩天後會滿、connection pool 使用率在高峰時逼近上限。這類 alert 不需要立即行動，但需要在工作時間排入 task。把 cause-based alert 設成 warning（不 page）、symptom-based alert 設成 critical（page on-call），能降低 noise。</p>
<h3 id="slo-based-alerting">SLO-based alerting</h3>
<p>SLO-based alerting 用 <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> 取代固定閾值。不是「error rate &gt; 1% 就告警」，而是「error budget 的消耗速度超過預期就告警」。</p>
<p>Burn rate alerting 的好處是自動適應基線。低流量時段的 1% error rate 可能只是幾筆錯誤、不值得 page；高流量時段的 0.5% error rate 可能代表大量使用者受影響。Burn rate 用「相對於 SLO 允許的錯誤量，目前消耗速度有多快」來判斷嚴重性，比固定閾值更能反映使用者影響。</p>
<p>SLO-based alert 的實作通常用 multi-window burn rate — 短視窗（5 分鐘）抓急性問題、長視窗（1 小時）抓慢性問題。兩個視窗都超過 burn rate 閾值時才觸發，減少單一 spike 造成的 false alarm。</p>
<p>SLI/SLO 訊號的詳細設計見 <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</a>。</p>
<h3 id="alert-的必要欄位">Alert 的必要欄位</h3>
<p>每個 alert rule 應該帶以下 metadata，讓收到 page 的 on-call 工程師在 30 秒內知道下一步：</p>
<ul>
<li><strong>Severity</strong>：critical（立即行動）/ warning（工作時間處理）/ info（記錄但不通知）</li>
<li><strong>Runbook link</strong>：對應的 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> URL，描述診斷步驟跟可能的修復動作</li>
<li><strong>Owner</strong>：負責這個 alert 的團隊或服務</li>
<li><strong>Dashboard link</strong>：點進去直接看相關 panel，不用自己找 dashboard</li>
<li><strong>Summary</strong>：一句話描述發生了什麼（<code>checkout error rate &gt; 2% for 5 minutes</code>），而非只有 alert rule 名稱</li>
</ul>
<p>缺少 runbook link 的 alert 等於「通知了但不告訴你做什麼」。On-call 工程師收到不認識的 alert 時，第一反應是 ack 然後繼續觀察 — 這就是 <a href="/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue</a> 的起點。</p>
<h2 id="alert-noise-control">Alert Noise Control</h2>
<h3 id="什麼是-noise">什麼是 noise</h3>
<p>Alert noise 是「觸發了但不需要人類行動」的 alert。包括：</p>
<ul>
<li><strong>False positive</strong>：條件觸發但實際沒問題（短暫 spike 觸發固定閾值、maintenance 期間的預期 error）</li>
<li><strong>Redundant alert</strong>：同一個問題觸發多個 alert（database 慢 → query timeout alert + error rate alert + latency alert 同時觸發）</li>
<li><strong>Stale alert</strong>：條件已經不適用（服務改版後舊 alert rule 沒更新、abandoned service 的 alert 還在）</li>
</ul>
<h3 id="noise-rate-量測">Noise rate 量測</h3>
<p>Noise rate = 不需要行動的 alert / 總 alert。追蹤方式是讓 on-call 工程師在 ack alert 時標記「actionable」或「noise」。月度彙整 noise rate，超過 30% 的 alert rule 進入治理流程（業界常用的基線閾值，Google SRE Workbook 建議 actionable rate 維持在 70% 以上；團隊可依自身容忍度調整）。</p>
<h3 id="降噪手段">降噪手段</h3>
<p><strong>Grouping</strong>：把同一個根因觸發的多個 alert 合併成一則通知。Alertmanager 的 <code>group_by</code> 讓同服務、同 alert name 的 alert 只發一次。</p>
<p><strong>Inhibition</strong>：高嚴重性 alert 抑制低嚴重性。Database down 觸發時，所有依賴該 database 的 query timeout alert 被抑制 — 根因已知、不需要每個症狀都通知。</p>
<p><strong>Silence / maintenance window</strong>：已知的維護活動期間暫停特定 alert。Silence 需要有過期時間，避免永久靜默掩蓋真實問題。</p>
<p><strong>Hysteresis</strong>：alert 觸發需要條件持續 N 分鐘（<code>for: 5m</code>），避免瞬間 spike 觸發。恢復也需要條件持續 N 分鐘，避免「反覆觸發 → 恢復」的 flapping。</p>
<h2 id="runbook-設計">Runbook 設計</h2>
<p><a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">Runbook</a> 是 alert 的行動指南。每個 critical alert 應該連到一份 runbook，描述「收到這個 alert 時該做什麼」。</p>
<p>Runbook 的有效結構：</p>
<ol>
<li><strong>症狀描述</strong>：這個 alert 代表什麼（「checkout error rate 超過 SLO burn rate」）</li>
<li><strong>影響評估</strong>：誰受影響、嚴重程度（「付款功能受影響、影響所有 checkout 流程」）</li>
<li><strong>診斷步驟</strong>：先看哪個 dashboard、查哪些 log、跑哪些 query</li>
<li><strong>可能的修復動作</strong>：restart service、scale up、rollback deployment、failover to backup</li>
<li><strong>升級路徑</strong>：如果 15 分鐘內無法解決，通知誰</li>
</ol>
<p>Runbook 的維護責任跟 alert 的 owner 一致。Alert rule 改了但 runbook 沒更新是常見的退化 — 把 runbook 的 last-reviewed date 作為 alert 治理的審計項目。</p>
<h2 id="dashboard-與-alert-的生命週期">Dashboard 與 Alert 的生命週期</h2>
<p>Dashboard 跟 alert 都有生命週期。建立時有用，但隨服務演進可能變得過時、冗餘或誤導。沒有生命週期管理的 dashboard / alert 系統會累積 debt — dashboard 數量膨脹但無人看、alert rule 堆疊但多數是 noise。</p>
<h3 id="ownership">Ownership</h3>
<p>每個 dashboard 跟每個 alert rule 都需要明確的 owner。Owner 負責：維護 panel / rule 的正確性、定期審視 noise rate 跟使用率、在服務變更時更新對應的 dashboard / alert。</p>
<p>沒有 owner 的 dashboard 跟 alert 應該有過期機制 — 超過 N 天沒有人訪問的 dashboard 標記為候選淘汰、超過 N 天沒有觸發的 alert rule 審視是否仍有意義。</p>
<h3 id="定期審視">定期審視</h3>
<p>Dashboard 跟 alert 的定期審視是 <a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 signal governance loop</a> 的一部分。每季或每次重大事故後，審視：</p>
<ul>
<li>哪些 alert 的 noise rate 過高、需要調整或刪除</li>
<li>哪些 dashboard 沒人訪問、可以合併或淘汰</li>
<li>事故中是否有缺少的 alert 或 dashboard panel</li>
</ul>
<p>Ownership 矩陣與 metadata 欄位的詳細設計見 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Dashboard 跟 alert 是否有效，最直接的訊號是 alert noise rate 跟 dashboard 訪問頻率 — noise rate 超過 30% 代表通知品質退化，dashboard 長期零訪問代表資訊跟決策脫節。</p>
<p>重點訊號包括：</p>
<ul>
<li>Alert 是否能對應到明確 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a>、<a href="/blog/backend/knowledge-cards/ownership/" data-link-title="Ownership" data-link-desc="說明 ownership 如何把問題、決策與交接責任固定到可執行角色">ownership</a> 與停止條件</li>
<li>Dashboard 是否有固定使用者與更新責任</li>
<li>Threshold 是否對齊 SLO、容量邊界或使用者影響</li>
<li>Noise rate 是否被追蹤並回寫治理流程</li>
<li>Dashboard panel 是否讀 recording rule 而非每次重算 raw data</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>Alert 跟 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 沒連、收到 page 不知道做什麼</li>
<li>Dashboard 數量爆量、無 owner、半年無人訪問</li>
<li>同一訊號多個 alert 重複觸發、無 grouping 或 inhibition</li>
<li>Alert noise rate &gt; 30%、ack 後無實際動作，形成 <a href="/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue</a></li>
<li>Alert threshold 用直覺數字、沒對齊 SLO / 商業承諾</li>
<li>Dashboard panel 載入慢、因為直接查 raw series 而非 recording rule</li>
<li>Maintenance window 過後 silence 沒移除、真實問題被掩蓋</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>指標堆疊 dashboard</td>
          <td>50 個 panel、看不出服務是否健康</td>
          <td>SLI 導向重構：第一排回答健康、第二排回答原因</td>
      </tr>
      <tr>
          <td>全部 cause-based alert</td>
          <td>CPU / disk / memory alert 頻繁但服務正常</td>
          <td>區分 symptom（page）跟 cause（warning）</td>
      </tr>
      <tr>
          <td>固定閾值 alert</td>
          <td>低流量時 false alarm、高流量時漏報</td>
          <td>改用 SLO burn rate alerting</td>
      </tr>
      <tr>
          <td>Alert 無 runbook</td>
          <td>On-call 收到 page 後自行摸索、MTTR 高</td>
          <td>每個 critical alert 必附 runbook link</td>
      </tr>
      <tr>
          <td>Alert 無 owner</td>
          <td>沒人維護的 alert rule 累積成 noise 來源</td>
          <td>每個 alert rule 帶 owner metadata、定期審視</td>
      </tr>
      <tr>
          <td>Dashboard 無過期機制</td>
          <td>三年累積 200 個 dashboard、多數沒人看</td>
          <td>訪問頻率追蹤 + 定期淘汰審視</td>
      </tr>
      <tr>
          <td>同一問題觸發 N 個 alert</td>
          <td>On-call 同時收到 5 則通知、不知道看哪個</td>
          <td>Alertmanager grouping + inhibition</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<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</a>：trace waterfall 作為 dashboard 的診斷入口</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 政策">4.6 SLI/SLO 訊號設計</a>：alert 的訊號源頭、burn rate alerting 的 SLI 依據</li>
<li><a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 訊號治理閉環</a>：alert / dashboard 的生命週期維運</li>
<li><a href="/blog/backend/04-observability/client-side-monitoring/" data-link-title="4.10 Client-side / Synthetic / RUM" data-link-desc="補 server-side 看不到的 user perceived 訊號">4.10 client-side / RUM</a>：補 server-side 看不到的 dashboard 維度</li>
<li><a href="/blog/backend/04-observability/anomaly-detection/" data-link-title="4.14 Anomaly Detection" data-link-desc="把 ML / statistical baseline 訊號跟 rule-based alert 整合">4.14 anomaly detection</a>：rule-based alert 之外的統計訊號</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：dashboard / alert 的 ownership 矩陣與 metadata 欄位</li>
<li><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：dashboard 查詢的效能與 recording rule</li>
</ul>
]]></content:encoded></item><item><title>4.5 可觀測性威脅建模（Threat Modeling）</title><link>https://tarrragon.github.io/blog/backend/04-observability/attacker-view-observability-risks/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/attacker-view-observability-risks/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>觀測系統為什麼需要威脅建模&lt;/li>
&lt;li>三類弱點：觀測盲區、告警失真、資料暴露&lt;/li>
&lt;li>每類弱點的判讀流程與修復方向&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">4.4 dashboard-alert&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安&lt;/a> 的分工&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>可觀測性威脅建模的判讀目標是「觀測系統本身有哪些弱點會讓事故更難處理、更慢收斂、或擴大成資安事件」。觀測系統是事故處理的核心工具 — 工具失靈時，事故的 MTTD（偵測時間）跟 MTTR（修復時間）都會被拉長。&lt;/p>
&lt;p>本章用三類弱點盤點觀測系統：觀測盲區（看不到問題）、告警失真（看到錯的東西）、資料暴露（觀測資料本身變成風險）。每類弱點有各自的判讀流程跟修復方向。&lt;/p>
&lt;p>跟傳統資安威脅建模的差異：資安威脅建模聚焦「攻擊者怎麼入侵系統」；觀測威脅建模聚焦「觀測系統的設計缺陷怎麼讓事故更難處理」。兩者的交叉點在資料暴露 — 觀測資料含 secret 或 PII 時，觀測弱點直接成為資安弱點。&lt;/p>
&lt;h2 id="哪些服務要先做觀測弱點盤點">哪些服務要先做觀測弱點盤點&lt;/h2>
&lt;p>下列情境同時出現時，觀測弱點會快速放大：&lt;/p>
&lt;ul>
&lt;li>服務數量增加，跨服務呼叫變深 — &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace&lt;/a> 斷鏈的影響面擴大&lt;/li>
&lt;li>值班依賴告警，但告警常常失真或過量 — &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue&lt;/a> 讓真正的問題被淹沒&lt;/li>
&lt;li>調查事故高度依賴人工搜尋 log — 缺少結構化查詢入口&lt;/li>
&lt;li>支援工具與觀測平台可接觸敏感資料 — 觀測資料的存取控制不足&lt;/li>
&lt;/ul>
&lt;h2 id="弱點一觀測盲區">弱點一：觀測盲區&lt;/h2>
&lt;p>觀測盲區是「問題存在但觀測系統看不到」的狀態。盲區的危險在於它讓團隊對系統狀態的判斷建立在不完整的資訊上 — 看起來一切正常，但其實有路徑沒被觀測到。&lt;/p>
&lt;h3 id="常見盲區">常見盲區&lt;/h3>
&lt;p>&lt;strong>Sampling 導致的盲區&lt;/strong>：head sampling 按固定比例丟棄 trace，低流量服務的錯誤樣本可能全部被丟。事故時查 trace 查不到，因為 sampling 把剛好那些 request 的 trace 丟了。修復方向是 tail sampling 或 minimum sample floor（見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/#sampling-%e7%ad%96%e7%95%a5" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 sampling 策略&lt;/a>）。&lt;/p>
&lt;p>&lt;strong>Uninstrumented 路徑&lt;/strong>：新上線的服務沒加 instrumentation、async worker 沒有 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/span/" data-link-title="Span" data-link-desc="說明 trace 中一段工作如何記錄耗時、狀態與關聯">span&lt;/a>、third-party SDK 的 HTTP call 沒被攔截。這些路徑在 &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="把跨服務依賴從文件變成自動發現的觀測訊號">service graph&lt;/a> 上不存在，事故時團隊甚至不知道有這條依賴。修復方向是把 instrumentation coverage 作為 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">readiness review&lt;/a> 的檢查項。&lt;/p>
&lt;p>&lt;strong>Context 斷鏈形成的局部盲區&lt;/strong>：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context&lt;/a> 在 queue、thread pool、background job 邊界斷掉後，下游的 span 成為孤兒。團隊可以看到下游服務有問題，但看不到跟上游 request 的因果關係。修復策略見 &lt;a href="https://tarrragon.github.io/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&lt;/a>。&lt;/p>
&lt;p>&lt;strong>Log schema 漂移&lt;/strong>：不同服務的 log 用不同欄位名稱記錄同一個概念（&lt;code>request_id&lt;/code> vs &lt;code>req_id&lt;/code> vs &lt;code>requestId&lt;/code>）。查詢時用 &lt;code>request_id&lt;/code> 搜尋會漏掉用其他名稱的服務。修復方向是 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">log schema&lt;/a> 的跨服務統一。&lt;/p>
&lt;h3 id="盲區的判讀方式">盲區的判讀方式&lt;/h3>
&lt;ul>
&lt;li>列出所有服務，標記哪些有 trace instrumentation、哪些沒有&lt;/li>
&lt;li>檢查 service graph 跟已知 architecture diagram 的差異 — 差異就是盲區&lt;/li>
&lt;li>用已知的跨服務 request 做 end-to-end trace 驗證，看有沒有斷點&lt;/li>
&lt;li>檢查 sampling policy，確認低流量服務跟 error sample 的保留率&lt;/li>
&lt;/ul>
&lt;h2 id="弱點二告警失真">弱點二：告警失真&lt;/h2>
&lt;p>告警失真是「觀測系統看到了、但告訴你的是錯的或沒用的」。失真比盲區更危險 — 盲區至少讓團隊知道「這裡沒資料、要用其他方式查」；失真讓團隊基於錯誤訊號做判斷。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>觀測系統為什麼需要威脅建模</li>
<li>三類弱點：觀測盲區、告警失真、資料暴露</li>
<li>每類弱點的判讀流程與修復方向</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> 跟 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安</a> 的分工</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>可觀測性威脅建模的判讀目標是「觀測系統本身有哪些弱點會讓事故更難處理、更慢收斂、或擴大成資安事件」。觀測系統是事故處理的核心工具 — 工具失靈時，事故的 MTTD（偵測時間）跟 MTTR（修復時間）都會被拉長。</p>
<p>本章用三類弱點盤點觀測系統：觀測盲區（看不到問題）、告警失真（看到錯的東西）、資料暴露（觀測資料本身變成風險）。每類弱點有各自的判讀流程跟修復方向。</p>
<p>跟傳統資安威脅建模的差異：資安威脅建模聚焦「攻擊者怎麼入侵系統」；觀測威脅建模聚焦「觀測系統的設計缺陷怎麼讓事故更難處理」。兩者的交叉點在資料暴露 — 觀測資料含 secret 或 PII 時，觀測弱點直接成為資安弱點。</p>
<h2 id="哪些服務要先做觀測弱點盤點">哪些服務要先做觀測弱點盤點</h2>
<p>下列情境同時出現時，觀測弱點會快速放大：</p>
<ul>
<li>服務數量增加，跨服務呼叫變深 — <a href="/blog/backend/knowledge-cards/trace/" data-link-title="Trace" data-link-desc="說明 trace 如何重建跨服務請求的路徑、耗時與依賴關係">trace</a> 斷鏈的影響面擴大</li>
<li>值班依賴告警，但告警常常失真或過量 — <a href="/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue</a> 讓真正的問題被淹沒</li>
<li>調查事故高度依賴人工搜尋 log — 缺少結構化查詢入口</li>
<li>支援工具與觀測平台可接觸敏感資料 — 觀測資料的存取控制不足</li>
</ul>
<h2 id="弱點一觀測盲區">弱點一：觀測盲區</h2>
<p>觀測盲區是「問題存在但觀測系統看不到」的狀態。盲區的危險在於它讓團隊對系統狀態的判斷建立在不完整的資訊上 — 看起來一切正常，但其實有路徑沒被觀測到。</p>
<h3 id="常見盲區">常見盲區</h3>
<p><strong>Sampling 導致的盲區</strong>：head sampling 按固定比例丟棄 trace，低流量服務的錯誤樣本可能全部被丟。事故時查 trace 查不到，因為 sampling 把剛好那些 request 的 trace 丟了。修復方向是 tail sampling 或 minimum sample floor（見 <a href="/blog/backend/04-observability/cardinality-cost-governance/#sampling-%e7%ad%96%e7%95%a5" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 sampling 策略</a>）。</p>
<p><strong>Uninstrumented 路徑</strong>：新上線的服務沒加 instrumentation、async worker 沒有 <a href="/blog/backend/knowledge-cards/span/" data-link-title="Span" data-link-desc="說明 trace 中一段工作如何記錄耗時、狀態與關聯">span</a>、third-party SDK 的 HTTP call 沒被攔截。這些路徑在 <a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">service graph</a> 上不存在，事故時團隊甚至不知道有這條依賴。修復方向是把 instrumentation coverage 作為 <a href="/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">readiness review</a> 的檢查項。</p>
<p><strong>Context 斷鏈形成的局部盲區</strong>：<a href="/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context</a> 在 queue、thread pool、background job 邊界斷掉後，下游的 span 成為孤兒。團隊可以看到下游服務有問題，但看不到跟上游 request 的因果關係。修復策略見 <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</a>。</p>
<p><strong>Log schema 漂移</strong>：不同服務的 log 用不同欄位名稱記錄同一個概念（<code>request_id</code> vs <code>req_id</code> vs <code>requestId</code>）。查詢時用 <code>request_id</code> 搜尋會漏掉用其他名稱的服務。修復方向是 <a href="/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">log schema</a> 的跨服務統一。</p>
<h3 id="盲區的判讀方式">盲區的判讀方式</h3>
<ul>
<li>列出所有服務，標記哪些有 trace instrumentation、哪些沒有</li>
<li>檢查 service graph 跟已知 architecture diagram 的差異 — 差異就是盲區</li>
<li>用已知的跨服務 request 做 end-to-end trace 驗證，看有沒有斷點</li>
<li>檢查 sampling policy，確認低流量服務跟 error sample 的保留率</li>
</ul>
<h2 id="弱點二告警失真">弱點二：告警失真</h2>
<p>告警失真是「觀測系統看到了、但告訴你的是錯的或沒用的」。失真比盲區更危險 — 盲區至少讓團隊知道「這裡沒資料、要用其他方式查」；失真讓團隊基於錯誤訊號做判斷。</p>
<h3 id="常見失真模式">常見失真模式</h3>
<p><strong>Threshold drift</strong>：alert 的閾值在設定時是合理的（error rate &gt; 1%），但服務改版後基線變了（正常 error rate 從 0.1% 變成 0.5%），閾值沒跟著調。結果是 alert 頻繁觸發但團隊知道是 false alarm — <a href="/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue</a> 開始累積。</p>
<p><strong>Aggregation 掩蓋</strong>：用 average latency 做 alert，tail latency 被掩蓋。Average 200ms 但 p99 是 5 秒 — 1% 的使用者體驗極差但 alert 沒觸發。修復方向是 <a href="/blog/backend/knowledge-cards/percentile/" data-link-title="Percentile" data-link-desc="說明 p95 與 p99 如何描述長尾延遲與使用者體驗">percentile</a> 跟 <a href="/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram</a>。</p>
<p><strong>Alert storm</strong>：單一根因觸發大量 alert（database 慢 → 所有依賴該 database 的服務都觸發 latency alert + error alert + timeout alert）。On-call 收到 20 則通知，分不清哪個是因、哪個是果。修復方向是 alert grouping 跟 inhibition（見 <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>）。</p>
<p><strong>Stale dashboard</strong>：Dashboard 的 panel 引用的 metric name 已改名、panel 的 query 因 label 變更而回空值。Dashboard 看起來正常（曲線是平的），但其實是 no data 被渲染成 zero。修復方向是 dashboard 的 no-data alert 跟定期審視。</p>
<h3 id="失真的判讀方式">失真的判讀方式</h3>
<ul>
<li>追蹤 alert noise rate（每月有多少 alert 是 actionable 的）</li>
<li>檢查 alert rule 的 threshold 跟當前 baseline 是否對齊</li>
<li>確認 SLI 用 percentile 而非 average</li>
<li>事故復盤時問「這次的事故，alert 有沒有在對的時間告訴我們對的事」</li>
</ul>
<h2 id="弱點三資料暴露">弱點三：資料暴露</h2>
<p>觀測資料本身是風險資產。Log 可能含 secret（API key、token、password）、trace 可能含 PII（使用者 email、電話號碼在 span attribute 中）、dashboard 可能對所有人開放且顯示敏感業務指標。</p>
<h3 id="常見暴露路徑">常見暴露路徑</h3>
<p><strong>Log 含 secret</strong>：SDK 或框架在 error 發生時把完整 request body 寫進 log，body 中的 API key、token、password 跟著進入 log storage。Log storage 的存取控制通常比 secret manager 寬鬆 — 有 log 讀取權限的人都能看到 secret。</p>
<p><strong>Trace attribute 含 PII</strong>：<code>http.url</code> attribute 帶完整 URL（含 query parameter 裡的 email 或 token）、<code>db.statement</code> attribute 帶完整 SQL（含 WHERE 子句的使用者 ID）。Trace storage 的保留期可能比業務資料庫長，PII 在 trace 裡存活的時間超過必要範圍。</p>
<p><strong>Dashboard 權限過寬</strong>：所有工程師都能看所有服務的 dashboard，包含財務相關的 metric（營收、訂單金額分布）。Dashboard 的存取控制粒度通常是「整個 Grafana instance」而非「per-dashboard」。</p>
<p><strong>Collector / pipeline 有管理員權限</strong>：OTel Collector 或 log aggregator 以 admin 權限部署，可以讀寫 secret、修改配置、存取所有資料。Collector 被入侵時，攻擊者可以把 redaction 規則關掉、讓後續的 log 全量暴露。</p>
<h3 id="暴露的修復方向">暴露的修復方向</h3>
<ul>
<li>SDK 端做 redaction（在送出前掃描已知 secret pattern 並替換成 <code>[REDACTED]</code>）</li>
<li>Collector 端做 attribute 過濾（在 pipeline 中移除敏感 attribute）</li>
<li>Log / trace storage 做存取控制（RBAC、per-team 隔離）</li>
<li>Dashboard 做權限分層（業務 dashboard 需要額外授權）</li>
<li>定期掃描 log storage 檢查是否有未 redact 的 secret pattern</li>
</ul>
<p>詳見 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安與資料保護</a> 跟 <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>。</p>
<h2 id="設計取捨訊號完整度與成本控制">設計取捨：訊號完整度與成本控制</h2>
<p>觀測覆蓋越完整，盲區越少、事故定位越快。同時儲存、查詢與維護成本也會上升。穩定做法是先定義核心訊號與最低欄位（<a href="/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">log schema</a> 的 correlation fields、<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> 的 availability + latency），再按高風險路徑逐步加深觀測。</p>
<p>「全收」的成本問題見 <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>；「選擇性收」的品質問題見 <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>
<p>判讀觀測弱點時，按三類依序盤點：</p>
<ol>
<li><strong>盲區</strong>：哪些服務或路徑沒有被觀測到？Sampling 是否丟掉高價值樣本？</li>
<li><strong>失真</strong>：Alert noise rate 有多高？Threshold 跟 baseline 是否對齊？SLI 用的是 average 還是 percentile？</li>
<li><strong>暴露</strong>：Log / trace 是否含 secret 或 PII？Dashboard 權限是否過寬？Collector 的存取權限是否最小化？</li>
</ol>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>事故時查 trace 查不到（sampling 丟掉）</li>
<li>Service graph 跟 architecture diagram 有明顯差異（uninstrumented 服務）</li>
<li>Alert noise rate &gt; 30%（threshold drift 或 aggregation 掩蓋）</li>
<li>同一事故觸發 10+ 個 alert（alert storm、缺 grouping / inhibition）</li>
<li>Log grep 到 API key 或 token（redaction 缺失）</li>
<li>Dashboard 對所有人開放且顯示營收指標（權限過寬）</li>
</ul>
<h2 id="交接路由">交接路由</h2>
<ul>
<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</a>：context 斷鏈的修復策略</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>：alert noise control、grouping、inhibition</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>：sampling 策略與保留決策</li>
<li><a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 signal governance</a>：alert / dashboard 的定期審視</li>
<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</a>：觀測資料的存取控制與稽核</li>
<li><a href="/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">4.16 readiness review</a>：instrumentation coverage 的上線前檢查</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>：sampling bias 跟 schema drift 的品質問題</li>
<li><a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安</a>：secret management、data masking、存取控制</li>
</ul>
]]></content:encoded></item><item><title>4.6 SLI 量測與 SLO 訊號設計</title><link>https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/sli-slo-signal/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>SLI 設計起點：user-journey 而非 system metric&lt;/li>
&lt;li>量測點選擇：edge / gateway / service / dependency 各自代表什麼&lt;/li>
&lt;li>Ratio metric vs latency &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/percentile/" data-link-title="Percentile" data-link-desc="說明 p95 與 p99 如何描述長尾延遲與使用者體驗">percentile&lt;/a>：何時用哪種&lt;/li>
&lt;li>&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> 訊號：multi-window multi-burn-rate alert&lt;/li>
&lt;li>&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> 計算所需的 metric 結構&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/metrics-basics/" data-link-title="4.2 metrics 與 SLI/SLO" data-link-desc="整理 counter、gauge、histogram 與服務健康指標">4.2 metrics&lt;/a> 的分工：4.2 是 counter/gauge/histogram 基礎、4.6 是 SLI 化的設計&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">4.4 dashboard-alert&lt;/a> 的分工：4.4 是 alert 規則治理、4.6 是 alert 的訊號源頭&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>SLI 訊號設計是把可靠性目標轉成可量測資料的步驟，責任是讓 SLO 政策建立在使用者旅程與服務結果上。&lt;/p>
&lt;p>CPU、memory、queue depth 可以提供系統背景，但 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI&lt;/a> 需要回答的是使用者層面的問題：request 是否成功、回應是否夠快、結果是否正確。SLI 量測的位置跟算式決定了 SLO 反映的是「使用者體驗」還是「基礎設施健康」— 兩者的判讀意義不同。&lt;/p>
&lt;p>本章處理的是 metric 到 SLI 的轉換。&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/metrics-basics/" data-link-title="4.2 metrics 與 SLI/SLO" data-link-desc="整理 counter、gauge、histogram 與服務健康指標">4.2&lt;/a> 定義 counter / gauge / histogram 的基礎型別；本章定義怎麼用這些型別組出代表使用者體驗的 SLI，並設計 burn rate alert 的訊號結構。SLO 政策本身（error budget freeze、release gate 決策）由 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">6.6 SLO 政策&lt;/a> 處理。&lt;/p>
&lt;h2 id="sli-設計起點user-journey">SLI 設計起點：User Journey&lt;/h2>
&lt;h3 id="從使用者操作推導-sli">從使用者操作推導 SLI&lt;/h3>
&lt;p>SLI 的設計起點是「使用者在做什麼、期待什麼結果」，不是「系統有什麼 metric 可以用」。&lt;/p>
&lt;p>一個 checkout 流程的使用者期待：request 成功（不會看到 error page）、回應夠快（不會等超過 3 秒）、結果正確（扣款金額正確）。對應三種 SLI：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Availability SLI&lt;/strong>：成功 request 的比例（&lt;code>successful_requests / total_requests&lt;/code>）&lt;/li>
&lt;li>&lt;strong>Latency SLI&lt;/strong>：回應時間在閾值內的比例（&lt;code>requests_under_3s / total_requests&lt;/code>）&lt;/li>
&lt;li>&lt;strong>Correctness SLI&lt;/strong>：結果正確的比例（需要業務邏輯判定，通常用特定 error code 或 reconciliation 結果）&lt;/li>
&lt;/ul>
&lt;p>每個 user journey 不需要三種 SLI 都有。Checkout 的 availability 跟 latency 是核心；correctness 靠事後對帳驗證。搜尋頁面的 latency 比 availability 更關鍵 — 使用者容忍偶發的「搜不到結果」但不容忍 5 秒的載入。&lt;/p>
&lt;h3 id="system-metric-跟-sli-的差異">System metric 跟 SLI 的差異&lt;/h3>
&lt;p>CPU &amp;gt; 90% 不是 SLI — 它是 cause signal。CPU 高但 latency 正常，使用者沒受影響。Disk usage &amp;gt; 85% 也不是 SLI — 它是 capacity signal，需要處理但不代表當下使用者體驗退化。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>SLI 設計起點：user-journey 而非 system metric</li>
<li>量測點選擇：edge / gateway / service / dependency 各自代表什麼</li>
<li>Ratio metric vs latency <a href="/blog/backend/knowledge-cards/percentile/" data-link-title="Percentile" data-link-desc="說明 p95 與 p99 如何描述長尾延遲與使用者體驗">percentile</a>：何時用哪種</li>
<li><a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">Burn rate</a> 訊號：multi-window multi-burn-rate alert</li>
<li><a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">Error budget</a> 計算所需的 metric 結構</li>
<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</a> 的分工：4.2 是 counter/gauge/histogram 基礎、4.6 是 SLI 化的設計</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> 的分工：4.4 是 alert 規則治理、4.6 是 alert 的訊號源頭</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>SLI 訊號設計是把可靠性目標轉成可量測資料的步驟，責任是讓 SLO 政策建立在使用者旅程與服務結果上。</p>
<p>CPU、memory、queue depth 可以提供系統背景，但 <a href="/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI</a> 需要回答的是使用者層面的問題：request 是否成功、回應是否夠快、結果是否正確。SLI 量測的位置跟算式決定了 SLO 反映的是「使用者體驗」還是「基礎設施健康」— 兩者的判讀意義不同。</p>
<p>本章處理的是 metric 到 SLI 的轉換。<a href="/blog/backend/04-observability/metrics-basics/" data-link-title="4.2 metrics 與 SLI/SLO" data-link-desc="整理 counter、gauge、histogram 與服務健康指標">4.2</a> 定義 counter / gauge / histogram 的基礎型別；本章定義怎麼用這些型別組出代表使用者體驗的 SLI，並設計 burn rate alert 的訊號結構。SLO 政策本身（error budget freeze、release gate 決策）由 <a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">6.6 SLO 政策</a> 處理。</p>
<h2 id="sli-設計起點user-journey">SLI 設計起點：User Journey</h2>
<h3 id="從使用者操作推導-sli">從使用者操作推導 SLI</h3>
<p>SLI 的設計起點是「使用者在做什麼、期待什麼結果」，不是「系統有什麼 metric 可以用」。</p>
<p>一個 checkout 流程的使用者期待：request 成功（不會看到 error page）、回應夠快（不會等超過 3 秒）、結果正確（扣款金額正確）。對應三種 SLI：</p>
<ul>
<li><strong>Availability SLI</strong>：成功 request 的比例（<code>successful_requests / total_requests</code>）</li>
<li><strong>Latency SLI</strong>：回應時間在閾值內的比例（<code>requests_under_3s / total_requests</code>）</li>
<li><strong>Correctness SLI</strong>：結果正確的比例（需要業務邏輯判定，通常用特定 error code 或 reconciliation 結果）</li>
</ul>
<p>每個 user journey 不需要三種 SLI 都有。Checkout 的 availability 跟 latency 是核心；correctness 靠事後對帳驗證。搜尋頁面的 latency 比 availability 更關鍵 — 使用者容忍偶發的「搜不到結果」但不容忍 5 秒的載入。</p>
<h3 id="system-metric-跟-sli-的差異">System metric 跟 SLI 的差異</h3>
<p>CPU &gt; 90% 不是 SLI — 它是 cause signal。CPU 高但 latency 正常，使用者沒受影響。Disk usage &gt; 85% 也不是 SLI — 它是 capacity signal，需要處理但不代表當下使用者體驗退化。</p>
<p>System metric 的價值在 root cause analysis，不在 SLI。事故中先看 SLI 判斷「使用者是否受影響」，確認受影響後再看 system metric 判斷「原因是什麼」。把 system metric 當 SLI 會讓 SLO 反映基礎設施噪音而非使用者體驗。</p>
<h2 id="量測點選擇">量測點選擇</h2>
<p>SLI 的量測點影響「看到的是誰的觀點」。同一個 request 在不同位置量測會得到不同的 latency 跟 success rate。</p>
<h3 id="edge--load-balancer">Edge / Load Balancer</h3>
<p>最貼近使用者的量測點。量到的 latency 包含 network round-trip + TLS handshake + 所有 backend 處理時間。Availability 反映的是使用者實際看到的 success rate（包含 load balancer 自身的 502/503）。</p>
<p>優點是最能代表使用者體驗。缺點是 load balancer 的 metric 粒度有限 — 通常只有 status code 跟 latency，不帶 service-level 的維度切分。</p>
<h3 id="api-gateway">API Gateway</h3>
<p>比 edge 更有應用層上下文。可以按 route / method / tenant 切分 SLI。量到的 latency 不含 network round-trip（已經進入服務網路），但包含 authentication、rate limiting 跟所有下游處理。</p>
<p>API gateway 是多數團隊的 SLI 量測起點 — 粒度足夠、位置夠近使用者、通常已有 instrumentation。</p>
<h3 id="service-level">Service level</h3>
<p>每個服務的 handler-level metric。可以看到單一服務的 latency 跟 error rate，但不含上下游的影響。適合做 service-level SLO（「order service 的 p99 latency &lt; 200ms」），但不直接代表 user-journey SLO。</p>
<p>Service-level SLI 的價值在於 SLO 階層化 — user-journey SLO 拆分成每個服務的 SLO，事故時能快速定位是哪個服務的 SLO 被打破。</p>
<h3 id="dependency-level">Dependency level</h3>
<p>量測外部依賴（database、cache、third-party API）的回應時間跟 error rate。Dependency metric 的角色是 SLI 退化時的歸因訊號，用來追溯因果鏈而非直接代表使用者體驗。Database latency 上升 → service latency 上升 → user-journey latency SLO 被打破 — dependency metric 幫助追溯因果鏈。</p>
<h2 id="sli-的-metric-結構">SLI 的 Metric 結構</h2>
<h3 id="ratio-metricavailability-跟-correctness">Ratio metric：availability 跟 correctness</h3>
<p>Availability SLI 的 metric 結構需要兩個 counter：total requests 跟 successful requests（或 failed requests）。SLI = good / total。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># Availability SLI
</span></span><span class="line"><span class="ln">2</span><span class="cl">http_requests_total{service=&#34;checkout&#34;, status=&#34;2xx&#34;} / http_requests_total{service=&#34;checkout&#34;}</span></span></code></pre></div><p>定義「good」的邊界需要明確。5xx 算 bad，4xx 呢？Client error（400）通常不算服務失敗；authentication failure（401/403）也不算。但 429（rate limit）可能代表服務容量不足，視情境可能算 bad。這個邊界要在 SLI 定義時明確寫下來。</p>
<h3 id="latency-metricthreshold-based-ratio">Latency metric：threshold-based ratio</h3>
<p>Latency SLI 用 <a href="/blog/backend/knowledge-cards/histogram/" data-link-title="Histogram" data-link-desc="說明 histogram 如何用分桶統計延遲、大小與分布">histogram</a> 量測，SLI 值是「在閾值內的 request 比例」。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># Latency SLI：p99 &lt; 500ms 的比例
</span></span><span class="line"><span class="ln">2</span><span class="cl">histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{service=&#34;checkout&#34;}[5m])) &lt; 0.5
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"># 或用 ratio 形式
</span></span><span class="line"><span class="ln">5</span><span class="cl">sum(rate(http_request_duration_seconds_bucket{le=&#34;0.5&#34;,service=&#34;checkout&#34;}[5m]))
</span></span><span class="line"><span class="ln">6</span><span class="cl">/ sum(rate(http_request_duration_seconds_count{service=&#34;checkout&#34;}[5m]))</span></span></code></pre></div><p>Latency 閾值的選擇要對齊使用者期待而非系統能力。使用者期待 checkout 在 3 秒內完成 — 這是閾值的來源，不是「系統平均 latency 是 200ms 所以閾值設 500ms」。</p>
<h3 id="label-設計">Label 設計</h3>
<p>SLI metric 的 label 需要足夠的切分能力（by service、by endpoint、by tenant），但受 <a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">cardinality</a> 預算約束。</p>
<p>最小 label set：service name + method（GET/POST）+ status class（2xx/4xx/5xx）。這組 label 支撐 service-level SLO 計算。</p>
<p>擴展 label：endpoint path（normalize 後，例如 <code>/api/orders/{id}</code> → <code>/api/orders/:id</code>）、tenant（多租戶場景）。每增加一個 label 維度，series 數量乘法增長 — 在 <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> 的 label 白名單中管理。</p>
<h2 id="burn-rate-與-multi-window-alert">Burn Rate 與 Multi-window Alert</h2>
<h3 id="burn-rate-的概念">Burn rate 的概念</h3>
<p><a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">Burn rate</a> 是「error budget 被消耗的速度」。Burn rate = 1 代表按 SLO 允許的速度正常消耗；burn rate = 10 代表消耗速度是允許值的 10 倍 — 如果持續下去，error budget 會在 SLO 週期的 1/10 內耗盡。</p>
<p>用 burn rate alert 取代固定閾值 alert 的好處：burn rate 自動適應流量。低流量時段的幾筆 error 可能 burn rate 很低（因為 total 也少、對 error budget 影響小）；高流量時段的相同 error rate 可能 burn rate 很高（因為 total 多、影響的使用者量大）。</p>
<h3 id="multi-window-multi-burn-rate">Multi-window multi-burn-rate</h3>
<p>單一時間窗口的 burn rate alert 會太吵（短窗口）或太晚（長窗口）。Multi-window 策略組合兩者：</p>
<table>
  <thead>
      <tr>
          <th>視窗組合</th>
          <th>Burn rate 閾值</th>
          <th>偵測速度</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>5min + 1hr</td>
          <td>14.4x</td>
          <td>快</td>
          <td>急性問題、page</td>
      </tr>
      <tr>
          <td>30min + 6hr</td>
          <td>6x</td>
          <td>中</td>
          <td>持續退化</td>
      </tr>
      <tr>
          <td>2hr + 3day</td>
          <td>1x</td>
          <td>慢</td>
          <td>慢性消耗</td>
      </tr>
  </tbody>
</table>
<p>14.4x 的來源：若 SLO 週期是 30 天、要在 1 小時內偵測到會耗盡 2% error budget 的問題，burn rate = (30 × 24) / 1 × 0.02 ≈ 14.4。6x 跟 1x 依此邏輯調整消耗比例跟偵測窗口。</p>
<p>短窗口（5min）抓急性：error rate 突然飆高、burn rate 衝到 14.4x。長窗口（1hr）做確認：退化確實持續、排除瞬間 spike。兩個窗口都超過閾值才觸發 alert，減少單一 spike 的 false alarm。</p>
<h3 id="recording-rule-支撐-burn-rate-計算">Recording rule 支撐 burn rate 計算</h3>
<p>Burn rate 的計算涉及多個時間窗口的 ratio metric。每次 alert evaluate 都重算會給 TSDB 帶來查詢壓力。用 <a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule</a> 把每個窗口的 error ratio 預計算，alert rule 讀 recording rule 的輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># Recording rule：5 分鐘窗口的 error ratio
</span></span><span class="line"><span class="ln">2</span><span class="cl">- record: slo:checkout:error_ratio:rate5m
</span></span><span class="line"><span class="ln">3</span><span class="cl">  expr: sum(rate(http_requests_total{service=&#34;checkout&#34;,status=~&#34;5..&#34;}[5m]))
</span></span><span class="line"><span class="ln">4</span><span class="cl">      / sum(rate(http_requests_total{service=&#34;checkout&#34;}[5m]))</span></span></code></pre></div><p>Alert rule 讀 recording rule 比每次重算 raw series 高效，也讓 burn rate 的計算邏輯集中管理。</p>
<h2 id="error-budget-的-metric-結構">Error Budget 的 Metric 結構</h2>
<p><a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">Error budget</a> 是 SLO 週期內允許的錯誤量。SLO = 99.9% 代表 30 天內允許 0.1% 的 request 失敗。Error budget = total requests × 0.001。</p>
<p>Error budget 的 metric 結構需要：</p>
<ul>
<li><strong>Total requests（rolling window）</strong>：過去 30 天的 total request count</li>
<li><strong>Failed requests（rolling window）</strong>：過去 30 天的 failed request count</li>
<li><strong>Budget consumed</strong>：failed / (total × (1 - SLO target))</li>
<li><strong>Budget remaining</strong>：1 - budget consumed</li>
</ul>
<p>Budget remaining 作為 dashboard panel 跟 <a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">release gate</a> 的輸入 — 餘額低於閾值時 freeze deployment。這個計算的 rolling window 用 recording rule 維護，避免每次查詢掃描 30 天的 raw data。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 SLI 設計時，先看量測點是否貼近使用者，再看算式是否能穩定支援 error budget。</p>
<p>重點訊號包括：</p>
<ul>
<li>Edge / gateway / service / dependency 的量測點是否各自有清楚責任</li>
<li>Latency percentile 與 ratio metric 是否對應不同使用者體驗</li>
<li><a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">Burn rate</a> 是否使用多時間窗，避免太吵或太晚</li>
<li>SLI label 是否有足夠切分能力，同時受 cardinality 預算約束</li>
<li>Error budget 的 rolling window 是否用 recording rule 維護</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>Alert 用 system metric（CPU / memory）而非 user-facing 訊號</li>
<li>Burn rate 只有單窗、噪音多或偵測太晚</li>
<li>SLI 計算用平均、不用 percentile</li>
<li>Error budget 算式分母不穩（流量低時誤觸發、高時稀釋）</li>
<li>SLI 量測點離使用者太遠（內部 service 而非 edge/gateway）</li>
<li>SLI 沒有定義「什麼算 good request」的邊界（4xx 算不算 bad）</li>
<li>Burn rate 計算每次重算 raw series、沒有 recording rule</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>System metric 當 SLI</td>
          <td>CPU/memory alert 頻繁但使用者沒受影響</td>
          <td>改用 user-facing ratio / latency SLI</td>
      </tr>
      <tr>
          <td>Burn rate 單窗</td>
          <td>短窗太吵或長窗太晚、alert 價值低</td>
          <td>組合 5min+1hr / 30min+6hr 多窗策略</td>
      </tr>
      <tr>
          <td>SLI 用 average latency</td>
          <td>Tail latency 被掩蓋、p99 使用者體驗失真</td>
          <td>改用 histogram percentile</td>
      </tr>
      <tr>
          <td>Good request 邊界不明</td>
          <td>4xx 算不算 bad、SLI 值忽高忽低</td>
          <td>明確定義 good/bad 分類、寫進 SLI spec</td>
      </tr>
      <tr>
          <td>Error budget 無 rolling</td>
          <td>月初 budget 就耗盡、剩下 20 天沒有保護機制</td>
          <td>用 rolling window 持續計算、預警消耗速度</td>
      </tr>
      <tr>
          <td>SLI label 無界</td>
          <td>每個 URL path 都是獨立 SLI、series 爆炸</td>
          <td>Normalize path、label 白名單、cardinality 預算</td>
      </tr>
      <tr>
          <td>SLO 無 owner</td>
          <td>沒人維護 SLI 定義跟閾值、退化時無人負責</td>
          <td>每個 SLO 帶 owner、定期審視</td>
      </tr>
  </tbody>
</table>
<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</a>：counter / gauge / histogram 基礎型別</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>：burn rate alert 的 noise control 跟 runbook</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 / cost</a>：SLI metric 的 cardinality 預算</li>
<li><a href="/blog/backend/04-observability/client-side-monitoring/" data-link-title="4.10 Client-side / Synthetic / RUM" data-link-desc="補 server-side 看不到的 user perceived 訊號">4.10 client-side / RUM</a>：user-journey-centric SLI 的前端訊號來源</li>
<li><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：recording rule 支撐 burn rate 計算</li>
<li><a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">6.6 SLO 政策</a>：error budget 餘額作為 freeze 條件</li>
<li><a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">6.8 release gate</a>：burn rate 觸發 freeze</li>
<li><a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">8.1 incident severity</a>：burn rate 對應 severity 門檻</li>
<li><a href="/blog/backend/04-observability/anomaly-detection/" data-link-title="4.14 Anomaly Detection" data-link-desc="把 ML / statistical baseline 訊號跟 rule-based alert 整合">4.14 anomaly detection</a>：跟 SLO threshold 的訊號分工</li>
</ul>
]]></content:encoded></item><item><title>4.7 Cardinality 治理與成本邊界</title><link>https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>cardinality 為何爆：unbounded label（user_id / request_id / url path）&lt;/li>
&lt;li>metrics 的 cardinality 影響：時序資料庫 series 爆炸、查詢退化&lt;/li>
&lt;li>log 的 cardinality 影響：索引膨脹、保留成本&lt;/li>
&lt;li>trace 的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">sampling&lt;/a> 策略：head sampling vs tail sampling、tradeoff&lt;/li>
&lt;li>cost-aware observability：成本作為治理輸入而非事後賬單&lt;/li>
&lt;li>governance 控制面：label 白名單、ingestion quota、保留階梯&lt;/li>
&lt;li>高峰場景：流量尖峰時 cardinality slope 是 leading indicator&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">4.1 log schema&lt;/a> 的分工：4.1 設計欄位、4.7 設邊界&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/metrics-basics/" data-link-title="4.2 metrics 與 SLI/SLO" data-link-desc="整理 counter、gauge、histogram 與服務健康指標">4.2 metrics&lt;/a> 的分工：4.2 是 metric 種類、4.7 是 label 治理&lt;/li>
&lt;li>反模式：所有事件都打高 cardinality label、預算耗盡才砍訊號、保留策略無階梯&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Cardinality 治理是把觀測維度當成有限資源管理的流程，責任是讓訊號足夠可切分，同時不讓儲存、查詢與告警成本失控。&lt;/p>
&lt;p>這一頁處理的是成本邊界。可觀測性需要有選擇地收集訊號；它把高價值維度留在可查詢路徑，把低價值或無界維度放到更合適的資料層。&lt;/p>
&lt;p>Cardinality 跟成本的關係是非線性的。Label 數目每增加一倍，metric series 數目可能呈乘法增長；查詢延遲、儲存大小、索引重建時間都會跟著放大。把 cardinality 視為一級治理項目，能避免「收得越多越好」的直覺推著成本上升。&lt;/p>
&lt;h2 id="cardinality-在不同訊號的失分模式">Cardinality 在不同訊號的失分模式&lt;/h2>
&lt;p>Cardinality 在 metric、log、trace 三類訊號的影響機制不同，失分模式也不同。把三者用同一套治理規則處理，會在某類訊號上過度限制、在另一類上失控。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>訊號類型&lt;/th>
 &lt;th>主要失分機制&lt;/th>
 &lt;th>控制手段&lt;/th>
 &lt;th>典型 trigger&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Metric&lt;/td>
 &lt;td>TSDB series 爆炸、查詢退化&lt;/td>
 &lt;td>label 白名單、bucketize、aggregation&lt;/td>
 &lt;td>user_id / request_id 進 label&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Log&lt;/td>
 &lt;td>索引膨脹、保留成本暴增&lt;/td>
 &lt;td>索引欄位限制、結構化分層、分流&lt;/td>
 &lt;td>完整 URL / payload 進索引欄位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Trace&lt;/td>
 &lt;td>sampling 後遺失高價值樣本&lt;/td>
 &lt;td>tail sampling、minimum sample floor、 exemplar&lt;/td>
 &lt;td>head sampling 比例固定&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Metric cardinality 是最敏感的維度。Prometheus 等 pull-based TSDB 在 series 數超過數百萬時查詢退化、aggregation 失準、recording rule 跑不完。Cloud 託管型 TSDB 雖然容量更大，但每個 active series 的單價非常具體，cardinality 直接對應 vendor 月帳單。&lt;/p>
&lt;p>Log cardinality 的失分比較緩慢。Log 的 unique 值多本身不會立即崩潰，但全文索引 + 結構化欄位索引會持續膨脹，到某個臨界點查詢從毫秒退化到秒、再到分鐘。一般診斷不易察覺，要靠 query latency 跟 index size 的長期趨勢才能發現。&lt;/p>
&lt;p>Trace cardinality 的問題是另一種：sampling 過於粗暴會丟失高價值樣本。低流量服務、錯誤樣本、長尾延遲樣本若被 head sampling 平均稀釋，事故時無 trace 可看。Trace 的治理重點是 sampling 策略而非單純限制 cardinality。&lt;/p>
&lt;h2 id="高-cardinality-的常見來源">高 cardinality 的常見來源&lt;/h2>
&lt;p>無界維度進入可查詢路徑是 cardinality 失控的最大來源。常見的「無意中變成 label」：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>User / tenant identifier&lt;/strong>：把 user_id 當 label 時，每個用戶都產生一條 series。10 萬用戶 = 10 萬條 series 乘以其他 label 的笛卡爾積。&lt;/li>
&lt;li>&lt;strong>Request / session identifier&lt;/strong>：request_id、session_id、trace_id 本質是無界的，進入 metric label 後 series 無限增長。&lt;/li>
&lt;li>&lt;strong>完整 URL / path parameters&lt;/strong>：&lt;code>/users/123/orders/456&lt;/code> 這類 path 進入 label，每個 unique URL 都是新 series。&lt;/li>
&lt;li>&lt;strong>錯誤訊息 / stack trace&lt;/strong>：把 raw error message 當 label 時，每次新錯誤 = 新 series。&lt;/li>
&lt;li>&lt;strong>時間戳跟亂數&lt;/strong>：偶發出現的 bug，把 timestamp、uuid 寫進 label。&lt;/li>
&lt;/ul>
&lt;p>這些都應該進 &lt;em>log&lt;/em> 或 &lt;em>trace&lt;/em> 的欄位，不該進 &lt;em>metric&lt;/em> 的 label。Metric 的 label 應該是有界的維度：service name、environment、region、status code、http method、error class。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>cardinality 為何爆：unbounded label（user_id / request_id / url path）</li>
<li>metrics 的 cardinality 影響：時序資料庫 series 爆炸、查詢退化</li>
<li>log 的 cardinality 影響：索引膨脹、保留成本</li>
<li>trace 的 <a href="/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">sampling</a> 策略：head sampling vs tail sampling、tradeoff</li>
<li>cost-aware observability：成本作為治理輸入而非事後賬單</li>
<li>governance 控制面：label 白名單、ingestion quota、保留階梯</li>
<li>高峰場景：流量尖峰時 cardinality slope 是 leading indicator</li>
<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> 的分工：4.1 設計欄位、4.7 設邊界</li>
<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</a> 的分工：4.2 是 metric 種類、4.7 是 label 治理</li>
<li>反模式：所有事件都打高 cardinality label、預算耗盡才砍訊號、保留策略無階梯</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Cardinality 治理是把觀測維度當成有限資源管理的流程，責任是讓訊號足夠可切分，同時不讓儲存、查詢與告警成本失控。</p>
<p>這一頁處理的是成本邊界。可觀測性需要有選擇地收集訊號；它把高價值維度留在可查詢路徑，把低價值或無界維度放到更合適的資料層。</p>
<p>Cardinality 跟成本的關係是非線性的。Label 數目每增加一倍，metric series 數目可能呈乘法增長；查詢延遲、儲存大小、索引重建時間都會跟著放大。把 cardinality 視為一級治理項目，能避免「收得越多越好」的直覺推著成本上升。</p>
<h2 id="cardinality-在不同訊號的失分模式">Cardinality 在不同訊號的失分模式</h2>
<p>Cardinality 在 metric、log、trace 三類訊號的影響機制不同，失分模式也不同。把三者用同一套治理規則處理，會在某類訊號上過度限制、在另一類上失控。</p>
<table>
  <thead>
      <tr>
          <th>訊號類型</th>
          <th>主要失分機制</th>
          <th>控制手段</th>
          <th>典型 trigger</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Metric</td>
          <td>TSDB series 爆炸、查詢退化</td>
          <td>label 白名單、bucketize、aggregation</td>
          <td>user_id / request_id 進 label</td>
      </tr>
      <tr>
          <td>Log</td>
          <td>索引膨脹、保留成本暴增</td>
          <td>索引欄位限制、結構化分層、分流</td>
          <td>完整 URL / payload 進索引欄位</td>
      </tr>
      <tr>
          <td>Trace</td>
          <td>sampling 後遺失高價值樣本</td>
          <td>tail sampling、minimum sample floor、 exemplar</td>
          <td>head sampling 比例固定</td>
      </tr>
  </tbody>
</table>
<p>Metric cardinality 是最敏感的維度。Prometheus 等 pull-based TSDB 在 series 數超過數百萬時查詢退化、aggregation 失準、recording rule 跑不完。Cloud 託管型 TSDB 雖然容量更大，但每個 active series 的單價非常具體，cardinality 直接對應 vendor 月帳單。</p>
<p>Log cardinality 的失分比較緩慢。Log 的 unique 值多本身不會立即崩潰，但全文索引 + 結構化欄位索引會持續膨脹，到某個臨界點查詢從毫秒退化到秒、再到分鐘。一般診斷不易察覺，要靠 query latency 跟 index size 的長期趨勢才能發現。</p>
<p>Trace cardinality 的問題是另一種：sampling 過於粗暴會丟失高價值樣本。低流量服務、錯誤樣本、長尾延遲樣本若被 head sampling 平均稀釋，事故時無 trace 可看。Trace 的治理重點是 sampling 策略而非單純限制 cardinality。</p>
<h2 id="高-cardinality-的常見來源">高 cardinality 的常見來源</h2>
<p>無界維度進入可查詢路徑是 cardinality 失控的最大來源。常見的「無意中變成 label」：</p>
<ul>
<li><strong>User / tenant identifier</strong>：把 user_id 當 label 時，每個用戶都產生一條 series。10 萬用戶 = 10 萬條 series 乘以其他 label 的笛卡爾積。</li>
<li><strong>Request / session identifier</strong>：request_id、session_id、trace_id 本質是無界的，進入 metric label 後 series 無限增長。</li>
<li><strong>完整 URL / path parameters</strong>：<code>/users/123/orders/456</code> 這類 path 進入 label，每個 unique URL 都是新 series。</li>
<li><strong>錯誤訊息 / stack trace</strong>：把 raw error message 當 label 時，每次新錯誤 = 新 series。</li>
<li><strong>時間戳跟亂數</strong>：偶發出現的 bug，把 timestamp、uuid 寫進 label。</li>
</ul>
<p>這些都應該進 <em>log</em> 或 <em>trace</em> 的欄位，不該進 <em>metric</em> 的 label。Metric 的 label 應該是有界的維度：service name、environment、region、status code、http method、error class。</p>
<h2 id="高峰場景的-cardinality-失控">高峰場景的 cardinality 失控</h2>
<p>高峰場景的 cardinality 治理責任是讓「平時可控的 series 上限」在尖峰時仍能維持決策可用。平時 cardinality 看似穩定，高峰時可能突然出現新 tenant、新 endpoint、新 error class 的湧入，把 series 推到平台極限；治理重點是把「成長斜率」「容量緩衝」「dry-run」「freshness gap」變成預先設計的訊號、而非高峰中即興救火。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/gaming-peak-signal-freshness-and-cardinality/" data-link-title="Gaming：高峰流量下的訊號新鮮度與 Cardinality" data-link-desc="在高峰事件中控制訊號延遲與維度爆炸，維持告警與定位可信度。">4.C2 Gaming 高峰流量下的訊號新鮮度與 Cardinality</a>：揭露「ingestion lag、cardinality growth slope、alert freshness gap」是高峰場景的核心治理項目（三個訊號名稱屬 case 直接列出）；以下做法基於通用工程知識展開。</p>
<p>高峰場景的可操作做法：</p>
<ol>
<li><strong>把 cardinality growth slope 視為 leading indicator</strong>：series 數目的成長斜率比絕對值更早反映異常。突然出現的快速上升通常意味著新 label 值湧入或既有 label 失控。</li>
<li><strong>預設容量 buffer</strong>：日常使用容量設在平台上限的 50-60%，留高峰時 cardinality 突發空間。把容量推到 90% 才追加治理會在高峰時來不及。</li>
<li><strong>高峰前的 dry-run</strong>：把預期高峰流量的 cardinality 估算進 capacity model，找出可能的 unbounded label。對應 <a href="/blog/backend/09-performance-capacity/capacity-planning/" data-link-title="9.6 容量規劃模型" data-link-desc="peak forecast、headroom budget、growth curve、autoscaling sizing">9.6 容量規劃模型</a>。</li>
<li><strong>Alert freshness gap 也要監控</strong>：高峰時 ingestion lag 上升、告警延遲、值班決策落在過期資料上的風險。把 alert freshness（資料時間 vs 當前時間）變成 dashboard 訊號。</li>
</ol>
<p>高峰結束後做 retrospective：哪些 label 在高峰時超出預期、哪些 alert 因延遲沒及時觸發、哪些 series 應該下次提前 bucketize。這個 retrospective 是治理閉環的一部分，由 <a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 signal-governance-loop</a> 處理長期回寫。</p>
<h2 id="sampling-策略">Sampling 策略</h2>
<p>本章是 04 模組的 sampling 策略 SSoT — Head / Tail / Adaptive / Exemplar 四類策略集中在此；sampling 對資料品質的失真風險（low-traffic bias、error sample loss、tail latency loss）由 <a href="/blog/backend/04-observability/telemetry-data-quality/#sampling-%e8%88%87%e4%bb%a3%e8%a1%a8%e6%80%a7" data-link-title="4.17 Telemetry Data Quality" data-link-desc="把 missing signal、schema drift、sampling bias 與 timestamp skew 變成資料品質問題">4.17 Sampling 與代表性</a> 處理；trace context 層的 sampling 配置由 <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> 處理。</p>
<p>Sampling 策略的核心責任是控制觀測成本、同時保留足以判讀的高價值樣本。固定比例 head sampling 是最常見、也是最容易丟失高價值樣本的策略。</p>
<table>
  <thead>
      <tr>
          <th>策略類型</th>
          <th>機制</th>
          <th>適用場景</th>
          <th>主要風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Head sampling</td>
          <td>在 trace 開始時決定是否採樣</td>
          <td>簡單、低延遲、collector 端低資源</td>
          <td>不知道 trace 結果就決定、可能丟錯誤</td>
      </tr>
      <tr>
          <td>Tail sampling</td>
          <td>等 trace 結束後再決定（看是否錯誤、長延遲）</td>
          <td>保留錯誤、保留 outlier</td>
          <td>collector 要 buffer 整條 trace、資源高</td>
      </tr>
      <tr>
          <td>Adaptive sampling</td>
          <td>按服務、tenant、流量動態調整比例</td>
          <td>多租戶、流量差異大</td>
          <td>規則複雜、需要監控 sampling rate</td>
      </tr>
      <tr>
          <td>Exemplar attachment</td>
          <td>metric 帶代表性 trace id 樣本</td>
          <td>從 metric 跳到 trace</td>
          <td>不解決 sampling 本身、是補充</td>
      </tr>
  </tbody>
</table>
<p>實務上常用組合：低流量服務用接近 100% 採樣（minimum sample floor）、高流量服務用 tail sampling 保留錯誤跟長尾、metric 帶 exemplar 讓從 dashboard 跳到 trace。</p>
<p>四類策略各自的適用情境：</p>
<ul>
<li><strong>Head sampling</strong> 適合單體應用、延遲敏感、collector 端資源吃緊的場景。代價是 trace 開始時無法判斷是否錯誤、會等比例丟掉錯誤樣本。</li>
<li><strong>Tail sampling</strong> 適合微服務、需保留錯誤跟長尾的場景。代價是 collector 要 buffer 整條 trace、記憶體跟 CPU 用量明顯增加、對 cluster gateway 容量規劃壓力大。</li>
<li><strong>Adaptive sampling</strong> 適合多租戶、流量差異大的場景。風險是規則複雜化會造成 sampling rate 漂移、必須持續監控每個 service / tenant 的實際保留比例、否則治理會失控。</li>
<li><strong>Exemplar attachment</strong> 補強 metric → trace 跳轉、不解決 sampling 本身。在已有 head/tail sampling 的場景上加 exemplar 是低成本高價值的做法。</li>
</ul>
<p>關鍵是 sampling policy 本身要可被服務團隊理解跟調整。把 sampling 規則寫在 collector 配置裡、版本化、跟著 release 一起管理；把當前 sampling rate 跟保留分布暴露在 dashboard 上。當服務團隊發現某段時間 trace 殘缺、要能直接查到 sampling policy 的當下值跟變更紀錄。</p>
<h2 id="控制面與保留階梯">控制面與保留階梯</h2>
<p>可操作的 cardinality / 成本治理控制面有四層，從預防到事後審計都要覆蓋。</p>
<ol>
<li><strong>設計時 label 白名單</strong>：服務團隊新增 metric 時要 review label 是否在白名單內。白名單列出有界維度（service、env、region、status_code、error_class、http_method），明確排除 user_id、request_id、完整 URL。</li>
<li><strong>Ingestion 層 quota 與 cardinality limit</strong>：collector 或 vendor 端設定每服務、每 tenant 的 series 上限。超過上限時觸發告警，並啟動 graceful 降級（保留高優先 series、其他暫停）。</li>
<li><strong>保留階梯</strong>：依資料熱度跟法規責任分層保留。熱資料（最近 7 天）full granularity、溫資料（7-30 天）aggregated、冷資料（30+ 天）長期歸檔。階梯設計要結合 <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> 的法規保留期。</li>
<li><strong>成本歸屬到 owner</strong>：把 ingestion、storage、query 成本拆到服務或團隊維度。沒有歸屬的成本會被視為平台問題，治理動力不會傳到產生成本的團隊。詳見 <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>。</li>
</ol>
<p>保留階梯的另一個價值是事故時的容量保護。當熱資料儲存接近滿載、可以加速冷化、主動釋放容量給當下事件、避免被動等保留期到再恢復。</p>
<h2 id="storage-tiering-對查詢能力的影響"><a href="/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">Storage tiering</a> 對查詢能力的影響</h2>
<p>保留階梯不只是成本工具，它直接決定不同時間範圍的查詢能力。每一層的儲存介質、索引密度、<a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup</a> 精度決定了該層能回答什麼問題、不能回答什麼問題。</p>
<h3 id="每一層能回答什麼">每一層能回答什麼</h3>
<p>Hot tier 保留完整精度與完整索引，能支援即席診斷的所有維度切片（by service、by tenant、by error code、by request id）。當資料從 hot 移到 warm，部分索引可能被移除、精度可能被 rollup 降低，能做的查詢從「特定 request id 的完整事件鏈」退化為「某服務過去兩週的 error rate 趨勢」。到 cold tier，通常只剩 timestamp + 少數結構化欄位的最小索引，細節查詢需要先 rehydrate 回 warm 或 hot 層。</p>
<p>這個退化是設計選擇，但需要被使用者感知。事故復盤時，如果團隊想查兩週前的特定 request 但資料已在 warm tier 且 request id 索引被移除，他們需要知道「不是沒有資料，而是需要 rehydrate 才能查」。</p>
<h3 id="跨層查詢的延遲跳變">跨層查詢的延遲跳變</h3>
<p>Dashboard 的時間範圍選擇直接觸發跨層查詢。使用者從「最近 1 小時」（全部在 hot tier）拉到「最近 7 天」（hot + warm tier），查詢延遲從毫秒跳到秒級。再拉到「最近 90 天」（hot + warm + cold tier），延遲可能跳到十秒甚至分鐘級。</p>
<p>這種延遲跳變在事故中的影響是：incident commander 想看長期趨勢來判斷異常是突發還是漸進時，dashboard 卡在載入。應對方式是在 dashboard 設計時就把「長時間趨勢」panel 指向 <a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule</a> 或 rollup series，讓它讀取預聚合資料而非跨層掃描 raw data。</p>
<h3 id="tier-邊界依訊號類型差異化">Tier 邊界依訊號類型差異化</h3>
<p>不同訊號類型的 tier 邊界應該不同。Error log 跟 trace 的事故診斷價值比 debug log 高，hot tier 保留期應該更長。<a href="/blog/backend/04-observability/audit-log-governance/" data-link-title="4.12 Audit Log 邊界與 PII 治理" data-link-desc="把稽核訊號從 operational log 拆出、按法規與不變性治理">Audit log</a> 因合規要求可能需要長期可查詢而非純歸檔。SLO-critical 的 metric series 可能需要 hot tier 保留 30 天來支援 monthly burn rate 計算，而 debug-level 的 metric 只需要 7 天 hot tier。</p>
<p>把所有訊號用同一個 tier 邊界管理（「全部 7 天 hot、30 天 warm、1 年 cold」）會讓高價值訊號過早退化、低價值訊號佔用過多 hot tier 容量。依訊號優先級設定差異化的 tier 邊界是保留階梯設計的進階步驟。</p>
<p>詳細的跨訊號查詢設計見 <a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 cardinality 時，先看維度是否有決策價值，再看它是否有上界。</p>
<p>重點訊號包括：</p>
<ul>
<li>user id、request id、完整 URL 是否進入不該承受的 metric label</li>
<li>log index 是否只索引常用查詢欄位</li>
<li>trace <a href="/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">sampling</a> 是否能優先保留高價值樣本</li>
<li><a href="/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention</a> 是否依資料熱度與法規責任分層</li>
<li>cardinality growth slope 是否被監控為 leading indicator</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>metric series 數量曲線陡升、TSDB 查詢退化</li>
<li>log ingestion 成本月對月雙位數成長</li>
<li>label 含 user_id / request_id / 完整 URL 直接送到 metric</li>
<li>ingestion quota 觸發時靠砍訊號救火、無 graceful 降階</li>
<li>保留策略全平、無冷熱分層、舊資料拖累查詢</li>
<li>高峰時 alert freshness gap 擴大、值班用過期資料</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>無界 label 進 metric</td>
          <td>user_id / request_id 在 label 中</td>
          <td>label 白名單、把細粒度放到 log / trace</td>
      </tr>
      <tr>
          <td>預算耗盡才砍訊號</td>
          <td>quota 觸發後緊急砍 series</td>
          <td>平時設成長告警、緩衝容量 50-60%</td>
      </tr>
      <tr>
          <td>保留策略全平</td>
          <td>所有 log / metric 都留 30 天</td>
          <td>依熱度跟法規分階、結合 audit retention</td>
      </tr>
      <tr>
          <td>Sampling 比例固定</td>
          <td>head sampling 10% 套全部服務</td>
          <td>低流量 100%、錯誤強制保留、tail sampling</td>
      </tr>
      <tr>
          <td>成本無歸屬</td>
          <td>平台付帳、團隊無動力治理</td>
          <td>歸屬到 service owner、進 cost attribution</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><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>：SLI metric 的 cardinality 上限</li>
<li><a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 signal-governance-loop</a>：高峰 retrospective 回寫治理</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 層 quota 執行</li>
<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 保留期銜接</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>：成本治理的責任分配層</li>
<li><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：storage tiering 對查詢能力的完整設計</li>
<li><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 容量成本</a>：observability 成本作為容量規劃輸入</li>
<li><a href="/blog/backend/04-observability/vendors/" data-link-title="可觀測性 Vendor 清單" data-link-desc="規劃 telemetry standard、metrics、logs、traces、APM 與 error tracking 的服務頁撰寫順序與判準">vendors</a>：各平台的 ingestion / query quota 模型</li>
</ul>
]]></content:encoded></item><item><title>4.8 訊號治理閉環</title><link>https://tarrragon.github.io/blog/backend/04-observability/signal-governance-loop/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/signal-governance-loop/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>為何訊號需要治理閉環：alert / metric / dashboard 是會老化的資產&lt;/li>
&lt;li>偵測缺口的來源：post-incident review、chaos test、日常 noise&lt;/li>
&lt;li>訊號生命週期：新增 → 調整 → 淘汰&lt;/li>
&lt;li>Alert 健康度量測&lt;/li>
&lt;li>Dashboard 健康度量測&lt;/li>
&lt;li>治理節奏與 ownership&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>訊號治理閉環是把事故、演練與日常使用經驗回寫到觀測系統的流程，責任是讓 alert、metric 與 dashboard 隨服務變化而更新。&lt;/p>
&lt;p>觀測資產會老化：服務拓撲會變、流量型態會變、告警接收者會離職或轉組。設定一次就不再動的 alert rule 會在數月後變成 noise 來源；建立一次就不再看的 dashboard 會累積成系統負擔。訊號治理把觀測系統當成需要持續維護的產品，而非建好就完成的基礎設施。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/dashboard-alert/" data-link-title="4.4 dashboard 與 alert 設計" data-link-desc="讓 dashboard 與 alert 對應 runbook 與容量趨勢">4.4 dashboard-alert&lt;/a> 的分工：4.4 處理設計（怎麼設計好的 dashboard 跟 alert），4.8 處理維運與淘汰（設計好之後怎麼讓它們持續有效）。&lt;/p>
&lt;h2 id="偵測缺口的來源">偵測缺口的來源&lt;/h2>
&lt;h3 id="post-incident-review">Post-incident review&lt;/h3>
&lt;p>每次事故的 &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> 都可能揭露偵測缺口 — 事故發生到被偵測到的時間太長、alert 觸發了但指向錯誤的方向、或根本沒有 alert 觸發。&lt;/p>
&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>訊號缺失&lt;/td>
 &lt;td>問題存在但沒有對應的 metric 或 trace&lt;/td>
 &lt;td>新增 metric / span&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Alert 太晚&lt;/td>
 &lt;td>Alert 在使用者投訴後才觸發&lt;/td>
 &lt;td>調整閾值或加短窗&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Alert 指向錯誤&lt;/td>
 &lt;td>Alert 觸發了但指向不相關的服務&lt;/td>
 &lt;td>修正 alert rule&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dashboard 沒有對應視圖&lt;/td>
 &lt;td>事故中需要看某個維度但現有 dashboard 沒有&lt;/td>
 &lt;td>新增 panel&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>關聯性斷裂&lt;/td>
 &lt;td>Log / trace / metric 無法用同一個 ID 串連&lt;/td>
 &lt;td>補 correlation field&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Post-incident review 的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/action-item-closure/" data-link-title="Action Item Closure" data-link-desc="說明事故行動項如何被驗證完成，而不是只停留在待辦清單">action items&lt;/a> 中標記為「detection gap」的項目，應該指派給觀測系統的 owner，帶明確的 metric / alert / dashboard 變更規格。&lt;/p>
&lt;h3 id="chaos-test-與演練">Chaos test 與演練&lt;/h3>
&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> 跟災難恢復演練會在受控條件下暴露觀測盲區。注入 dependency failure 後，觀測系統是否在預期時間內觸發 alert？Alert 是否指向正確的方向？Dashboard 是否有足夠的 panel 支援診斷？&lt;/p>
&lt;p>演練揭露的盲區跟事故揭露的盲區性質相同，但成本更低 — 在受控環境發現的缺口不會拉長真實事故的 MTTR。&lt;/p>
&lt;h3 id="日常-noise-累積">日常 noise 累積&lt;/h3>
&lt;p>Alert noise 的日常累積是漸進式的退化 — 每個月新增幾個 alert rule 但沒有淘汰舊的，noise rate 從 10% 慢慢升到 30% 再到 50%。退化的訊號是 on-call 工程師開始忽略某些 alert（先 ack 再看、或直接 resolve 不看）。&lt;/p>
&lt;h2 id="訊號生命週期">訊號生命週期&lt;/h2>
&lt;h3 id="新增">新增&lt;/h3>
&lt;p>新訊號的來源：新服務上線時的 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">readiness review&lt;/a> 檢查、post-incident review 的 detection gap、chaos test 暴露的盲區、新功能上線時的 SLI 定義。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>為何訊號需要治理閉環：alert / metric / dashboard 是會老化的資產</li>
<li>偵測缺口的來源：post-incident review、chaos test、日常 noise</li>
<li>訊號生命週期：新增 → 調整 → 淘汰</li>
<li>Alert 健康度量測</li>
<li>Dashboard 健康度量測</li>
<li>治理節奏與 ownership</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>訊號治理閉環是把事故、演練與日常使用經驗回寫到觀測系統的流程，責任是讓 alert、metric 與 dashboard 隨服務變化而更新。</p>
<p>觀測資產會老化：服務拓撲會變、流量型態會變、告警接收者會離職或轉組。設定一次就不再動的 alert rule 會在數月後變成 noise 來源；建立一次就不再看的 dashboard 會累積成系統負擔。訊號治理把觀測系統當成需要持續維護的產品，而非建好就完成的基礎設施。</p>
<p>跟 <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> 的分工：4.4 處理設計（怎麼設計好的 dashboard 跟 alert），4.8 處理維運與淘汰（設計好之後怎麼讓它們持續有效）。</p>
<h2 id="偵測缺口的來源">偵測缺口的來源</h2>
<h3 id="post-incident-review">Post-incident review</h3>
<p>每次事故的 <a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">post-incident review</a> 都可能揭露偵測缺口 — 事故發生到被偵測到的時間太長、alert 觸發了但指向錯誤的方向、或根本沒有 alert 觸發。</p>
<p>偵測缺口的分類：</p>
<table>
  <thead>
      <tr>
          <th>缺口類型</th>
          <th>典型表現</th>
          <th>回寫方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>訊號缺失</td>
          <td>問題存在但沒有對應的 metric 或 trace</td>
          <td>新增 metric / span</td>
      </tr>
      <tr>
          <td>Alert 太晚</td>
          <td>Alert 在使用者投訴後才觸發</td>
          <td>調整閾值或加短窗</td>
      </tr>
      <tr>
          <td>Alert 指向錯誤</td>
          <td>Alert 觸發了但指向不相關的服務</td>
          <td>修正 alert rule</td>
      </tr>
      <tr>
          <td>Dashboard 沒有對應視圖</td>
          <td>事故中需要看某個維度但現有 dashboard 沒有</td>
          <td>新增 panel</td>
      </tr>
      <tr>
          <td>關聯性斷裂</td>
          <td>Log / trace / metric 無法用同一個 ID 串連</td>
          <td>補 correlation field</td>
      </tr>
  </tbody>
</table>
<p>Post-incident review 的 <a href="/blog/backend/knowledge-cards/action-item-closure/" data-link-title="Action Item Closure" data-link-desc="說明事故行動項如何被驗證完成，而不是只停留在待辦清單">action items</a> 中標記為「detection gap」的項目，應該指派給觀測系統的 owner，帶明確的 metric / alert / dashboard 變更規格。</p>
<h3 id="chaos-test-與演練">Chaos test 與演練</h3>
<p><a href="/blog/backend/knowledge-cards/chaos-test/" data-link-title="Chaos Test" data-link-desc="說明透過受控故障注入驗證系統在異常條件下的恢復能力">Chaos test</a> 跟災難恢復演練會在受控條件下暴露觀測盲區。注入 dependency failure 後，觀測系統是否在預期時間內觸發 alert？Alert 是否指向正確的方向？Dashboard 是否有足夠的 panel 支援診斷？</p>
<p>演練揭露的盲區跟事故揭露的盲區性質相同，但成本更低 — 在受控環境發現的缺口不會拉長真實事故的 MTTR。</p>
<h3 id="日常-noise-累積">日常 noise 累積</h3>
<p>Alert noise 的日常累積是漸進式的退化 — 每個月新增幾個 alert rule 但沒有淘汰舊的，noise rate 從 10% 慢慢升到 30% 再到 50%。退化的訊號是 on-call 工程師開始忽略某些 alert（先 ack 再看、或直接 resolve 不看）。</p>
<h2 id="訊號生命週期">訊號生命週期</h2>
<h3 id="新增">新增</h3>
<p>新訊號的來源：新服務上線時的 <a href="/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">readiness review</a> 檢查、post-incident review 的 detection gap、chaos test 暴露的盲區、新功能上線時的 SLI 定義。</p>
<p>新增訊號時要同時定義：metric / alert 的 owner、預期的 noise rate baseline、review 週期、淘汰條件。沒有 owner 跟 review 週期的訊號會在累積後變成治理負擔。</p>
<h3 id="調整">調整</h3>
<p>調整的觸發條件：alert threshold 跟當前 baseline 偏差過大、dashboard panel 的資料來源（metric name、label）已改變、alert 的 runbook link 過期、noise rate 超過團隊可接受的上限。</p>
<p>調整是訊號治理的主要日常工作。多數訊號不需要刪除，但需要隨服務演進跟著更新。</p>
<h3 id="淘汰">淘汰</h3>
<p>淘汰的觸發條件：alert rule 超過 N 天（例如 180 天）沒有觸發、dashboard 超過 N 天沒有人訪問、metric 被 recording rule 取代後原始查詢不再使用、服務已下線但 alert / dashboard 還在。</p>
<p>淘汰需要 owner 確認。自動淘汰（超過 180 天不觸發就自動刪除）風險太高 — 有些 alert 本來就是極低頻但極高價值（年度高峰才觸發的 capacity alert）。安全做法是自動標記候選淘汰，由 owner 在定期審視中決定保留或刪除。</p>
<h2 id="alert-健康度量測">Alert 健康度量測</h2>
<p>Alert 的健康度用四個指標追蹤：</p>
<p><strong>Noise rate</strong>：不需要行動的 alert / 總 alert。On-call 在 ack 時標記 actionable / noise。月度彙整。目標：&lt; 30%。</p>
<p><strong>MTTD（Mean Time to Detect）</strong>：事故開始到 alert 觸發的時間。從 incident timeline 回溯。目標：跟 SLO burn rate window 對齊（急性問題 &lt; 5 分鐘）。</p>
<p><strong>False positive rate</strong>：alert 觸發但事後確認沒有問題 / 總 alert。跟 noise rate 不同 — noise 包含 redundant alert（有問題但重複），false positive 是真的沒問題。</p>
<p><strong>Coverage</strong>：有 alert 覆蓋的 user journey / 總 user journey。未覆蓋的 user journey 代表潛在的偵測盲區。</p>
<h2 id="dashboard-健康度量測">Dashboard 健康度量測</h2>
<p>Dashboard 的健康度用三個指標追蹤：</p>
<p><strong>訪問頻率</strong>：每個 dashboard 的每週 / 每月訪問次數。Grafana 的 usage analytics 或 access log 可以提供。長期零訪問的 dashboard 是候選淘汰。</p>
<p><strong>Data freshness</strong>：Dashboard panel 是否顯示有效資料。Panel 因 metric name 改變或 label 漂移而回空值時，曲線看起來是平的零線 — 容易被誤讀成「一切正常」。定期掃描所有 panel 的 no-data 狀態。</p>
<p><strong>Owner coverage</strong>：有 owner 的 dashboard / 總 dashboard。沒有 owner 的 dashboard 沒人負責更新，退化只是時間問題。</p>
<h2 id="治理節奏">治理節奏</h2>
<p>訊號治理需要固定節奏，避免「只在事故後才補訊號、平時不管」的反應式治理。</p>
<p><strong>事故驅動（每次事故後）</strong>：Post-incident review 的 detection gap action items 在兩週內 close — 新增 / 調整的 metric、alert、dashboard 已部署並驗證。</p>
<p><strong>定期審視（每季）</strong>：</p>
<ul>
<li>Alert noise rate 報告：noise rate &gt; 30% 的 alert rule 進入調整或淘汰流程</li>
<li>Dashboard 訪問頻率報告：零訪問 dashboard 進入淘汰審視</li>
<li>Orphan alert / dashboard（owner 離職或轉組、未交接）指派新 owner</li>
</ul>
<p><strong>年度回顧</strong>：</p>
<ul>
<li>觀測覆蓋率（有 instrumentation 的服務 / 總服務）</li>
<li>SLI / SLO 的量測點跟閾值是否需要調整（業務變化、流量變化）</li>
<li>觀測成本 vs 事故成本的 ROI 評估</li>
</ul>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀訊號治理時，先看缺口是否有來源，再看改善項是否真的關閉。</p>
<p>重點訊號包括：</p>
<ul>
<li><a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">Post-incident review</a> 是否把偵測缺口轉成具體 metric / alert / dashboard 變更</li>
<li><a href="/blog/backend/knowledge-cards/chaos-test/" data-link-title="Chaos Test" data-link-desc="說明透過受控故障注入驗證系統在異常條件下的恢復能力">Chaos test</a> 或 DR 演練是否暴露新的觀測盲區</li>
<li>Alert noise、ack time、false positive 是否有趨勢追蹤</li>
<li>Orphan dashboard 與過期 alert 是否有定期清理節奏</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>Alert 數量只增不減、無淘汰流程</li>
<li>Alert noise rate &gt; 30%、ack 後無實際動作</li>
<li>Dashboard 半年無人訪問、仍存在於主目錄</li>
<li>Post-incident review action items 大半 open &gt; 90 天</li>
<li>同類事故重複發生、觀測系統無更新</li>
<li>Alert owner 離職後無人接手、alert 成為孤兒</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Alert 只增不減</td>
          <td>數百個 alert rule、多數是 noise</td>
          <td>定期審視 + 自動標記候選淘汰</td>
      </tr>
      <tr>
          <td>Dashboard 全是裝飾</td>
          <td>事故時沒人打開、只有 demo 時展示</td>
          <td>追蹤訪問頻率、零訪問的淘汰</td>
      </tr>
      <tr>
          <td>Post-incident action 永遠 open</td>
          <td>Detection gap 被記錄但半年沒 close</td>
          <td>兩週 close 期限、逾期自動升級</td>
      </tr>
      <tr>
          <td>治理只在事故後才啟動</td>
          <td>平時不管、出事才補</td>
          <td>建立每季定期審視節奏</td>
      </tr>
      <tr>
          <td>Orphan alert 無人負責</td>
          <td>Owner 離職後 alert 持續觸發但沒人處理</td>
          <td>交接流程 + orphan 掃描</td>
      </tr>
      <tr>
          <td>Chaos test 不看觀測面</td>
          <td>只看服務恢復、不看 alert 跟 dashboard 表現</td>
          <td>Chaos hypothesis 包含觀測預期</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<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>：alert / dashboard 的設計原則</li>
<li><a href="/blog/backend/04-observability/attacker-view-observability-risks/" data-link-title="4.5 可觀測性威脅建模（Threat Modeling）" data-link-desc="從觀測盲區、告警失真與資料暴露風險，盤點 observability 的主要弱點">4.5 威脅建模</a>：告警失真作為觀測弱點</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>：新訊號的成本邊界</li>
<li><a href="/blog/backend/04-observability/anomaly-detection/" data-link-title="4.14 Anomaly Detection" data-link-desc="把 ML / statistical baseline 訊號跟 rule-based alert 整合">4.14 anomaly detection</a>：anomaly false positive 的淘汰</li>
<li><a href="/blog/backend/04-observability/observability-readiness-review/" data-link-title="4.16 Observability Readiness Review" data-link-desc="在服務上線、重大變更與演練前檢查 log / metric / trace / alert 是否可支援事故判讀">4.16 readiness review</a>：上線前的觀測覆蓋檢查</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：ownership 矩陣</li>
<li><a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">8.5 post-incident review</a>：action items 回寫機制</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 三個模組的雙向反饋串成可判讀循環，定義閉環健康度判讀訊號">8.11 閉環</a>：跨模組視角的閉環</li>
</ul>
]]></content:encoded></item><item><title>4.9 Continuous Profiling</title><link>https://tarrragon.github.io/blog/backend/04-observability/continuous-profiling/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/continuous-profiling/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>Continuous profiling 的定位：metrics / logs / traces 之外的第四角&lt;/li>
&lt;li>Profile 維度：CPU、heap、allocations、lock contention、goroutine / async task&lt;/li>
&lt;li>Always-on vs on-demand：何時用哪種&lt;/li>
&lt;li>Flame graph 與版本差異比較&lt;/li>
&lt;li>Overhead 控制&lt;/li>
&lt;li>Vendor 定位&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Continuous profiling 是把 CPU、memory、allocation 與 lock contention 變成長期可比較的 production 訊號，責任是補上 metrics、logs、traces 看不到的 callstack 成本。&lt;/p>
&lt;p>Metrics 會告訴你「CPU usage 上升了」，trace 會告訴你「這條 request 的 latency 從 200ms 變成 800ms」，profile 會告訴你「增加的 600ms 花在哪幾個 function call、哪幾行程式碼」。Profile 是唯一能精確到 callstack level 的觀測訊號。&lt;/p>
&lt;p>「Continuous」的關鍵差異是：傳統 profiling 是事故時才手動開啟，continuous profiling 是 production 常駐的低開銷採樣。事故時不需要重現問題 — baseline profile 已經在那裡，直接跟事故期間的 profile 做 diff。&lt;/p>
&lt;h2 id="profile-維度">Profile 維度&lt;/h2>
&lt;p>不同的 profile 維度回答不同的效能問題。服務的退化模式決定需要哪些維度。&lt;/p>
&lt;h3 id="cpu-profile">CPU profile&lt;/h3>
&lt;p>回答「CPU 時間花在哪些 function」。最常用的 profile 維度。適合診斷 latency 退化（某個 function 開始佔更多 CPU 時間）跟 CPU 利用率異常（某段程式碼意外進入 hot path）。&lt;/p>
&lt;p>CPU profile 用 sampling 方式採集 — 定期（例如每秒 100 次）記錄當前的 callstack。統計意義上，出現在 sample 中的次數跟實際 CPU 消耗成正比。Sampling 頻率越高精度越好，但 overhead 也越高。&lt;/p>
&lt;h3 id="heap--memory-profile">Heap / memory profile&lt;/h3>
&lt;p>回答「memory 被哪些 function 持有」。適合診斷 memory leak（allocation 持續增長、GC 回收不了）跟 GC pressure（大量短命物件導致 GC 頻繁）。&lt;/p>
&lt;p>Heap profile 記錄的是某個時間點的 live object 分布。Allocation profile 記錄的是一段時間內誰做了多少 allocation — 兩者互補。Memory leak 用 heap profile 的時間趨勢看；GC pressure 用 allocation profile 看。&lt;/p>
&lt;h3 id="lock-contention-profile">Lock contention profile&lt;/h3>
&lt;p>回答「哪些 lock 的等待時間最長」。適合診斷 mutex contention（多個 thread / goroutine 搶同一把 lock、等待時間累積成 latency）。&lt;/p>
&lt;p>Lock profile 在高並發服務的診斷中特別有用。Metrics 只能看到整體 latency 上升；trace 能看到某個 span 變慢；lock profile 能精確定位是哪把 lock 在哪個 callstack 被等待。&lt;/p>
&lt;h3 id="goroutine--async-task-profile">Goroutine / async task profile&lt;/h3>
&lt;p>Go 的 goroutine profile 回答「有多少 goroutine、它們在做什麼（running / waiting / blocked）」。Goroutine leak（goroutine 數量持續增長、都在等待某個 channel 或 lock）是 Go 服務常見的退化模式。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>Continuous profiling 的定位：metrics / logs / traces 之外的第四角</li>
<li>Profile 維度：CPU、heap、allocations、lock contention、goroutine / async task</li>
<li>Always-on vs on-demand：何時用哪種</li>
<li>Flame graph 與版本差異比較</li>
<li>Overhead 控制</li>
<li>Vendor 定位</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Continuous profiling 是把 CPU、memory、allocation 與 lock contention 變成長期可比較的 production 訊號，責任是補上 metrics、logs、traces 看不到的 callstack 成本。</p>
<p>Metrics 會告訴你「CPU usage 上升了」，trace 會告訴你「這條 request 的 latency 從 200ms 變成 800ms」，profile 會告訴你「增加的 600ms 花在哪幾個 function call、哪幾行程式碼」。Profile 是唯一能精確到 callstack level 的觀測訊號。</p>
<p>「Continuous」的關鍵差異是：傳統 profiling 是事故時才手動開啟，continuous profiling 是 production 常駐的低開銷採樣。事故時不需要重現問題 — baseline profile 已經在那裡，直接跟事故期間的 profile 做 diff。</p>
<h2 id="profile-維度">Profile 維度</h2>
<p>不同的 profile 維度回答不同的效能問題。服務的退化模式決定需要哪些維度。</p>
<h3 id="cpu-profile">CPU profile</h3>
<p>回答「CPU 時間花在哪些 function」。最常用的 profile 維度。適合診斷 latency 退化（某個 function 開始佔更多 CPU 時間）跟 CPU 利用率異常（某段程式碼意外進入 hot path）。</p>
<p>CPU profile 用 sampling 方式採集 — 定期（例如每秒 100 次）記錄當前的 callstack。統計意義上，出現在 sample 中的次數跟實際 CPU 消耗成正比。Sampling 頻率越高精度越好，但 overhead 也越高。</p>
<h3 id="heap--memory-profile">Heap / memory profile</h3>
<p>回答「memory 被哪些 function 持有」。適合診斷 memory leak（allocation 持續增長、GC 回收不了）跟 GC pressure（大量短命物件導致 GC 頻繁）。</p>
<p>Heap profile 記錄的是某個時間點的 live object 分布。Allocation profile 記錄的是一段時間內誰做了多少 allocation — 兩者互補。Memory leak 用 heap profile 的時間趨勢看；GC pressure 用 allocation profile 看。</p>
<h3 id="lock-contention-profile">Lock contention profile</h3>
<p>回答「哪些 lock 的等待時間最長」。適合診斷 mutex contention（多個 thread / goroutine 搶同一把 lock、等待時間累積成 latency）。</p>
<p>Lock profile 在高並發服務的診斷中特別有用。Metrics 只能看到整體 latency 上升；trace 能看到某個 span 變慢；lock profile 能精確定位是哪把 lock 在哪個 callstack 被等待。</p>
<h3 id="goroutine--async-task-profile">Goroutine / async task profile</h3>
<p>Go 的 goroutine profile 回答「有多少 goroutine、它們在做什麼（running / waiting / blocked）」。Goroutine leak（goroutine 數量持續增長、都在等待某個 channel 或 lock）是 Go 服務常見的退化模式。</p>
<p>其他語言有對應的概念：Java 的 thread dump、Node.js 的 async resource tracking、Python 的 asyncio task inspection。</p>
<h2 id="always-on-vs-on-demand">Always-on vs On-demand</h2>
<h3 id="always-oncontinuous">Always-on（continuous）</h3>
<p>Production 常駐的低開銷 profiling。CPU sampling 頻率降低（每秒 19 或 100 次，避免跟系統 timer 共振），heap sampling 用語言 runtime 內建機制（Go 的 <code>runtime/pprof</code>、Java 的 JFR）。</p>
<p>Always-on 的核心價值是 baseline — 平時就有 profile 資料，事故時可以跟 baseline 做 diff，看「哪些 function 的 CPU 消耗跟平時不同」。沒有 baseline 的 profiling 只能看「現在的 profile 長什麼樣」，無法判斷哪些是異常的。</p>
<h3 id="on-demand">On-demand</h3>
<p>事故中或效能調查時手動開啟的高精度 profiling。Sampling 頻率更高、涵蓋更多維度、但 overhead 也更高（可能影響 production 服務的 latency）。</p>
<p>On-demand profiling 適合在 always-on profile 定位到可疑 function 後，做更細粒度的 callstack 分析。兩者搭配使用 — always-on 做日常監控跟 baseline，on-demand 做事故深挖。</p>
<h3 id="overhead-控制">Overhead 控制</h3>
<p>Continuous profiling 的可行性取決於 overhead 是否夠低。目標是 CPU overhead &lt; 1%、memory overhead &lt; 10MB。</p>
<p>影響 overhead 的因素：</p>
<ul>
<li><strong>Sampling 頻率</strong>：CPU profile 每秒 100 次 vs 1000 次，overhead 差一個數量級</li>
<li><strong>採集機制</strong>：eBPF-based profiler（Parca、Pyroscope eBPF）在 kernel 層採集，overhead 比 language-level profiler 低；language runtime 內建機制（Go pprof、Java JFR）overhead 居中；instrumentation-based profiler overhead 最高</li>
<li><strong>資料傳輸</strong>：profile 資料定期傳到 backend 的網路跟序列化成本</li>
</ul>
<p>Production 部署前要用 benchmark 驗證 overhead。在 load test 環境開啟 profiling、比較開啟前後的 latency p99 跟 CPU usage — 差異超過 1% 要調整 sampling 頻率或換更輕量的 profiler。</p>
<h2 id="flame-graph-與版本差異比較">Flame Graph 與版本差異比較</h2>
<h3 id="flame-graph">Flame graph</h3>
<p>Flame graph 是 profile 資料的標準視覺化。X 軸是 callstack 的寬度（代表 sample 佔比 = 資源消耗佔比），Y 軸是 callstack 深度（底部是 root function、頂部是 leaf function）。寬的矩形代表消耗多、窄的代表消耗少。</p>
<p>讀 flame graph 的方式是「從寬的開始看」— 最寬的矩形是當前最大的資源消耗者。如果某個 function 佔整個 flame graph 的 40%，它就是最值得最佳化的候選。</p>
<h3 id="diff-flame-graph">Diff flame graph</h3>
<p>Diff flame graph 是兩個 profile 的差異視覺化。紅色代表新版本消耗增加、綠色代表減少。適合用在：</p>
<ul>
<li><strong>版本間比較</strong>：v1.2.3 vs v1.2.4 的 CPU profile diff，看新版本哪些 function 變慢</li>
<li><strong>Canary 對照</strong>：canary instance vs baseline instance 的即時 diff</li>
<li><strong>事故 vs baseline</strong>：事故期間的 profile vs 平時的 profile</li>
</ul>
<p>Diff flame graph 需要 profile 帶 version / deploy label。Profile 跟版本標記失聯時，跨版本比較只能靠手動對照時間範圍 — 精確度跟效率都會下降。</p>
<h2 id="vendor-定位">Vendor 定位</h2>
<table>
  <thead>
      <tr>
          <th>Vendor</th>
          <th>採集機制</th>
          <th>語言支援</th>
          <th>定位</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pyroscope</td>
          <td>SDK + eBPF</td>
          <td>Go, Java, Python, Ruby</td>
          <td>開源自架，Grafana 生態整合</td>
      </tr>
      <tr>
          <td>Parca</td>
          <td>eBPF</td>
          <td>語言無關（kernel 級）</td>
          <td>開源自架，零 instrumentation</td>
      </tr>
      <tr>
          <td>Datadog Profiler</td>
          <td>Agent + SDK</td>
          <td>Go, Java, Python, .NET</td>
          <td>託管，跟 APM trace 整合</td>
      </tr>
      <tr>
          <td>Polar Signals</td>
          <td>eBPF（Parca Cloud）</td>
          <td>語言無關</td>
          <td>託管 Parca</td>
      </tr>
  </tbody>
</table>
<p>選擇要點：如果已有 Grafana 生態（Prometheus + Loki + Tempo），Pyroscope 整合最自然。如果不想改 application code（零 instrumentation），eBPF-based 的 Parca 是選項。如果已用 Datadog APM，Datadog Profiler 跟 trace 的整合（從 trace span 跳到對應的 profile）是獨有優勢。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Continuous profiling 的持續價值取決於兩件事：profile 能否按版本做 diff（沒有 baseline 就無法判斷哪些 callstack 是異常的），以及 overhead 能否低到 production 常駐（overhead 過高等於回到「事故時才開」的模式）。</p>
<p>重點訊號包括：</p>
<ul>
<li>Profile 是否帶有 service、version、environment 與 deploy label</li>
<li>Flame graph diff 是否能對照 canary / baseline</li>
<li>CPU、heap、lock、allocation 是否覆蓋主要退化模式</li>
<li>Production sampling 是否足夠低成本且常駐穩定</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>同一段熱點程式碼反覆出現在事故 RCA 中、無 baseline profile</li>
<li>CPU / memory 異常時靠重現除錯、無 production profile 可對照</li>
<li>版本升級後 latency 退化、定位具體 callstack 需要重現環境</li>
<li>Profile 跟 commit / version label 失聯、跨版本 diff 需要人工對照</li>
<li>Profiling overhead 過高、production 環境常駐成本過高</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Profiling 只在事故時才開</td>
          <td>事故時開 profiler 需要時間、問題可能已消失</td>
          <td>Always-on continuous profiling</td>
      </tr>
      <tr>
          <td>Production sampling rate = 0</td>
          <td>Profile 只存在於 staging、production 沒資料</td>
          <td>調低 sampling 頻率到 overhead &lt; 1%</td>
      </tr>
      <tr>
          <td>Profile 跟 version 失聯</td>
          <td>Diff 只能靠時間範圍猜、無法精確比較</td>
          <td>Profile metadata 帶 version / commit hash label</td>
      </tr>
      <tr>
          <td>只看 CPU profile</td>
          <td>Memory leak 跟 lock contention 被忽略</td>
          <td>按服務退化模式選擇 profile 維度</td>
      </tr>
      <tr>
          <td>Profile 資料沒有保留策略</td>
          <td>儲存持續成長、舊 profile 佔空間但沒被查</td>
          <td>依版本保留（每版本保留 N 天）</td>
      </tr>
  </tbody>
</table>
<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</a>：metrics 是聚合訊號、profile 是 callstack 級別</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</a>：trace 是 request 維度、profile 是 process 維度</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 / cost</a>：profile 儲存量與保留策略</li>
<li><a href="/blog/backend/04-observability/rule-level-cpu-signal-governance/" data-link-title="4.21 Rule-level CPU Signal Governance" data-link-desc="把規則與策略執行成本變成可觀測訊號，避免控制面小變更在資料面形成 CPU 熱點。">4.21 rule-level CPU signal</a>：規則執行成本的 CPU 訊號治理</li>
<li><a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">8.5 post-incident review</a>：RCA 引用 profile flame graph</li>
</ul>
]]></content:encoded></item><item><title>4.10 Client-side / Synthetic / RUM</title><link>https://tarrragon.github.io/blog/backend/04-observability/client-side-monitoring/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/client-side-monitoring/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>Server-side 觀測的盲區&lt;/li>
&lt;li>RUM（Real User Monitoring）：真實用戶端訊號&lt;/li>
&lt;li>Synthetic monitoring：主動探測&lt;/li>
&lt;li>Core Web Vitals 與 backend SLI 的整合&lt;/li>
&lt;li>Client trace 跟 server trace 的串接&lt;/li>
&lt;li>Vendor 定位&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Client-side、Synthetic 與 RUM 訊號是把使用者實際感知納入觀測系統的資料來源，責任是補上 server-side 指標看不到的網路、瀏覽器、地區與裝置差異。&lt;/p>
&lt;p>服務端 200 率正常只代表 backend 有回應。使用者是否真的能完成操作，還要看 DNS 解析、CDN 快取、ISP 路由、瀏覽器渲染與 client-side JavaScript 執行。這些環節每一個都可能讓使用者的體驗跟 server-side dashboard 顯示的完全不同。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">monitoring 模組&lt;/a> 的分工：monitoring 模組聚焦「非 server 端 runtime 的監控體系」（SDK 設計、collector 架構、rule engine）；本章聚焦「backend 觀測系統如何整合 client-side 訊號」。交叉點是事件格式跟 transport。&lt;/p>
&lt;h2 id="server-side-觀測的盲區">Server-side 觀測的盲區&lt;/h2>
&lt;p>Server-side 觀測能看到「request 到達 server 之後發生了什麼」，看不到「request 到達 server 之前」跟「response 離開 server 之後」的環節。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>環節&lt;/th>
 &lt;th>Server 能看到嗎&lt;/th>
 &lt;th>影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>DNS 解析&lt;/td>
 &lt;td>看不到&lt;/td>
 &lt;td>DNS 異常讓使用者完全到不了 server&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CDN / edge 故障&lt;/td>
 &lt;td>看不到&lt;/td>
 &lt;td>CDN 返回 stale 或 error、server 無感&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>ISP 路由異常&lt;/td>
 &lt;td>看不到&lt;/td>
 &lt;td>特定地區使用者延遲暴增&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>TLS handshake&lt;/td>
 &lt;td>部分看得到&lt;/td>
 &lt;td>Certificate 問題讓部分 client 連不上&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Browser rendering&lt;/td>
 &lt;td>看不到&lt;/td>
 &lt;td>TTFB 正常但 LCP / CLS 很差&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Client-side JS error&lt;/td>
 &lt;td>看不到&lt;/td>
 &lt;td>功能壞了但 API call 正常&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>弱網 / offline&lt;/td>
 &lt;td>看不到&lt;/td>
 &lt;td>Request timeout 或完全沒發出&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>這些盲區意味著 server-side 的「一切正常」跟使用者的「用不了」可以同時存在。&lt;/p>
&lt;h2 id="rumreal-user-monitoring">RUM（Real User Monitoring）&lt;/h2>
&lt;p>RUM 在使用者的瀏覽器或 app 中嵌入監控 SDK，收集真實使用者的效能跟錯誤資料。跟 synthetic monitoring 的差異是 RUM 看的是真實流量，能反映真實的地理分布、裝置差異跟網路條件。&lt;/p>
&lt;h3 id="核心指標">核心指標&lt;/h3>
&lt;p>&lt;strong>頁面效能&lt;/strong>：First Contentful Paint（FCP）、Largest Contentful Paint（LCP）、Cumulative Layout Shift（CLS）、Interaction to Next Paint（INP）。這四個指標（Core Web Vitals 系列）是 Google 定義的使用者體驗量化標準。&lt;/p>
&lt;p>&lt;strong>JS error&lt;/strong>：未捕獲的 exception、promise rejection、resource loading failure。RUM SDK 自動攔截（&lt;code>window.onerror&lt;/code>、&lt;code>unhandledrejection&lt;/code>），帶 stack trace、browser info、page URL。&lt;/p>
&lt;p>&lt;strong>API call 效能&lt;/strong>：從 client 端量測的 API latency（包含 DNS + TCP + TLS + server processing + response download）。跟 server-side 量測的差異就是網路延遲跟 client 處理時間。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>Server-side 觀測的盲區</li>
<li>RUM（Real User Monitoring）：真實用戶端訊號</li>
<li>Synthetic monitoring：主動探測</li>
<li>Core Web Vitals 與 backend SLI 的整合</li>
<li>Client trace 跟 server trace 的串接</li>
<li>Vendor 定位</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Client-side、Synthetic 與 RUM 訊號是把使用者實際感知納入觀測系統的資料來源，責任是補上 server-side 指標看不到的網路、瀏覽器、地區與裝置差異。</p>
<p>服務端 200 率正常只代表 backend 有回應。使用者是否真的能完成操作，還要看 DNS 解析、CDN 快取、ISP 路由、瀏覽器渲染與 client-side JavaScript 執行。這些環節每一個都可能讓使用者的體驗跟 server-side dashboard 顯示的完全不同。</p>
<p>跟 <a href="/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">monitoring 模組</a> 的分工：monitoring 模組聚焦「非 server 端 runtime 的監控體系」（SDK 設計、collector 架構、rule engine）；本章聚焦「backend 觀測系統如何整合 client-side 訊號」。交叉點是事件格式跟 transport。</p>
<h2 id="server-side-觀測的盲區">Server-side 觀測的盲區</h2>
<p>Server-side 觀測能看到「request 到達 server 之後發生了什麼」，看不到「request 到達 server 之前」跟「response 離開 server 之後」的環節。</p>
<table>
  <thead>
      <tr>
          <th>環節</th>
          <th>Server 能看到嗎</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DNS 解析</td>
          <td>看不到</td>
          <td>DNS 異常讓使用者完全到不了 server</td>
      </tr>
      <tr>
          <td>CDN / edge 故障</td>
          <td>看不到</td>
          <td>CDN 返回 stale 或 error、server 無感</td>
      </tr>
      <tr>
          <td>ISP 路由異常</td>
          <td>看不到</td>
          <td>特定地區使用者延遲暴增</td>
      </tr>
      <tr>
          <td>TLS handshake</td>
          <td>部分看得到</td>
          <td>Certificate 問題讓部分 client 連不上</td>
      </tr>
      <tr>
          <td>Browser rendering</td>
          <td>看不到</td>
          <td>TTFB 正常但 LCP / CLS 很差</td>
      </tr>
      <tr>
          <td>Client-side JS error</td>
          <td>看不到</td>
          <td>功能壞了但 API call 正常</td>
      </tr>
      <tr>
          <td>弱網 / offline</td>
          <td>看不到</td>
          <td>Request timeout 或完全沒發出</td>
      </tr>
  </tbody>
</table>
<p>這些盲區意味著 server-side 的「一切正常」跟使用者的「用不了」可以同時存在。</p>
<h2 id="rumreal-user-monitoring">RUM（Real User Monitoring）</h2>
<p>RUM 在使用者的瀏覽器或 app 中嵌入監控 SDK，收集真實使用者的效能跟錯誤資料。跟 synthetic monitoring 的差異是 RUM 看的是真實流量，能反映真實的地理分布、裝置差異跟網路條件。</p>
<h3 id="核心指標">核心指標</h3>
<p><strong>頁面效能</strong>：First Contentful Paint（FCP）、Largest Contentful Paint（LCP）、Cumulative Layout Shift（CLS）、Interaction to Next Paint（INP）。這四個指標（Core Web Vitals 系列）是 Google 定義的使用者體驗量化標準。</p>
<p><strong>JS error</strong>：未捕獲的 exception、promise rejection、resource loading failure。RUM SDK 自動攔截（<code>window.onerror</code>、<code>unhandledrejection</code>），帶 stack trace、browser info、page URL。</p>
<p><strong>API call 效能</strong>：從 client 端量測的 API latency（包含 DNS + TCP + TLS + server processing + response download）。跟 server-side 量測的差異就是網路延遲跟 client 處理時間。</p>
<h3 id="切分維度">切分維度</h3>
<p>RUM 資料的價值在於可以按維度切分：地區（哪個國家 / 城市慢）、裝置（mobile vs desktop、iOS vs Android）、網路型態（4G vs wifi vs 3G）、瀏覽器（Chrome vs Safari vs Firefox）。</p>
<p>切分後的資料能回答 server-side 回答不了的問題：「為什麼巴西的使用者比美國慢 3 倍？」（CDN 沒覆蓋巴西）、「為什麼 Safari 的 error rate 比 Chrome 高？」（某個 JS API 在 Safari 的行為不同）。</p>
<h3 id="取樣與成本">取樣與成本</h3>
<p>RUM 的事件量跟使用者流量成正比。高流量網站的 RUM 資料量可能很大（每秒數千筆 page view + error + resource timing），成本隨之上升。</p>
<p>RUM 的取樣策略跟 server-side trace sampling 類似：可以全收（低流量網站）、按比例取樣（高流量）、或按條件取樣（error 全收、正常 page view 取樣）。取樣後的資料仍能看到趨勢跟 percentile，但個別 session 的完整 replay 需要該 session 被取樣到。</p>
<h2 id="synthetic-monitoring">Synthetic Monitoring</h2>
<p>Synthetic monitoring 用自動化的 <a href="/blog/backend/knowledge-cards/probe/" data-link-title="Probe" data-link-desc="說明平台如何透過 probe 判斷服務狀態與接流量條件">probe</a> 從外部網路定期發起請求，測量 availability 跟 latency。跟 RUM 的差異是 synthetic 是主動探測（沒有真實使用者也能跑），能 24/7 持續監控。</p>
<h3 id="適用場景">適用場景</h3>
<p><strong>Availability 探測</strong>：每分鐘從多個地區對關鍵頁面或 API endpoint 發 request，確認可達性。DNS 異常、CDN 故障、TLS 過期 — 這些 server-side 看不到的問題，synthetic probe 能第一時間抓到。</p>
<p><strong>SLO probe</strong>：用 synthetic probe 量測關鍵 user journey 的端到端 latency（login → homepage → checkout），作為 SLO 的 client-side 量測點。</p>
<p><strong>Third-party 依賴監控</strong>：探測 payment gateway、SSO provider、CDN 的可用性。這些外部依賴故障時 server-side 只能看到 timeout 或 error code，synthetic probe 能從使用者的角度看到完整影響。</p>
<h3 id="常見陷阱">常見陷阱</h3>
<p>Synthetic probe 的探測路徑必須跟真實使用者一致。Probe 從 datacenter 內部發 request、走內部 DNS、不經過 CDN — 這種 probe 量到的 latency 跟 availability 不代表真實使用者的體驗。</p>
<p>Probe 應該從外部網路、經過公開 DNS、經過 CDN / edge、用真實 browser（headless Chrome）渲染頁面。Catchpoint、Pingdom、Datadog Synthetic 都提供從多個公開地理位置發 probe 的能力。</p>
<h2 id="core-web-vitals-與-backend-sli-的整合">Core Web Vitals 與 Backend SLI 的整合</h2>
<p>Core Web Vitals（LCP、CLS、INP）是 client-side 的使用者體驗指標。Backend SLI（availability、latency p99）是 server-side 的服務健康指標。兩者各自反映不同層面、需要整合看才能得到完整圖像。</p>
<p>整合方式是在 dashboard 上並排顯示：backend SLI panel 旁邊放 RUM 的 LCP / INP panel。當 backend latency 正常但 LCP 退化，問題在 frontend rendering 或 CDN；當 backend latency 升高且 LCP 同步退化，問題在 backend。</p>
<p><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> 的 user-journey-centric SLI 應該同時考慮 server-side 跟 client-side 的量測點。只看 server-side 的 SLI 會低估使用者實際感知的延遲。</p>
<h2 id="client-trace-跟-server-trace-的串接">Client Trace 跟 Server Trace 的串接</h2>
<p>RUM SDK 跟 backend 的 trace 串接讓一個 user action 的完整路徑可追蹤 — 從 button click 到 browser 發 API request 到 backend 處理到 response rendering。</p>
<p>串接方式是 RUM SDK 在發起 API request 時注入 <a href="/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context</a> header（W3C <code>traceparent</code>）。Backend 的 trace instrumentation 提取 header、建立 child span。完整的 trace waterfall 從 browser span 開始、經過 backend span、到 database span。</p>
<p>串接的條件是 RUM SDK 跟 backend SDK 使用相同的 trace context format。OTel 生態（browser SDK + backend SDK）天然支援；混用 vendor 時需要確認 header format 一致。</p>
<h2 id="vendor-定位">Vendor 定位</h2>
<table>
  <thead>
      <tr>
          <th>Vendor</th>
          <th>RUM</th>
          <th>Synthetic</th>
          <th>特點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Datadog RUM</td>
          <td>有</td>
          <td>有</td>
          <td>跟 APM trace 整合、session replay</td>
      </tr>
      <tr>
          <td>Sentry</td>
          <td>有</td>
          <td>無</td>
          <td>Error tracking 為主、效能次之</td>
      </tr>
      <tr>
          <td>New Relic Browser</td>
          <td>有</td>
          <td>有</td>
          <td>全棧觀測整合</td>
      </tr>
      <tr>
          <td>Catchpoint</td>
          <td>無</td>
          <td>有</td>
          <td>Synthetic 專精、全球 probe 網路</td>
      </tr>
      <tr>
          <td>Pingdom</td>
          <td>無</td>
          <td>有</td>
          <td>簡單 availability probe</td>
      </tr>
      <tr>
          <td>Grafana Faro</td>
          <td>有</td>
          <td>無</td>
          <td>開源、Grafana 生態整合</td>
      </tr>
  </tbody>
</table>
<p>選擇要點：已有 APM vendor 的團隊優先用同 vendor 的 RUM（trace 串接最自然）。只需要 availability probe 的用 Pingdom 或 Synthetic 功能。需要 session replay（重現使用者操作序列）的選 Datadog RUM 或 Sentry。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 client-side monitoring 時，先看訊號是否代表真實使用者，再看 synthetic probe 是否覆蓋關鍵旅程。</p>
<p>重點訊號包括：</p>
<ul>
<li>RUM 是否能按地區、裝置、網路型態與瀏覽器切分</li>
<li>Synthetic probe 是否從外部網路與真實入口進入</li>
<li>Core Web Vitals 是否能和 backend SLI 並排比較</li>
<li>Client trace / session 是否能和 server trace 串接</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>使用者回報慢但 server-side latency 正常</li>
<li>CDN / edge 故障時內部 dashboard 全綠</li>
<li>行動弱網場景無 visibility、僅有 wifi 桌面端訊號</li>
<li>Synthetic probe 從 datacenter 內部跑、路徑跟真實使用者不同</li>
<li>客戶投訴定位耗時長、無 client 端 trace / RUM session</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SLO 只看 server 200 率</td>
          <td>CDN / DNS 故障時 SLO 一切正常</td>
          <td>加 synthetic probe 跟 RUM 作為 SLI 來源</td>
      </tr>
      <tr>
          <td>Synthetic probe 走內部網路</td>
          <td>Probe latency 跟真實使用者差距大</td>
          <td>Probe 從外部公開網路、經 DNS / CDN 路徑</td>
      </tr>
      <tr>
          <td>RUM 無取樣策略</td>
          <td>高流量時 RUM 成本失控</td>
          <td>按條件取樣（error 全收、正常取樣）</td>
      </tr>
      <tr>
          <td>Client trace 跟 server 斷裂</td>
          <td>看不到 browser → server 的完整路徑</td>
          <td>RUM SDK 注入 W3C trace context header</td>
      </tr>
      <tr>
          <td>只看 overall LCP</td>
          <td>全球平均看起來好但特定地區體驗極差</td>
          <td>按地區 / 裝置 / 網路切分 RUM 資料</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><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>：user-journey-centric SLI 需要 client-side 量測點</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</a>：client trace 跟 server trace 的 context 串接</li>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署</a>：CDN / edge 配置變更影響 RUM 訊號</li>
<li><a href="/blog/backend/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">08 incident response</a>：客戶感知影響量化</li>
<li><a href="/blog/monitoring/" data-link-title="監控實務指南" data-link-desc="整理非伺服器端運行時的監控體系 — 行為蒐集、錯誤回報、效能指標、生命週期追蹤，從自架方案到商業方案的完整知識路線">Monitoring 模組</a>：非 server 端的監控體系設計</li>
<li><a href="/blog/backend/04-observability/client-server-trace-integration/" data-link-title="4.24 Client-to-Server 端到端觀測串接" data-link-desc="用一個結帳場景走完 browser click → trace context → server span → 統一 waterfall 的完整實作鏈路">4.24 Client-to-Server 觀測串接</a>：從 browser click 到 server span 的完整 trace 鏈路實作</li>
</ul>
]]></content:encoded></item><item><title>4.11 Telemetry Pipeline 架構</title><link>https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/telemetry-pipeline/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>為何要把 telemetry 當 pipeline 看：每層有獨立失敗模式與成本邊界&lt;/li>
&lt;li>分層責任：agent（採集）、collector（聚合 / 轉換）、ingest（寫入 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a>）、storage（保留 / 查詢）、query（dashboard / alert）&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure&lt;/a>：collector 端緩衝、ingest 滿時的降級策略&lt;/li>
&lt;li>OpenTelemetry Collector 的角色：vendor-neutral 中介層&lt;/li>
&lt;li>pipeline 失敗時的 graceful &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/degradation/" data-link-title="Degradation" data-link-desc="說明服務部分能力失效時如何保留核心功能與控制風險">degradation&lt;/a>：訊號斷一層、其他層仍可用&lt;/li>
&lt;li>multi-tenant 環境的 quota / 隔離&lt;/li>
&lt;li>觀測遷移流程：先換 collector 再換 instrumentation、雙軌期保留對照&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 cardinality&lt;/a> 的分工：4.7 是治理輸入、4.11 是 pipeline 執行&lt;/li>
&lt;li>反模式：pipeline 是黑盒、無 self-monitoring；agent 直連 vendor 無 collector 中介；ingest 滿時直接 drop 無告警&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Telemetry pipeline 是把訊號從 service process 帶到查詢與告警面的資料路徑，責任是讓採集、轉換、寫入、儲存與查詢各層都有可觀測的邊界。&lt;/p>
&lt;p>這一頁處理的是觀測系統本身的可靠性。當 pipeline 是黑盒，訊號消失時團隊需要額外排查服務是否真的沒事件，或 agent、collector、ingest、query 哪一層失效。&lt;/p>
&lt;p>Pipeline 視角的另一個價值是把採集策略跟儲存後端解耦。應用層只需要產生標準訊號，pipeline 處理 schema 轉換、sampling、enrichment、routing 與 vendor 對接；當儲存後端或 vendor 改變時，應用層不必重新 instrument。&lt;/p>
&lt;h2 id="分層責任與失敗模式">分層責任與失敗模式&lt;/h2>
&lt;p>Pipeline 各層責任不同，失敗模式也不同。把 pipeline 視為單一黑盒會讓事故定位停在「訊號不見了」這層觀察，無法回答是哪一層的問題。&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>Agent&lt;/td>
 &lt;td>從 process / host 抓取原始訊號&lt;/td>
 &lt;td>升版需重啟、container restart 造成短期缺洞&lt;/td>
 &lt;td>export queue depth、dropped batches&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Collector&lt;/td>
 &lt;td>聚合、轉換、enrichment、routing&lt;/td>
 &lt;td>OOM、配置漂移、規則衝突&lt;/td>
 &lt;td>receiver / processor / exporter 指標&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Ingest&lt;/td>
 &lt;td>接收並寫入 buffer 或排隊&lt;/td>
 &lt;td>滿載拒收（429）、區域故障&lt;/td>
 &lt;td>ingestion success rate、queue depth&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Storage&lt;/td>
 &lt;td>保留資料、支援查詢索引&lt;/td>
 &lt;td>索引膨脹、保留策略誤刪、查詢退化&lt;/td>
 &lt;td>storage size、query latency&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query&lt;/td>
 &lt;td>dashboard / alert / 即席查詢&lt;/td>
 &lt;td>查詢逾時、aggregate 失真、permission 漂移&lt;/td>
 &lt;td>query QPS、p95 latency、permission 拒絕&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Agent 層的關鍵風險是部署綁定。若 agent 跟應用同進程，升版需要重啟服務；若 agent 是獨立 DaemonSet 或 sidecar，升版可以獨立進行，但要承擔網路與資源額外開銷。Agent 自身故障時，service 看起來健康，dashboard 看起來空，事故指揮會把這個空白誤讀成系統靜默。&lt;/p>
&lt;p>Collector 層是 pipeline 最有彈性的地方，也是最容易漏掉自我觀測的地方。OpenTelemetry Collector 的 receiver / processor / exporter 各自有 metrics，部署時要把這些 metrics 自身送回觀測平台。配置漂移是長期維護的主要失敗：sampling 規則改了沒紀錄、attribute 重命名沒同步、tail sampling decision window 縮短，都會讓下游看到的訊號跟以前不同。Collector 的三種部署位置（agent / gateway / sidecar）與 pipeline 設計細節見 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/vendors/opentelemetry/collector-deployment-patterns/" data-link-title="OTel Collector 部署模式：agent / gateway / sidecar 與 pipeline 設計" data-link-desc="說明 OpenTelemetry Collector 三種部署位置的責任分工、receivers/processors/exporters pipeline 設計，以及 collector 失效、記憶體壓力與 backpressure 的故障演練與容量邊界">OTel Collector 部署模式&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>為何要把 telemetry 當 pipeline 看：每層有獨立失敗模式與成本邊界</li>
<li>分層責任：agent（採集）、collector（聚合 / 轉換）、ingest（寫入 <a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a>）、storage（保留 / 查詢）、query（dashboard / alert）</li>
<li><a href="/blog/backend/knowledge-cards/buffer/" data-link-title="Buffer" data-link-desc="說明系統如何用暫存空間吸收短暫速度差與尖峰流量">buffer</a> 與 <a href="/blog/backend/knowledge-cards/backpressure/" data-link-title="Backpressure" data-link-desc="說明下游處理速度不足時系統如何讓上游依下游能力送出工作">backpressure</a>：collector 端緩衝、ingest 滿時的降級策略</li>
<li>OpenTelemetry Collector 的角色：vendor-neutral 中介層</li>
<li>pipeline 失敗時的 graceful <a href="/blog/backend/knowledge-cards/degradation/" data-link-title="Degradation" data-link-desc="說明服務部分能力失效時如何保留核心功能與控制風險">degradation</a>：訊號斷一層、其他層仍可用</li>
<li>multi-tenant 環境的 quota / 隔離</li>
<li>觀測遷移流程：先換 collector 再換 instrumentation、雙軌期保留對照</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> 的分工：4.7 是治理輸入、4.11 是 pipeline 執行</li>
<li>反模式：pipeline 是黑盒、無 self-monitoring；agent 直連 vendor 無 collector 中介；ingest 滿時直接 drop 無告警</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Telemetry pipeline 是把訊號從 service process 帶到查詢與告警面的資料路徑，責任是讓採集、轉換、寫入、儲存與查詢各層都有可觀測的邊界。</p>
<p>這一頁處理的是觀測系統本身的可靠性。當 pipeline 是黑盒，訊號消失時團隊需要額外排查服務是否真的沒事件，或 agent、collector、ingest、query 哪一層失效。</p>
<p>Pipeline 視角的另一個價值是把採集策略跟儲存後端解耦。應用層只需要產生標準訊號，pipeline 處理 schema 轉換、sampling、enrichment、routing 與 vendor 對接；當儲存後端或 vendor 改變時，應用層不必重新 instrument。</p>
<h2 id="分層責任與失敗模式">分層責任與失敗模式</h2>
<p>Pipeline 各層責任不同，失敗模式也不同。把 pipeline 視為單一黑盒會讓事故定位停在「訊號不見了」這層觀察，無法回答是哪一層的問題。</p>
<table>
  <thead>
      <tr>
          <th>分層</th>
          <th>主要責任</th>
          <th>典型失敗模式</th>
          <th>健康訊號</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Agent</td>
          <td>從 process / host 抓取原始訊號</td>
          <td>升版需重啟、container restart 造成短期缺洞</td>
          <td>export queue depth、dropped batches</td>
      </tr>
      <tr>
          <td>Collector</td>
          <td>聚合、轉換、enrichment、routing</td>
          <td>OOM、配置漂移、規則衝突</td>
          <td>receiver / processor / exporter 指標</td>
      </tr>
      <tr>
          <td>Ingest</td>
          <td>接收並寫入 buffer 或排隊</td>
          <td>滿載拒收（429）、區域故障</td>
          <td>ingestion success rate、queue depth</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>保留資料、支援查詢索引</td>
          <td>索引膨脹、保留策略誤刪、查詢退化</td>
          <td>storage size、query latency</td>
      </tr>
      <tr>
          <td>Query</td>
          <td>dashboard / alert / 即席查詢</td>
          <td>查詢逾時、aggregate 失真、permission 漂移</td>
          <td>query QPS、p95 latency、permission 拒絕</td>
      </tr>
  </tbody>
</table>
<p>Agent 層的關鍵風險是部署綁定。若 agent 跟應用同進程，升版需要重啟服務；若 agent 是獨立 DaemonSet 或 sidecar，升版可以獨立進行，但要承擔網路與資源額外開銷。Agent 自身故障時，service 看起來健康，dashboard 看起來空，事故指揮會把這個空白誤讀成系統靜默。</p>
<p>Collector 層是 pipeline 最有彈性的地方，也是最容易漏掉自我觀測的地方。OpenTelemetry Collector 的 receiver / processor / exporter 各自有 metrics，部署時要把這些 metrics 自身送回觀測平台。配置漂移是長期維護的主要失敗：sampling 規則改了沒紀錄、attribute 重命名沒同步、tail sampling decision window 縮短，都會讓下游看到的訊號跟以前不同。Collector 的三種部署位置（agent / gateway / sidecar）與 pipeline 設計細節見 <a href="/blog/backend/04-observability/vendors/opentelemetry/collector-deployment-patterns/" data-link-title="OTel Collector 部署模式：agent / gateway / sidecar 與 pipeline 設計" data-link-desc="說明 OpenTelemetry Collector 三種部署位置的責任分工、receivers/processors/exporters pipeline 設計，以及 collector 失效、記憶體壓力與 backpressure 的故障演練與容量邊界">OTel Collector 部署模式</a>。</p>
<p>Ingest 層的失敗模式集中在容量邊界。當 vendor 端 quota 觸發或內部 queue 滿，ingest 會回 429 或直接丟棄；應用層通常無感、dashboard 顯示流量下降。這層需要把拒收事件本身變成告警訊號、讓事故定位即時看到拒收量、避免靠事後對賬發現。</p>
<p>Storage 跟 query 層的失敗多半是漸進式：保留策略誤刪、查詢隨時間退化、索引隨流量膨脹。這類失敗不會在當下觸發告警，要靠週期性審視 storage size、query latency 與 retention compliance 才能發現。</p>
<h2 id="buffer-與-backpressure">Buffer 與 Backpressure</h2>
<p>Buffer 是 pipeline 吸收瞬時尖峰的緩衝，責任是讓 collector 跟 ingest 在後端短暫故障或速率不足時仍保住高價值訊號。</p>
<ul>
<li><strong>In-memory queue</strong>：吸收秒級尖峰、容量小、process 重啟會丟。</li>
<li><strong>Persistent queue</strong>（local disk、Kafka）：吸收分鐘到小時級積壓、有持久性、需要額外運維成本。</li>
<li><strong>Spillover storage</strong>（S3 等冷儲存）：當 hot path 滿載時，把低優先訊號暫存到便宜後端、之後 replay。</li>
</ul>
<p>Backpressure 策略決定 buffer 滿時的行為。<code>block</code> 策略會讓上游採集慢下來、可能影響應用；<code>drop oldest</code> 跟 <code>drop newest</code> 各自影響 timeline 的開始或結束；<code>sample-by-priority</code> 則保留錯誤、長尾與低流量樣本、丟棄一般成功 request。Buffer 跟 backpressure 策略要在容量規劃階段顯式設定、進 release flow、避免事故時臨時拍定。</p>
<p>Buffer 對事故判讀的影響是 freshness。當 buffer 累積分鐘級資料時，dashboard 看到的指標其實落後當前狀態；incident commander 看到 error rate 下降時，需要知道是真的恢復還是 buffer 尚未排空。把 buffer depth 跟 ingest delay 暴露成 dashboard 指標，能避免事中決策建立在過期資料上。</p>
<p>Buffer 跟 backpressure 怎麼選：低延遲容忍 + 容量充足的場景用 in-memory queue + <code>drop oldest</code>（保留最新狀態）；高訊號完整性需求（例：audit log、事故證據）用 persistent queue + <code>block</code> 或 <code>sample-by-priority</code>；高流量爆量但允許部分遺失（例：debug log）用 spillover storage + <code>drop newest</code>。事故時的回退路徑是「在 backpressure 政策中先標明哪類訊號絕對保留、哪類訊號可丟」、避免事故當下臨時決定。</p>
<h2 id="opentelemetry-collector-的中介定位">OpenTelemetry Collector 的中介定位</h2>
<p>OpenTelemetry Collector 把採集、轉換與 routing 從應用程式抽離，責任是讓觀測 vendor 跟採集 SDK 各自演進。</p>
<p>Collector 在 pipeline 中扮演三個角色：</p>
<ol>
<li><strong>Vendor-neutral 中介</strong>：應用層只需 export OTLP，collector 端決定要不要把資料同時送到多個後端（Datadog、Honeycomb、self-hosted Prometheus）。切換 vendor 時不需要改應用層。</li>
<li><strong>Schema / sampling 集中治理</strong>：attribute 重命名、敏感欄位 redaction、tail sampling decision、cardinality 限制都集中在 collector，不分散在每個服務。</li>
<li><strong>Topology 適配層</strong>：collector 可以部署為 sidecar（與應用同 Pod）、DaemonSet（每個 node 一份）或 gateway（集中接收）。不同部署形態適合不同規模與隔離需求，並不互斥；大型部署常見「應用 → sidecar → cluster gateway → 後端」的多級拓樸。</li>
</ol>
<p>對應 <a href="/blog/backend/04-observability/cases/cloud-trace-otlp-adoption/" data-link-title="4.C5 Google Cloud：Cloud Trace 導入 OTLP 入口" data-link-desc="觀測平台從專有入口擴展到 OTLP 標準通道的案例。">4.C5 Cloud Trace OTLP 導入</a>：標準化傳輸協定降低跨環境的 instrumentation 重複，揭露「資料通道標準化」是觀測平台轉換的常見起點。對應 <a href="/blog/backend/04-observability/cases/adot-eks-observability-pipeline-migration/" data-link-title="4.C6 AWS：ADOT on EKS 管線遷移" data-link-desc="從分散式 agent 組合轉成 OpenTelemetry collector 管線治理。">4.C6 ADOT on EKS 管線遷移</a>：多代理混用在規模化時放大配置漂移，揭露 collector 集中治理的營運價值。兩個案例的具體實作差異留給原案例，本章關注的是 collector 在 pipeline 中的責任邊界。</p>
<h2 id="觀測遷移的執行順序">觀測遷移的執行順序</h2>
<p>觀測遷移的執行順序決定短期雙軌成本能否轉化為長期語意一致性。把替換風險限制在採集中介層、是先換 collector / agent、再換應用層 instrumentation 的設計理由。</p>
<p>可重複套用的順序是先換採集中介、再換採集點：</p>
<ol>
<li><strong>先換 collector / agent</strong>：把 collector 從 vendor-specific 換成 vendor-neutral（如 OTel Collector），同時保留舊 vendor 的 exporter，讓資料同時送到新舊後端。這層替換對應用層無感，可以快速完成。</li>
<li><strong>建立雙軌對照</strong>：以新舊後端對照 SLI 是否一致（query 設計、偏差閾值、退出條件等對照細節由 <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><strong>逐步改應用端 instrumentation</strong>：把應用層的 vendor-specific SDK 換成 OTel SDK，分服務分批進行。每批切換後重跑對照驗證。</li>
<li><strong>以對照驗證進入 release gate</strong>：在 release pipeline 加上「新舊管線 SLI 偏差」檢查，作為遷移階段的閘門。對照穩定後才能關閉舊管線。</li>
</ol>
<p>執行順序的設計理由：collector 是 vendor-neutral 抽象、可以雙軌並存承受對照成本；應用層 instrumentation 改動會跨眾多 service team、變更面廣、要在 collector 對照穩定後才大規模推進。把次序反過來容易在 instrumentation 全面改完才發現 collector 抽象有缺失、被迫重做。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/xray-to-opentelemetry-migration/" data-link-title="4.C4 AWS：X-Ray 到 OpenTelemetry 轉換" data-link-desc="觀測儀表從 vendor-specific SDK 轉向 OpenTelemetry 的治理重點。">4.C4 X-Ray 到 OpenTelemetry 轉換</a>：揭露「先 collector 後 instrumentation」的階段切換方向。對應 <a href="/blog/backend/04-observability/cases/datadog-otel-migration-practice/" data-link-title="4.C7 Datadog：OTel 相容遷移實務" data-link-desc="APM 採集從專有代理轉向 OTel 相容模式的治理案例。">4.C7 Datadog OTel 相容遷移實務</a>：揭露「雙軌期成本跟語意漂移是遷移期主要風險」（單一 agent 安裝是次要議題）。本章關注的是執行順序，schema drift 跟資料品質的對照驗證細節由 <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>
<p>遷移節奏由團隊規模、可承受雙軌成本、配置漂移風險與治理成熟度共同決定。本段聚焦遷移期的節奏取捨；常態 ownership 配置由 <a href="/blog/backend/04-observability/observability-operating-model/#%e8%a6%8f%e6%a8%a1%e5%b7%ae%e7%95%b0%e4%b8%8b%e7%9a%84%e8%a7%92%e8%89%b2%e9%85%8d%e7%bd%ae" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 規模差異下的角色配置</a> 處理，兩者 lens 不同。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/" data-link-title="4.C10 對照：規模差異下的觀測遷移" data-link-desc="觀測遷移在不同規模團隊下的流程與風險差異。">4.C10 規模差異下觀測遷移</a>：揭露三種規模團隊的失敗模式骨架；以下三段的具體操作做法均屬通用工程知識展開、case 本身只列方向。</p>
<p>小團隊的核心風險是雙軌維護消耗人力。同時看兩套 dashboard、雙倍 alert noise、雙倍 on-call 負擔，很容易讓遷移本身拖累業務維運。小團隊適合用「短期對照、快速收斂」策略：把對照期壓到一個迭代週期內，固定一個服務作為先導，把問題在小範圍內收斂，再快速複製到其他服務。</p>
<p>中型團隊的失敗模式集中在 schema 漂移。服務數量增加後，attribute 命名一致性、service name 規約、label cardinality 邊界容易在雙軌期擴散。中型團隊要在遷移開始前先固化 semantic convention，並在 collector 層自動校驗；不固化會在遷移後拼湊出多套互相矛盾的 dashboard。</p>
<p>大型團隊的主要失敗集中在治理面：collector 拓樸（sidecar / DaemonSet / gateway 的選擇）、sampling 政策、成本分攤、tenant 隔離都會在遷移後顯著影響成本與告警品質。大型團隊用「pilot region 先行、其他 region 批次跟進」策略、把 collector 配置版本化、變更接到 release gate。大型團隊的回退單位通常是 region 或 tenant 群、不是整體切回。</p>
<p>三類團隊的共同教訓是：先決定「何時可以關閉舊管線」的退出條件，再開始遷移。沒有退出條件的雙軌會無限期延長，最後在成本壓力下被動關閉，反而失去對照驗證的能力。</p>
<h2 id="遷移漂移的回退判讀">遷移漂移的回退判讀</h2>
<p>漂移回退的責任是把降級決策權跟資料採集分離、讓回退保留可分析的對照證據。直接關閉新管線會失去漂移原因的線索、後續再遷移容易出同樣的事故。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/" data-link-title="4.C9 反例：OTel 遷移後訊號漂移" data-link-desc="雙軌採集未對齊導致告警與 SLO 判讀失真。">4.C9 OTel 遷移訊號漂移反例</a>：揭露遷移失敗的主要型態是語意漂移、回退要保留對照證據。</p>
<p>漂移發生時，主要訊號是「兩套儀表板看似都有資料、但對同一事故的判讀不同」。新舊管線對同一服務的 error rate 長期偏離、missing span 或 missing metric 比例上升、alert 噪音增加但事故量沒對應增加，都是漂移在 pipeline 層的表現。</p>
<p>回退判讀的核心是分辨「遷移問題」跟「服務問題」。比較穩定的回退節奏：</p>
<ol>
<li>先停止讓新管線主導告警跟 SLO 判定，把告警入口切回舊管線。</li>
<li>保留新管線採集、但只作為對照證據，不參與決策。</li>
<li>用對照資料找出語意漂移點（attribute 名稱、sampling 規則、aggregation 視窗），分項修正。</li>
<li>修正後重新進入雙軌對照、確認偏差收斂、再讓新管線恢復主導。</li>
</ol>
<p>這個流程把回退視為降級決策權的釋放、而非整體關閉訊號採集。把回退做成可重播流程，下次遷移才能避免在錯誤訊號上做服務回退。</p>
<h2 id="multi-tenant-與-quota">Multi-tenant 與 Quota</h2>
<p>Pipeline 的多租戶治理責任是讓單一服務或團隊的爆量不會拖累其他租戶。沒有租戶隔離時，單一服務的 cardinality 爆炸或 sampling 失控會直接耗盡 pipeline 容量。</p>
<p>可操作的隔離手段：</p>
<ul>
<li><strong>Ingestion quota per tenant</strong>：限制單一服務的 ingest rate，超過時觸發降級或退單。</li>
<li><strong>Buffer 與 storage 分區</strong>：高優先 tenant 使用獨立 buffer 或 storage shard，避免 noisy neighbor。</li>
<li><strong>Sampling 政策 per tenant</strong>：成本敏感 tenant 走較高採樣比例，關鍵 tenant 走 minimum sample floor。</li>
<li><strong>Cost attribution</strong>：把 ingestion、storage、query 成本拆到 tenant，回到 <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>。</li>
</ul>
<p>Quota 觸發時的告警設計比 quota 本身更重要。沒有告警的 quota 等於沒有 quota，因為觸發後訊號靜默，事故定位會把靜默誤讀為系統穩定。</p>
<h2 id="讀取路徑作為-pipeline-的延伸">讀取路徑作為 pipeline 的延伸</h2>
<p>Pipeline 的分層敘事（agent → collector → ingest → storage → query）在 query 這層停得太早。寫入路徑的資料從 agent 流到 storage 是單向的；讀取路徑從 query engine 向 storage 發起請求，方向相反、效能瓶頸不同、治理責任也不同。把 query 視為 pipeline 的終端消費者而非獨立系統，才能完整理解觀測資料的生命週期。</p>
<h3 id="query-engine-的責任邊界">Query engine 的責任邊界</h3>
<p>Query engine 在 pipeline 中的責任是把儲存層的資料轉換成使用者可操作的回應。這包括 query planning（決定掃描哪些 shard、哪些 tier）、聚合計算（rate / sum / quantile）、結果快取與 query 排程。</p>
<p>Query engine 的設計取捨跟儲存層不同。儲存層追求寫入吞吐與持久性；query engine 追求查詢延遲與併發能力。兩者獨立擴展 — 寫入量大但查詢量小的場景，storage 需要更多容量但 query engine 不需要；反過來，dashboard 多但寫入量穩定的場景，query engine 需要更多 CPU 但 storage 不需要。</p>
<h3 id="query-time-的資源隔離">Query-time 的資源隔離</h3>
<p>Query engine 服務三種查詢模式：alert rule evaluation（系統關鍵、定期、不可延遲）、dashboard 刷新（高頻、穩定、可容忍短暫延遲）、即席診斷（偶發、突增、事故中最需要低延遲）。三者搶同一個 query engine 時，穩定的背景負載會擠壓突發的即席查詢。</p>
<p>資源隔離的可操作方式：</p>
<ul>
<li><strong>Query priority</strong>：alert evaluation 最高、即席查詢次之、dashboard 最低。Alert 不能因為 dashboard 重查詢排隊而漏發。</li>
<li><strong>Query queue 分離</strong>：不同類型的查詢進不同的 queue，各自有併發上限。Thanos / Mimir 的 query-frontend 支援 query 分類與排程。</li>
<li><strong>Query timeout 差異化</strong>：alert evaluation 設短 timeout（跑不完就是問題）、即席查詢設中等 timeout、dashboard 的大範圍查詢允許較長 timeout。</li>
<li><strong>Query cost estimation</strong>：在查詢執行前估算掃描量，超過閾值的查詢降級或拒絕，避免單一 heavy query 拖垮整個 query engine。</li>
</ul>
<h3 id="buffer-lag-對查詢-freshness-的影響">Buffer lag 對查詢 freshness 的影響</h3>
<p>寫入面的 buffer lag 會直接影響讀取面的 freshness。當 collector 或 ingest 端有分鐘級的 buffer 累積，query engine 讀到的是延遲過的資料。Dashboard 顯示的 error rate 可能反映的是兩分鐘前的狀態；incident commander 看到 error rate 下降，可能是 buffer 開始排空而非服務真的恢復。</p>
<p>把 buffer lag 轉成查詢面的可見指標是基本的設計要求。在 dashboard 上顯示「資料延遲：目前最新資料點是 N 秒前」，讓讀取者知道自己看到的資料有多新。當 lag 超過告警閾值，除了觸發 pipeline 健康告警外，dashboard 本身也應該標示警告狀態。</p>
<p>跨訊號類型的查詢設計見 <a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 telemetry pipeline 時，先看每一層是否有健康訊號，再看滿載時是否能降級。</p>
<p>重點訊號包括：</p>
<ul>
<li>agent、collector、ingest、storage、query 是否各自有 SLI</li>
<li>buffer 與 backpressure 是否能保住高價值訊號</li>
<li>multi-tenant quota 是否能隔離單一服務爆量</li>
<li>collector 是否保留 vendor-neutral 的轉換空間</li>
<li>遷移期是否有雙軌對照、是否有退出條件</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>訊號間歇性消失、需要人工判斷是 pipeline 還是 service 問題</li>
<li>agent 升版需要 service 重啟、運維成本高</li>
<li>ingest 拒收（429）發生時、應用層無感</li>
<li>切換 vendor 需要改所有 service 的 instrumentation</li>
<li>pipeline 自身無 SLI、健康度靠經驗判斷</li>
<li>遷移期雙軌維護過久、退出條件不明</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Pipeline 是黑盒</td>
          <td>訊號消失時靠經驗判斷層級</td>
          <td>每層暴露 SLI、量化 self-monitoring</td>
      </tr>
      <tr>
          <td>Agent 直連 vendor 無中介層</td>
          <td>切換 vendor 要改所有應用層</td>
          <td>加 collector 作為 vendor-neutral 中介</td>
      </tr>
      <tr>
          <td>Ingest 拒收靜默</td>
          <td>429 觸發但應用層 / 告警都無感</td>
          <td>把拒收事件變成告警與 dashboard 指標</td>
      </tr>
      <tr>
          <td>雙軌無退出條件</td>
          <td>遷移期無限延長、成本不斷雙倍</td>
          <td>預設退出 SLI 偏差閾值、加入 release gate</td>
      </tr>
      <tr>
          <td>配置漂移無版本控制</td>
          <td>collector 規則改了沒紀錄</td>
          <td>collector 配置進 git、變更走 release flow</td>
      </tr>
  </tbody>
</table>
<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</a>：pipeline 各層的 quota</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><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：collector / pipeline 的 ownership 邊界</li>
<li><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：讀取路徑的系統設計與資源治理</li>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署</a>：collector 部署形態（DaemonSet / sidecar / gateway）</li>
<li><a href="/blog/backend/06-reliability/" data-link-title="模組六：可靠性驗證流程" data-link-desc="用 SRE 領域詞彙建問題節點、以服務級案例庫累積驗證脈絡，先建概念與案例庫再進實作交接">6.4 chaos</a>：pipeline 故障模擬作為 chaos 場景</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>：pipeline 各層的成本歸屬</li>
<li><a href="/blog/backend/04-observability/cases/cloudflare-internal-observability-architecture/" data-link-title="4.C12 Cloudflare：內部觀測平台的三層能力" data-link-desc="全球 300&#43; edge 節點的觀測架構，把 monitoring、analytics 與 forensics 拆成三個獨立能力層。">4.C12 Cloudflare 內部觀測</a>：大規模自建 pipeline 的三層能力設計</li>
</ul>
]]></content:encoded></item><item><title>4.12 Audit Log 邊界與 PII 治理</title><link>https://tarrragon.github.io/blog/backend/04-observability/audit-log-governance/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/audit-log-governance/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">audit log&lt;/a> 跟 operational log 的本質差異：對象、不變性、保留、法規&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">audit log&lt;/a> 該記什麼：who / what / when / where / outcome、不可被應用層改寫&lt;/li>
&lt;li>不變性保證：append-only storage、tamper-evident hash chain、independent retention&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/pii/" data-link-title="PII" data-link-desc="說明可識別個人的資料如何影響權限、遮罩、保留與稽核">PII&lt;/a> 治理：log 中的 PII 偵測、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/data-masking/" data-link-title="Data Masking" data-link-desc="說明敏感資料如何在顯示、匯出、log 與測試資料中降低暴露">data masking&lt;/a>、tokenization、最小揭露原則&lt;/li>
&lt;li>法規維度：GDPR / HIPAA / SOC2 / 個資法 對保留期與存取的要求&lt;/li>
&lt;li>跨團隊存取證據連續性：避免責任鏈斷在團隊邊界&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/log-schema/" data-link-title="4.1 log schema 與搜尋規劃" data-link-desc="整理 log 欄位、索引與搜尋策略">4.1 log schema&lt;/a> 的分工：4.1 是欄位設計、4.12 是治理邊界&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安&lt;/a> 的交接：稽核責任邊界&lt;/li>
&lt;li>反模式：audit 跟 operational 混在同 stream；PII 直接打進 log；audit log 跟 application DB 同保留期&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">Audit log&lt;/a> 是把責任、授權與敏感操作留下可稽核證據的訊號，責任是支援合規、責任追蹤與安全事件調查。&lt;/p>
&lt;p>這一頁處理的是 governance 邊界。Operational log 服務於除錯，audit log 服務於證據；兩者可以共享部分欄位，但保留、不變性、存取權限與 PII 規則不同。&lt;/p>
&lt;p>Audit log 的治理優先序跟 operational log 相反。Operational log 優先服務 &lt;em>當下&lt;/em> 的事故定位、追求即時性與覆蓋廣度；audit log 優先服務 &lt;em>未來&lt;/em> 的責任追蹤、追求完整性、不變性與長期可查詢。當這兩種優先序衝突時，audit 治理要勝過 operational 便利性。&lt;/p>
&lt;h2 id="兩種-log-的責任分工">兩種 log 的責任分工&lt;/h2>
&lt;p>Audit log 跟 operational log 承擔兩條獨立治理鏈：前者服務證據與責任追蹤、後者服務除錯與事故定位。兩者在對象、保留、不變性、權限與粒度上的差異決定它們需要走分開的 pipeline、storage 與保留策略。把 audit log 視為 operational log 的子集、混在同一 stream 治理、會在第一次合規稽核或法規請求時讓證據鏈被打斷（典型徵兆是「靠 grep operational log 拼湊稽核需求」）。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>Operational log&lt;/th>
 &lt;th>Audit log&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>主要對象&lt;/td>
 &lt;td>工程師、SRE、IC&lt;/td>
 &lt;td>合規、法務、安全事件調查、外部稽核&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>主要目的&lt;/td>
 &lt;td>還原事件、定位 root cause&lt;/td>
 &lt;td>證明授權、責任追蹤、事件不可否認&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>保留期&lt;/td>
 &lt;td>7-30 天為典型、依除錯需求&lt;/td>
 &lt;td>數月到數年、依法規與合約&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>不變性&lt;/td>
 &lt;td>通常可被 rotate、aggregate、re-index&lt;/td>
 &lt;td>append-only、tamper-evident&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>存取權限&lt;/td>
 &lt;td>工程團隊廣泛存取&lt;/td>
 &lt;td>最小授權、存取本身也要被稽核&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>內容粒度&lt;/td>
 &lt;td>高頻、雜訊容忍&lt;/td>
 &lt;td>低頻、語意精準、欄位穩定&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>查詢期望&lt;/td>
 &lt;td>秒級、即席&lt;/td>
 &lt;td>分鐘到小時級、結構化、可重現&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Operational log 在 incident timeline 還原時是主力證據。它的失分容忍度高：丟掉 1% 的 log 通常不影響 root cause 分析。&lt;/p>
&lt;p>Audit log 的失分容忍度極低。一次授權記錄遺失、一個欄位漂移、一段時區錯位，都可能讓事後責任追蹤失效。這個差異決定 audit log 必須走獨立 pipeline、獨立 storage、獨立保留策略。&lt;/p>
&lt;h2 id="核心欄位與不變性">核心欄位與不變性&lt;/h2>
&lt;p>Audit event 的核心責任是回答五個問題：誰（who）、做了什麼（what）、何時（when）、在哪（where）、結果如何（outcome）。任一欄位缺失，責任追蹤鏈就有缺口。&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>who&lt;/td>
 &lt;td>認證主體（user id、service account）&lt;/td>
 &lt;td>用 IP 代替主體 → 多人共用無法區分&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>what&lt;/td>
 &lt;td>操作類型 + 對象 ID&lt;/td>
 &lt;td>只記操作不記對象 → 無法重現範圍&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>when&lt;/td>
 &lt;td>事件時間（含時區）+ ingest 時間&lt;/td>
 &lt;td>單一 timestamp → 無法判斷漂移&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>where&lt;/td>
 &lt;td>來源 IP、region、tenant、session&lt;/td>
 &lt;td>缺 tenant → 跨租戶事件無法區分&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>outcome&lt;/td>
 &lt;td>成功 / 失敗 / 拒絕 + 拒絕原因&lt;/td>
 &lt;td>只記成功 → 失敗操作無痕跡&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>不變性保證有三層遞進：&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">audit log</a> 跟 operational log 的本質差異：對象、不變性、保留、法規</li>
<li><a href="/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">audit log</a> 該記什麼：who / what / when / where / outcome、不可被應用層改寫</li>
<li>不變性保證：append-only storage、tamper-evident hash chain、independent retention</li>
<li><a href="/blog/backend/knowledge-cards/pii/" data-link-title="PII" data-link-desc="說明可識別個人的資料如何影響權限、遮罩、保留與稽核">PII</a> 治理：log 中的 PII 偵測、<a href="/blog/backend/knowledge-cards/data-masking/" data-link-title="Data Masking" data-link-desc="說明敏感資料如何在顯示、匯出、log 與測試資料中降低暴露">data masking</a>、tokenization、最小揭露原則</li>
<li>法規維度：GDPR / HIPAA / SOC2 / 個資法 對保留期與存取的要求</li>
<li>跨團隊存取證據連續性：避免責任鏈斷在團隊邊界</li>
<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> 的分工：4.1 是欄位設計、4.12 是治理邊界</li>
<li>跟 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資安</a> 的交接：稽核責任邊界</li>
<li>反模式：audit 跟 operational 混在同 stream；PII 直接打進 log；audit log 跟 application DB 同保留期</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p><a href="/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">Audit log</a> 是把責任、授權與敏感操作留下可稽核證據的訊號，責任是支援合規、責任追蹤與安全事件調查。</p>
<p>這一頁處理的是 governance 邊界。Operational log 服務於除錯，audit log 服務於證據；兩者可以共享部分欄位，但保留、不變性、存取權限與 PII 規則不同。</p>
<p>Audit log 的治理優先序跟 operational log 相反。Operational log 優先服務 <em>當下</em> 的事故定位、追求即時性與覆蓋廣度；audit log 優先服務 <em>未來</em> 的責任追蹤、追求完整性、不變性與長期可查詢。當這兩種優先序衝突時，audit 治理要勝過 operational 便利性。</p>
<h2 id="兩種-log-的責任分工">兩種 log 的責任分工</h2>
<p>Audit log 跟 operational log 承擔兩條獨立治理鏈：前者服務證據與責任追蹤、後者服務除錯與事故定位。兩者在對象、保留、不變性、權限與粒度上的差異決定它們需要走分開的 pipeline、storage 與保留策略。把 audit log 視為 operational log 的子集、混在同一 stream 治理、會在第一次合規稽核或法規請求時讓證據鏈被打斷（典型徵兆是「靠 grep operational log 拼湊稽核需求」）。</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Operational log</th>
          <th>Audit log</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>主要對象</td>
          <td>工程師、SRE、IC</td>
          <td>合規、法務、安全事件調查、外部稽核</td>
      </tr>
      <tr>
          <td>主要目的</td>
          <td>還原事件、定位 root cause</td>
          <td>證明授權、責任追蹤、事件不可否認</td>
      </tr>
      <tr>
          <td>保留期</td>
          <td>7-30 天為典型、依除錯需求</td>
          <td>數月到數年、依法規與合約</td>
      </tr>
      <tr>
          <td>不變性</td>
          <td>通常可被 rotate、aggregate、re-index</td>
          <td>append-only、tamper-evident</td>
      </tr>
      <tr>
          <td>存取權限</td>
          <td>工程團隊廣泛存取</td>
          <td>最小授權、存取本身也要被稽核</td>
      </tr>
      <tr>
          <td>內容粒度</td>
          <td>高頻、雜訊容忍</td>
          <td>低頻、語意精準、欄位穩定</td>
      </tr>
      <tr>
          <td>查詢期望</td>
          <td>秒級、即席</td>
          <td>分鐘到小時級、結構化、可重現</td>
      </tr>
  </tbody>
</table>
<p>Operational log 在 incident timeline 還原時是主力證據。它的失分容忍度高：丟掉 1% 的 log 通常不影響 root cause 分析。</p>
<p>Audit log 的失分容忍度極低。一次授權記錄遺失、一個欄位漂移、一段時區錯位，都可能讓事後責任追蹤失效。這個差異決定 audit log 必須走獨立 pipeline、獨立 storage、獨立保留策略。</p>
<h2 id="核心欄位與不變性">核心欄位與不變性</h2>
<p>Audit event 的核心責任是回答五個問題：誰（who）、做了什麼（what）、何時（when）、在哪（where）、結果如何（outcome）。任一欄位缺失，責任追蹤鏈就有缺口。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>內容</th>
          <th>失分風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>who</td>
          <td>認證主體（user id、service account）</td>
          <td>用 IP 代替主體 → 多人共用無法區分</td>
      </tr>
      <tr>
          <td>what</td>
          <td>操作類型 + 對象 ID</td>
          <td>只記操作不記對象 → 無法重現範圍</td>
      </tr>
      <tr>
          <td>when</td>
          <td>事件時間（含時區）+ ingest 時間</td>
          <td>單一 timestamp → 無法判斷漂移</td>
      </tr>
      <tr>
          <td>where</td>
          <td>來源 IP、region、tenant、session</td>
          <td>缺 tenant → 跨租戶事件無法區分</td>
      </tr>
      <tr>
          <td>outcome</td>
          <td>成功 / 失敗 / 拒絕 + 拒絕原因</td>
          <td>只記成功 → 失敗操作無痕跡</td>
      </tr>
  </tbody>
</table>
<p>不變性保證有三層遞進：</p>
<ol>
<li><strong>Append-only storage</strong>：寫入後不可修改、不可刪除。一般 object storage（S3 Object Lock、GCS Bucket Lock）或 immutable database table 可實作。</li>
<li><strong>Tamper-evident hash chain</strong>：每個 audit event 含前一個 event 的 hash，篡改任一筆會破壞整條 chain。需要週期性 anchor 到外部時間戳服務或第三方公證。</li>
<li><strong>Independent retention</strong>：audit log 的保留期跟 application DB 解耦，application 刪資料不影響 audit。retention 由合規團隊定義、不由應用團隊調整。</li>
</ol>
<p>對應 <a href="/blog/backend/04-observability/cases/fintech-audit-evidence-observability/" data-link-title="FinTech：審計證據鏈的可觀測性設計" data-link-desc="把交易與存取事件轉成可回查證據，降低合規審核與事故判讀落差。">4.C1 FinTech 審計證據鏈</a>：揭露「audit log completeness、event correlation integrity、retention policy drift」是合規場景的核心治理項目，本章關注的是治理邊界跟欄位設計，事件相關的 evidence 包裝由 <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</a> 處理。</p>
<h2 id="跨團隊存取證據連續性">跨團隊存取證據連續性</h2>
<p>跨團隊 audit 治理的核心責任是維持責任鏈在團隊邊界上的連續性。應用團隊記應用層事件、基礎設施團隊記 infra 層存取、IAM 團隊記授權變更，三段證據各自必要、但只有拼接後才能還原一次跨團隊敏感操作。常見失敗來自團隊邊界上的責任鏈斷裂 — 而非單一團隊技術不到位 — 任一段缺失都會讓事後復盤無法閉合。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/" data-link-title="Healthcare：存取可追溯性與保留邊界" data-link-desc="在資料主權限制下，建立可追溯存取證據與分層保留策略。">4.C3 Healthcare 存取可追溯性與保留邊界</a>：揭露「access evidence continuity、retention boundary violations、timestamp integrity」三個方向。Healthcare 場景把這個問題放大，但跨團隊存取連續性是所有合規場景的共同議題。</p>
<p>讓存取證據跨團隊連續的可操作做法：</p>
<ol>
<li><strong>共用 correlation field</strong>：把 request id、trace id、session id 拉到應用層、infra 層、IAM 層共用，讓三段 log 可以拼起來。</li>
<li><strong>明確團隊 ownership 邊界</strong>：每類 audit event 指定唯一 owner team，避免「應該是另一隊負責」的責任轉嫁。</li>
<li><strong>跨團隊 retention 對齊</strong>：應用 audit、infra audit、IAM audit 的保留期要對齊或互為超集，避免一段過期一段還在的拼接斷裂。</li>
<li><strong>跨團隊查詢入口</strong>：合規團隊有單一查詢介面能跨三段 log 拉同一 correlation id 的完整證據鏈。</li>
</ol>
<p>把這些做法寫進 <a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a> 的 ownership 矩陣，能避免單次合規請求引發跨團隊的拼接工作。</p>
<h2 id="retention-與保留策略漂移">Retention 與保留策略漂移</h2>
<p>Retention 是 audit log 跟 operational log 最大的治理差異。Operational log 通常用 30-90 天 rotation；audit log 依資料類型跟法規可能要 1-10 年。</p>
<p>把 audit log 跟 operational log 用同一條 retention 策略治理，會在合規稽核時被抓出來。常見的失敗：</p>
<ul>
<li>audit log 跟 application DB 同保留 90 天、不符 GDPR / HIPAA / 金融法規。</li>
<li>audit log 經過 aggregation 處理、原始事件丟失、但 aggregated view 無法滿足法規要求。</li>
<li>retention 策略由應用團隊調整、不經合規團隊審批、容易在成本壓力下被縮短。</li>
</ul>
<p>Retention 漂移的偵測手段：把 retention compliance 變成可查詢的訊號。週期性對照各類 audit log 的實際留存時間跟政策要求、偏差超過閾值時觸發告警、讓漂移在治理週期內就被處理、避免等到稽核時才發現。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/fintech-audit-evidence-observability/" data-link-title="FinTech：審計證據鏈的可觀測性設計" data-link-desc="把交易與存取事件轉成可回查證據，降低合規審核與事故判讀落差。">4.C1 FinTech retention policy drift</a> 跟 <a href="/blog/backend/04-observability/cases/healthcare-access-traceability-and-retention/" data-link-title="Healthcare：存取可追溯性與保留邊界" data-link-desc="在資料主權限制下，建立可追溯存取證據與分層保留策略。">4.C3 Healthcare retention boundary violations</a>：兩個案例的判讀訊號都把 retention 偏離列為一級訊號（兩 case 的表格行明示這點）；本章在此基礎上補上「偏離視為治理事件、retention compliance 變成可查詢訊號」的展開、屬章節推論。</p>
<p>保留階梯（hot / warm / cold tier）與成本歸屬的詳細設計見 <a href="/blog/backend/04-observability/cardinality-cost-governance/#%e6%8e%a7%e5%88%b6%e9%9d%a2%e8%88%87%e4%bf%9d%e7%95%99%e9%9a%8e%e6%a2%af" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 控制面與保留階梯</a>。</p>
<h2 id="pii-治理與最小揭露">PII 治理與最小揭露</h2>
<p><a href="/blog/backend/knowledge-cards/pii/" data-link-title="PII" data-link-desc="說明可識別個人的資料如何影響權限、遮罩、保留與稽核">PII</a> 在 log 治理裡是雙重風險：寫入時的合規風險、長期保留時的外洩風險。Audit log 的長保留期讓 PII 風險被放大。</p>
<p>可操作的 PII 治理層次：</p>
<ol>
<li><strong>寫入前 redaction</strong>：應用層在輸出 log 時用結構化欄位 + 顯式 marking，避免把整個 request body 序列化進 log。</li>
<li><strong>Pipeline 層 PII 偵測</strong>：collector 加上 PII pattern 偵測（信用卡號、身分證、token），預設遮罩、例外要顯式授權。</li>
<li><strong>Tokenization / pseudonymization</strong>：把直接識別碼換成 token，token 跟原值的映射存在獨立、受嚴格授權的 vault 中。</li>
<li><strong>存取本身的稽核</strong>：誰存取了哪段 audit log、何時存取、為什麼存取，本身也是 audit event。</li>
</ol>
<p>最小揭露原則的實作關鍵是「預設遮罩、需要時申請」。把預設值設成揭露，會在某次事故除錯為了方便而打開、之後忘記關閉。預設遮罩讓每次解碼都是可追蹤的事件。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 audit log 時，先看事件是否能回答 who / what / when / where / outcome，再看資料是否受到獨立保護。</p>
<p>重點訊號包括：</p>
<ul>
<li>audit event 是否不可由一般應用流程修改</li>
<li><a href="/blog/backend/knowledge-cards/pii/" data-link-title="PII" data-link-desc="說明可識別個人的資料如何影響權限、遮罩、保留與稽核">PII</a> 是否經過 redaction、tokenization 或最小揭露</li>
<li><a href="/blog/backend/knowledge-cards/retention/" data-link-title="Retention" data-link-desc="說明資料或事件保留多久，以及保留期限如何影響重放與成本">retention</a> 是否符合法規與客戶合約要求</li>
<li>security incident 與 operational incident 是否能引用同一條證據鏈</li>
<li>跨團隊存取的 correlation field 是否連續</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>稽核需求出現時、靠 grep operational log 拼湊</li>
<li>log 中發現 credit card / 身分證 / token 等 PII</li>
<li>audit log 跟 application 同 retention（30 / 90 天）、不符法規</li>
<li>應用層帳號可寫入 / 修改 audit log</li>
<li>法規稽核請求耗時數週、事件鏈定位需要人工補洞</li>
<li>跨團隊查詢同一 correlation id 拼不出完整鏈</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Audit 跟 operational 同 stream</td>
          <td>用一條 pipeline 處理所有 log</td>
          <td>拆獨立 pipeline、獨立 storage</td>
      </tr>
      <tr>
          <td>PII 直接進 log</td>
          <td>信用卡、身分證在 raw log 中可見</td>
          <td>Pipeline 層偵測 + 預設 redaction</td>
      </tr>
      <tr>
          <td>同保留期治理</td>
          <td>audit log 跟 application DB 同 90 天</td>
          <td>依法規重訂保留期、retention compliance 變成告警</td>
      </tr>
      <tr>
          <td>應用層可改寫 audit</td>
          <td>service account 對 audit storage 有 write/delete 權限</td>
          <td>append-only + tamper-evident hash chain</td>
      </tr>
      <tr>
          <td>跨團隊責任鏈斷裂</td>
          <td>同一事件三段 log 互不關聯</td>
          <td>共用 correlation field、跨團隊 retention 對齊</td>
      </tr>
  </tbody>
</table>
<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>：欄位設計</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 / cost</a>：audit 的長期保留成本</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：跨團隊 audit ownership 矩陣</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 evidence package</a>：audit log 進入 evidence 交接</li>
<li><a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">07 資料保護</a>：<a href="/blog/backend/knowledge-cards/pii/" data-link-title="PII" data-link-desc="說明可識別個人的資料如何影響權限、遮罩、保留與稽核">PII</a> redaction 與責任邊界</li>
<li><a href="/blog/backend/knowledge-cards/post-incident-review/" data-link-title="Post-Incident Review" data-link-desc="說明事故後如何完成復盤、學習與改進閉環">8.5 post-incident review</a>：事故證據鏈引用 audit log</li>
<li><a href="/blog/backend/08-incident-response/security-vs-operational-incident/" data-link-title="8.17 Security Incident vs Operational Incident 分流" data-link-desc="把資安事故跟可用性事故的 IR 流程分支點明確化">8.17 security vs operational IR</a>：證據鏈來源</li>
<li><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：鑑識回溯查詢模式跟 audit log 的長期查詢設計</li>
</ul>
]]></content:encoded></item><item><title>4.13 Service Topology 與 Dependency Map</title><link>https://tarrragon.github.io/blog/backend/04-observability/service-topology/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/service-topology/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>為何依賴拓撲需要獨立節點：人工維護的依賴圖永遠過時&lt;/li>
&lt;li>拓撲訊號的來源：trace（4.3）、service mesh（mTLS / sidecar）、network flow log&lt;/li>
&lt;li>服務 graph 的維度：呼叫頻率、latency、錯誤率、版本&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>動態叢集下的拓撲追蹤：擴縮事件如何回寫拓撲訊號&lt;/li>
&lt;li>跟 &lt;a href="https://tarrragon.github.io/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&lt;/a> 的分工：trace 是單 request、topology 是統計聚合&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 deployment platform&lt;/a> 的整合：service mesh 部署&lt;/li>
&lt;li>反模式：架構圖只在 wiki 上、跟實際流量漂移；新依賴上線缺 review；拓撲圖回答「這服務掛了誰受影響」需要人工追查&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Service topology 是把跨服務依賴從文件轉成可觀測資料的能力，責任是讓團隊能用實際呼叫關係判斷依賴、影響面與變更風險。&lt;/p>
&lt;p>這一頁處理的是服務關係圖。Trace 解釋單次 request、topology 解釋一段時間內的依賴結構；兩者合起來才能回答「這個服務壞了會影響誰」。&lt;/p>
&lt;p>人工維護的依賴圖在快速變動的微服務環境下會持續漂移。新服務上線、舊服務下架、依賴方向反轉、版本切換都會發生在 wiki 圖更新之前；事故時依賴 wiki 圖判讀 blast radius，會把過期的依賴結構誤當成當前事實。&lt;/p>
&lt;h2 id="拓撲訊號的來源">拓撲訊號的來源&lt;/h2>
&lt;p>Service topology 的可信度取決於資料來源是否反映真實流量。常見的訊號來源各有覆蓋範圍跟限制：&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>Trace（4.3）&lt;/td>
 &lt;td>應用層呼叫關係、含 latency / 錯誤率&lt;/td>
 &lt;td>需要 instrumentation 覆蓋、有採樣偏誤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Service mesh&lt;/td>
 &lt;td>sidecar / mTLS 拦截的所有跨服務流量&lt;/td>
 &lt;td>依賴 mesh 部署、不含外部依賴&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Network flow log&lt;/td>
 &lt;td>L3 / L4 連線記錄、含外部依賴&lt;/td>
 &lt;td>缺少應用語意、難判斷哪個 service&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>API gateway log&lt;/td>
 &lt;td>外部入口流量、含 client / API 維度&lt;/td>
 &lt;td>只看到 gateway 視角、不知道內部呼叫&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>實務上常用組合：trace 作為主要來源（提供應用語意跟錯誤率），service mesh 作為補充（補上未 instrument 的服務），network flow log 作為兜底（揭露未管理的外部依賴）。&lt;/p>
&lt;p>把不同來源的拓撲訊號合併時，要顯式記錄每段依賴的來源。當 trace 看不到某段依賴、service mesh 卻看得到時，可能意味著 instrumentation 缺失或服務 bypass mesh，這本身是治理訊號。&lt;/p>
&lt;h2 id="服務-graph-的維度">服務 Graph 的維度&lt;/h2>
&lt;p>服務 graph 的責任是把跨服務依賴量化成可判讀的訊號、支援事故決策跟容量規劃。每段依賴關係要帶上維度（頻率、latency、錯誤率、版本、可選性）、才能在事故時被直接使用、而非只能呈現拓撲輪廓。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>呼叫頻率&lt;/strong>：高頻依賴跟低頻依賴的失效影響不同。高頻依賴失效會立即放大成 5xx，低頻依賴失效可能要數小時才浮現。&lt;/li>
&lt;li>&lt;strong>Latency 分布&lt;/strong>：依賴 p50 / p99 latency 決定下游 timeout 應該設多少。沒有 latency 訊號的依賴圖無法支援 timeout 設計。&lt;/li>
&lt;li>&lt;strong>Error rate&lt;/strong>：依賴的錯誤率提供 budget 訊號。當某依賴錯誤率上升，下游應觸發降級、保護自身可用性、避免進入無限重試放大故障。&lt;/li>
&lt;li>&lt;strong>版本 / API contract&lt;/strong>：依賴的版本變化跟 API contract 變更要進拓撲訊號。版本升級後若某段依賴消失，可能是 contract breaking。&lt;/li>
&lt;li>&lt;strong>方向跟可選性&lt;/strong>：是必要依賴（失效 = 服務失敗）還是可選依賴（失效 = 功能降級），影響事故分級。&lt;/li>
&lt;/ul>
&lt;p>這些維度進入拓撲訊號後，配合 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/dependency-reliability-budget/" data-link-title="6.14 Dependency Reliability Budget" data-link-desc="把內外依賴的可靠性納入 SLO 計算與設計約束">6.14 dependency budget&lt;/a> 才能把依賴可靠性變成可量化決策。&lt;/p>
&lt;h2 id="依賴變化的治理">依賴變化的治理&lt;/h2>
&lt;p>依賴關係的變化本身是訊號。新增依賴、刪除依賴、依賴方向反轉，都是值得告警的事件。沒有依賴變化偵測時，新服務接入往往跳過依賴 review，事故發生才從 trace 反查到「原來這條 path 已經接了三個月」。&lt;/p>
&lt;p>可操作的依賴變化告警：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>新增依賴 alert&lt;/strong>：當 trace 出現新的 service-to-service 呼叫，觸發 review。新依賴是否在預期內、是否經過 contract review、是否有 fallback。&lt;/li>
&lt;li>&lt;strong>依賴消失 alert&lt;/strong>：某段穩定存在的依賴在 N 分鐘內 trace 看不到，可能是 instrumentation 漏、可能是上游被誤改、可能是真實事故的早期訊號。&lt;/li>
&lt;li>&lt;strong>依賴方向反轉&lt;/strong>：A → B 變成 B → A 通常意味著 refactor 或誤改、應該觸發 review。&lt;/li>
&lt;li>&lt;strong>循環依賴偵測&lt;/strong>：環狀依賴會在事故時放大恢復難度、應該在拓撲訊號層級就阻擋。&lt;/li>
&lt;/ol>
&lt;h2 id="動態叢集下的拓撲訊號">動態叢集下的拓撲訊號&lt;/h2>
&lt;p>動態叢集下拓撲訊號的責任是讓觀測模型追上實際依賴結構的變化。Pod 數量浮動、node 換代、service IP 變化、跨 cluster 流量重新分配都會在分鐘級內改變服務間的可達性、若拓撲訊號停留在週期性快照、事故時看到的會是過期結構。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>為何依賴拓撲需要獨立節點：人工維護的依賴圖永遠過時</li>
<li>拓撲訊號的來源：trace（4.3）、service mesh（mTLS / sidecar）、network flow log</li>
<li>服務 graph 的維度：呼叫頻率、latency、錯誤率、版本</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>動態叢集下的拓撲追蹤：擴縮事件如何回寫拓撲訊號</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</a> 的分工：trace 是單 request、topology 是統計聚合</li>
<li>跟 <a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 deployment platform</a> 的整合：service mesh 部署</li>
<li>反模式：架構圖只在 wiki 上、跟實際流量漂移；新依賴上線缺 review；拓撲圖回答「這服務掛了誰受影響」需要人工追查</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Service topology 是把跨服務依賴從文件轉成可觀測資料的能力，責任是讓團隊能用實際呼叫關係判斷依賴、影響面與變更風險。</p>
<p>這一頁處理的是服務關係圖。Trace 解釋單次 request、topology 解釋一段時間內的依賴結構；兩者合起來才能回答「這個服務壞了會影響誰」。</p>
<p>人工維護的依賴圖在快速變動的微服務環境下會持續漂移。新服務上線、舊服務下架、依賴方向反轉、版本切換都會發生在 wiki 圖更新之前；事故時依賴 wiki 圖判讀 blast radius，會把過期的依賴結構誤當成當前事實。</p>
<h2 id="拓撲訊號的來源">拓撲訊號的來源</h2>
<p>Service topology 的可信度取決於資料來源是否反映真實流量。常見的訊號來源各有覆蓋範圍跟限制：</p>
<table>
  <thead>
      <tr>
          <th>來源</th>
          <th>覆蓋範圍</th>
          <th>主要限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Trace（4.3）</td>
          <td>應用層呼叫關係、含 latency / 錯誤率</td>
          <td>需要 instrumentation 覆蓋、有採樣偏誤</td>
      </tr>
      <tr>
          <td>Service mesh</td>
          <td>sidecar / mTLS 拦截的所有跨服務流量</td>
          <td>依賴 mesh 部署、不含外部依賴</td>
      </tr>
      <tr>
          <td>Network flow log</td>
          <td>L3 / L4 連線記錄、含外部依賴</td>
          <td>缺少應用語意、難判斷哪個 service</td>
      </tr>
      <tr>
          <td>API gateway log</td>
          <td>外部入口流量、含 client / API 維度</td>
          <td>只看到 gateway 視角、不知道內部呼叫</td>
      </tr>
  </tbody>
</table>
<p>實務上常用組合：trace 作為主要來源（提供應用語意跟錯誤率），service mesh 作為補充（補上未 instrument 的服務），network flow log 作為兜底（揭露未管理的外部依賴）。</p>
<p>把不同來源的拓撲訊號合併時，要顯式記錄每段依賴的來源。當 trace 看不到某段依賴、service mesh 卻看得到時，可能意味著 instrumentation 缺失或服務 bypass mesh，這本身是治理訊號。</p>
<h2 id="服務-graph-的維度">服務 Graph 的維度</h2>
<p>服務 graph 的責任是把跨服務依賴量化成可判讀的訊號、支援事故決策跟容量規劃。每段依賴關係要帶上維度（頻率、latency、錯誤率、版本、可選性）、才能在事故時被直接使用、而非只能呈現拓撲輪廓。</p>
<ul>
<li><strong>呼叫頻率</strong>：高頻依賴跟低頻依賴的失效影響不同。高頻依賴失效會立即放大成 5xx，低頻依賴失效可能要數小時才浮現。</li>
<li><strong>Latency 分布</strong>：依賴 p50 / p99 latency 決定下游 timeout 應該設多少。沒有 latency 訊號的依賴圖無法支援 timeout 設計。</li>
<li><strong>Error rate</strong>：依賴的錯誤率提供 budget 訊號。當某依賴錯誤率上升，下游應觸發降級、保護自身可用性、避免進入無限重試放大故障。</li>
<li><strong>版本 / API contract</strong>：依賴的版本變化跟 API contract 變更要進拓撲訊號。版本升級後若某段依賴消失，可能是 contract breaking。</li>
<li><strong>方向跟可選性</strong>：是必要依賴（失效 = 服務失敗）還是可選依賴（失效 = 功能降級），影響事故分級。</li>
</ul>
<p>這些維度進入拓撲訊號後，配合 <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> 才能把依賴可靠性變成可量化決策。</p>
<h2 id="依賴變化的治理">依賴變化的治理</h2>
<p>依賴關係的變化本身是訊號。新增依賴、刪除依賴、依賴方向反轉，都是值得告警的事件。沒有依賴變化偵測時，新服務接入往往跳過依賴 review，事故發生才從 trace 反查到「原來這條 path 已經接了三個月」。</p>
<p>可操作的依賴變化告警：</p>
<ol>
<li><strong>新增依賴 alert</strong>：當 trace 出現新的 service-to-service 呼叫，觸發 review。新依賴是否在預期內、是否經過 contract review、是否有 fallback。</li>
<li><strong>依賴消失 alert</strong>：某段穩定存在的依賴在 N 分鐘內 trace 看不到，可能是 instrumentation 漏、可能是上游被誤改、可能是真實事故的早期訊號。</li>
<li><strong>依賴方向反轉</strong>：A → B 變成 B → A 通常意味著 refactor 或誤改、應該觸發 review。</li>
<li><strong>循環依賴偵測</strong>：環狀依賴會在事故時放大恢復難度、應該在拓撲訊號層級就阻擋。</li>
</ol>
<h2 id="動態叢集下的拓撲訊號">動態叢集下的拓撲訊號</h2>
<p>動態叢集下拓撲訊號的責任是讓觀測模型追上實際依賴結構的變化。Pod 數量浮動、node 換代、service IP 變化、跨 cluster 流量重新分配都會在分鐘級內改變服務間的可達性、若拓撲訊號停留在週期性快照、事故時看到的會是過期結構。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/airbnb-observability-k8s-scale-signals/" data-link-title="4.C8 Airbnb：Kubernetes 規模化下的觀測訊號治理" data-link-desc="叢集擴縮與工作負載變動如何回寫觀測模型。">4.C8 Airbnb K8s 規模化下的觀測訊號治理</a>：揭露「叢集擴縮跟工作負載變動需要回寫觀測模型」「叢集層指標跟服務層指標要分開治理」「擴縮事件跟事故關聯要可回溯」三個方向（case 直接列出）；以下展開的 service 層級節點、跨 cluster failover、drill-down 設計屬通用 K8s observability 經驗、case 本身未細說。</p>
<p>動態叢集對拓撲訊號的挑戰有三個面向、性質不同、各自的對應做法也不同。</p>
<p><strong>拓撲節點不穩定</strong> 是資料模型層的問題。Pod 短暫存在、IP 不固定、若直接把 Pod 當拓撲節點、graph 會分鐘級持續抖動、事故時看到的依賴結構不可信。對應做法是把節點層級從 Pod / IP 提升到 service（service name + version + region）、把 instance / Pod 層級放到 dashboard drill-down、讓主拓撲圖反映穩定的服務依賴而非瞬時實例分布。</p>
<p><strong>擴縮事件 vs 真實事故區分</strong> 是訊號分辨層的問題。HPA scale-up / scale-down、cluster autoscaler 加 node 失敗、Pod 重啟、health check 短暫失敗，這些擴縮動作本身會產生跟事故相似的訊號（5xx 短暫升高、reconnect、依賴連線中斷）、若沒分辨機制、值班會把擴縮過程的正常波動誤判成事故、或把真正的事故誤判成擴縮。對應做法是把擴縮事件本身打進 timeline、跟事故 timeline 共用同一張圖、判讀時對齊看。</p>
<p><strong>跨 cluster 流量變化</strong> 是視角層的問題。multi-cluster 部署下、流量可能因 cluster 變更從 cluster A 切到 cluster B、若拓撲圖只看單 cluster 視角、B cluster 突增的流量會被解讀為 traffic spike、漏掉真正的 failover 事件。對應做法是讓拓撲圖呈現跨 cluster 邊界、把 cluster 間流量變化也標到圖上、避免 cluster 邊界成為觀測盲區。</p>
<p>把叢集層指標（node count、Pod count、HPA event）跟服務層指標（call rate、error rate、latency）分開治理，是動態叢集環境的基本要求。叢集層指標的 owner 通常是 platform team、服務層指標的 owner 通常是 service team，兩者放在同一 dashboard 上要清楚標示來源跟責任。</p>
<p>擴縮事件回溯到事故關聯的另一個價值是 capacity retrospective。當 HPA 在事故前後觸發、scale-up 是否足夠、scale-down 是否過快，都需要把擴縮 timeline 跟事故 timeline 拼起來看，回到 <a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 容量成本</a> 跟 <a href="/blog/backend/09-performance-capacity/" data-link-title="模組九：效能工程與容量規劃" data-link-desc="把『目前配置能撐多少、要加多少機器』變成可量化、可驗證、可改進的工程流程">9.6 容量規劃</a> 的回寫。</p>
<h2 id="blast-radius-推導">Blast Radius 推導</h2>
<p>Blast radius 分析的核心責任是回答「如果這個服務或依賴失效、哪些上游 / 下游會受影響、影響多深」。沒有實時拓撲訊號時，這個分析靠經驗、容易低估或高估。</p>
<p>實時 topology 加上依賴可選性標記後，blast radius 可以分層推導：</p>
<ul>
<li><strong>直接下游</strong>：直接呼叫該服務的服務、立即受影響。</li>
<li><strong>間接下游</strong>：透過中間服務間接依賴、影響時間延後。</li>
<li><strong>可降級下游</strong>：依賴是 optional、失效會觸發降級但不失敗。</li>
<li><strong>必要下游</strong>：依賴是 mandatory、失效會傳播成服務失敗。</li>
</ul>
<p>事故時把 blast radius 從拓撲推導出來、再對照實際看到的 5xx 跟 SLO <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a>、能驗證影響面是否符合預期。當實際影響超出推導 blast radius、通常意味著存在未紀錄依賴。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 topology 時，先看資料是否來自真實流量，再看依賴變化是否能被治理。</p>
<p>重點訊號包括：</p>
<ul>
<li>service graph 是否包含呼叫方向、頻率、latency 與 error rate</li>
<li>新增依賴是否能觸發 review 或 alert</li>
<li><a href="/blog/backend/knowledge-cards/blast-radius/" data-link-title="Blast Radius" data-link-desc="說明事故影響面如何估算與隔離">blast radius</a> 是否能從上游 / 下游關係推導</li>
<li>topology 是否能餵給 dependency budget 與事故型態判讀</li>
<li>動態擴縮事件是否打進 timeline、能跟事故區分</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>事故時回答「誰呼叫這服務」需要人工追查</li>
<li>新服務接入無依賴 review、出事後才發現連結</li>
<li>架構文件跟實際呼叫關係漂移、半年沒更新</li>
<li>service mesh 部署但拓撲訊號未被使用</li>
<li>循環依賴存在但無人發現</li>
<li>擴縮事件造成的短暫錯誤被誤判成事故</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Wiki 架構圖</td>
          <td>圖跟實際流量漂移半年</td>
          <td>從 trace / mesh 自動生成、持續更新</td>
      </tr>
      <tr>
          <td>新依賴無 review</td>
          <td>trace 出現新依賴沒人知道</td>
          <td>新依賴 alert、依賴 review 進 release flow</td>
      </tr>
      <tr>
          <td>拓撲節點用 Pod / Instance</td>
          <td>動態叢集下圖持續抖動</td>
          <td>service 層級節點、Pod 放 drill-down</td>
      </tr>
      <tr>
          <td>叢集跟服務指標混在一張圖</td>
          <td>platform 跟 service 責任不清</td>
          <td>分層 dashboard、明確 owner</td>
      </tr>
      <tr>
          <td>Blast radius 靠經驗推導</td>
          <td>影響面評估不準、事後才發現遺漏</td>
          <td>從拓撲訊號自動推導、跟實際影響對照</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<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</a>：拓撲訊號的原始來源</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：叢集層 / 服務層 ownership 分工</li>
<li><a href="/blog/backend/05-deployment-platform/" data-link-title="模組五：部署平台與網路入口" data-link-desc="整理 Kubernetes、systemd、load balancer、container 與服務生命週期合約">05 部署</a>：service mesh 配置</li>
<li>6.5 pre-mortem（規劃中）：依賴失效路徑分析</li>
<li><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 capacity cost</a>：擴縮事件 retrospective</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/08-incident-response/" data-link-title="模組八：事故處理與復盤" data-link-desc="用 IR 領域詞彙建問題節點、以服務級案例庫累積事故脈絡，先建概念與案例庫再進實作交接">8.9 事故型態庫</a>：<a href="/blog/backend/knowledge-cards/cascading-failure/" data-link-title="Cascading Failure" data-link-desc="說明局部故障如何透過等待、重試與資源耗盡擴散到整個系統">cascading failure</a> 型態的拓撲依據</li>
</ul>
]]></content:encoded></item><item><title>4.14 Anomaly Detection</title><link>https://tarrragon.github.io/blog/backend/04-observability/anomaly-detection/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/anomaly-detection/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>Anomaly detection 跟 rule-based alert 的分工&lt;/li>
&lt;li>Baseline 模型類別&lt;/li>
&lt;li>Anomaly 訊號的處理路徑&lt;/li>
&lt;li>False positive 與 alert noise 共用預算&lt;/li>
&lt;li>Explainability：anomaly 要能定位到維度&lt;/li>
&lt;li>Vendor 定位&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Anomaly detection 是用統計基線或模型找出偏離常態的訊號，責任是補上 rule-based alert 難以事先列舉的變化。&lt;/p>
&lt;p>Rule-based alert 抓已知模式 — 團隊事先定義「error rate &amp;gt; 1% 就告警」。Anomaly detection 抓未知模式 — 系統觀察到「今天的 latency 分布跟過去 30 天的同時段不同」。兩者互補：rule-based 精確但只能抓團隊已預見的問題，anomaly detection 有噪音但能發現團隊沒想到的退化。&lt;/p>
&lt;p>Anomaly 適合作為提示層（hint），通常先進 dashboard 或低 severity 路由，再由 SLO 判讀或人工確認決定是否升級。把 anomaly 直接接 page 是噪音爆量的常見原因。&lt;/p>
&lt;h2 id="跟-rule-based-alert-的分工">跟 Rule-based Alert 的分工&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>Rule-based alert&lt;/th>
 &lt;th>Anomaly detection&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>觸發條件&lt;/td>
 &lt;td>固定閾值或 burn rate&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>False positive&lt;/td>
 &lt;td>閾值對齊時低&lt;/td>
 &lt;td>較高（季節性未建模、促銷、release）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>適合的 severity&lt;/td>
 &lt;td>Critical / Warning&lt;/td>
 &lt;td>Info / Warning（確認後才升級）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>維護成本&lt;/td>
 &lt;td>隨服務變化需調整閾值&lt;/td>
 &lt;td>模型要持續 retrain 或校正&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>最有效的整合方式：rule-based alert 處理已知的 SLO violation（symptom-based、高 severity），anomaly detection 處理趨勢異常跟 novel failure mode（低 severity、dashboard widget）。兩者共用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue&lt;/a> 的 noise budget — anomaly 的 false positive 也算進整體 noise rate。&lt;/p>
&lt;h2 id="baseline-模型類別">Baseline 模型類別&lt;/h2>
&lt;h3 id="seasonal-baseline">Seasonal baseline&lt;/h3>
&lt;p>按日夜、週末、節慶、促銷等週期建立基線。同一個指標的「正常範圍」在週一上午跟週日凌晨不同。Seasonal model 用歷史同期資料建立預期帶（expected band），偏離帶外視為 anomaly。&lt;/p>
&lt;p>Seasonal baseline 的失敗模式是週期性假設錯誤 — 業務改變後流量模式跟歷史不同（新產品上線改變了週末流量），模型用錯誤的基線判斷。需要定期驗證模型跟實際流量的吻合度。&lt;/p>
&lt;h3 id="moving-window-baseline">Moving window baseline&lt;/h3>
&lt;p>用過去 N 分鐘 / 小時的資料建立動態基線。比 seasonal model 簡單、延遲更低，但對突發變化更敏感（release 後 latency 自然變化可能觸發 anomaly）。&lt;/p>
&lt;p>Moving window 適合不需要週期性建模的指標 — 連線數、queue depth、goroutine count 等「預期穩定、突變代表問題」的指標。&lt;/p>
&lt;h3 id="ml-basedforecast--clustering">ML-based（forecast / clustering）&lt;/h3>
&lt;p>用機器學習模型做時間序列預測（Prophet、ARIMA）或高維度聚類（isolation forest、DBSCAN）。能處理複雜的多變量異常（A 指標上升 + B 指標下降 = 異常，但各自單獨看都在正常範圍）。&lt;/p>
&lt;p>ML 模型的成本是訓練、retrain、模型版本管理跟 explainability。多數團隊的起步方式是先用 seasonal + moving window（不需要 ML pipeline），等 false positive 管理穩定後再引入 ML。&lt;/p>
&lt;h2 id="anomaly-訊號的處理路徑">Anomaly 訊號的處理路徑&lt;/h2>
&lt;p>Anomaly detection 的輸出是「這個指標在這段時間偏離基線」— 候選訊號，不是確認的問題。處理路徑決定 anomaly 是有用的提示還是噪音來源。&lt;/p>
&lt;p>&lt;strong>Dashboard widget&lt;/strong>：anomaly 標記在 time series panel 上（標色、annotation），讓巡視 dashboard 的工程師注意到。低成本、零噪音（不通知任何人）、但需要有人主動看。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>Anomaly detection 跟 rule-based alert 的分工</li>
<li>Baseline 模型類別</li>
<li>Anomaly 訊號的處理路徑</li>
<li>False positive 與 alert noise 共用預算</li>
<li>Explainability：anomaly 要能定位到維度</li>
<li>Vendor 定位</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Anomaly detection 是用統計基線或模型找出偏離常態的訊號，責任是補上 rule-based alert 難以事先列舉的變化。</p>
<p>Rule-based alert 抓已知模式 — 團隊事先定義「error rate &gt; 1% 就告警」。Anomaly detection 抓未知模式 — 系統觀察到「今天的 latency 分布跟過去 30 天的同時段不同」。兩者互補：rule-based 精確但只能抓團隊已預見的問題，anomaly detection 有噪音但能發現團隊沒想到的退化。</p>
<p>Anomaly 適合作為提示層（hint），通常先進 dashboard 或低 severity 路由，再由 SLO 判讀或人工確認決定是否升級。把 anomaly 直接接 page 是噪音爆量的常見原因。</p>
<h2 id="跟-rule-based-alert-的分工">跟 Rule-based Alert 的分工</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>Rule-based alert</th>
          <th>Anomaly detection</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>觸發條件</td>
          <td>固定閾值或 burn rate</td>
          <td>偏離統計基線</td>
      </tr>
      <tr>
          <td>抓什麼</td>
          <td>已知模式（團隊事先定義）</td>
          <td>未知模式（歷史基線判斷）</td>
      </tr>
      <tr>
          <td>精確度</td>
          <td>高（閾值明確）</td>
          <td>低到中（統計偏差 = 候選，需要確認）</td>
      </tr>
      <tr>
          <td>False positive</td>
          <td>閾值對齊時低</td>
          <td>較高（季節性未建模、促銷、release）</td>
      </tr>
      <tr>
          <td>適合的 severity</td>
          <td>Critical / Warning</td>
          <td>Info / Warning（確認後才升級）</td>
      </tr>
      <tr>
          <td>維護成本</td>
          <td>隨服務變化需調整閾值</td>
          <td>模型要持續 retrain 或校正</td>
      </tr>
  </tbody>
</table>
<p>最有效的整合方式：rule-based alert 處理已知的 SLO violation（symptom-based、高 severity），anomaly detection 處理趨勢異常跟 novel failure mode（低 severity、dashboard widget）。兩者共用 <a href="/blog/backend/knowledge-cards/alert-fatigue/" data-link-title="Alert Fatigue" data-link-desc="說明過多低品質告警如何降低 on-call 反應品質">alert fatigue</a> 的 noise budget — anomaly 的 false positive 也算進整體 noise rate。</p>
<h2 id="baseline-模型類別">Baseline 模型類別</h2>
<h3 id="seasonal-baseline">Seasonal baseline</h3>
<p>按日夜、週末、節慶、促銷等週期建立基線。同一個指標的「正常範圍」在週一上午跟週日凌晨不同。Seasonal model 用歷史同期資料建立預期帶（expected band），偏離帶外視為 anomaly。</p>
<p>Seasonal baseline 的失敗模式是週期性假設錯誤 — 業務改變後流量模式跟歷史不同（新產品上線改變了週末流量），模型用錯誤的基線判斷。需要定期驗證模型跟實際流量的吻合度。</p>
<h3 id="moving-window-baseline">Moving window baseline</h3>
<p>用過去 N 分鐘 / 小時的資料建立動態基線。比 seasonal model 簡單、延遲更低，但對突發變化更敏感（release 後 latency 自然變化可能觸發 anomaly）。</p>
<p>Moving window 適合不需要週期性建模的指標 — 連線數、queue depth、goroutine count 等「預期穩定、突變代表問題」的指標。</p>
<h3 id="ml-basedforecast--clustering">ML-based（forecast / clustering）</h3>
<p>用機器學習模型做時間序列預測（Prophet、ARIMA）或高維度聚類（isolation forest、DBSCAN）。能處理複雜的多變量異常（A 指標上升 + B 指標下降 = 異常，但各自單獨看都在正常範圍）。</p>
<p>ML 模型的成本是訓練、retrain、模型版本管理跟 explainability。多數團隊的起步方式是先用 seasonal + moving window（不需要 ML pipeline），等 false positive 管理穩定後再引入 ML。</p>
<h2 id="anomaly-訊號的處理路徑">Anomaly 訊號的處理路徑</h2>
<p>Anomaly detection 的輸出是「這個指標在這段時間偏離基線」— 候選訊號，不是確認的問題。處理路徑決定 anomaly 是有用的提示還是噪音來源。</p>
<p><strong>Dashboard widget</strong>：anomaly 標記在 time series panel 上（標色、annotation），讓巡視 dashboard 的工程師注意到。低成本、零噪音（不通知任何人）、但需要有人主動看。</p>
<p><strong>Low severity alert（info / warning）</strong>：anomaly 進入 alerting pipeline，但 severity 設為 info 或 warning。不 page on-call、但記錄在 alert history 中。事故發生後可以回溯「事故前有沒有 anomaly 提早預警」。</p>
<p><strong>Conditional escalation</strong>：anomaly 搭配 rule-based 條件升級。「Latency 偏離基線 + error rate 超過 SLO <a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a>」→ 升級為 critical。單獨的 anomaly 不足以 page，但跟其他訊號組合時有判讀價值。</p>
<h2 id="explainability">Explainability</h2>
<p>Anomaly 觸發時，工程師需要回答「為什麼異常」 — 是哪個服務、哪個 endpoint、哪個 tenant、哪個地區導致的。只告訴你「overall latency 異常」但不說維度，診斷價值有限。</p>
<p>可操作的 explainability 有兩層：</p>
<p><strong>維度歸因</strong>：anomaly detection 系統自動拆分異常到子維度 — 「overall latency 異常，主要來自 region=us-east + endpoint=/api/search」。Datadog Watchdog 跟 New Relic AI 提供這種維度下鑽能力。</p>
<p><strong>Root cause hint</strong>：anomaly 跟其他訊號（deploy event、config change、dependency error spike）的時間關聯。「Latency anomaly 開始的時間跟 v2.3.1 deploy 吻合」— 提示 root cause 可能跟 deploy 有關。</p>
<h2 id="vendor-定位">Vendor 定位</h2>
<table>
  <thead>
      <tr>
          <th>Vendor</th>
          <th>定位</th>
          <th>特點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Datadog Watchdog</td>
          <td>託管 anomaly + 維度歸因</td>
          <td>跟 APM / log / metric 整合、auto-detect</td>
      </tr>
      <tr>
          <td>New Relic AI</td>
          <td>託管 anomaly + root cause suggest</td>
          <td>全棧觀測整合</td>
      </tr>
      <tr>
          <td>Prophet（自建）</td>
          <td>開源 time series forecast</td>
          <td>需要自建 pipeline、training、serving</td>
      </tr>
      <tr>
          <td>Anomalo</td>
          <td>資料品質 anomaly</td>
          <td>偏 data pipeline、非 infra 觀測</td>
      </tr>
  </tbody>
</table>
<p>自建 vs 託管的判準：團隊是否有 ML pipeline 維運能力。託管方案的好處是零 ML 運維、跟觀測平台深度整合；自建的好處是可控性高、可以針對業務邏輯客製模型。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Anomaly detection 最常見的失敗是 baseline 沒對齊流量週期（週末自然下降被判成異常）跟異常觸發後無法歸因到具體維度（只知道「latency 異常」但看不出是哪個 service、哪個 region）。</p>
<p>重點訊號包括：</p>
<ul>
<li>Baseline 是否理解日夜、週末、節慶與促銷週期</li>
<li>Anomaly 是否能指出 service、tenant、region 或 endpoint 維度</li>
<li>False positive 是否納入 alert noise governance</li>
<li>Anomaly 與 rule-based alert 是否有清楚分工</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>Alert 規則寫到數百條、仍漏掉 novel failure mode</li>
<li>已知 anomaly 訊號被忽略、靠人工巡視 dashboard</li>
<li>Anomaly 觸發後無人能解釋「為什麼異常」</li>
<li>模型未對齊週期性（週末 / 節慶 / promo）造成噪音</li>
<li>同一指標 anomaly + rule alert 重複觸發、無協調</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Anomaly 直接接 page</td>
          <td>On-call 被統計偏差淹沒</td>
          <td>Anomaly 先走 info/warning、conditional 才升級</td>
      </tr>
      <tr>
          <td>Baseline 沒對齊季節性</td>
          <td>週末 / 節慶流量自然變化觸發 false positive</td>
          <td>用 seasonal model 或 exclude 已知事件窗口</td>
      </tr>
      <tr>
          <td>Anomaly 跟 rule alert 重複</td>
          <td>同一問題兩個來源觸發、noise 翻倍</td>
          <td>共用 noise budget、anomaly 在 rule 已觸發時抑制</td>
      </tr>
      <tr>
          <td>模型不可解釋</td>
          <td>Anomaly fired 但工程師不知道看什麼</td>
          <td>要求維度歸因能力、否則只作 dashboard widget</td>
      </tr>
      <tr>
          <td>自建 ML 但無 retrain pipeline</td>
          <td>模型用半年前的 baseline、precision 持續下降</td>
          <td>建立定期 retrain 或改用託管方案</td>
      </tr>
  </tbody>
</table>
<h2 id="交接路由">交接路由</h2>
<ul>
<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>：anomaly 升級 alert 的條件</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 政策">4.6 SLI/SLO</a>：跟 SLO burn rate 的訊號分工</li>
<li><a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 signal governance</a>：anomaly false positive 的淘汰</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：anomaly 系統的 ownership</li>
</ul>
]]></content:encoded></item><item><title>4.15 Cost Attribution / Chargeback</title><link>https://tarrragon.github.io/blog/backend/04-observability/cost-attribution/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/cost-attribution/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>為何需要 attribution：共享平台模式下成本無人擁有&lt;/li>
&lt;li>拆分維度：team / service / environment / tenant / cost driver&lt;/li>
&lt;li>拆分的訊號來源：metric label / log tag / span attribute&lt;/li>
&lt;li>Showback vs chargeback&lt;/li>
&lt;li>Attribution dashboard 設計&lt;/li>
&lt;li>Vendor 帳單拆分能力&lt;/li>
&lt;li>反模式&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Cost attribution 是把 observability 成本拆回團隊、服務、環境與成本來源的治理能力，責任是讓使用訊號的人也看見訊號成本。&lt;/p>
&lt;p>Observability 平台（自架或託管）的成本來自三個層面：ingestion（收了多少資料）、storage / retention（保留了多久）、query（查了多少次跟多大範圍）。沒有 attribution 時，這三層的成本由平台團隊背，產品團隊把 observability 當免費資源 — 新增 metric label、延長 retention、加 dashboard panel 都沒有成本意識。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 cardinality&lt;/a> 的分工：4.7 是技術治理工具（控制 cardinality、sampling、retention 階梯），4.15 是組織治理工具（讓成本對應到 owner、驅動 owner 採取行動）。&lt;/p>
&lt;h2 id="拆分維度">拆分維度&lt;/h2>
&lt;h3 id="按-service--team">按 service / team&lt;/h3>
&lt;p>最基本的拆分。每個服務產生的 ingestion 量（events/sec、series count、log volume）歸到服務 owner。團隊是多個服務的集合。&lt;/p>
&lt;p>實作方式：metric 跟 log 的 &lt;code>service&lt;/code> label / tag 是拆分的基礎。如果 label 穩定且全覆蓋，用 &lt;code>sum by (service)&lt;/code> 就能拆分 ingestion 成本。Label 不穩定（部分服務沒打 service tag）或 label 值漂移（service name 改名但 cost 系統沒更新）會讓拆分不準。&lt;/p>
&lt;h3 id="按-environment">按 environment&lt;/h3>
&lt;p>Production / staging / dev 環境的成本各自歸因。常見發現是 staging 環境的 observability 成本跟 production 相當 — staging 開了跟 production 一樣的 retention、sampling 率、dashboard，但 staging 的觀測需求遠低於 production。&lt;/p>
&lt;p>可操作的做法：staging 跟 dev 環境用更短的 retention（7 天 vs production 的 30 天）、更高的 sampling 比例、關閉不需要的 dashboard。把 environment 的成本差異展示在 attribution dashboard 上，讓團隊自行判斷 staging 的 observability 是否過度。&lt;/p>
&lt;h3 id="按-cost-driver-type">按 cost driver type&lt;/h3>
&lt;p>Ingestion / storage / query 三層的成本增長模式不同、控制手段也不同。&lt;/p>
&lt;p>&lt;strong>Ingestion 成本&lt;/strong>：跟 events/sec 跟 series count 成正比。控制手段是 sampling、cardinality 限制、低價值訊號過濾。歸因到產生訊號的服務。&lt;/p>
&lt;p>&lt;strong>Storage / retention 成本&lt;/strong>：跟資料量 × 保留期成正比。控制手段是 retention 階梯（&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7&lt;/a>）、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">storage tiering&lt;/a>。歸因到資料保留政策的 owner。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>為何需要 attribution：共享平台模式下成本無人擁有</li>
<li>拆分維度：team / service / environment / tenant / cost driver</li>
<li>拆分的訊號來源：metric label / log tag / span attribute</li>
<li>Showback vs chargeback</li>
<li>Attribution dashboard 設計</li>
<li>Vendor 帳單拆分能力</li>
<li>反模式</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>Cost attribution 是把 observability 成本拆回團隊、服務、環境與成本來源的治理能力，責任是讓使用訊號的人也看見訊號成本。</p>
<p>Observability 平台（自架或託管）的成本來自三個層面：ingestion（收了多少資料）、storage / retention（保留了多久）、query（查了多少次跟多大範圍）。沒有 attribution 時，這三層的成本由平台團隊背，產品團隊把 observability 當免費資源 — 新增 metric label、延長 retention、加 dashboard panel 都沒有成本意識。</p>
<p>跟 <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> 的分工：4.7 是技術治理工具（控制 cardinality、sampling、retention 階梯），4.15 是組織治理工具（讓成本對應到 owner、驅動 owner 採取行動）。</p>
<h2 id="拆分維度">拆分維度</h2>
<h3 id="按-service--team">按 service / team</h3>
<p>最基本的拆分。每個服務產生的 ingestion 量（events/sec、series count、log volume）歸到服務 owner。團隊是多個服務的集合。</p>
<p>實作方式：metric 跟 log 的 <code>service</code> label / tag 是拆分的基礎。如果 label 穩定且全覆蓋，用 <code>sum by (service)</code> 就能拆分 ingestion 成本。Label 不穩定（部分服務沒打 service tag）或 label 值漂移（service name 改名但 cost 系統沒更新）會讓拆分不準。</p>
<h3 id="按-environment">按 environment</h3>
<p>Production / staging / dev 環境的成本各自歸因。常見發現是 staging 環境的 observability 成本跟 production 相當 — staging 開了跟 production 一樣的 retention、sampling 率、dashboard，但 staging 的觀測需求遠低於 production。</p>
<p>可操作的做法：staging 跟 dev 環境用更短的 retention（7 天 vs production 的 30 天）、更高的 sampling 比例、關閉不需要的 dashboard。把 environment 的成本差異展示在 attribution dashboard 上，讓團隊自行判斷 staging 的 observability 是否過度。</p>
<h3 id="按-cost-driver-type">按 cost driver type</h3>
<p>Ingestion / storage / query 三層的成本增長模式不同、控制手段也不同。</p>
<p><strong>Ingestion 成本</strong>：跟 events/sec 跟 series count 成正比。控制手段是 sampling、cardinality 限制、低價值訊號過濾。歸因到產生訊號的服務。</p>
<p><strong>Storage / retention 成本</strong>：跟資料量 × 保留期成正比。控制手段是 retention 階梯（<a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7</a>）、<a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup</a> 跟 <a href="/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">storage tiering</a>。歸因到資料保留政策的 owner。</p>
<p><strong>Query 成本</strong>：跟查詢次數 × 掃描量成正比。控制手段是 <a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule</a>、query cache、query cost estimation（<a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23</a>）。歸因到 dashboard 跟 alert rule 的 owner。</p>
<p>三層分開歸因的價值是精確定位成本增長來源。「這個月成本增長 30%」→ 是 ingestion 增長（某服務開了新 metric）還是 query 增長（某人加了 heavy dashboard panel）？分層歸因讓回答這個問題只需要查一個 dashboard。</p>
<h3 id="按-tenant多租戶場景">按 tenant（多租戶場景）</h3>
<p>Multi-tenant 平台的 observability 成本跟 tenant 的活躍度有關。大 tenant 產生的事件量可能是小 tenant 的 100 倍，但如果 observability 成本平攤，小 tenant 補貼大 tenant。</p>
<p>Tenant-level attribution 需要 metric / log / trace 帶 tenant label。Label 的 cardinality 問題在 <a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7</a> 處理 — tenant label 在 metric 層通常過高 cardinality（每個 tenant 一條 series），可以改在 log 或 trace 層按 tenant 統計 ingestion 量。</p>
<h2 id="showback-vs-chargeback">Showback vs Chargeback</h2>
<p><strong>Showback</strong>：讓團隊看到自己產生的 observability 成本，但不實際扣款。透明化驅動行為改變 — 當 team A 發現自己的 log ingestion 成本是其他團隊的 5 倍時，自然會開始檢視「是不是 debug log 開太多」。</p>
<p><strong>Chargeback</strong>：把 observability 成本從團隊的預算中實際扣除。驅動力更強，但需要精確的 attribution（誤差會讓團隊不信任系統）跟組織層面的支持（財務流程、管理層買單）。</p>
<p>多數團隊的起步方式是 showback。Showback 的 attribution 精度要求比 chargeback 低 — 差 10-20% 的歸因不影響行為改變的驅動力。Chargeback 需要差 &lt; 5% 才能讓團隊接受。</p>
<h2 id="attribution-dashboard-設計">Attribution Dashboard 設計</h2>
<p>Attribution dashboard 回答三個問題：</p>
<ol>
<li><strong>誰在燒？</strong> — 按 service / team 排序的成本排行榜。前 10 個服務通常佔 70-80% 的成本。</li>
<li><strong>燒在哪一層？</strong> — 前 10 個服務的 ingestion / storage / query 成本比例。</li>
<li><strong>趨勢是什麼？</strong> — 月對月的成本趨勢、哪些服務的成本增長最快。</li>
</ol>
<p>Dashboard 的更新頻率可以低（每天或每週），因為 attribution 驅動的是策略決策而非即時操作。Panel 讀 pre-aggregated 資料（daily cost summary table），查詢成本本身很低。</p>
<p>Attribution dashboard 的 owner 是 observability platform team，但 actionable insight 的 owner 是各服務團隊。Platform team 負責維護 attribution 的精確性跟 dashboard 的正確性；服務團隊負責看自己的成本趨勢跟採取控制行動。</p>
<h2 id="vendor-帳單拆分能力">Vendor 帳單拆分能力</h2>
<table>
  <thead>
      <tr>
          <th>Vendor</th>
          <th>帳單拆分能力</th>
          <th>限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Datadog</td>
          <td>Usage attribution by tag（service / team / env）</td>
          <td>需要事先定義 attribution tag</td>
      </tr>
      <tr>
          <td>Honeycomb</td>
          <td>Team-based usage tracking</td>
          <td>按 dataset 拆分、不按 service</td>
      </tr>
      <tr>
          <td>Grafana Cloud</td>
          <td>Usage dashboard by data source</td>
          <td>需自建 attribution layer</td>
      </tr>
      <tr>
          <td>自架 Prometheus + Loki</td>
          <td>自建 cost model（series count × price / log volume × price）</td>
          <td>完全自定義但維護成本高</td>
      </tr>
  </tbody>
</table>
<p>自架的 attribution 精度最高（因為完全可控），但維護成本也最高。託管 vendor 通常提供 service 或 team 級的 usage attribution，但跨 ingestion / storage / query 的分層拆分需要用 vendor API 自建 dashboard。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>Cost attribution 的核心目標是讓成本對應到能採取行動的 <a href="/blog/backend/knowledge-cards/ownership/" data-link-title="Ownership" data-link-desc="說明 ownership 如何把問題、決策與交接責任固定到可執行角色">owner</a> — 成本只有總額而無歸屬時，沒有團隊有動力控制。</p>
<p>重點訊號包括：</p>
<ul>
<li>Ingestion、retention、query 是否能分開歸因</li>
<li>Team / service / environment label 是否穩定</li>
<li>Showback 是否足以改變行為，或需要 chargeback</li>
<li>高成本訊號是否能對應事故、SLO 或除錯價值</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>成本季度增長、無人能說「哪個團隊 / 服務在燒」</li>
<li>高成本服務跟高價值服務不對應、無 ROI 視角</li>
<li>平台團隊背所有預算、產品團隊把 observability 當免費資源</li>
<li>Attribution dashboard 存在但無 owner、半年沒看</li>
<li>Vendor 帳單只有總額、無服務級拆分</li>
<li>Staging 的 observability 成本跟 production 相當但無人注意</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>平台吸收所有成本</td>
          <td>產品團隊沒成本意識、ingestion 無限增長</td>
          <td>Showback 起步、讓團隊看到自己的成本</td>
      </tr>
      <tr>
          <td>Attribution 顆粒度太粗</td>
          <td>只有總額、定位成本來源要人工拆帳</td>
          <td>按 service + cost driver type 拆分</td>
      </tr>
      <tr>
          <td>Chargeback 精度不夠</td>
          <td>團隊質疑歸因結果、不信任系統</td>
          <td>先用 showback、精度穩定後再轉 chargeback</td>
      </tr>
      <tr>
          <td>Attribution label 漂移</td>
          <td>Service name 改了但 cost 系統沒更新</td>
          <td>Label 同步機制 + 定期 reconciliation</td>
      </tr>
      <tr>
          <td>成本只看帳單不看 ROI</td>
          <td>砍最貴的 metric 但那是 SLO 唯一訊號來源</td>
          <td>成本決策同時評估「砍掉後事故定位會變慢多少」</td>
      </tr>
  </tbody>
</table>
<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</a>：技術層面的成本治理工具</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 各層的成本歸屬</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：platform team 跟 service team 的 cost ownership</li>
<li><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：query 成本的 estimation 跟治理</li>
<li><a href="/blog/backend/06-reliability/capacity-cost/" data-link-title="6.9 容量與成本邊界" data-link-desc="把容量規劃跟成本約束變成驗證輸入">6.9 capacity / cost</a>：observability 成本作為整體容量規劃的一部分</li>
<li><a href="/blog/backend/04-observability/cases/observability-cost-governance-at-scale/" data-link-title="4.C14 觀測平台成本治理：從帳單驚嚇到可預測成本" data-link-desc="觀測帳單持續超線性成長時，用 cost attribution、cardinality budget、log tiering 跟 adaptive sampling 建立可預測成本模型。">4.C14 觀測平台成本治理</a>：從帳單驚嚇到可預測成本的綜合情境</li>
</ul>
]]></content:encoded></item><item><title>4.16 Observability Readiness Review</title><link>https://tarrragon.github.io/blog/backend/04-observability/observability-readiness-review/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/observability-readiness-review/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>readiness review 的責任：在 production 前確認訊號能支援分級、定位、回復與復盤&lt;/li>
&lt;li>檢查面向：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/log-schema/" data-link-title="Log Schema" data-link-desc="說明結構化 log 欄位如何支援搜尋、關聯與事故排查">log schema&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">dashboard&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/alert/" data-link-title="Alert" data-link-desc="說明 alert 如何把需要處理的服務症狀轉成可行動通知">alert&lt;/a>&lt;/li>
&lt;li>上線前判準：核心 user journey 是否有 SLI、錯誤是否有 correlation key、依賴是否可追蹤&lt;/li>
&lt;li>變更前判準：新依賴、新 queue、新 feature flag 是否帶出新訊號需求&lt;/li>
&lt;li>演練前判準：game day / chaos / DR drill 是否能被 04 訊號觀察&lt;/li>
&lt;li>跟 06 的交接：readiness 缺口進入 reliability readiness / release gate&lt;/li>
&lt;li>跟 08 的交接：readiness 缺口影響 severity trigger、runbook 與 decision log&lt;/li>
&lt;li>反模式：服務先上線、事故後才補 dashboard；alert 有通知但缺定位欄位；trace 需要人工對回 log&lt;/li>
&lt;/ul>
&lt;p>Observability readiness review 的價值在於把「事故時才會被問到的問題」提前成上線條件。服務進 production 前，團隊需要先確認訊號能回答三件事：哪裡出問題、影響到誰、下一步由誰處理。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Observability readiness review 是把「訊號是否足以支援操作」變成上線前檢查的流程，責任是讓服務進入 production 前已具備基本診斷能力。&lt;/p>
&lt;p>這一頁處理的是準備度。工具已存在時，仍需要確認訊號是否對應使用者旅程、依賴邊界、事故分級與復盤證據。&lt;/p>
&lt;p>readiness review 不等於打勾清單。它是一次跨角色對齊：服務團隊確認事件語意，平台團隊確認採集與查詢路徑，on-call 確認事故前 10 分鐘真的能定位。三者同時成立，才算可操作準備度。&lt;/p>
&lt;h2 id="適用情境">適用情境&lt;/h2>
&lt;p>Observability readiness review 適合放在服務生命週期的高風險節點。這些節點共同特徵是：一旦變更進入 production，第一次異常就會依賴既有訊號做判讀。&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>核心旅程、依賴、owner 是否可觀測&lt;/td>
 &lt;td>事故初期只能靠人工猜測&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>重大變更&lt;/td>
 &lt;td>新 queue、新依賴、新 flag 的訊號&lt;/td>
 &lt;td>新風險進 production 後才暴露&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>架構拆分&lt;/td>
 &lt;td>trace、correlation、service name&lt;/td>
 &lt;td>事件鏈跨服務後斷裂&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>演練前&lt;/td>
 &lt;td>chaos、load、DR 行為是否可被看見&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;/tbody>
&lt;/table>
&lt;p>新服務上線時，readiness review 的責任是確認基本診斷能力已經存在。典型服務至少要能從 request、tenant、region、dependency 與錯誤分類回到同一條事件鏈，讓 on-call 能在前 10 分鐘判斷影響範圍。&lt;/p>
&lt;p>重大變更時，readiness review 的責任是確認變更帶來的新風險已有訊號。加入新的外部 API、queue、background job、feature flag 或資料同步流程，都會增加新的失效面；每個失效面都應有對應 log、metric、trace 或 alert。&lt;/p>
&lt;p>演練前，readiness review 的責任是確認驗證行為能被觀測。chaos experiment、load test 或 DR drill 需要同時產生故障與判讀證據，讓團隊能確認 &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;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 observability readiness 時，先看服務的核心旅程是否有訊號，再看事故時能否從症狀走到原因。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>核心 user journey 是否有 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI/SLO&lt;/a> 與 error rate&lt;/li>
&lt;li>log 是否有 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id&lt;/a> 與 tenant 欄位&lt;/li>
&lt;li>trace 是否覆蓋同步、async、queue 與 background job 邊界&lt;/li>
&lt;li>dashboard 是否能支援 on-call 的前 10 分鐘判讀&lt;/li>
&lt;li>alert 是否能連到 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a> 與 owner&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>request / trace / tenant 可串成同一條事件鏈&lt;/td>
 &lt;td>欄位命名不一致、跨服務拼接失敗&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>服務健康&lt;/td>
 &lt;td>SLI 與 error rate 能反映核心旅程&lt;/td>
 &lt;td>指標只反映系統資源、不反映用戶結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>路徑可視&lt;/td>
 &lt;td>trace 能覆蓋 sync + async + queue&lt;/td>
 &lt;td>background job 與 queue 邊界斷鏈&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>操作入口&lt;/td>
 &lt;td>dashboard / alert 能支撐前 10 分鐘&lt;/td>
 &lt;td>告警有通知、沒有定位與下一步&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="review-流程">Review 流程&lt;/h2>
&lt;p>Readiness review 的流程是從使用者旅程走向操作路由。先從服務承諾的體驗開始，再反推工具與訊號清單，才能讓監控資產對應事故時的實際判讀。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>readiness review 的責任：在 production 前確認訊號能支援分級、定位、回復與復盤</li>
<li>檢查面向：<a href="/blog/backend/knowledge-cards/log-schema/" data-link-title="Log Schema" data-link-desc="說明結構化 log 欄位如何支援搜尋、關聯與事故排查">log schema</a>、<a href="/blog/backend/knowledge-cards/metrics/" data-link-title="Metrics" data-link-desc="說明指標如何描述服務趨勢、容量與健康狀態">metrics</a>、<a href="/blog/backend/knowledge-cards/trace-context/" data-link-title="Trace Context" data-link-desc="說明跨服務 request 如何用 trace context 串起路徑與耗時">trace context</a>、<a href="/blog/backend/knowledge-cards/dashboard/" data-link-title="Dashboard" data-link-desc="說明 dashboard 如何把關鍵訊號組成可判讀的服務狀態畫面">dashboard</a>、<a href="/blog/backend/knowledge-cards/alert/" data-link-title="Alert" data-link-desc="說明 alert 如何把需要處理的服務症狀轉成可行動通知">alert</a></li>
<li>上線前判準：核心 user journey 是否有 SLI、錯誤是否有 correlation key、依賴是否可追蹤</li>
<li>變更前判準：新依賴、新 queue、新 feature flag 是否帶出新訊號需求</li>
<li>演練前判準：game day / chaos / DR drill 是否能被 04 訊號觀察</li>
<li>跟 06 的交接：readiness 缺口進入 reliability readiness / release gate</li>
<li>跟 08 的交接：readiness 缺口影響 severity trigger、runbook 與 decision log</li>
<li>反模式：服務先上線、事故後才補 dashboard；alert 有通知但缺定位欄位；trace 需要人工對回 log</li>
</ul>
<p>Observability readiness review 的價值在於把「事故時才會被問到的問題」提前成上線條件。服務進 production 前，團隊需要先確認訊號能回答三件事：哪裡出問題、影響到誰、下一步由誰處理。</p>
<h2 id="概念定位">概念定位</h2>
<p>Observability readiness review 是把「訊號是否足以支援操作」變成上線前檢查的流程，責任是讓服務進入 production 前已具備基本診斷能力。</p>
<p>這一頁處理的是準備度。工具已存在時，仍需要確認訊號是否對應使用者旅程、依賴邊界、事故分級與復盤證據。</p>
<p>readiness review 不等於打勾清單。它是一次跨角色對齊：服務團隊確認事件語意，平台團隊確認採集與查詢路徑，on-call 確認事故前 10 分鐘真的能定位。三者同時成立，才算可操作準備度。</p>
<h2 id="適用情境">適用情境</h2>
<p>Observability readiness review 適合放在服務生命週期的高風險節點。這些節點共同特徵是：一旦變更進入 production，第一次異常就會依賴既有訊號做判讀。</p>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>檢查重點</th>
          <th>缺口代價</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>新服務上線</td>
          <td>核心旅程、依賴、owner 是否可觀測</td>
          <td>事故初期只能靠人工猜測</td>
      </tr>
      <tr>
          <td>重大變更</td>
          <td>新 queue、新依賴、新 flag 的訊號</td>
          <td>新風險進 production 後才暴露</td>
      </tr>
      <tr>
          <td>架構拆分</td>
          <td>trace、correlation、service name</td>
          <td>事件鏈跨服務後斷裂</td>
      </tr>
      <tr>
          <td>演練前</td>
          <td>chaos、load、DR 行為是否可被看見</td>
          <td>演練結果缺少可驗證證據</td>
      </tr>
      <tr>
          <td>事故後</td>
          <td>復盤缺口是否回寫成新訊號</td>
          <td>同類事故仍以相同盲區重演</td>
      </tr>
  </tbody>
</table>
<p>新服務上線時，readiness review 的責任是確認基本診斷能力已經存在。典型服務至少要能從 request、tenant、region、dependency 與錯誤分類回到同一條事件鏈，讓 on-call 能在前 10 分鐘判斷影響範圍。</p>
<p>重大變更時，readiness review 的責任是確認變更帶來的新風險已有訊號。加入新的外部 API、queue、background job、feature flag 或資料同步流程，都會增加新的失效面；每個失效面都應有對應 log、metric、trace 或 alert。</p>
<p>演練前，readiness review 的責任是確認驗證行為能被觀測。chaos experiment、load test 或 DR drill 需要同時產生故障與判讀證據，讓團隊能確認 <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>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 observability readiness 時，先看服務的核心旅程是否有訊號，再看事故時能否從症狀走到原因。</p>
<p>重點訊號包括：</p>
<ul>
<li>核心 user journey 是否有 <a href="/blog/backend/knowledge-cards/sli-slo/" data-link-title="SLI / SLO" data-link-desc="說明服務品質指標與服務品質目標如何連接產品承諾">SLI/SLO</a> 與 error rate</li>
<li>log 是否有 <a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id</a>、<a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id</a> 與 tenant 欄位</li>
<li>trace 是否覆蓋同步、async、queue 與 background job 邊界</li>
<li>dashboard 是否能支援 on-call 的前 10 分鐘判讀</li>
<li>alert 是否能連到 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a> 與 owner</li>
</ul>
<table>
  <thead>
      <tr>
          <th>檢查面向</th>
          <th>最小可用判準</th>
          <th>常見失真</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>事件關聯</td>
          <td>request / trace / tenant 可串成同一條事件鏈</td>
          <td>欄位命名不一致、跨服務拼接失敗</td>
      </tr>
      <tr>
          <td>服務健康</td>
          <td>SLI 與 error rate 能反映核心旅程</td>
          <td>指標只反映系統資源、不反映用戶結果</td>
      </tr>
      <tr>
          <td>路徑可視</td>
          <td>trace 能覆蓋 sync + async + queue</td>
          <td>background job 與 queue 邊界斷鏈</td>
      </tr>
      <tr>
          <td>操作入口</td>
          <td>dashboard / alert 能支撐前 10 分鐘</td>
          <td>告警有通知、沒有定位與下一步</td>
      </tr>
  </tbody>
</table>
<h2 id="review-流程">Review 流程</h2>
<p>Readiness review 的流程是從使用者旅程走向操作路由。先從服務承諾的體驗開始，再反推工具與訊號清單，才能讓監控資產對應事故時的實際判讀。</p>
<ol>
<li>定義核心旅程與失敗後果。</li>
<li>對每個旅程列出依賴、async workflow 與資料寫入點。</li>
<li>為每個失效點指定 log、metric、trace 或 dashboard。</li>
<li>驗證 alert 是否連到 owner、runbook 與下一步動作。</li>
<li>標記尚未補齊的訊號缺口，決定是否阻擋上線或納入 follow-up。</li>
</ol>
<p>核心旅程是 readiness review 的錨點。購物服務的核心旅程可能是 checkout、payment、order confirmation；內容平台可能是 upload、publish、read path；B2B API 可能是 authentication、request processing、webhook delivery。訊號需要優先對到這些旅程，再補 CPU、memory 與 pod restart 等資源層訊號。</p>
<p>依賴圖是 readiness review 的第二層。每個資料庫、cache、broker、third-party API、object storage 與 internal service 都應能被定位為 upstream 或 downstream，並且在 trace、metric 或 log 中留下可查詢欄位。</p>
<p>操作路由是 readiness review 的交付物。當 alert 觸發時，on-call 需要知道先看哪個 dashboard、用哪個 query、找哪個 owner、用哪個 runbook、何時升級到 incident commander。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>服務上線 checklist 有監控項目，但沒有事故判讀欄位</li>
<li>新依賴上線後，dashboard 看不到 upstream / downstream 影響</li>
<li>alert 觸發後仍需要人工 grep 多個系統拼事件鏈</li>
<li>chaos 或 DR 演練產生故障，但 04 訊號沒有反映出預期現象</li>
<li>事故復盤 action item 反覆要求「補監控」</li>
</ul>
<p>在真實服務中，最常見的 readiness 缺口是工具已存在，但工具沒有對到決策。例如 alert 可以 page on-call，但查詢第一步就要跨三個系統手動對帳，代表 readiness 還停在可見層，尚未進入可操作層。</p>
<h2 id="控制面">控制面</h2>
<p>Readiness review 的控制面是把檢查結果轉成可執行決策。每個缺口都要被分類為阻擋、降級接受或後續改善，並且留下 owner 與期限。</p>
<table>
  <thead>
      <tr>
          <th>缺口類型</th>
          <th>判斷方式</th>
          <th>處理路由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>阻擋</td>
          <td>影響核心旅程、事故時無替代判讀</td>
          <td>暫停上線，補 04 訊號或 06 readiness</td>
      </tr>
      <tr>
          <td>降級接受</td>
          <td>風險可被 runbook 或人工查證承接</td>
          <td>標記限制，接到 08 intake 與 decision log</td>
      </tr>
      <tr>
          <td>後續改善</td>
          <td>不影響首輪定位，但影響長期治理</td>
          <td>進入 04.8 signal governance loop</td>
      </tr>
      <tr>
          <td>淘汰整理</td>
          <td>舊 dashboard 或 alert 干擾判讀</td>
          <td>進入 4.18 operating model</td>
      </tr>
  </tbody>
</table>
<p>阻擋條件應該以「事故時是否能決策」為核心。核心旅程 SLI、request correlation、upstream / downstream 分辨能力與 alert owner 都是第一次事故能否被接住的基本條件。</p>
<p>降級接受需要明確寫出限制。若某個低流量背景任務暫時缺 trace，但有 log query、DLQ dashboard 與人工 replay 流程可以承接，團隊可以接受短期限制；限制需要進入 <a href="/blog/backend/knowledge-cards/incident-decision-log/" data-link-title="Incident Decision Log" data-link-desc="說明事故期間如何保留決策、證據、owner 與回退條件">incident decision log</a>，避免事中被誤讀為完整訊號。</p>
<p>後續改善適合處理長期品質問題。dashboard 可用但查詢成本過高、alert 可行但 noise 偏高、欄位命名需要統一，這些缺口適合進入 signal governance，讓上線決策與長期治理分流。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Observability readiness 的反模式通常來自把「有監控」誤當成「可操作」。監控存在只是起點，能支援判讀、路由與回復才是 readiness。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>事後補 dashboard</td>
          <td>事故發生後才知道缺哪些面板</td>
          <td>把核心旅程面板列為上線條件</td>
      </tr>
      <tr>
          <td>告警只有通知</td>
          <td>on-call 收到 page 後仍需重新找證據</td>
          <td>alert 必須帶 owner 與 runbook</td>
      </tr>
      <tr>
          <td>trace 需要人工拼 log</td>
          <td>跨服務路徑靠 request id 手動對回</td>
          <td>統一 trace context 與 log 欄位</td>
      </tr>
      <tr>
          <td>readiness 只看平台工具</td>
          <td>平台 green，但服務旅程不可判讀</td>
          <td>從 user journey 反推訊號需求</td>
      </tr>
      <tr>
          <td>checklist 無阻擋條件</td>
          <td>每次都勾選通過，但缺口持續存在</td>
          <td>定義 block / accept / follow-up</td>
      </tr>
  </tbody>
</table>
<p>事後補 dashboard 的風險是把第一次事故變成探索行為。事故期間的主要工作應是止血與決策；如果團隊還在建立第一個查詢、猜欄位語意、找 owner，代表 readiness 沒有完成。</p>
<p>告警只有通知會把壓力丟給 on-call。有效 alert 應該同時提供症狀、範圍、第一個查詢入口與下一步路由，讓值班者能直接進入判讀流程。</p>
<h2 id="與-06-和-08-的關係">與 06 和 08 的關係</h2>
<p>Observability readiness 是可靠性驗證與事故處理的輸入層。06 需要用它判斷驗證前提是否成立，08 需要用它判斷事故 evidence 是否足以啟動流程。</p>
<p>在 06 中，readiness 缺口會影響 load test、chaos、DR drill 與 release gate。驗證行為需要可觀測訊號支撐，測試結果才足以證明系統維持在可接受狀態內。</p>
<p>在 08 中，readiness 缺口會影響 severity trigger、incident intake 與 decision log。若 evidence 不完整，事故指揮需要先標記資料限制，再決定是否升級、降級或等待更多證據。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.1 log schema：補事件關聯欄位</li>
<li>04.2 metrics：補服務健康與容量指標</li>
<li>04.3 tracing：補跨服務與 async context</li>
<li>04.4 dashboard / alert：補操作入口與通知條件</li>
<li><a href="/blog/backend/04-observability/attacker-view-observability-risks/" data-link-title="4.5 可觀測性威脅建模（Threat Modeling）" data-link-desc="從觀測盲區、告警失真與資料暴露風險，盤點 observability 的主要弱點">4.5 威脅建模</a>：觀測盲區跟資料暴露的上線前檢查</li>
<li>06.19 reliability readiness：把觀測準備度納入上線前門檻</li>
<li>08.18 incident intake：把訊號接進事故 intake 與 evidence triage</li>
</ul>
]]></content:encoded></item><item><title>4.17 Telemetry Data Quality</title><link>https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/telemetry-data-quality/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>telemetry data quality 的責任：確認觀測資料本身可信&lt;/li>
&lt;li>缺漏類型：missing signal、partial trace、dropped log、stale metric&lt;/li>
&lt;li>漂移類型：schema drift、label drift、service name drift、semantic convention drift&lt;/li>
&lt;li>偏誤類型：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">sampling&lt;/a> bias、low-traffic bias、high-cardinality truncation&lt;/li>
&lt;li>時間類型：clock skew、ingest delay、out-of-order event、timezone mismatch&lt;/li>
&lt;li>品質指標：completeness、freshness、consistency、accuracy、coverage&lt;/li>
&lt;li>跟 4.11 telemetry pipeline 的分工：pipeline 看路徑，data quality 看資料可信度&lt;/li>
&lt;li>反模式：dashboard 看起來正常但資料少一半；trace sample 漏掉錯誤；timestamp 導致 timeline 錯序&lt;/li>
&lt;/ul>
&lt;p>Telemetry data quality 的核心是把「觀測資料失真」當成一級事件。服務事故判讀建立在觀測資料上，資料品質不穩時，團隊會把資料缺口誤讀成系統行為，進而做出錯誤分級、錯誤回復或錯誤 SLO 判斷。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Telemetry data quality 是把觀測資料當成資料產品治理的能力，責任是讓 log、metric、trace 與 alert 的判讀建立在可信資料上。&lt;/p>
&lt;p>這一頁處理的是資料可信度。訊號存在不等於訊號可信；缺漏、漂移、偏誤與時間錯位都會讓事故判讀走向錯誤路徑。&lt;/p>
&lt;p>資料品質治理最有效的做法是把品質指標產品化：讓 completeness、freshness、drift、sampling coverage 也進 dashboard 與告警，讓團隊在事故前就能看見資料限制。&lt;/p>
&lt;h2 id="品質模型">品質模型&lt;/h2>
&lt;p>Telemetry data quality 的品質模型由五個面向組成。這五個面向分別回答資料是否存在、是否及時、是否一致、是否代表真實流量，以及是否足以覆蓋關鍵旅程。&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>Completeness&lt;/td>
 &lt;td>該出現的訊號是否完整出現&lt;/td>
 &lt;td>drop rate、coverage、gap&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Freshness&lt;/td>
 &lt;td>訊號是否足夠接近事件發生時間&lt;/td>
 &lt;td>ingest delay、stale metric&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Consistency&lt;/td>
 &lt;td>欄位、命名與語意是否跨服務一致&lt;/td>
 &lt;td>schema drift、label drift&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Accuracy&lt;/td>
 &lt;td>數值與事件語意是否反映真實狀態&lt;/td>
 &lt;td>duplicate event、wrong unit&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Coverage&lt;/td>
 &lt;td>高風險旅程與低流量邊界是否被涵蓋&lt;/td>
 &lt;td>sampling policy、trace ratio&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Completeness 是事故判讀的基礎。log、metric 或 trace 的缺口如果沒有被標示，dashboard 會呈現一條看似平順的線，實際上可能只是 ingestion pipeline 丟了資料。&lt;/p>
&lt;p>Freshness 決定資料能否支援事中決策。告警延遲、metric scrape delay、trace export queue backlog 與 log indexing lag 都會讓 incident commander 用過期資料判斷是否擴大或回復。&lt;/p>
&lt;p>Consistency 決定資料能否跨服務拼接。service name、region、tenant、environment、error class 與 semantic convention 若在不同系統漂移，單一服務看起來正常，跨服務事件鏈卻會斷裂。&lt;/p>
&lt;p>Accuracy 決定資料能否代表真實狀態。常見問題包含錯誤單位、重複計數、counter reset 誤判、histogram bucket 設錯與 status code mapping 錯誤。&lt;/p>
&lt;p>Coverage 決定資料能否覆蓋高風險邊界。低流量服務、VIP tenant、錯誤樣本、長尾 latency 與 rare dependency failure 常被 sampling 或聚合策略稀釋。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 telemetry data quality 時，先看資料是否完整與新鮮，再看不同訊號之間是否能互相對齊。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>log / metric / trace 是否有 coverage 與 drop rate&lt;/li>
&lt;li>schema 是否有版本與 drift 偵測&lt;/li>
&lt;li>sampling 是否保留錯誤、高延遲與低流量樣本&lt;/li>
&lt;li>timestamp 是否能支援 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline&lt;/a> 還原&lt;/li>
&lt;li>dashboard 是否標示資料延遲、缺口與查詢範圍&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>drop rate、coverage 可被量測&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>sampling 覆蓋高風險樣本&lt;/td>
 &lt;td>錯誤被平均化，誤判風險&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>時間性&lt;/td>
 &lt;td>timestamp 與 delay 可追蹤&lt;/td>
 &lt;td>timeline 錯序，決策先後顛倒&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="缺漏與漂移">缺漏與漂移&lt;/h2>
&lt;p>缺漏是 telemetry data quality 最容易造成錯誤安全感的問題。缺漏發生時，圖表通常不會直接報錯，而是呈現較低的流量、較少的錯誤或不完整的 trace。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>telemetry data quality 的責任：確認觀測資料本身可信</li>
<li>缺漏類型：missing signal、partial trace、dropped log、stale metric</li>
<li>漂移類型：schema drift、label drift、service name drift、semantic convention drift</li>
<li>偏誤類型：<a href="/blog/backend/knowledge-cards/sampling/" data-link-title="Sampling" data-link-desc="說明觀測資料如何抽樣以控制成本並保留診斷能力">sampling</a> bias、low-traffic bias、high-cardinality truncation</li>
<li>時間類型：clock skew、ingest delay、out-of-order event、timezone mismatch</li>
<li>品質指標：completeness、freshness、consistency、accuracy、coverage</li>
<li>跟 4.11 telemetry pipeline 的分工：pipeline 看路徑，data quality 看資料可信度</li>
<li>反模式：dashboard 看起來正常但資料少一半；trace sample 漏掉錯誤；timestamp 導致 timeline 錯序</li>
</ul>
<p>Telemetry data quality 的核心是把「觀測資料失真」當成一級事件。服務事故判讀建立在觀測資料上，資料品質不穩時，團隊會把資料缺口誤讀成系統行為，進而做出錯誤分級、錯誤回復或錯誤 SLO 判斷。</p>
<h2 id="概念定位">概念定位</h2>
<p>Telemetry data quality 是把觀測資料當成資料產品治理的能力，責任是讓 log、metric、trace 與 alert 的判讀建立在可信資料上。</p>
<p>這一頁處理的是資料可信度。訊號存在不等於訊號可信；缺漏、漂移、偏誤與時間錯位都會讓事故判讀走向錯誤路徑。</p>
<p>資料品質治理最有效的做法是把品質指標產品化：讓 completeness、freshness、drift、sampling coverage 也進 dashboard 與告警，讓團隊在事故前就能看見資料限制。</p>
<h2 id="品質模型">品質模型</h2>
<p>Telemetry data quality 的品質模型由五個面向組成。這五個面向分別回答資料是否存在、是否及時、是否一致、是否代表真實流量，以及是否足以覆蓋關鍵旅程。</p>
<table>
  <thead>
      <tr>
          <th>品質面向</th>
          <th>核心問題</th>
          <th>常見資料</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Completeness</td>
          <td>該出現的訊號是否完整出現</td>
          <td>drop rate、coverage、gap</td>
      </tr>
      <tr>
          <td>Freshness</td>
          <td>訊號是否足夠接近事件發生時間</td>
          <td>ingest delay、stale metric</td>
      </tr>
      <tr>
          <td>Consistency</td>
          <td>欄位、命名與語意是否跨服務一致</td>
          <td>schema drift、label drift</td>
      </tr>
      <tr>
          <td>Accuracy</td>
          <td>數值與事件語意是否反映真實狀態</td>
          <td>duplicate event、wrong unit</td>
      </tr>
      <tr>
          <td>Coverage</td>
          <td>高風險旅程與低流量邊界是否被涵蓋</td>
          <td>sampling policy、trace ratio</td>
      </tr>
  </tbody>
</table>
<p>Completeness 是事故判讀的基礎。log、metric 或 trace 的缺口如果沒有被標示，dashboard 會呈現一條看似平順的線，實際上可能只是 ingestion pipeline 丟了資料。</p>
<p>Freshness 決定資料能否支援事中決策。告警延遲、metric scrape delay、trace export queue backlog 與 log indexing lag 都會讓 incident commander 用過期資料判斷是否擴大或回復。</p>
<p>Consistency 決定資料能否跨服務拼接。service name、region、tenant、environment、error class 與 semantic convention 若在不同系統漂移，單一服務看起來正常，跨服務事件鏈卻會斷裂。</p>
<p>Accuracy 決定資料能否代表真實狀態。常見問題包含錯誤單位、重複計數、counter reset 誤判、histogram bucket 設錯與 status code mapping 錯誤。</p>
<p>Coverage 決定資料能否覆蓋高風險邊界。低流量服務、VIP tenant、錯誤樣本、長尾 latency 與 rare dependency failure 常被 sampling 或聚合策略稀釋。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 telemetry data quality 時，先看資料是否完整與新鮮，再看不同訊號之間是否能互相對齊。</p>
<p>重點訊號包括：</p>
<ul>
<li>log / metric / trace 是否有 coverage 與 drop rate</li>
<li>schema 是否有版本與 drift 偵測</li>
<li>sampling 是否保留錯誤、高延遲與低流量樣本</li>
<li>timestamp 是否能支援 <a href="/blog/backend/knowledge-cards/incident-timeline/" data-link-title="Incident Timeline" data-link-desc="說明事故時間線如何支援判斷、溝通與復盤">incident timeline</a> 還原</li>
<li>dashboard 是否標示資料延遲、缺口與查詢範圍</li>
</ul>
<table>
  <thead>
      <tr>
          <th>品質面向</th>
          <th>最小可用判準</th>
          <th>失真後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>完整性</td>
          <td>drop rate、coverage 可被量測</td>
          <td>事故定位依賴不完整證據</td>
      </tr>
      <tr>
          <td>一致性</td>
          <td>欄位語意與命名跨服務一致</td>
          <td>事件鏈需要人工拼接</td>
      </tr>
      <tr>
          <td>代表性</td>
          <td>sampling 覆蓋高風險樣本</td>
          <td>錯誤被平均化，誤判風險</td>
      </tr>
      <tr>
          <td>時間性</td>
          <td>timestamp 與 delay 可追蹤</td>
          <td>timeline 錯序，決策先後顛倒</td>
      </tr>
  </tbody>
</table>
<h2 id="缺漏與漂移">缺漏與漂移</h2>
<p>缺漏是 telemetry data quality 最容易造成錯誤安全感的問題。缺漏發生時，圖表通常不會直接報錯，而是呈現較低的流量、較少的錯誤或不完整的 trace。</p>
<table>
  <thead>
      <tr>
          <th>缺漏類型</th>
          <th>真實服務樣貌</th>
          <th>判讀風險</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Missing signal</td>
          <td>新服務路徑沒有 instrument</td>
          <td>核心旅程失敗但 dashboard 正常</td>
      </tr>
      <tr>
          <td>Partial trace</td>
          <td>async job 或 queue consumer 缺 span</td>
          <td>事件鏈停在同步 request</td>
      </tr>
      <tr>
          <td>Dropped log</td>
          <td>ingest burst 時 log 被丟棄</td>
          <td>錯誤率下降被誤判為恢復</td>
      </tr>
      <tr>
          <td>Stale metric</td>
          <td>scrape 成功但資料停在舊 timestamp</td>
          <td>incident timeline 被拉歪</td>
      </tr>
  </tbody>
</table>
<p>Missing signal 代表觀測需求沒有覆蓋服務路徑。常見場景是新 feature flag 開啟後走到新 code path，但 SLI、log schema 與 trace 還停在舊路徑。</p>
<p>Partial trace 代表跨邊界 context 缺少完整傳遞。request 進入 queue 後，如果 message 缺少 correlation id 或 consumer 缺少 span，團隊只能知道 request 發出去，背景流程的失敗時間與失敗點會留在盲區。</p>
<p>Dropped log 代表資料流量超過 pipeline 或成本限制。burst error 發生時，如果 log pipeline 開始 sampling 或丟棄，事故團隊看到的錯誤量會比真實狀態少。</p>
<p>Schema drift 是長期維護最常見的品質問題。欄位改名、label 粒度改變、service name 不一致、semantic convention 升級，都會讓查詢與 dashboard 在沒有明顯錯誤的情況下失準。</p>
<h2 id="sampling-與代表性">Sampling 與代表性</h2>
<p>本段聚焦 sampling 對資料品質的失真風險；sampling 策略（Head / Tail / Adaptive / Exemplar）的 SSoT 在 <a href="/blog/backend/04-observability/cardinality-cost-governance/#sampling-%e7%ad%96%e7%95%a5" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7 Sampling 策略</a>。</p>
<p>Sampling 的責任是控制觀測成本，同時保留足以判讀的高價值樣本。sampling policy 若只按固定比例抽樣，最容易丟掉低頻但高風險的事件。</p>
<table>
  <thead>
      <tr>
          <th>Sampling 風險</th>
          <th>失真方式</th>
          <th>控制面</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Low-traffic bias</td>
          <td>低流量服務樣本太少</td>
          <td>對低流量服務設定 minimum sample floor</td>
      </tr>
      <tr>
          <td>Error sample loss</td>
          <td>錯誤 request 被普通比例抽掉</td>
          <td>對 error、timeout、high latency 強制保留</td>
      </tr>
      <tr>
          <td>Tenant skew</td>
          <td>大 tenant 壓過小 tenant</td>
          <td>以 tenant 或 plan 做分層 sampling</td>
      </tr>
      <tr>
          <td>Cardinality truncation</td>
          <td>高維度 label 被截斷或合併</td>
          <td>標示 truncation，保留 top-K 與 overflow</td>
      </tr>
      <tr>
          <td>Tail latency loss</td>
          <td>長尾 latency 被平均值掩蓋</td>
          <td>使用 histogram 與 exemplar</td>
      </tr>
  </tbody>
</table>
<p>Low-traffic bias 會讓小服務或小 tenant 的問題長期不可見。這些路徑平時量小，但可能承擔高價值客戶、管理操作或資安事件；抽樣策略需要保留最低樣本量。</p>
<p>Error sample loss 會直接破壞事故判讀。錯誤、timeout、retry exhausted、DLQ、payment failure 與 authorization failure 應該有更高保留權重，因為它們代表決策價值高於普通成功 request。</p>
<p>Cardinality truncation 需要明確揭露。當平台為了成本截斷 label 或聚合 tenant 維度時，dashboard 應標示資料限制，讓讀者知道當下看的是聚合視角與可用粒度。</p>
<h2 id="時間對齊">時間對齊</h2>
<p>時間對齊是 incident timeline 的基礎能力。事件發生時間、採集時間、寫入時間、查詢時間與顯示時區若未分清，事故復盤會把原因與結果順序看反。</p>
<table>
  <thead>
      <tr>
          <th>時間問題</th>
          <th>常見來源</th>
          <th>事故後果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Clock skew</td>
          <td>host、container、client 時鐘不同</td>
          <td>事件先後被重排</td>
      </tr>
      <tr>
          <td>Ingest delay</td>
          <td>exporter queue 或 indexing lag</td>
          <td>告警與圖表晚於真實事件</td>
      </tr>
      <tr>
          <td>Out-of-order event</td>
          <td>async pipeline 或 retry 寫入</td>
          <td>同一 trace 的 span 順序錯亂</td>
      </tr>
      <tr>
          <td>Timezone mismatch</td>
          <td>人工紀錄與平台顯示時區不同</td>
          <td>對外通訊與內部 timeline 衝突</td>
      </tr>
  </tbody>
</table>
<p>Clock skew 會讓跨服務事件鏈失去可信度。若 API、worker、database proxy 與 observability collector 的時間基準不同，trace 中的等待點可能看起來是負時間或錯誤順序。</p>
<p>Ingest delay 會影響事中決策。incident commander 看到 error rate 下降時，需要知道資料是即時下降，還是 pipeline 還沒收完高峰區段。</p>
<p>Timezone mismatch 常出現在 status page、support ticket、vendor notice 與內部 timeline 對接時。所有事故證據都應保留原始時間與標準化時間，避免復盤時重排錯誤。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>同一事故在 log、metric、trace 中呈現不同時間線</li>
<li>service name / region / tenant label 在不同系統拼不起來</li>
<li>低流量服務的錯誤被 sampling 稀釋</li>
<li>pipeline drop 發生但 dashboard 沒提示資料缺口</li>
<li>post-incident review 發現判讀基於不完整資料</li>
</ul>
<p>常見場景是「圖看起來穩，但資料在悄悄掉」。例如 ingest 層 partial drop 後 error rate 下降，看似健康，實際是訊號少了高風險區段。這類情況若沒有資料品質指標，會讓事故決策建立在錯誤安全感上。</p>
<h2 id="控制面">控制面</h2>
<p>Telemetry data quality 的控制面是把資料限制顯性化。資料品質不需要追求完美，但需要讓讀者知道目前能相信什麼、限制在哪裡、何時需要改用其他 evidence。</p>
<ol>
<li>為每種 telemetry 設定品質指標。</li>
<li>在 dashboard 標示 freshness、coverage 與 known gap。</li>
<li>對 schema drift、drop rate 與 sampling policy 建立告警。</li>
<li>在 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">incident decision log</a> 記錄資料限制。</li>
<li>在 post-incident review 中回寫造成判讀錯誤的資料品質缺口。</li>
</ol>
<p>品質指標本身也需要 owner。平台團隊可以維護 pipeline drop、ingest delay 與 semantic convention；服務團隊需要維護 service-specific schema、business event 與 user journey coverage。</p>
<p>資料限制應直接出現在操作入口。若某 dashboard 的 trace sample 只保留 10%、某 tenant label 被聚合、某時間區段有 log gap，讀者應在同一個畫面看到限制，並把限制納入當下決策。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Telemetry data quality 的反模式來自把查詢結果視為事實本身。查詢結果只是資料產品的輸出，仍然受採集、轉換、抽樣、儲存與查詢限制影響。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>dashboard 即事實</td>
          <td>圖表下降就判斷服務恢復</td>
          <td>顯示資料延遲與 coverage</td>
      </tr>
      <tr>
          <td>schema 漂移無治理</td>
          <td>查詢突然少資料但沒人知道</td>
          <td>欄位版本與 drift 偵測</td>
      </tr>
      <tr>
          <td>sampling policy 黑箱</td>
          <td>錯誤樣本被抽掉仍用比例推估</td>
          <td>公開 sampling policy 與例外規則</td>
      </tr>
      <tr>
          <td>timeline 單時間戳</td>
          <td>只記顯示時間，不記事件原始時間</td>
          <td>同時保留 event / ingest / query</td>
      </tr>
      <tr>
          <td>成本截斷不標示</td>
          <td>高 cardinality 被合併但仍當完整資料</td>
          <td>標示 truncation 與聚合粒度</td>
      </tr>
  </tbody>
</table>
<p>dashboard 即事實會讓事故決策失去資料謙遜。圖表顯示健康時，仍要確認資料有沒有缺口、延遲或抽樣偏誤，尤其在 pipeline 自身承受壓力時。</p>
<p>sampling policy 黑箱會降低服務團隊的風險判讀品質。平台可以為成本抽樣，但抽樣規則要能被服務團隊理解，並且允許錯誤、高延遲與低流量關鍵路徑保留更高權重。</p>
<h2 id="遷移期的雙軌對照驗證">遷移期的雙軌對照驗證</h2>
<p>觀測平台遷移是資料品質最容易失分的窗口。新舊管線並存期間，若沒有顯式對照驗證，語意漂移會在 dashboard 看起來「都有資料」的情況下緩慢偏離，直到事故時才浮現。</p>
<p>雙軌對照的核心責任是把新管線當被檢驗的對象、用舊管線作為對照基準。新舊管線同時採集相同訊號、用相同 query 對照 error rate、p95 latency、<a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a>、trace coverage 是否一致；偏差超過閾值時先停止下一步遷移、保留證據後再決定下一步。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/datadog-otel-migration-practice/" data-link-title="4.C7 Datadog：OTel 相容遷移實務" data-link-desc="APM 採集從專有代理轉向 OTel 相容模式的治理案例。">4.C7 Datadog OTel 相容遷移實務</a>：揭露「先建立雙軌採集對照、用品質指標決定何時關閉舊管線」的做法。對應 <a href="/blog/backend/04-observability/cases/failure-otel-migration-signal-drift/" data-link-title="4.C9 反例：OTel 遷移後訊號漂移" data-link-desc="雙軌採集未對齊導致告警與 SLO 判讀失真。">4.C9 OTel 遷移訊號漂移反例</a>：揭露遷移失敗的主要風險來自語意漂移 — metric 名稱、label、sampling、aggregation 在新舊管線間出現微小差異，導致同一現象被歸到不同 service / label / latency bucket。</p>
<p>可重複套用的對照驗證做法：</p>
<ol>
<li><strong>固定一組 baseline query</strong>：選定關鍵服務的核心 SLI query（error rate、p99 latency、throughput），新舊管線各跑一份、定期比對。</li>
<li><strong>設定偏差閾值</strong>：每個 SLI 設可接受偏差（例如 ±5%）。超過閾值的時段標記為待調查，不能無視。</li>
<li><strong>追蹤 missing signal 比例</strong>：missing span、missing metric、missing log 的比例是漂移的早期指標。比例持續上升時，停止下一批服務切換。</li>
<li><strong>退出條件顯式化</strong>：「對照偏差連續 N 天 &lt; X%」作為關閉舊管線的退出條件，把雙軌期變成有界的、不是無限延長。</li>
</ol>
<p>遷移期的告警條件本身也是治理項目。新舊管線對同服務的 error rate 長期偏離、missing span / missing metric 比例持續上升、同一事件在兩套 dashboard 得到相反結論、這些都該成為高優先告警、讓漂移在發生當下即時可見、避免堆積到 retrospective 才被注意。</p>
<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> 處理，本章關注的是品質指標的對照設計。</p>
<h2 id="與-slo-和事故的關係">與 SLO 和事故的關係</h2>
<p>Telemetry data quality 是 SLO 與事故 evidence 的可信度前提。SLI 若建立在失真資料上，<a href="/blog/backend/knowledge-cards/error-budget/" data-link-title="Error Budget" data-link-desc="說明 SLO 允許的失敗額度如何影響發版與可靠性投入">error budget</a>、burn rate alert 與 release freeze 都會被錯誤資料牽動。</p>
<p>在 SLO 場景中，資料品質缺口會直接改變可靠性政策。若 availability SLI 漏掉 mobile client、region label 漂移、error sample 被抽掉，團隊會高估可靠性並繼續放行高風險變更。</p>
<p>在事故場景中，資料品質限制需要進入 <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">incident decision log</a>。當 IC 做出升級、降級、等待或 rollback 決策時，應同時記錄當下 evidence 的 completeness、freshness 與 confidence。</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>：治理欄位漂移</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 / cost</a>：sampling 策略矩陣、高維度截斷與成本取捨</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>：追查 drop、delay 與 ingest 問題</li>
<li><a href="/blog/backend/04-observability/anomaly-detection/" data-link-title="4.14 Anomaly Detection" data-link-desc="把 ML / statistical baseline 訊號跟 rule-based alert 整合">4.14 anomaly detection</a>：避免模型學到偏誤資料</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：品質指標的 platform / service ownership 邊界</li>
<li><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><a href="/blog/backend/04-observability/observability-query-design/" data-link-title="4.23 觀測查詢設計" data-link-desc="把觀測資料的讀取路徑當系統設計問題處理：三種查詢模式、storage tiering、pre-aggregation 與資源治理">4.23 觀測查詢設計</a>：pre-aggregation 跟 raw data 的一致性驗證</li>
<li><a href="/blog/backend/04-observability/cases/discord-storage-growth-observability-gap/" data-link-title="4.C13 Discord：從儲存問題回推觀測缺口" data-link-desc="每次儲存遷移都暴露觀測盲區，把儲存成長問題重新框架為訊號設計問題。">4.C13 Discord 儲存→觀測缺口</a>：儲存演進反覆暴露觀測盲區的教訓</li>
</ul>
]]></content:encoded></item><item><title>4.18 Observability Operating Model</title><link>https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/observability-operating-model/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>operating model 的責任：定義誰擁有訊號、誰維護 dashboard、誰處理 alert、誰承擔成本&lt;/li>
&lt;li>角色分工：platform team、service team、on-call、incident commander、security / compliance&lt;/li>
&lt;li>ownership 欄位：owner、review cadence、retention、cost center、runbook link、deprecation date&lt;/li>
&lt;li>生命週期：新增、審核、使用、修訂、淘汰&lt;/li>
&lt;li>治理節奏：dashboard review、alert review、cost review、post-incident write-back&lt;/li>
&lt;li>跟 4.15 cost attribution 的關係：成本歸屬是 operating model 的一部分&lt;/li>
&lt;li>跟 08 的關係：事故時使用同一組 owner 與 escalation route&lt;/li>
&lt;li>反模式：平台團隊擁有所有 alert；service team 不看 dashboard；成本無 owner&lt;/li>
&lt;/ul>
&lt;p>Observability operating model 的價值是把觀測從「工具責任」改成「服務責任」。平台團隊提供共用能力，服務團隊提供業務語意，on-call 使用這些資產做決策；operating model 負責固定三者的接口。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Observability operating model 是把觀測資產的責任分配明確化的治理模型，責任是讓訊號有人維護、告警有人回應、成本有人決策。&lt;/p>
&lt;p>這一頁處理的是 ownership。可觀測性需要平台工具、服務脈絡、操作責任與淘汰條件一起維持。&lt;/p>
&lt;p>這層的判準是事故當下能否立刻知道誰要看哪個面板、誰有權調整閾值、誰負責決定淘汰過期訊號。dashboard 數量與 alert 覆蓋率只是輔助訊號。&lt;/p>
&lt;h2 id="角色分工">角色分工&lt;/h2>
&lt;p>Observability operating model 的角色分工以「誰能做決策」為核心。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>Platform team&lt;/td>
 &lt;td>採集、儲存、查詢、成本與標準&lt;/td>
 &lt;td>pipeline、schema convention、quota&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Service team&lt;/td>
 &lt;td>服務語意、核心旅程與業務事件&lt;/td>
 &lt;td>service dashboard、SLI、alert rule&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>On-call&lt;/td>
 &lt;td>事中判讀、runbook 使用與升級&lt;/td>
 &lt;td>silence、escalate、incident intake&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Incident commander&lt;/td>
 &lt;td>事故優先序、通訊節奏與決策紀錄&lt;/td>
 &lt;td>severity、rollback、status update&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Security / compliance&lt;/td>
 &lt;td>audit log、PII、retention 與 evidence&lt;/td>
 &lt;td>retention、masking、access review&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Finance / cost owner&lt;/td>
 &lt;td>成本歸屬、預算與 chargeback&lt;/td>
 &lt;td>quota、retention tier、cost review&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Platform team 的責任是維持共同語言。它需要定義 service name、environment、region、tenant、trace context、retention tier 與成本政策，讓跨服務查詢可行。&lt;/p>
&lt;p>Service team 的責任是維持服務語意。它需要定義哪些 user journey 是核心、哪些錯誤影響用戶、哪些 dependency failure 需要 alert、哪些 dashboard 仍有操作價值。&lt;/p>
&lt;p>On-call 的責任是把資產用在事中決策。alert 應能帶到 dashboard、runbook 與 owner，讓 operating model 真正進入操作流程。&lt;/p>
&lt;p>Security / compliance 的責任是把觀測資料的證據價值與資料風險同時納入治理。audit log、PII redaction、retention 與 access review 需要在觀測模型中有明確 owner。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 operating model 時，先看每個觀測資產是否有 owner，再看 owner 是否有權限與節奏採取行動。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>dashboard 是否有明確使用者與 review cadence&lt;/li>
&lt;li>alert 是否有 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook&lt;/a>、owner 與 escalation path&lt;/li>
&lt;li>高成本訊號是否能對應服務價值與成本中心&lt;/li>
&lt;li>post-incident review 是否能回寫到訊號 owner&lt;/li>
&lt;li>orphan dashboard 與 stale alert 是否有清理流程&lt;/li>
&lt;/ul>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>資產類型&lt;/th>
 &lt;th>Owner&lt;/th>
 &lt;th>週期&lt;/th>
 &lt;th>關閉條件&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Dashboard&lt;/td>
 &lt;td>service team + on-call&lt;/td>
 &lt;td>月檢&lt;/td>
 &lt;td>無使用者、無判讀價值&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Alert&lt;/td>
 &lt;td>service owner&lt;/td>
 &lt;td>週檢&lt;/td>
 &lt;td>重複、誤報高、無行動&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Query / Schema&lt;/td>
 &lt;td>platform + service&lt;/td>
 &lt;td>變更檢&lt;/td>
 &lt;td>欄位漂移、查詢成本失控&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Cost Attribution&lt;/td>
 &lt;td>cost owner&lt;/td>
 &lt;td>月檢&lt;/td>
 &lt;td>成本缺少服務價值對應&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="觀測資產欄位">觀測資產欄位&lt;/h2>
&lt;p>Observability asset 需要像服務 artifact 一樣有 metadata。沒有 metadata 的 dashboard、alert、query 與 schema 會在幾個月後變成無人敢刪、無人敢改、也無人信任的資產。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>operating model 的責任：定義誰擁有訊號、誰維護 dashboard、誰處理 alert、誰承擔成本</li>
<li>角色分工：platform team、service team、on-call、incident commander、security / compliance</li>
<li>ownership 欄位：owner、review cadence、retention、cost center、runbook link、deprecation date</li>
<li>生命週期：新增、審核、使用、修訂、淘汰</li>
<li>治理節奏：dashboard review、alert review、cost review、post-incident write-back</li>
<li>跟 4.15 cost attribution 的關係：成本歸屬是 operating model 的一部分</li>
<li>跟 08 的關係：事故時使用同一組 owner 與 escalation route</li>
<li>反模式：平台團隊擁有所有 alert；service team 不看 dashboard；成本無 owner</li>
</ul>
<p>Observability operating model 的價值是把觀測從「工具責任」改成「服務責任」。平台團隊提供共用能力，服務團隊提供業務語意，on-call 使用這些資產做決策；operating model 負責固定三者的接口。</p>
<h2 id="概念定位">概念定位</h2>
<p>Observability operating model 是把觀測資產的責任分配明確化的治理模型，責任是讓訊號有人維護、告警有人回應、成本有人決策。</p>
<p>這一頁處理的是 ownership。可觀測性需要平台工具、服務脈絡、操作責任與淘汰條件一起維持。</p>
<p>這層的判準是事故當下能否立刻知道誰要看哪個面板、誰有權調整閾值、誰負責決定淘汰過期訊號。dashboard 數量與 alert 覆蓋率只是輔助訊號。</p>
<h2 id="角色分工">角色分工</h2>
<p>Observability operating model 的角色分工以「誰能做決策」為核心。owner 是有權維護、調整、下架或升級觀測資產的人，名義聯絡人只能作為補充欄位。</p>
<table>
  <thead>
      <tr>
          <th>角色</th>
          <th>核心責任</th>
          <th>決策權限</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Platform team</td>
          <td>採集、儲存、查詢、成本與標準</td>
          <td>pipeline、schema convention、quota</td>
      </tr>
      <tr>
          <td>Service team</td>
          <td>服務語意、核心旅程與業務事件</td>
          <td>service dashboard、SLI、alert rule</td>
      </tr>
      <tr>
          <td>On-call</td>
          <td>事中判讀、runbook 使用與升級</td>
          <td>silence、escalate、incident intake</td>
      </tr>
      <tr>
          <td>Incident commander</td>
          <td>事故優先序、通訊節奏與決策紀錄</td>
          <td>severity、rollback、status update</td>
      </tr>
      <tr>
          <td>Security / compliance</td>
          <td>audit log、PII、retention 與 evidence</td>
          <td>retention、masking、access review</td>
      </tr>
      <tr>
          <td>Finance / cost owner</td>
          <td>成本歸屬、預算與 chargeback</td>
          <td>quota、retention tier、cost review</td>
      </tr>
  </tbody>
</table>
<p>Platform team 的責任是維持共同語言。它需要定義 service name、environment、region、tenant、trace context、retention tier 與成本政策，讓跨服務查詢可行。</p>
<p>Service team 的責任是維持服務語意。它需要定義哪些 user journey 是核心、哪些錯誤影響用戶、哪些 dependency failure 需要 alert、哪些 dashboard 仍有操作價值。</p>
<p>On-call 的責任是把資產用在事中決策。alert 應能帶到 dashboard、runbook 與 owner，讓 operating model 真正進入操作流程。</p>
<p>Security / compliance 的責任是把觀測資料的證據價值與資料風險同時納入治理。audit log、PII redaction、retention 與 access review 需要在觀測模型中有明確 owner。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 operating model 時，先看每個觀測資產是否有 owner，再看 owner 是否有權限與節奏採取行動。</p>
<p>重點訊號包括：</p>
<ul>
<li>dashboard 是否有明確使用者與 review cadence</li>
<li>alert 是否有 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">runbook</a>、owner 與 escalation path</li>
<li>高成本訊號是否能對應服務價值與成本中心</li>
<li>post-incident review 是否能回寫到訊號 owner</li>
<li>orphan dashboard 與 stale alert 是否有清理流程</li>
</ul>
<table>
  <thead>
      <tr>
          <th>資產類型</th>
          <th>Owner</th>
          <th>週期</th>
          <th>關閉條件</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Dashboard</td>
          <td>service team + on-call</td>
          <td>月檢</td>
          <td>無使用者、無判讀價值</td>
      </tr>
      <tr>
          <td>Alert</td>
          <td>service owner</td>
          <td>週檢</td>
          <td>重複、誤報高、無行動</td>
      </tr>
      <tr>
          <td>Query / Schema</td>
          <td>platform + service</td>
          <td>變更檢</td>
          <td>欄位漂移、查詢成本失控</td>
      </tr>
      <tr>
          <td>Cost Attribution</td>
          <td>cost owner</td>
          <td>月檢</td>
          <td>成本缺少服務價值對應</td>
      </tr>
  </tbody>
</table>
<h2 id="觀測資產欄位">觀測資產欄位</h2>
<p>Observability asset 需要像服務 artifact 一樣有 metadata。沒有 metadata 的 dashboard、alert、query 與 schema 會在幾個月後變成無人敢刪、無人敢改、也無人信任的資產。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>責任</th>
          <th>判讀用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Owner</td>
          <td>指定維護與決策責任</td>
          <td>事故時知道找誰</td>
      </tr>
      <tr>
          <td>User</td>
          <td>說明誰會使用這個資產</td>
          <td>判斷是否仍有操作價值</td>
      </tr>
      <tr>
          <td>Runbook link</td>
          <td>連到下一步操作</td>
          <td>讓 alert 能轉成行動</td>
      </tr>
      <tr>
          <td>Review cadence</td>
          <td>定義檢視頻率</td>
          <td>避免 stale dashboard / alert</td>
      </tr>
      <tr>
          <td>Cost center</td>
          <td>對應服務或團隊成本</td>
          <td>支援 chargeback 與 retention 決策</td>
      </tr>
      <tr>
          <td>Retention tier</td>
          <td>指定保存時間與查詢粒度</td>
          <td>平衡法規、事故與成本</td>
      </tr>
      <tr>
          <td>Deprecation date</td>
          <td>標示預計下架或重檢日期</td>
          <td>避免觀測資產永久堆積</td>
      </tr>
      <tr>
          <td>Data limitation</td>
          <td>標示抽樣、缺口與聚合限制</td>
          <td>避免事中誤讀資料</td>
      </tr>
  </tbody>
</table>
<p>Owner 欄位要搭配權限才有意義。有效 owner 需要能調整 threshold、更新 dashboard、下架 query 或決定 retention，讓 ownership 成為可執行責任。</p>
<p>User 欄位能避免 dashboard 變成展示資產。面板若沒有明確使用者，例如 on-call、service owner、capacity planner 或 compliance reviewer，就很難判斷它是否仍值得維護。</p>
<p>Runbook link 是 alert 從通知變成行動的關鍵。每個可 page 的 alert 都應連到第一步查詢、初始判讀、升級條件與 rollback / degrade / wait 的決策路由。</p>
<p>Cost center 讓觀測成本有業務語意。高 cardinality、長 retention、full-fidelity trace 與大量 log indexing 都有價值，但價值需要由能受益的服務或團隊承擔與檢視。</p>
<h2 id="生命週期">生命週期</h2>
<p>Observability operating model 的生命週期是新增、審核、使用、修訂與淘汰。這個生命週期讓訊號保持有用，並讓觀測資產累積在可治理範圍內。</p>
<ol>
<li>新增：服務變更、事故復盤、演練需求或合規要求產生新訊號。</li>
<li>審核：確認 schema、成本、owner、runbook 與 retention。</li>
<li>使用：進入 dashboard、alert、incident intake 或 SLO 計算。</li>
<li>修訂：根據噪音、缺口、成本與使用頻率調整。</li>
<li>淘汰：移除 stale alert、orphan dashboard、過期 query 與無價值高成本訊號。</li>
</ol>
<p>新增訊號需要清楚的需求來源。最好的來源是 user journey、SLO、incident review、game day 或 audit requirement；最弱的來源是「可能有用」。</p>
<p>審核訊號需要同時看語意與成本。欄位是否穩定、cardinality 是否可控、retention 是否合理、PII 是否被遮罩、owner 是否能維護，都是訊號上線前的固定問題。</p>
<p>淘汰是 operating model 的必要能力。舊 alert 沒有人敢關，會增加 alert fatigue；舊 dashboard 沒有人敢刪，會讓事故時不知道哪個面板可信。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>alert 觸發後沒人知道該由平台或服務團隊處理</li>
<li>dashboard 存在但半年無人打開</li>
<li>成本暴增時只能找平台團隊吸收</li>
<li>post-incident review 指派 action item，但沒有訊號 owner</li>
<li>service team 調整欄位後，平台查詢與 dashboard 斷裂</li>
</ul>
<p>實務上常見的治理斷點是「有 owner 名字，缺 owner 權限」。owner 需要能調整 alert、建立或下架 dashboard、分配成本，治理流程才會停在資產責任人，減少回流到平台集中處理的積壓。</p>
<h2 id="治理節奏">治理節奏</h2>
<p>Operating model 的治理節奏把觀測資產拉回日常工程流程。review cadence 的重點是定期回答「這個資產還能支援決策嗎」，會議只是其中一種執行形式。</p>
<table>
  <thead>
      <tr>
          <th>節奏</th>
          <th>核心問題</th>
          <th>典型輸出</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Dashboard review</td>
          <td>面板是否仍有人用、是否對應旅程</td>
          <td>更新、合併、下架</td>
      </tr>
      <tr>
          <td>Alert review</td>
          <td>alert 是否可行動、噪音是否可接受</td>
          <td>threshold 調整、silence、runbook</td>
      </tr>
      <tr>
          <td>Cost review</td>
          <td>成本是否對應服務價值</td>
          <td>retention tier、sampling policy</td>
      </tr>
      <tr>
          <td>Schema review</td>
          <td>欄位是否穩定、是否跨服務一致</td>
          <td>schema migration、drift 修正</td>
      </tr>
      <tr>
          <td>Post-incident write-back</td>
          <td>復盤缺口是否回寫到訊號與 owner</td>
          <td>新 alert、新 dashboard、新 runbook</td>
      </tr>
  </tbody>
</table>
<p>Dashboard review 應看使用情境與操作價值。面板需要支援 on-call 的前 10 分鐘、capacity planning 或 SLO review；脫離這些用途的面板適合合併、重命名或下架。</p>
<p>Alert review 應看行動品質。alert 若經常觸發但缺少明確處置，通常更適合變成 dashboard signal、ticket 或長期治理項。</p>
<p>Cost review 應看服務價值。觀測成本上升不一定是壞事，但需要能說明這些成本降低了哪一種事故風險、合規風險或容量風險。</p>
<h2 id="規模差異下的角色配置">規模差異下的角色配置</h2>
<p>Operating model 的角色配置隨組織規模調整。可投入的治理人力、可承受的協調成本、可維持的審核頻率三項一起決定當前該採哪種配置。把大組織的治理模型套到小團隊會造成過度治理；把小團隊的鬆散模型套到大組織會造成責任懸空。</p>
<p>本段聚焦常態 ownership 配置（不同規模下角色矩陣的差異）；遷移期的節奏取捨由 <a href="/blog/backend/04-observability/telemetry-pipeline/#%e8%a6%8f%e6%a8%a1%e5%b7%ae%e7%95%b0%e4%b8%8b%e7%9a%84%e9%81%b7%e7%a7%bb%e7%af%80%e5%a5%8f" data-link-title="4.11 Telemetry Pipeline 架構" data-link-desc="把 log / metric / trace 的 agent → collector → ingest → storage → query 分層治理">4.11 規模差異下的遷移節奏</a> 處理、兩者 lens 不同。</p>
<p>對應 <a href="/blog/backend/04-observability/cases/contrast-observability-rollout-by-scale/" data-link-title="4.C10 對照：規模差異下的觀測遷移" data-link-desc="觀測遷移在不同規模團隊下的流程與風險差異。">4.C10 規模差異下觀測遷移</a>：揭露「規模差異會放大不同治理失分模式」的方向；case 主場景是觀測遷移、本章將此 frame 借用到常態 operating model 場景、以下展開屬通用工程知識補充。</p>
<p>小型組織的 operating model 重點是「角色合一、節奏明確」。一個 SRE 同時承擔 platform、service、on-call、cost owner 多重身份。治理重點是顯式記錄當前 ownership 跟 review cadence、避免角色合一被誤讀成默契傳遞（「大家都管 = 沒人管」是典型失敗）。Dashboard review、alert review、cost review 可以合併在同一個月會中，但要有具體的決議紀錄。</p>
<p>中型組織開始出現 platform 跟 service team 的分化，治理失分集中在介面定義。schema convention、cardinality 限制、cost center 命名規約若未在 platform / service 之間明確化，會在跨服務查詢時持續出現拼接斷裂。中型組織適合先固化「平台保證什麼、服務保證什麼」的契約，再擴大角色拆分。</p>
<p>大型組織的 operating model 牽涉多層 platform team、跨地區 on-call、合規 / 安全 / 財務的橫切責任。治理失分的核心來源是審核節奏跟不上資產成長速度 — 角色分工通常已經清晰，但每週 / 每月人工 review 數百個 dashboard / alert 不切實際。大型組織需要自動化的 stale dashboard 偵測、orphan alert 提示、retention compliance 報表，把 review 從手動週期變成事件驅動，讓治理隨資產數量自動擴展。</p>
<p>三類組織的共同前提是先把 ownership 視為可演進的、再決定當前該採哪種配置。組織成長過程中 ownership 矩陣會反覆調整，每次調整都要把新配置寫進文件、進入 release / runbook 流程、讓 ownership 變更跟釋出流程同步可見。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Observability operating model 的反模式通常是責任集中或責任懸空。前者讓平台團隊成為所有訊號的瓶頸，後者讓服務團隊在事故時找不到可信入口。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>平台擁有所有 alert</td>
          <td>服務語意缺失，告警只能看基礎設施</td>
          <td>service owner 擁有服務級 alert</td>
      </tr>
      <tr>
          <td>服務各自為政</td>
          <td>欄位、命名、retention 不一致</td>
          <td>platform 提供 schema convention</td>
      </tr>
      <tr>
          <td>owner 缺權限</td>
          <td>只能被追責，缺少資產修正能力</td>
          <td>owner 取得調整、下架與預算權限</td>
      </tr>
      <tr>
          <td>成本無歸屬</td>
          <td>高成本訊號由平台吸收</td>
          <td>cost center 與 retention tier</td>
      </tr>
      <tr>
          <td>復盤無回寫</td>
          <td>action item 停在文件</td>
          <td>write-back 到 dashboard / alert</td>
      </tr>
  </tbody>
</table>
<p>平台擁有所有 alert 會讓服務語意被削弱。平台知道 pipeline 與 infra，但通常不知道某個錯誤是否影響 checkout、資料同步、帳單或客戶 SLA。</p>
<p>服務各自為政會讓跨服務事故難以判讀。每個服務都可以有自己的 dashboard，但 service name、environment、region、tenant、error class 與 trace context 需要共用標準。</p>
<p>復盤無回寫會讓 operating model 停在文件。post-incident review 揭露的偵測缺口、runbook 缺口與成本缺口都應回到對應 owner 的資產生命週期。</p>
<h2 id="與事故流程的關係">與事故流程的關係</h2>
<p>Observability operating model 是事故流程的責任基礎。事故期間，IC 需要知道哪些訊號可信、哪個 owner 能解釋欄位、誰能調整 alert、誰能決定保留或匯出 evidence。</p>
<p>在 incident command 中，observability owner 不一定是 incident commander，但必須能提供訊號解釋與操作建議。當 telemetry data quality 有限制時，owner 需要把限制交給 scribe 或 <a href="/blog/backend/knowledge-cards/incident-decision-log/" data-link-title="Incident Decision Log" data-link-desc="說明事故期間如何保留決策、證據、owner 與回退條件">decision log</a>。</p>
<p>在 runbook lifecycle 中，dashboard、alert 與 query 都應被視為 runbook 的依賴。runbook 更新時，如果沒有同步更新觀測資產，下一次事故仍會走到舊入口。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<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>：設計 owner、runbook 與停止條件</li>
<li><a href="/blog/backend/04-observability/signal-governance-loop/" data-link-title="4.8 訊號治理閉環" data-link-desc="把 postmortem 揭露的偵測缺口回寫成新訊號、讓觀測能力隨事故學習成長">4.8 signal governance loop</a>：淘汰 stale alert 與 orphan dashboard</li>
<li><a href="/blog/backend/04-observability/service-topology/" data-link-title="4.13 Service Topology 與 Dependency Map" data-link-desc="把跨服務依賴從文件變成自動發現的觀測訊號">4.13 service topology</a>：動態叢集環境下、cluster 層 vs 服務層的 ownership 路由</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>：把成本接回 owner 與服務</li>
<li>08.2 incident command roles：事故時使用相同 ownership 模型</li>
<li>08.16 runbook lifecycle：把觀測資產接進 runbook 版本治理</li>
</ul>
]]></content:encoded></item><item><title>4.19 Debuggability by Design</title><link>https://tarrragon.github.io/blog/backend/04-observability/debuggability-by-design/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/debuggability-by-design/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>debuggability by design 的責任：讓系統設計本身支援定位、重現與證據收集&lt;/li>
&lt;li>API 設計：request id、error code、idempotency key、semantic status&lt;/li>
&lt;li>async workflow：message id、correlation id、retry count、dead-letter reason&lt;/li>
&lt;li>dependency call：timeout、fallback、upstream response、circuit state&lt;/li>
&lt;li>error model：可分類錯誤、可追蹤錯誤鏈、可對應使用者影響&lt;/li>
&lt;li>診斷入口：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/diagnostic-endpoint/" data-link-title="Diagnostic Endpoint" data-link-desc="說明健康檢查、診斷與調試入口如何控制暴露面">diagnostic endpoint&lt;/a>、health check、probe&lt;/li>
&lt;li>跟語言教材的分工：語言處理 logger / error chain，04 處理跨服務診斷能力&lt;/li>
&lt;li>反模式：事後補 log；錯誤只回 500；async 任務缺 correlation id；依賴失敗無上下文&lt;/li>
&lt;/ul>
&lt;p>Debuggability by design 的核心是讓系統在設計時就暴露足夠上下文。事故時需要的資訊若沒有在 API、message、dependency call 與 error model 層留下來，後端平台再完整也只能收集到片段訊號。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Debuggability by design 是把可診斷性當成服務設計輸入的做法，責任是讓系統在出問題時自然留下定位所需的脈絡。&lt;/p>
&lt;p>這一頁處理的是設計前移。觀測工具只能收集系統吐出的訊號；如果 API、async workflow、dependency call 與 error model 沒有診斷欄位，事後補平台也只能看到破碎片段。&lt;/p>
&lt;p>這層與可觀測平台互補：平台負責收、存、查，設計負責產生可判讀語意。兩者任一缺失，都會讓事故定位時間呈倍數增加。&lt;/p>
&lt;h2 id="設計輸入">設計輸入&lt;/h2>
&lt;p>Debuggability by design 的設計輸入是「未來出問題時需要回答什麼問題」。系統設計時先列出這些問題，才能決定 API、message、dependency call 與 error model 要留下哪些欄位。&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>request id、tenant、user journey&lt;/td>
 &lt;td>API、log schema、trace&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>這個 async 任務從哪裡來&lt;/td>
 &lt;td>correlation id、message id、causation id&lt;/td>
 &lt;td>queue、worker、event log&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>失敗來自本服務還是外部依賴&lt;/td>
 &lt;td>upstream name、timeout、response class&lt;/td>
 &lt;td>HTTP client、adapter&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>這個錯誤能否重試或回放&lt;/td>
 &lt;td>retry count、idempotency key、DLQ reason&lt;/td>
 &lt;td>worker、consumer、DLQ&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>事故時能否安全查系統狀態&lt;/td>
 &lt;td>diagnostic endpoint、probe、read-only view&lt;/td>
 &lt;td>admin / diagnostic surface&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Request id 與 trace id 的責任不同。request id 通常對應對外請求與支援查詢，trace id 對應跨服務路徑；兩者互相連結時，支援查詢與工程診斷都會有穩定入口。&lt;/p>
&lt;p>Correlation id 與 causation id 能讓 async workflow 保留因果。事件進入 queue、fan-out、retry、DLQ 或 replay 後，團隊需要知道它從哪個 request 或上游事件來，並且知道目前是哪一次處理嘗試。&lt;/p>
&lt;p>Diagnostic endpoint 的責任是提供低風險查詢入口。它是受權限、速率、遮罩與審計保護的操作面，讓 on-call 能查健康、依賴、queue、cache 或 feature flag 狀態。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀 debuggability 時，先看關鍵流程是否保留 correlation，再看錯誤是否能路由到下一步。&lt;/p>
&lt;p>重點訊號包括：&lt;/p>
&lt;ul>
&lt;li>API request 是否有穩定 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id&lt;/a> 與錯誤分類&lt;/li>
&lt;li>async message 是否有 correlation id、retry count 與 DLQ reason&lt;/li>
&lt;li>dependency call 是否記錄 upstream、timeout、fallback 與 response class&lt;/li>
&lt;li>error chain 是否能連到 trace、log 與 user impact&lt;/li>
&lt;li>diagnostic endpoint 是否能支援 on-call 的低風險查詢&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>API&lt;/td>
 &lt;td>request id、error code、idempotency key&lt;/td>
 &lt;td>快速對齊請求與結果&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Async / Queue&lt;/td>
 &lt;td>message id、correlation id、retry reason&lt;/td>
 &lt;td>還原跨流程事件鏈&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Dependency&lt;/td>
 &lt;td>upstream、timeout、fallback state&lt;/td>
 &lt;td>分辨本地問題與外部依賴問題&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Error Model&lt;/td>
 &lt;td>error class、context、impact hint&lt;/td>
 &lt;td>路由到正確處理流程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="api-可診斷性">API 可診斷性&lt;/h2>
&lt;p>API 可診斷性的責任是讓每一次 request 都能被支援、工程與事故流程共同定位。API 不只回傳成功或失敗，也要留下足夠語意讓團隊知道錯在哪個層級。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>debuggability by design 的責任：讓系統設計本身支援定位、重現與證據收集</li>
<li>API 設計：request id、error code、idempotency key、semantic status</li>
<li>async workflow：message id、correlation id、retry count、dead-letter reason</li>
<li>dependency call：timeout、fallback、upstream response、circuit state</li>
<li>error model：可分類錯誤、可追蹤錯誤鏈、可對應使用者影響</li>
<li>診斷入口：<a href="/blog/backend/knowledge-cards/diagnostic-endpoint/" data-link-title="Diagnostic Endpoint" data-link-desc="說明健康檢查、診斷與調試入口如何控制暴露面">diagnostic endpoint</a>、health check、probe</li>
<li>跟語言教材的分工：語言處理 logger / error chain，04 處理跨服務診斷能力</li>
<li>反模式：事後補 log；錯誤只回 500；async 任務缺 correlation id；依賴失敗無上下文</li>
</ul>
<p>Debuggability by design 的核心是讓系統在設計時就暴露足夠上下文。事故時需要的資訊若沒有在 API、message、dependency call 與 error model 層留下來，後端平台再完整也只能收集到片段訊號。</p>
<h2 id="概念定位">概念定位</h2>
<p>Debuggability by design 是把可診斷性當成服務設計輸入的做法，責任是讓系統在出問題時自然留下定位所需的脈絡。</p>
<p>這一頁處理的是設計前移。觀測工具只能收集系統吐出的訊號；如果 API、async workflow、dependency call 與 error model 沒有診斷欄位，事後補平台也只能看到破碎片段。</p>
<p>這層與可觀測平台互補：平台負責收、存、查，設計負責產生可判讀語意。兩者任一缺失，都會讓事故定位時間呈倍數增加。</p>
<h2 id="設計輸入">設計輸入</h2>
<p>Debuggability by design 的設計輸入是「未來出問題時需要回答什麼問題」。系統設計時先列出這些問題，才能決定 API、message、dependency call 與 error model 要留下哪些欄位。</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>需要的設計輸入</th>
          <th>常見位置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>這次失敗影響哪個請求或用戶</td>
          <td>request id、tenant、user journey</td>
          <td>API、log schema、trace</td>
      </tr>
      <tr>
          <td>這個 async 任務從哪裡來</td>
          <td>correlation id、message id、causation id</td>
          <td>queue、worker、event log</td>
      </tr>
      <tr>
          <td>失敗來自本服務還是外部依賴</td>
          <td>upstream name、timeout、response class</td>
          <td>HTTP client、adapter</td>
      </tr>
      <tr>
          <td>這個錯誤能否重試或回放</td>
          <td>retry count、idempotency key、DLQ reason</td>
          <td>worker、consumer、DLQ</td>
      </tr>
      <tr>
          <td>事故時能否安全查系統狀態</td>
          <td>diagnostic endpoint、probe、read-only view</td>
          <td>admin / diagnostic surface</td>
      </tr>
  </tbody>
</table>
<p>Request id 與 trace id 的責任不同。request id 通常對應對外請求與支援查詢，trace id 對應跨服務路徑；兩者互相連結時，支援查詢與工程診斷都會有穩定入口。</p>
<p>Correlation id 與 causation id 能讓 async workflow 保留因果。事件進入 queue、fan-out、retry、DLQ 或 replay 後，團隊需要知道它從哪個 request 或上游事件來，並且知道目前是哪一次處理嘗試。</p>
<p>Diagnostic endpoint 的責任是提供低風險查詢入口。它是受權限、速率、遮罩與審計保護的操作面，讓 on-call 能查健康、依賴、queue、cache 或 feature flag 狀態。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀 debuggability 時，先看關鍵流程是否保留 correlation，再看錯誤是否能路由到下一步。</p>
<p>重點訊號包括：</p>
<ul>
<li>API request 是否有穩定 <a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id</a> 與錯誤分類</li>
<li>async message 是否有 correlation id、retry count 與 DLQ reason</li>
<li>dependency call 是否記錄 upstream、timeout、fallback 與 response class</li>
<li>error chain 是否能連到 trace、log 與 user impact</li>
<li>diagnostic endpoint 是否能支援 on-call 的低風險查詢</li>
</ul>
<table>
  <thead>
      <tr>
          <th>設計層</th>
          <th>最小可診斷欄位</th>
          <th>事故價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>API</td>
          <td>request id、error code、idempotency key</td>
          <td>快速對齊請求與結果</td>
      </tr>
      <tr>
          <td>Async / Queue</td>
          <td>message id、correlation id、retry reason</td>
          <td>還原跨流程事件鏈</td>
      </tr>
      <tr>
          <td>Dependency</td>
          <td>upstream、timeout、fallback state</td>
          <td>分辨本地問題與外部依賴問題</td>
      </tr>
      <tr>
          <td>Error Model</td>
          <td>error class、context、impact hint</td>
          <td>路由到正確處理流程</td>
      </tr>
  </tbody>
</table>
<h2 id="api-可診斷性">API 可診斷性</h2>
<p>API 可診斷性的責任是讓每一次 request 都能被支援、工程與事故流程共同定位。API 不只回傳成功或失敗，也要留下足夠語意讓團隊知道錯在哪個層級。</p>
<table>
  <thead>
      <tr>
          <th>API 欄位</th>
          <th>設計責任</th>
          <th>事故價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Request ID</td>
          <td>對齊客訴、log、trace 與支援查詢</td>
          <td>從用戶回報回到後端事件</td>
      </tr>
      <tr>
          <td>Error code</td>
          <td>穩定分類錯誤語意</td>
          <td>分辨 validation、auth、quota</td>
      </tr>
      <tr>
          <td>Idempotency key</td>
          <td>保護重試與重播</td>
          <td>避免 recovery 時重複副作用</td>
      </tr>
      <tr>
          <td>Semantic status</td>
          <td>表達可重試、已接受、部分完成</td>
          <td>支援客戶端與後端一致處置</td>
      </tr>
      <tr>
          <td>Impact hint</td>
          <td>標示 user-facing 或 internal-only</td>
          <td>支援 severity 初判</td>
      </tr>
  </tbody>
</table>
<p>Request ID 是支援與工程之間的共同鑰匙。客戶只知道某次操作失敗，支援需要 request id 或可查詢等價欄位，才能把客訴轉成 incident intake evidence。</p>
<p>Error code 應該表達穩定語意，並保持內部實作封裝。<code>PAYMENT_PROVIDER_TIMEOUT</code>、<code>QUOTA_EXCEEDED</code>、<code>TOKEN_EXPIRED</code> 這類分類能支援路由；隨程式碼結構變動的錯誤字串則會讓查詢與客戶端處置不穩定。</p>
<p>Idempotency key 是 recovery 的診斷欄位。當 retry、rollback、replay 或補償流程啟動時，團隊需要知道哪些請求已被接受、哪些副作用已完成、哪些可以安全重送。</p>
<h2 id="async-workflow-可診斷性">Async Workflow 可診斷性</h2>
<p>Async workflow 可診斷性的責任是讓事件離開同步 request 後仍保留因果鏈。queue、worker、event handler 與 scheduled job 會把時間拉長、路徑拉開，欄位不足時最容易形成診斷斷點。</p>
<table>
  <thead>
      <tr>
          <th>Async 欄位</th>
          <th>設計責任</th>
          <th>事故價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Message ID</td>
          <td>標識單一訊息</td>
          <td>查詢 delivery、ack、redelivery</td>
      </tr>
      <tr>
          <td>Correlation ID</td>
          <td>串回原始 request 或 workflow</td>
          <td>還原跨流程事件鏈</td>
      </tr>
      <tr>
          <td>Retry count</td>
          <td>記錄處理嘗試次數</td>
          <td>分辨 transient 與 poison case</td>
      </tr>
      <tr>
          <td>DLQ reason</td>
          <td>記錄進入 dead-letter queue 原因</td>
          <td>支援 replay 與修復排序</td>
      </tr>
      <tr>
          <td>Consumer version</td>
          <td>標示處理程式版本</td>
          <td>追查 rollout 或 schema 相容性</td>
      </tr>
  </tbody>
</table>
<p>Message ID 讓團隊能看見單一訊息的生命週期。它應該能串到 publish、broker delivery、consumer ack、redelivery、DLQ 與 replay。</p>
<p>Correlation ID 讓 async 任務保留業務脈絡。缺少 correlation id 時，DLQ dashboard 只能顯示失敗數量，tenant、request 與 user journey 影響範圍會留在人工追查階段。</p>
<p>Retry count 與 DLQ reason 讓回復路徑可排序。高 retry count 可能代表下游依賴失效，也可能代表 poison message；兩者需要不同處置。</p>
<h2 id="dependency-call-可診斷性">Dependency Call 可診斷性</h2>
<p>Dependency call 可診斷性的責任是讓團隊分辨本地問題、下游問題與保護機制啟動。每一次外部依賴呼叫都應留下足夠上下文，支援等待、降級、切換或升級 vendor incident 的判斷。</p>
<table>
  <thead>
      <tr>
          <th>Dependency 欄位</th>
          <th>設計責任</th>
          <th>事故價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Upstream name</td>
          <td>穩定標示依賴服務</td>
          <td>分辨哪個下游失效</td>
      </tr>
      <tr>
          <td>Deadline</td>
          <td>標示呼叫預算</td>
          <td>判斷 timeout 設計是否合理</td>
      </tr>
      <tr>
          <td>Response class</td>
          <td>聚合成功、4xx、5xx、timeout</td>
          <td>支援 error rate 與 vendor triage</td>
      </tr>
      <tr>
          <td>Fallback state</td>
          <td>記錄是否進入降級</td>
          <td>判斷用戶影響是否被吸收</td>
      </tr>
      <tr>
          <td>Circuit state</td>
          <td>記錄 circuit breaker 狀態</td>
          <td>分辨保護機制或真實恢復</td>
      </tr>
  </tbody>
</table>
<p>Upstream name 需要是穩定維度。若每個 adapter 使用不同名稱，dashboard 與 trace 很難把同一個供應商或內部依賴聚合在一起。</p>
<p>Deadline 是 dependency call 的診斷欄位。timeout 發生時，團隊需要知道是下游慢、呼叫預算過短、queue backlog 導致開始太晚，還是 retry policy 放大壓力。</p>
<p>Fallback state 讓事故團隊知道保護是否生效。服務錯誤率可能沒上升，是因為 fallback 吸收了下游失敗；若沒有 fallback 訊號，團隊會低估風險。</p>
<h2 id="error-model-可診斷性">Error Model 可診斷性</h2>
<p>Error model 可診斷性的責任是把錯誤轉成可分類、可路由、可復盤的語意。錯誤不只服務於程式控制流，也服務於事故判讀與使用者影響評估。</p>
<table>
  <thead>
      <tr>
          <th>錯誤層級</th>
          <th>設計責任</th>
          <th>路由方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Validation error</td>
          <td>輸入不符合契約</td>
          <td>API contract / client 修正</td>
      </tr>
      <tr>
          <td>Authorization error</td>
          <td>身分或權限不足</td>
          <td>IAM / security triage</td>
      </tr>
      <tr>
          <td>Dependency error</td>
          <td>外部依賴回應失敗或超時</td>
          <td>vendor / downstream triage</td>
      </tr>
      <tr>
          <td>Capacity error</td>
          <td>資源、queue 或 quota 不足</td>
          <td>capacity / load shedding</td>
      </tr>
      <tr>
          <td>Data consistency error</td>
          <td>寫入、讀取或 migration 不一致</td>
          <td>reliability / migration gate</td>
      </tr>
  </tbody>
</table>
<p>錯誤分類應該讓下一步明確。<code>internal error</code> 適合作為最後防線；主要分類需要支援 on-call 判斷是重試、降級、rollback、升級資安，還是進入資料修復。</p>
<p>Error chain 需要保留上下文。過度包裝錯誤會讓原始 dependency、timeout、request id 或 schema version 消失；完全不包裝則會把底層細節直接丟給外部使用者。好的 error model 會分開內部診斷語意與外部穩定契約。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>事故時只能看到「500」，需要重跑才能定位原因</li>
<li>queue message 進 DLQ 後缺少原始 request 脈絡</li>
<li>外部 API timeout 無 upstream 名稱、耗時與 fallback 狀態</li>
<li>錯誤被包裝後 trace 與 error chain 斷裂</li>
<li>health check 顯示 healthy，但核心旅程已經失效</li>
</ul>
<p>典型情境是 queue 任務在三次重試後進 DLQ，但缺少 request 與 tenant 脈絡。工程師可以看到「失敗很多」，後續需要先補「誰受影響、哪個流程壞、該先修哪一段」的判讀資訊。這就是設計期缺欄位造成的診斷斷點。</p>
<h2 id="控制面">控制面</h2>
<p>Debuggability by design 的控制面是把診斷欄位納入設計審查與契約驗證。可診斷性若只靠事後補 log，會在每次新 API、新 workflow 或新 dependency 上重複遺漏。</p>
<ol>
<li>在 API design review 中檢查 request id、error code、idempotency 與 impact hint。</li>
<li>在 async workflow review 中檢查 message id、correlation、retry 與 DLQ reason。</li>
<li>在 dependency review 中檢查 timeout、deadline、fallback 與 upstream naming。</li>
<li>在 error model review 中檢查分類、內外部語意與 error chain。</li>
<li>在 contract testing 中驗證關鍵診斷欄位與錯誤語意。</li>
</ol>
<p>設計審查需要明確區分必填欄位與情境欄位。request id、trace context、error class 與 owner 通常是跨服務必填；idempotency key、DLQ reason、circuit state 則依 workflow 與依賴類型決定。</p>
<p>Contract testing 可以保護可診斷性。若 API 或 event schema 調整後移除了 correlation id、error code 或 retry metadata，測試應該阻擋這類破壞，因為它會讓事故判讀退回人工拼接。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Debuggability by design 的反模式是把診斷能力推遲到事故後。事故後補 log 可以修下一次，已發生事件的證據缺口則會留在復盤限制中。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>事後補 log</td>
          <td>每次事故才知道缺哪個欄位</td>
          <td>設計審查納入診斷欄位</td>
      </tr>
      <tr>
          <td>錯誤只回 500</td>
          <td>客戶、支援與 on-call 缺少分類</td>
          <td>建立穩定 error code 與 error class</td>
      </tr>
      <tr>
          <td>Async 缺 correlation</td>
          <td>DLQ 只有失敗數量，無業務脈絡</td>
          <td>message schema 保留因果欄位</td>
      </tr>
      <tr>
          <td>Dependency 黑箱</td>
          <td>timeout 只顯示本地錯誤</td>
          <td>adapter 統一 upstream 與 response class</td>
      </tr>
      <tr>
          <td>Diagnostic endpoint 無治理</td>
          <td>查詢有用但風險過高或無審計</td>
          <td>權限、遮罩、速率與 audit log</td>
      </tr>
  </tbody>
</table>
<p>事後補 log 的代價是已發生事故會留下復盤缺口。若缺少原始 request、tenant、message 或 dependency 欄位，工程師只能用間接推論重建時間線。</p>
<p>錯誤只回 500 會把所有問題導向同一條路由。validation、authorization、dependency、capacity 與 data consistency 的處置完全不同，錯誤模型應該支援這些分流。</p>
<p>Diagnostic endpoint 無治理會把可診斷性變成資安風險。診斷入口需要最小權限、資料遮罩、速率限制與 audit log，並且只提供事故判讀需要的 read-only 資訊。</p>
<h2 id="與語言教材的分工">與語言教材的分工</h2>
<p>Debuggability by design 位在 Backend 服務設計層。語言教材負責如何在特定 runtime 中傳遞 context、包裝 error、實作 middleware、處理 async local storage 或 goroutine context；本章負責定義跨語言都需要保留的診斷語意。</p>
<p>同步 runtime 的重點是 thread-local、connection pool 與 blocking dependency call 是否能保留 request context。async runtime 的重點是 task、promise、callback 與 queue boundary 是否能保留 trace context。goroutine 或 lightweight task runtime 的重點是廉價並發是否放大下游壓力，並且是否保留 deadline 與 cancellation。</p>
<p>不同語言可以用不同實作方式，但 API、async workflow、dependency call 與 error model 的診斷責任相同。這也是 Backend 章節保留跨語言抽象的理由。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.1 log schema：定義診斷欄位</li>
<li>04.3 tracing：保留跨服務 context</li>
<li>04.11 telemetry pipeline：確保診斷訊號能被採集</li>
<li>06.10 contract testing：把錯誤模型與外部契約納入驗證</li>
<li>08.18 incident intake：把設計期留下的診斷欄位轉成 evidence</li>
</ul>
]]></content:encoded></item><item><title>4.20 Observability Evidence Package</title><link>https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/</link><pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/observability-evidence-package/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>evidence package 的責任：把分散的 observability 資料包成可交給 reliability 與 incident response 的證據&lt;/li>
&lt;li>資料來源：log、metric、trace、audit log、dashboard、query、client-side signal、deployment event&lt;/li>
&lt;li>欄位：source、time range、owner、query link、data quality、confidence、known gap、retention&lt;/li>
&lt;li>跟 4.17 的關係：telemetry data quality 提供資料限制，evidence package 提供交接格式&lt;/li>
&lt;li>跟 6.23 的關係：可靠性驗證使用同一格式保存 experiment evidence&lt;/li>
&lt;li>跟 8.18 / 8.19 的關係：事故 intake 與 decision log 使用同一組 evidence link&lt;/li>
&lt;li>反模式：只貼 dashboard 截圖；query 沒有時間窗；evidence 沒標示 sampling / freshness 限制&lt;/li>
&lt;/ul>
&lt;p>Observability &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> 的核心是把可觀測資料從「查詢結果」升級成「可交接證據」。事故與驗證需要一組能說明來源、時間窗、可信度、限制與 owner 的 evidence。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Observability evidence package 是可觀測性模組交給可靠性驗證與事故處理的證據包，責任是讓 log、metric、trace 與 audit log 能被重用、回放與復盤。&lt;/p>
&lt;p>這一頁處理的是交接格式。4.17 &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 變成資料品質問題">Telemetry Data Quality&lt;/a> 說明資料是否可信；evidence package 說明如何把可信度、查詢入口與限制一起交給下游流程。&lt;/p>
&lt;p>證據包的價值在於保存判讀上下文。只有截圖時，讀者看不到 query、時間窗、sampling、資料延遲與 owner；有 evidence package 時，後續 release gate、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/incident-decision-log/" data-link-title="Incident Decision Log" data-link-desc="說明事故期間如何保留決策、證據、owner 與回退條件">incident decision log&lt;/a> 與 post-incident review 才能回放同一組事實。&lt;/p>
&lt;h2 id="evidence-欄位">Evidence 欄位&lt;/h2>
&lt;p>Evidence 欄位的責任是讓每個觀測證據都可查、可解釋、可追蹤。欄位不需要複雜，但要覆蓋事中判讀與事後復盤的最小需求。&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>Source&lt;/td>
 &lt;td>標示資料來源&lt;/td>
 &lt;td>區分 log、metric、trace、audit&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/time-range/" data-link-title="Time Range" data-link-desc="說明證據、查詢與事故判讀如何用時間窗保留可回放上下文">Time range&lt;/a>&lt;/td>
 &lt;td>標示查詢時間窗&lt;/td>
 &lt;td>對齊 incident timeline&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/query-link/" data-link-title="Query Link" data-link-desc="說明證據包如何保存可重跑查詢入口，而不是只保留截圖或口頭結論">Query link&lt;/a>&lt;/td>
 &lt;td>保留可重跑查詢&lt;/td>
 &lt;td>支援 handoff 與復盤&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Owner&lt;/td>
 &lt;td>指定可解釋資料的人&lt;/td>
 &lt;td>避免 evidence 失去語意&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/data-quality/" data-link-title="Data Quality" data-link-desc="說明證據欄位如何標示 completeness、freshness、sampling 與資料限制">Data quality&lt;/a>&lt;/td>
 &lt;td>標示 completeness / freshness&lt;/td>
 &lt;td>防止資料限制被誤讀&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/confidence/" data-link-title="Confidence" data-link-desc="說明證據包如何標示 confirmed、suspected 或 needs follow-up 的判讀信心">Confidence&lt;/a>&lt;/td>
 &lt;td>標示 confirmed / suspected&lt;/td>
 &lt;td>支援分級與決策&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/known-gap/" data-link-title="Known Gap" data-link-desc="說明證據包如何明確保存已知缺口，避免下游高估證據完整性">Known gap&lt;/a>&lt;/td>
 &lt;td>標示 missing signal 或 drift&lt;/td>
 &lt;td>回寫 04 readiness 與 data quality&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Retention&lt;/td>
 &lt;td>標示保存期限&lt;/td>
 &lt;td>支援 audit、PIR 與長事故&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Source 欄位讓讀者知道 evidence 的能力邊界。Metric 適合看趨勢，log 適合看事件細節，trace 適合看路徑，audit log 適合看責任鏈。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>evidence package 的責任：把分散的 observability 資料包成可交給 reliability 與 incident response 的證據</li>
<li>資料來源：log、metric、trace、audit log、dashboard、query、client-side signal、deployment event</li>
<li>欄位：source、time range、owner、query link、data quality、confidence、known gap、retention</li>
<li>跟 4.17 的關係：telemetry data quality 提供資料限制，evidence package 提供交接格式</li>
<li>跟 6.23 的關係：可靠性驗證使用同一格式保存 experiment evidence</li>
<li>跟 8.18 / 8.19 的關係：事故 intake 與 decision log 使用同一組 evidence link</li>
<li>反模式：只貼 dashboard 截圖；query 沒有時間窗；evidence 沒標示 sampling / freshness 限制</li>
</ul>
<p>Observability <a href="/blog/backend/knowledge-cards/evidence-package/" data-link-title="Evidence Package" data-link-desc="說明觀測、驗證與事故流程如何把證據包成可交接、可回放的 artifact">evidence package</a> 的核心是把可觀測資料從「查詢結果」升級成「可交接證據」。事故與驗證需要一組能說明來源、時間窗、可信度、限制與 owner 的 evidence。</p>
<h2 id="概念定位">概念定位</h2>
<p>Observability evidence package 是可觀測性模組交給可靠性驗證與事故處理的證據包，責任是讓 log、metric、trace 與 audit log 能被重用、回放與復盤。</p>
<p>這一頁處理的是交接格式。4.17 <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 變成資料品質問題">Telemetry Data Quality</a> 說明資料是否可信；evidence package 說明如何把可信度、查詢入口與限制一起交給下游流程。</p>
<p>證據包的價值在於保存判讀上下文。只有截圖時，讀者看不到 query、時間窗、sampling、資料延遲與 owner；有 evidence package 時，後續 release gate、<a href="/blog/backend/knowledge-cards/incident-decision-log/" data-link-title="Incident Decision Log" data-link-desc="說明事故期間如何保留決策、證據、owner 與回退條件">incident decision log</a> 與 post-incident review 才能回放同一組事實。</p>
<h2 id="evidence-欄位">Evidence 欄位</h2>
<p>Evidence 欄位的責任是讓每個觀測證據都可查、可解釋、可追蹤。欄位不需要複雜，但要覆蓋事中判讀與事後復盤的最小需求。</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>責任</th>
          <th>判讀用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>標示資料來源</td>
          <td>區分 log、metric、trace、audit</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/knowledge-cards/time-range/" data-link-title="Time Range" data-link-desc="說明證據、查詢與事故判讀如何用時間窗保留可回放上下文">Time range</a></td>
          <td>標示查詢時間窗</td>
          <td>對齊 incident timeline</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/knowledge-cards/query-link/" data-link-title="Query Link" data-link-desc="說明證據包如何保存可重跑查詢入口，而不是只保留截圖或口頭結論">Query link</a></td>
          <td>保留可重跑查詢</td>
          <td>支援 handoff 與復盤</td>
      </tr>
      <tr>
          <td>Owner</td>
          <td>指定可解釋資料的人</td>
          <td>避免 evidence 失去語意</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/knowledge-cards/data-quality/" data-link-title="Data Quality" data-link-desc="說明證據欄位如何標示 completeness、freshness、sampling 與資料限制">Data quality</a></td>
          <td>標示 completeness / freshness</td>
          <td>防止資料限制被誤讀</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/knowledge-cards/confidence/" data-link-title="Confidence" data-link-desc="說明證據包如何標示 confirmed、suspected 或 needs follow-up 的判讀信心">Confidence</a></td>
          <td>標示 confirmed / suspected</td>
          <td>支援分級與決策</td>
      </tr>
      <tr>
          <td><a href="/blog/backend/knowledge-cards/known-gap/" data-link-title="Known Gap" data-link-desc="說明證據包如何明確保存已知缺口，避免下游高估證據完整性">Known gap</a></td>
          <td>標示 missing signal 或 drift</td>
          <td>回寫 04 readiness 與 data quality</td>
      </tr>
      <tr>
          <td>Retention</td>
          <td>標示保存期限</td>
          <td>支援 audit、PIR 與長事故</td>
      </tr>
  </tbody>
</table>
<p>Source 欄位讓讀者知道 evidence 的能力邊界。Metric 適合看趨勢，log 適合看事件細節，trace 適合看路徑，audit log 適合看責任鏈。</p>
<p><a href="/blog/backend/knowledge-cards/time-range/" data-link-title="Time Range" data-link-desc="說明證據、查詢與事故判讀如何用時間窗保留可回放上下文">Time range</a> 是 evidence package 的基本欄位。事故前後 30 分鐘、部署期間、DR drill 時窗、<a href="/blog/backend/knowledge-cards/burn-rate/" data-link-title="Burn Rate" data-link-desc="說明 error budget 消耗速度如何支援告警與事故分級">burn rate</a> 短窗與長窗都需要明確，否則同一張圖可能被不同人解讀成不同結論。</p>
<p><a href="/blog/backend/knowledge-cards/query-link/" data-link-title="Query Link" data-link-desc="說明證據包如何保存可重跑查詢入口，而不是只保留截圖或口頭結論">Query link</a> 比截圖更重要。截圖適合溝通當下狀態，query link 才能讓下一班 on-call、可靠性 owner 或 PIR reviewer 重跑同一個判讀。</p>
<p><a href="/blog/backend/knowledge-cards/data-quality/" data-link-title="Data Quality" data-link-desc="說明證據欄位如何標示 completeness、freshness、sampling 與資料限制">Data quality</a> 欄位讓 evidence 保留限制。sampling ratio、ingest delay、schema drift、log drop、cardinality truncation 與 timestamp skew 都應直接出現在證據包中。</p>
<h2 id="資料來源">資料來源</h2>
<p>Evidence package 的資料來源要按判讀責任分層。每一層回答的問題不同，下游使用時也要保留這個差異。</p>
<table>
  <thead>
      <tr>
          <th>資料來源</th>
          <th>回答問題</th>
          <th>常見限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Log</td>
          <td>單一事件發生了什麼</td>
          <td>schema drift、drop、PII masking</td>
      </tr>
      <tr>
          <td>Metric</td>
          <td>趨勢是否偏離穩態</td>
          <td>聚合粒度、cardinality、延遲</td>
      </tr>
      <tr>
          <td>Trace</td>
          <td>失效卡在哪個服務或依賴邊界</td>
          <td>sampling、async 斷鏈</td>
      </tr>
      <tr>
          <td>Audit log</td>
          <td>高風險操作與責任鏈如何形成</td>
          <td>權限限制、retention、法規要求</td>
      </tr>
      <tr>
          <td>Dashboard</td>
          <td>操作視角如何快速判讀</td>
          <td>面板版本、查詢成本、owner</td>
      </tr>
      <tr>
          <td>Client-side signal</td>
          <td>使用者感知是否和 server 一致</td>
          <td>browser / region / device bias</td>
      </tr>
      <tr>
          <td>Deployment event</td>
          <td>近期變更是否與異常時間線重疊</td>
          <td>rollout 粒度、feature flag owner</td>
      </tr>
  </tbody>
</table>
<p>Log evidence 適合進入 incident intake。它要保留 request id、tenant、region、error class 與 trace id，讓事故候選能被查證。</p>
<p>Metric evidence 適合進入 SLO、release gate 與 <a href="/blog/backend/knowledge-cards/steady-state/" data-link-title="Steady State" data-link-desc="說明可靠性實驗與事故恢復如何定義系統應維持的可接受狀態">steady state</a> 判讀。它要保留時間窗、分母分子、聚合粒度與資料延遲，讓 burn rate 與容量判斷可回放。</p>
<p>Trace evidence 適合支援 dependency 與 async workflow 判讀。它要標示 sampling policy 與缺失 span，讓下游知道 trace 能支持到哪個邊界。</p>
<p>Audit log evidence 適合支援資安、資料修復與高風險操作。它要保留 access path、retention、masking 與 chain of custody 限制。</p>
<h2 id="打包流程">打包流程</h2>
<p>Evidence package 的打包流程是從問題開始。先問下游要做什麼決策，再選擇足以支援該決策的資料與工具入口。</p>
<ol>
<li>定義 evidence 要支援的決策：readiness、release gate、incident intake、decision log 或 PIR。</li>
<li>選擇最小資料集合：metric 看趨勢、log 看事件、trace 看路徑、audit 看責任。</li>
<li>補上 time range、query link、owner 與 data quality。</li>
<li>標示 confidence 與 known gap。</li>
<li>把缺口回寫到 4.16 readiness、4.17 data quality 或 4.18 operating model。</li>
</ol>
<p>Readiness 用的 evidence package 要回答「服務是否能被判讀」。它重視核心旅程、依賴、dashboard、alert、trace 與 owner。</p>
<p>Reliability 用的 evidence package 要回答「驗證是否有結果」。它重視 steady state、stop condition、experiment timeline、SLO burn 與回復訊號。</p>
<p>Incident 用的 evidence package 要回答「事故是否需要啟動、升級或回退」。它重視 source、impact scope、confidence、decision log 與 stakeholder update。</p>
<p>資料庫 migration 用的 evidence package 要回答「資料語意是否能進入下一階段」。它重視 <a href="/blog/backend/knowledge-cards/validation-query/" data-link-title="Validation Query" data-link-desc="說明遷移、回填與修復期間如何用查詢證明資料語意是否一致">validation query</a>、row count、mismatch sample、replication lag、slow query 與資料限制；完整服務路徑可接到 <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>
<h2 id="案例中的證據包判讀">案例中的證據包判讀</h2>
<p>證據包的價值要放回真實事故才看得清楚。Cloudflare 2019 與 AWS S3 2017 都不是「缺資料」，而是「資料若沒被包成可交接證據，決策會慢、通訊會亂、回寫會斷」。</p>
<p>Cloudflare 2019 的第一波判讀來自跨區 CPU、5xx 與 latency 同步惡化。這組訊號如果只有圖表截圖，團隊只能知道「全網變慢」；把 query link、time range、rule rollout event 與 confidence 一起交接，才能快速形成「先回滾規則」的決策。</p>
<p>AWS S3 2017 的關鍵是恢復分層：GET/LIST/DELETE 與 PUT 回線時間不同，且狀態頁通訊入口也受依賴影響。證據包若保留 subsystem 狀態、操作類型影響範圍與已知限制，對外更新才不會把「部分恢復」誤寫成「全面恢復」。</p>
<p>兩個案例共同指向同一個判讀原則：證據包要保留「能支持當下決策」的最小閉環，蒐集越多越好的思路反而製造噪音，至少包含事件時間窗、跨訊號對位、資料限制與決策責任人。</p>
<h2 id="誤判風險與修正路徑">誤判風險與修正路徑</h2>
<p>事故中的誤判多半源自證據包缺少判讀上下文，演算法本身很少是問題。當 evidence 只有結論沒有限制，下游就會把暫時訊號當成穩定事實。</p>
<table>
  <thead>
      <tr>
          <th>誤判場景</th>
          <th>為何會誤判</th>
          <th>修正路徑</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>圖表短暫回穩就宣告恢復</td>
          <td>缺少時間窗與回線連續性門檻</td>
          <td>在 evidence 補 recovery window 與 steady state 對位</td>
      </tr>
      <tr>
          <td>trace 看起來正常</td>
          <td>缺 sampling ratio 與 missing span</td>
          <td>在 evidence 補 data quality 與 known gap</td>
      </tr>
      <tr>
          <td>對外說法過度樂觀</td>
          <td>缺 subsystem 分層狀態與限制說明</td>
          <td>在 evidence 補 scope / limitation / next update</td>
      </tr>
      <tr>
          <td>回滾決策反覆</td>
          <td>缺 deployment event 與影響範圍對位</td>
          <td>在 evidence 補 rollout event、impact scope 與 owner</td>
      </tr>
      <tr>
          <td>復盤找不到依據</td>
          <td>只留截圖，沒有 query 與時間窗</td>
          <td>在 evidence 補 query link 與 retention</td>
      </tr>
  </tbody>
</table>
<p>修正路徑的核心是把 evidence package 當成事故中的工作物，而不是事故後整理物。當下有完整欄位，後續 8.19 決策紀錄才有可回放證據，8.22 回寫才有可追蹤缺口。</p>
<h2 id="常見反模式">常見反模式</h2>
<p>Evidence package 的反模式通常來自把資料貼出來就當作證據交接。證據需要上下文，否則只是一段輸出。</p>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只貼 dashboard 截圖</td>
          <td>事後缺少可重跑查詢</td>
          <td>保留 query link 與 time range</td>
      </tr>
      <tr>
          <td>Query 無時間窗</td>
          <td>同一查詢不同時間跑出不同結論</td>
          <td>標準化 time range</td>
      </tr>
      <tr>
          <td>缺資料品質限制</td>
          <td>sampling / drop / delay 被忽略</td>
          <td>引用 4.17 data quality 欄位</td>
      </tr>
      <tr>
          <td>Evidence 無 owner</td>
          <td>下游無人能解釋欄位語意</td>
          <td>指定 service / platform owner</td>
      </tr>
      <tr>
          <td>Retention 未標示</td>
          <td>PIR 或 audit 時證據已過期</td>
          <td>標示 retention 與保存責任</td>
      </tr>
  </tbody>
</table>
<p>只貼 dashboard 截圖會讓 evidence 失去可回放性。截圖可以當摘要，query、時間窗與資料限制則提供復盤與交接能力。</p>
<p>缺資料品質限制會讓下游高估證據。若 trace sampling 只保留 10%、log pipeline 有 drop、metric 有 ingest delay，這些限制要跟證據一起交接。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>4.16 observability readiness：補 evidence package 所需的訊號入口</li>
<li>4.17 telemetry data quality：標示 completeness、freshness、drift 與 sampling 限制</li>
<li>4.18 operating model：指定 evidence owner、retention 與 review cadence</li>
<li>1.7 Schema Migration Rollout 證據：把 validation query 與資料限制包成 migration gate 可用的證據</li>
<li>6.23 verification evidence handoff：把驗證結果包成同一格式</li>
<li>8.18 incident intake：把 evidence package 轉成事故候選</li>
<li>8.19 incident decision log：把 evidence package 連到事中決策</li>
</ul>
]]></content:encoded></item><item><title>4.21 Rule-level CPU Signal Governance</title><link>https://tarrragon.github.io/blog/backend/04-observability/rule-level-cpu-signal-governance/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/rule-level-cpu-signal-governance/</guid><description>&lt;p>Rule-level CPU signal governance 的核心責任是讓規則與策略執行成本可被提前判讀，避免高成本規則在全域 rollout 後才以 5xx 與 latency 形式被動暴露。&lt;/p>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>Rule-level CPU signal governance 是把「哪一條規則在吃 CPU」變成可量測、可回退、可治理的觀測能力，責任是補上服務級 CPU 指標看不到的規則層風險。&lt;/p>
&lt;p>服務級 CPU 只告訴團隊「系統變慢了」，rule-level 訊號才告訴團隊「是哪個規則讓系統變慢」。兩者一起存在，事故才能從症狀快速收斂到可操作原因。&lt;/p>
&lt;h2 id="核心判讀">核心判讀&lt;/h2>
&lt;p>判讀順序是先看服務級異常，再下鑽到規則層成本分佈。若 CPU、latency、5xx 同步惡化，且 rule hit 分佈在短時間發生偏移，通常代表規則層出現新的成本熱點。&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>Rule hit rate 突增&lt;/td>
 &lt;td>某規則命中流量異常放大&lt;/td>
 &lt;td>先核對最近規則推送與 traffic pattern&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rule-level CPU p95 / p99 上升&lt;/td>
 &lt;td>規則執行成本惡化&lt;/td>
 &lt;td>先降級或回退高成本規則&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>CPU hotspot 只集中在少數規則&lt;/td>
 &lt;td>問題可收斂到有限規則集合&lt;/td>
 &lt;td>優先處理 top-N 規則&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>回退後 rule-level 成本快速回穩&lt;/td>
 &lt;td>異常與新規則高度關聯&lt;/td>
 &lt;td>凍結同批 rollout，進入 replay 驗證&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Rule trace 缺失&lt;/td>
 &lt;td>無法確認成本來自哪個分支與 payload&lt;/td>
 &lt;td>先補埋點再擴大 rollout&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="訊號模型">訊號模型&lt;/h2>
&lt;p>Rule-level CPU 訊號模型的重點是同時保留成本、命中與上下文。只有成本沒有命中，無法判斷影響面；只有命中沒有成本，無法判斷風險等級。&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>rule_id / rule_version&lt;/td>
 &lt;td>對應具體規則版本&lt;/td>
 &lt;td>規則改版未更新版本標記&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>match_count&lt;/td>
 &lt;td>量測命中流量&lt;/td>
 &lt;td>未按 tenant / region 分層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>exec_cpu_ms&lt;/td>
 &lt;td>量測規則執行成本&lt;/td>
 &lt;td>只看平均值，忽略長尾&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>input_class&lt;/td>
 &lt;td>區分 payload 類型與風險來源&lt;/td>
 &lt;td>缺少分類導致 replay 不可重現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rollout_stage&lt;/td>
 &lt;td>對齊分批 rollout 狀態&lt;/td>
 &lt;td>觀測資料無法對應 rollout 階段&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>fallback_action&lt;/td>
 &lt;td>記錄降級、旁路或阻擋策略是否觸發&lt;/td>
 &lt;td>事故後難以回放決策&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="控制面">控制面&lt;/h2>
&lt;p>Rule-level CPU signal governance 的控制面是把「測到異常後要怎麼停」直接接到 rollout 流程，而不是只做監控展示。&lt;/p>
&lt;ol>
&lt;li>對高風險規則建立 rule-level CPU baseline 與異常門檻。&lt;/li>
&lt;li>把 rule-level 訊號接到 staged rollout gate。&lt;/li>
&lt;li>對 top-N 高成本規則建立自動降級或回退條件。&lt;/li>
&lt;li>在 evidence package 記錄當次 rollout 的 rule-level 成本分佈與限制。&lt;/li>
&lt;li>在 post-incident review 回寫新 payload 類型與新風險樣式。&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>只看服務級 CPU&lt;/td>
 &lt;td>知道有問題但找不到高成本規則&lt;/td>
 &lt;td>補 rule_id / version / cost 埋點&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>規則測試只跑功能正確&lt;/td>
 &lt;td>事故時才看見計算成本爆點&lt;/td>
 &lt;td>增加 representative payload replay&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>rollout 與觀測脫鉤&lt;/td>
 &lt;td>分批推送但缺乏階段判讀依據&lt;/td>
 &lt;td>把 rollout_stage 變成必填訊號欄位&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>回退無證據包&lt;/td>
 &lt;td>復盤只剩結論，缺成本時間線&lt;/td>
 &lt;td>接 4.20 evidence package&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/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 規則推送安全閘門&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Cloudflare 2019 事故顯示高成本 regex 可以在全網同步推送下快速放大。Rule-level CPU 訊號治理的價值是把這類風險前移到 rollout 過程，而不是等到全球 5xx 才回頭排查。&lt;/p></description><content:encoded><![CDATA[<p>Rule-level CPU signal governance 的核心責任是讓規則與策略執行成本可被提前判讀，避免高成本規則在全域 rollout 後才以 5xx 與 latency 形式被動暴露。</p>
<h2 id="概念定位">概念定位</h2>
<p>Rule-level CPU signal governance 是把「哪一條規則在吃 CPU」變成可量測、可回退、可治理的觀測能力，責任是補上服務級 CPU 指標看不到的規則層風險。</p>
<p>服務級 CPU 只告訴團隊「系統變慢了」，rule-level 訊號才告訴團隊「是哪個規則讓系統變慢」。兩者一起存在，事故才能從症狀快速收斂到可操作原因。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀順序是先看服務級異常，再下鑽到規則層成本分佈。若 CPU、latency、5xx 同步惡化，且 rule hit 分佈在短時間發生偏移，通常代表規則層出現新的成本熱點。</p>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>代表意義</th>
          <th>第一波決策價值</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Rule hit rate 突增</td>
          <td>某規則命中流量異常放大</td>
          <td>先核對最近規則推送與 traffic pattern</td>
      </tr>
      <tr>
          <td>Rule-level CPU p95 / p99 上升</td>
          <td>規則執行成本惡化</td>
          <td>先降級或回退高成本規則</td>
      </tr>
      <tr>
          <td>CPU hotspot 只集中在少數規則</td>
          <td>問題可收斂到有限規則集合</td>
          <td>優先處理 top-N 規則</td>
      </tr>
      <tr>
          <td>回退後 rule-level 成本快速回穩</td>
          <td>異常與新規則高度關聯</td>
          <td>凍結同批 rollout，進入 replay 驗證</td>
      </tr>
      <tr>
          <td>Rule trace 缺失</td>
          <td>無法確認成本來自哪個分支與 payload</td>
          <td>先補埋點再擴大 rollout</td>
      </tr>
  </tbody>
</table>
<h2 id="訊號模型">訊號模型</h2>
<p>Rule-level CPU 訊號模型的重點是同時保留成本、命中與上下文。只有成本沒有命中，無法判斷影響面；只有命中沒有成本，無法判斷風險等級。</p>
<table>
  <thead>
      <tr>
          <th>訊號欄位</th>
          <th>用途</th>
          <th>常見陷阱</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>rule_id / rule_version</td>
          <td>對應具體規則版本</td>
          <td>規則改版未更新版本標記</td>
      </tr>
      <tr>
          <td>match_count</td>
          <td>量測命中流量</td>
          <td>未按 tenant / region 分層</td>
      </tr>
      <tr>
          <td>exec_cpu_ms</td>
          <td>量測規則執行成本</td>
          <td>只看平均值，忽略長尾</td>
      </tr>
      <tr>
          <td>input_class</td>
          <td>區分 payload 類型與風險來源</td>
          <td>缺少分類導致 replay 不可重現</td>
      </tr>
      <tr>
          <td>rollout_stage</td>
          <td>對齊分批 rollout 狀態</td>
          <td>觀測資料無法對應 rollout 階段</td>
      </tr>
      <tr>
          <td>fallback_action</td>
          <td>記錄降級、旁路或阻擋策略是否觸發</td>
          <td>事故後難以回放決策</td>
      </tr>
  </tbody>
</table>
<h2 id="控制面">控制面</h2>
<p>Rule-level CPU signal governance 的控制面是把「測到異常後要怎麼停」直接接到 rollout 流程，而不是只做監控展示。</p>
<ol>
<li>對高風險規則建立 rule-level CPU baseline 與異常門檻。</li>
<li>把 rule-level 訊號接到 staged rollout gate。</li>
<li>對 top-N 高成本規則建立自動降級或回退條件。</li>
<li>在 evidence package 記錄當次 rollout 的 rule-level 成本分佈與限制。</li>
<li>在 post-incident review 回寫新 payload 類型與新風險樣式。</li>
</ol>
<h2 id="常見反模式">常見反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>只看服務級 CPU</td>
          <td>知道有問題但找不到高成本規則</td>
          <td>補 rule_id / version / cost 埋點</td>
      </tr>
      <tr>
          <td>規則測試只跑功能正確</td>
          <td>事故時才看見計算成本爆點</td>
          <td>增加 representative payload replay</td>
      </tr>
      <tr>
          <td>rollout 與觀測脫鉤</td>
          <td>分批推送但缺乏階段判讀依據</td>
          <td>把 rollout_stage 變成必填訊號欄位</td>
      </tr>
      <tr>
          <td>回退無證據包</td>
          <td>復盤只剩結論，缺成本時間線</td>
          <td>接 4.20 evidence package</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/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">6.24 規則推送安全閘門</a></li>
</ul>
<p>Cloudflare 2019 事故顯示高成本 regex 可以在全網同步推送下快速放大。Rule-level CPU 訊號治理的價值是把這類風險前移到 rollout 過程，而不是等到全球 5xx 才回頭排查。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li>04.17： <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 變成資料品質問題">Telemetry Data Quality</a></li>
<li>04.20： <a href="/blog/backend/04-observability/observability-evidence-package/" data-link-title="4.20 Observability Evidence Package" data-link-desc="把 log、metric、trace、audit 與資料品質限制包成可交接證據">Observability Evidence Package</a></li>
<li>06.24： <a href="/blog/backend/06-reliability/rule-rollout-safety-gate/" data-link-title="6.24 規則推送安全閘門" data-link-desc="把規則、策略與控制面配置推送從部署步驟升級為可靠性 gate，避免小變更在秒級擴散成全網事故。">Rule Rollout Safety Gate</a></li>
<li>08.19： <a href="/blog/backend/08-incident-response/incident-decision-log/" data-link-title="8.19 Incident Decision Log" data-link-desc="把事中假設、決策、證據、回退條件與責任人留下可復盤紀錄">Incident Decision Log</a></li>
</ul>
]]></content:encoded></item><item><title>4.22 Checkout API Evidence Package 實作示範</title><link>https://tarrragon.github.io/blog/backend/04-observability/checkout-api-evidence-package/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/checkout-api-evidence-package/</guid><description>&lt;p>Checkout API evidence package 的核心責任是把同一條交易路徑的訊號整理成可交接證據，讓放行與事故判斷用到同一組事實。&lt;/p>
&lt;h2 id="服務路徑與邊界">服務路徑與邊界&lt;/h2>
&lt;p>本篇服務路徑是 &lt;code>client -&amp;gt; checkout-api -&amp;gt; payment-adapter -&amp;gt; order-db&lt;/code>。觀測邊界只處理「這條路徑目前是否可判讀」，不處理重試策略與回退決策本身；後者交給 06 與 08。&lt;/p>
&lt;p>要先定義 evidence package 的最小欄位：&lt;code>Source&lt;/code>、&lt;code>Time range&lt;/code>、&lt;code>Query link&lt;/code>、&lt;code>Owner&lt;/code>、&lt;code>Data quality&lt;/code>、&lt;code>Confidence&lt;/code>、&lt;code>Known gap&lt;/code>。這些欄位在事故期與放行期共用，避免兩套語言。&lt;/p>
&lt;h2 id="實作步驟">實作步驟&lt;/h2>
&lt;ol>
&lt;li>固定交易路徑的觀測主鍵：&lt;code>trace_id&lt;/code>、&lt;code>order_id&lt;/code>、&lt;code>tenant_id&lt;/code>、&lt;code>region&lt;/code>。&lt;/li>
&lt;li>建立三組查詢入口：延遲分布（p50/p95/p99）、錯誤率與錯誤類別、下游 payment dependency timeout。&lt;/li>
&lt;li>為每組查詢補欄位：時間窗、資料延遲、採樣比例、目前 owner。&lt;/li>
&lt;li>在 deploy 前把同一份 evidence package 連到 &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>事故期間把同一份 evidence package 連到 &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;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>p95 latency 升高但 error rate 無明顯變化&lt;/td>
 &lt;td>可能是下游慢查詢或連線池飽和&lt;/td>
 &lt;td>先查 dependency span 與 DB wait&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>payment timeout 增加且 trace 斷在 adapter&lt;/td>
 &lt;td>下游依賴退化，不是本地 CPU 飽和&lt;/td>
 &lt;td>進 6.8 依賴風險 gate，限制放行&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>log 有錯誤但 metric 沒反映&lt;/td>
 &lt;td>訊號覆蓋不一致或聚合粒度不對&lt;/td>
 &lt;td>回寫 data quality，補 query 與聚合維度&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>dashboard 正常但客訴增加&lt;/td>
 &lt;td>可觀測性盲區或取樣偏差&lt;/td>
 &lt;td>提升 client-side signal 權重並標示 known gap&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同版不同區域行為差異大&lt;/td>
 &lt;td>區域配置或依賴拓樸差異，非單點程式回歸&lt;/td>
 &lt;td>補 region 維度 evidence，進 8.18 分流 triage&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="常見誤區">常見誤區&lt;/h2>
&lt;p>把 evidence package 寫成 dashboard 截圖集合，會失去可重跑性。沒有 query link 與時間窗，事故交班時很難重建判讀脈絡。&lt;/p>
&lt;p>把 confidence 省略也會導致誤判。事故前期資料常不完整，若不標示 &lt;code>suspected&lt;/code> 與 &lt;code>known gap&lt;/code>，下游決策容易把猜測當成結論。&lt;/p>
&lt;h2 id="案例回寫">案例回寫&lt;/h2>
&lt;p>這條路徑可用 &lt;a href="https://tarrragon.github.io/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">GCP 2019 Network Incident&lt;/a> 回寫。先看跨服務訊號如何失真，再回到本章檢查欄位是否能支撐「先分流、再判斷」。&lt;/p>
&lt;p>這個案例主要支撐的是「證據欄位完整度」判讀，不直接支撐 release gate 停損門檻設計；停損規則要回到 6.8。&lt;/p>
&lt;h2 id="跨模組路由">跨模組路由&lt;/h2>
&lt;ol>
&lt;li>與 4.17 的交接：資料限制與偏差回到 &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 變成資料品質問題">Telemetry Data Quality&lt;/a>。&lt;/li>
&lt;li>與 6.8 的交接：放行判斷使用同一份 evidence package。&lt;/li>
&lt;li>與 6.23 的交接：驗證證據欄位對齊 &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 可用證據">Verification Evidence Handoff&lt;/a>。&lt;/li>
&lt;li>與 8.19 的交接：事故決策直接引用 evidence link 與 confidence。&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;p>要把證據轉成放行條件，接著讀 &lt;a href="https://tarrragon.github.io/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 實作示範&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Checkout API evidence package 的核心責任是把同一條交易路徑的訊號整理成可交接證據，讓放行與事故判斷用到同一組事實。</p>
<h2 id="服務路徑與邊界">服務路徑與邊界</h2>
<p>本篇服務路徑是 <code>client -&gt; checkout-api -&gt; payment-adapter -&gt; order-db</code>。觀測邊界只處理「這條路徑目前是否可判讀」，不處理重試策略與回退決策本身；後者交給 06 與 08。</p>
<p>要先定義 evidence package 的最小欄位：<code>Source</code>、<code>Time range</code>、<code>Query link</code>、<code>Owner</code>、<code>Data quality</code>、<code>Confidence</code>、<code>Known gap</code>。這些欄位在事故期與放行期共用，避免兩套語言。</p>
<h2 id="實作步驟">實作步驟</h2>
<ol>
<li>固定交易路徑的觀測主鍵：<code>trace_id</code>、<code>order_id</code>、<code>tenant_id</code>、<code>region</code>。</li>
<li>建立三組查詢入口：延遲分布（p50/p95/p99）、錯誤率與錯誤類別、下游 payment dependency timeout。</li>
<li>為每組查詢補欄位：時間窗、資料延遲、採樣比例、目前 owner。</li>
<li>在 deploy 前把同一份 evidence package 連到 <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>事故期間把同一份 evidence package 連到 <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>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀重點</th>
          <th>對應動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>p95 latency 升高但 error rate 無明顯變化</td>
          <td>可能是下游慢查詢或連線池飽和</td>
          <td>先查 dependency span 與 DB wait</td>
      </tr>
      <tr>
          <td>payment timeout 增加且 trace 斷在 adapter</td>
          <td>下游依賴退化，不是本地 CPU 飽和</td>
          <td>進 6.8 依賴風險 gate，限制放行</td>
      </tr>
      <tr>
          <td>log 有錯誤但 metric 沒反映</td>
          <td>訊號覆蓋不一致或聚合粒度不對</td>
          <td>回寫 data quality，補 query 與聚合維度</td>
      </tr>
      <tr>
          <td>dashboard 正常但客訴增加</td>
          <td>可觀測性盲區或取樣偏差</td>
          <td>提升 client-side signal 權重並標示 known gap</td>
      </tr>
      <tr>
          <td>同版不同區域行為差異大</td>
          <td>區域配置或依賴拓樸差異，非單點程式回歸</td>
          <td>補 region 維度 evidence，進 8.18 分流 triage</td>
      </tr>
  </tbody>
</table>
<h2 id="常見誤區">常見誤區</h2>
<p>把 evidence package 寫成 dashboard 截圖集合，會失去可重跑性。沒有 query link 與時間窗，事故交班時很難重建判讀脈絡。</p>
<p>把 confidence 省略也會導致誤判。事故前期資料常不完整，若不標示 <code>suspected</code> 與 <code>known gap</code>，下游決策容易把猜測當成結論。</p>
<h2 id="案例回寫">案例回寫</h2>
<p>這條路徑可用 <a href="/blog/backend/08-incident-response/cases/gcp/2019-us-network-congestion-multi-service-incident/" data-link-title="GCP 2019 US Network Congestion Multi-service Incident" data-link-desc="2019-06-02 Google Cloud 因美國區域網路壅塞造成多服務退化的事故解析：跨產品依賴、流量控制與區域隔離判讀。">GCP 2019 Network Incident</a> 回寫。先看跨服務訊號如何失真，再回到本章檢查欄位是否能支撐「先分流、再判斷」。</p>
<p>這個案例主要支撐的是「證據欄位完整度」判讀，不直接支撐 release gate 停損門檻設計；停損規則要回到 6.8。</p>
<h2 id="跨模組路由">跨模組路由</h2>
<ol>
<li>與 4.17 的交接：資料限制與偏差回到 <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 變成資料品質問題">Telemetry Data Quality</a>。</li>
<li>與 6.8 的交接：放行判斷使用同一份 evidence package。</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 的交接：事故決策直接引用 evidence link 與 confidence。</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<p>要把證據轉成放行條件，接著讀 <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>。</p>
]]></content:encoded></item><item><title>4.23 觀測查詢設計</title><link>https://tarrragon.github.io/blog/backend/04-observability/observability-query-design/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/observability-query-design/</guid><description>&lt;h2 id="大綱">大綱&lt;/h2>
&lt;ul>
&lt;li>觀測資料的讀寫不對稱：一種寫入路徑對應多種讀取路徑&lt;/li>
&lt;li>三種查詢模式：即席診斷、聚合趨勢、鑑識回溯&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">Storage tiering&lt;/a> 與查詢路由：hot / warm / cold 不只是成本分層、是查詢能力分層&lt;/li>
&lt;li>Pre-aggregation 策略：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/materialized-view/" data-link-title="Materialized View" data-link-desc="說明預先計算並儲存查詢結果以加速讀取的資料結構">materialized view&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup&lt;/a> 的使用情境與維護成本&lt;/li>
&lt;li>Query 資源治理：priority、queue 分離、timeout 差異化、cost estimation&lt;/li>
&lt;li>觀測領域的讀寫分離：&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/cqrs/" data-link-title="CQRS" data-link-desc="說明讀寫不對稱時為何需要分離查詢與寫入責任、分離的判準與代價">CQRS&lt;/a> 的特化應用&lt;/li>
&lt;li>反模式：把 raw log 當 OLAP 查、dashboard 查詢直打 raw storage 無 pre-aggregation、recording rule 跟 raw query 重複計算&lt;/li>
&lt;/ul>
&lt;h2 id="概念定位">概念定位&lt;/h2>
&lt;p>觀測查詢設計是把「產生訊號之後怎麼被讀取」當成獨立的系統設計問題。觀測資料的寫入路徑（agent → collector → ingest → storage）在 &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> 處理；本章處理的是讀取路徑 — 從 storage 經 query engine 到 dashboard、alert 與即席查詢的資料流。&lt;/p>
&lt;p>寫入路徑的設計目標是吞吐穩定、schema 一致、成本可控；讀取路徑的設計目標是在不同的時間壓力下，用對的精度取回對的切面。兩者的效能瓶頸不同、擴展方向不同、治理責任也不同。把讀取當寫入的附屬處理，會在流量成長後遇到「寫入正常但查詢崩潰」的局面。&lt;/p>
&lt;h2 id="觀測資料的讀寫不對稱">觀測資料的讀寫不對稱&lt;/h2>
&lt;p>觀測資料有一個 application data 不常見的特性：同一份資料被多種完全不同的查詢形狀讀取，每種查詢的時間壓力、精度需求、結果形狀差距可以到三個數量級。&lt;/p>
&lt;p>寫入面相對單純。不管是 log、metric 還是 trace，寫入都是 append-only、schema 由產生端定義、吞吐由流量決定。寫入路徑的設計問題集中在 cardinality 控制（&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7&lt;/a>）、pipeline 可靠性（&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>）與 sampling 策略。&lt;/p>
&lt;p>讀取面則至少有三種模式，各自有獨立的 SLA、索引需求與資源消耗模型。把三種模式混在同一個未分化的 query engine 裡，會在任何一種模式的負載增長時拖累其他模式。&lt;/p>
&lt;h2 id="三種查詢模式">三種查詢模式&lt;/h2>
&lt;h3 id="即席診斷">即席診斷&lt;/h3>
&lt;p>事故中的查詢，責任是在秒級內定位問題。&lt;/p>
&lt;p>查詢形狀是精確 filter + 短時間範圍：拿一個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id&lt;/a> 查關聯事件、拿一個 error code 加 time window 撈錯誤樣本、拿一個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id&lt;/a> 展開完整 span tree。&lt;/p>
&lt;p>對儲存的要求：需要 hot tier 的完整索引、完整精度、毫秒到秒級回應。即席查詢幾乎不命中 warm 或 cold tier — 事故通常發生在「現在」或「剛才」。&lt;/p>
&lt;p>資源特性：低頻（事故時才有）、單次掃描量小、但延遲要求最嚴格。事故中的每一秒等待都在消耗 MTTR。&lt;/p>
&lt;h3 id="聚合趨勢">聚合趨勢&lt;/h3>
&lt;p>Dashboard 跟 alert rule 的查詢，責任是提供持續的服務健康視圖。&lt;/p></description><content:encoded><![CDATA[<h2 id="大綱">大綱</h2>
<ul>
<li>觀測資料的讀寫不對稱：一種寫入路徑對應多種讀取路徑</li>
<li>三種查詢模式：即席診斷、聚合趨勢、鑑識回溯</li>
<li><a href="/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">Storage tiering</a> 與查詢路由：hot / warm / cold 不只是成本分層、是查詢能力分層</li>
<li>Pre-aggregation 策略：<a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule</a>、<a href="/blog/backend/knowledge-cards/materialized-view/" data-link-title="Materialized View" data-link-desc="說明預先計算並儲存查詢結果以加速讀取的資料結構">materialized view</a>、<a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup</a> 的使用情境與維護成本</li>
<li>Query 資源治理：priority、queue 分離、timeout 差異化、cost estimation</li>
<li>觀測領域的讀寫分離：<a href="/blog/backend/knowledge-cards/cqrs/" data-link-title="CQRS" data-link-desc="說明讀寫不對稱時為何需要分離查詢與寫入責任、分離的判準與代價">CQRS</a> 的特化應用</li>
<li>反模式：把 raw log 當 OLAP 查、dashboard 查詢直打 raw storage 無 pre-aggregation、recording rule 跟 raw query 重複計算</li>
</ul>
<h2 id="概念定位">概念定位</h2>
<p>觀測查詢設計是把「產生訊號之後怎麼被讀取」當成獨立的系統設計問題。觀測資料的寫入路徑（agent → collector → ingest → storage）在 <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> 處理；本章處理的是讀取路徑 — 從 storage 經 query engine 到 dashboard、alert 與即席查詢的資料流。</p>
<p>寫入路徑的設計目標是吞吐穩定、schema 一致、成本可控；讀取路徑的設計目標是在不同的時間壓力下，用對的精度取回對的切面。兩者的效能瓶頸不同、擴展方向不同、治理責任也不同。把讀取當寫入的附屬處理，會在流量成長後遇到「寫入正常但查詢崩潰」的局面。</p>
<h2 id="觀測資料的讀寫不對稱">觀測資料的讀寫不對稱</h2>
<p>觀測資料有一個 application data 不常見的特性：同一份資料被多種完全不同的查詢形狀讀取，每種查詢的時間壓力、精度需求、結果形狀差距可以到三個數量級。</p>
<p>寫入面相對單純。不管是 log、metric 還是 trace，寫入都是 append-only、schema 由產生端定義、吞吐由流量決定。寫入路徑的設計問題集中在 cardinality 控制（<a href="/blog/backend/04-observability/cardinality-cost-governance/" data-link-title="4.7 Cardinality 治理與成本邊界" data-link-desc="把 metric / log / trace 的 cardinality 與成本作為平台一級治理議題">4.7</a>）、pipeline 可靠性（<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>）與 sampling 策略。</p>
<p>讀取面則至少有三種模式，各自有獨立的 SLA、索引需求與資源消耗模型。把三種模式混在同一個未分化的 query engine 裡，會在任何一種模式的負載增長時拖累其他模式。</p>
<h2 id="三種查詢模式">三種查詢模式</h2>
<h3 id="即席診斷">即席診斷</h3>
<p>事故中的查詢，責任是在秒級內定位問題。</p>
<p>查詢形狀是精確 filter + 短時間範圍：拿一個 <a href="/blog/backend/knowledge-cards/request-id/" data-link-title="Request ID" data-link-desc="說明單次 request 的識別碼如何支援 log 搜尋與問題定位">request id</a> 查關聯事件、拿一個 error code 加 time window 撈錯誤樣本、拿一個 <a href="/blog/backend/knowledge-cards/trace-id/" data-link-title="Trace ID" data-link-desc="說明分散式追蹤中同一條呼叫路徑的識別碼">trace id</a> 展開完整 span tree。</p>
<p>對儲存的要求：需要 hot tier 的完整索引、完整精度、毫秒到秒級回應。即席查詢幾乎不命中 warm 或 cold tier — 事故通常發生在「現在」或「剛才」。</p>
<p>資源特性：低頻（事故時才有）、單次掃描量小、但延遲要求最嚴格。事故中的每一秒等待都在消耗 MTTR。</p>
<h3 id="聚合趨勢">聚合趨勢</h3>
<p>Dashboard 跟 alert rule 的查詢，責任是提供持續的服務健康視圖。</p>
<p>查詢形狀是 group by + aggregation + 中等時間範圍：過去 5 分鐘的 error rate by service、過去 1 小時的 latency p99 by endpoint、過去 24 小時的 log volume by level。Dashboard 每 30 秒到 1 分鐘刷新，alert rule 每 1 到 5 分鐘 evaluate。</p>
<p>對儲存的要求：可以讀 <a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">recording rule</a> 或 <a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup</a> 的預聚合資料，不需要完整精度。延遲容忍比即席查詢寬（秒級到十秒級），但查詢頻率比即席查詢高兩到三個數量級。</p>
<p>資源特性：高頻、穩定、佔 query engine 的常態負載大頭。一個 Grafana dashboard 有 20 個 panel、每 30 秒刷新一次 = 每分鐘 40 個查詢；十個團隊各自有 dashboard = 每分鐘 400 個背景查詢。</p>
<h3 id="鑑識回溯">鑑識回溯</h3>
<p>事後分析、合規稽核與根因調查的查詢，責任是在大時間範圍內還原完整脈絡。</p>
<p>查詢形狀是寬時間範圍 + 條件掃描：過去 30 天某 tenant 的所有 authentication failure、過去 90 天某 API 的 error 分布演變、某次事故前後 48 小時的完整 log 流。</p>
<p>對儲存的要求：會命中 warm 甚至 cold tier。完整性比延遲重要 — 漏掉一筆 audit log 比多等 30 秒更嚴重。可能需要 rehydrate（把 cold tier 歸檔資料暫時載回可查詢狀態）。</p>
<p>資源特性：低頻但單次掃描量極大。一個 cold tier 的全量掃描可能佔用 query engine 數分鐘的計算資源。</p>
<h3 id="三種模式的設計衝突">三種模式的設計衝突</h3>
<p>三種模式搶同一個 query engine 時，聚合趨勢的穩定高頻負載會佔滿常態資源、擠壓即席診斷的突發需求；鑑識回溯的大範圍掃描會吃掉臨時資源、拖慢同時進行的即席查詢。</p>
<p>事故中是衝突最嚴重的時刻：incident commander 在做即席診斷、dashboard 在高頻刷新聚合趨勢、事後調查團隊可能同時在做鑑識回溯。三種負載同時打在同一個 query engine 上，誰先退讓取決於 query 資源治理的設計。</p>
<h2 id="storage-tiering-與查詢路由">Storage tiering 與查詢路由</h2>
<p><a href="/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">Storage tiering</a> 在讀取路徑上的責任不只是降低儲存成本，而是為不同時間範圍的查詢提供對應的查詢能力。每一層的儲存介質、索引密度、資料精度共同決定該層能回答什麼問題。</p>
<h3 id="每一層的查詢能力">每一層的查詢能力</h3>
<table>
  <thead>
      <tr>
          <th>層級</th>
          <th>查詢延遲</th>
          <th>可用索引</th>
          <th>資料精度</th>
          <th>適合的查詢模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Hot</td>
          <td>毫秒到秒</td>
          <td>完整結構化索引 + 全文索引</td>
          <td>原始精度</td>
          <td>即席診斷</td>
      </tr>
      <tr>
          <td>Warm</td>
          <td>秒到十秒</td>
          <td>結構化索引（可能移除低價值欄位索引）</td>
          <td>原始或輕度 rollup</td>
          <td>聚合趨勢</td>
      </tr>
      <tr>
          <td>Cold</td>
          <td>十秒到分鐘</td>
          <td>最小索引（timestamp + service + tenant）</td>
          <td>rollup 或歸檔</td>
          <td>鑑識回溯</td>
      </tr>
  </tbody>
</table>
<p>查詢跨越 tier 邊界時，回應時間由最慢的 tier 決定。Dashboard 時間範圍從「最近 1 小時」（全部 hot）拉到「最近 30 天」（hot + warm + cold），查詢延遲可能從毫秒跳到分鐘。這個延遲跳變需要在 dashboard UI 上提示使用者。</p>
<h3 id="查詢路由的設計">查詢路由的設計</h3>
<p>查詢路由的責任是根據查詢的時間範圍跟精度需求，自動選擇最合適的 tier 跟資料精度。</p>
<ul>
<li>時間範圍在 hot tier 內：直接查 raw data，完整精度。</li>
<li>時間範圍跨越 hot 跟 warm：hot 部分查 raw data、warm 部分查 rollup series，query engine 負責拼接。</li>
<li>時間範圍延伸到 cold tier：cold 部分需要 rehydrate 或走 object storage 查詢路徑，延遲大幅增加。</li>
</ul>
<p>查詢路由的透明度影響使用者信任。使用者需要知道目前看到的資料是什麼精度、來自哪一層、是否有 freshness lag。Grafana 的 annotation 機制可以在 dashboard 上標示 tier 邊界跟精度切換點，避免使用者把精度變化誤讀成服務異常。</p>
<h3 id="rehydrate-的操作成本">Rehydrate 的操作成本</h3>
<p>Cold tier 的資料通常儲存在 object storage（S3、GCS、Azure Blob），查詢前需要 rehydrate — 把資料從歸檔格式解壓、重建索引、載入到可查詢狀態。這個操作有時間成本（分鐘到小時）、儲存成本（臨時佔用 hot/warm 空間）跟計算成本（CPU 用在解壓跟索引重建）。</p>
<p>Rehydrate 是事故事後分析跟合規稽核的常見操作。設計 tiering 時要把 rehydrate 的 SLA（多久可以完成）、容量（同時可以 rehydrate 多少資料）跟觸發方式（手動 / API / 自動 policy）納入規劃。</p>
<h2 id="pre-aggregation-策略">Pre-aggregation 策略</h2>
<p>Pre-aggregation 是把讀取時的計算成本轉移到寫入時的策略。觀測領域有三種常見的 pre-aggregation 機制，適用場景跟維護成本不同。</p>
<h3 id="recording-rule">Recording rule</h3>
<p><a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">Recording rule</a> 在 TSDB 層定期執行 query expression，把聚合結果寫成新 series。適合 metrics 的高頻聚合查詢（SLO burn rate、error ratio、跨服務 latency summary）。</p>
<p>Recording rule 的維護成本集中在規則增長後的管理。數百條 recording rule 需要命名慣例、版本控制、執行時間監控（rule evaluation duration）與定期審計（是否有 rule 不再被 dashboard 或 alert 引用）。</p>
<h3 id="log-to-metric-轉換">Log-to-metric 轉換</h3>
<p>在 collector 端把高頻 log pattern 轉成 metric。適合「從 log 衍生的聚合查詢」— 例如把 <code>level=error</code> 的 log 計數轉成 error_log_total counter，把 specific exception 的出現率轉成 gauge。</p>
<p>Log-to-metric 的好處是讓 dashboard 讀 metric 而非重掃 log volume。維護成本在於 collector 配置要跟 log schema 保持同步 — log 的 field name 改了，轉換規則沒跟著改，metric 會靜默歸零。</p>
<h3 id="rollup--downsampling">Rollup / downsampling</h3>
<p><a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">Rollup</a> 把高精度時間序列聚合成低精度版本。適合長時間範圍的趨勢查詢（90 天 error rate 趨勢、capacity planning 的年度成長曲線）。</p>
<p>Rollup 的設計關鍵是聚合函數必須按 metric type 選擇。Counter 用 sum、gauge 用 average（或 min/max 保留極端值）、histogram 需要保留 bucket boundary 而非做 average（否則 percentile 計算會失真）。混用聚合函數是 rollup 最常見的 silent data corruption。</p>
<h3 id="pre-aggregation-的維護成本">Pre-aggregation 的維護成本</h3>
<p>Pre-aggregation 不是免費的。每一條 recording rule、每一個 log-to-metric 轉換、每一層 rollup 都需要：</p>
<ul>
<li><strong>儲存空間</strong>：預聚合結果本身佔用 series 或 index 空間，增加 <a href="/blog/backend/knowledge-cards/metric-cardinality/" data-link-title="Metric Cardinality" data-link-desc="說明 metric label 組合數量如何影響觀測成本與查詢穩定性">cardinality</a> 負擔。</li>
<li><strong>計算資源</strong>：定期執行聚合需要 CPU，rule evaluation lag 會讓 dashboard 看到過期資料。</li>
<li><strong>配置維護</strong>：規則需要跟 schema、label、service 保持同步，漂移會靜默產生錯誤資料。</li>
<li><strong>除錯成本</strong>：dashboard 讀的是 recording rule 輸出，事故時可能需要同時查 raw data 驗證 recording rule 是否正確。</li>
</ul>
<p>設計時的判準是：預聚合的讀取節省是否大於維護成本。高頻讀取（dashboard auto-refresh、alert evaluation）的聚合計算值得 pre-aggregation；低頻讀取（月度報表、偶發 ad-hoc query）直接查 raw data 更簡單。</p>
<h2 id="query-資源治理">Query 資源治理</h2>
<p>觀測平台的 query engine 是共用資源，需要顯式的治理機制避免單一查詢類型或單一使用者耗盡資源。</p>
<h3 id="query-priority-與排程">Query priority 與排程</h3>
<p>Query engine 需要知道每個查詢的優先級，在資源不足時讓高優先查詢先執行。</p>
<table>
  <thead>
      <tr>
          <th>查詢類型</th>
          <th>建議優先級</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Alert evaluate</td>
          <td>最高</td>
          <td>告警延遲直接影響 MTTD，不可因其他查詢排隊而漏發</td>
      </tr>
      <tr>
          <td>即席診斷</td>
          <td>高</td>
          <td>事故中的查詢，每秒延遲消耗 MTTR</td>
      </tr>
      <tr>
          <td>Dashboard 刷新</td>
          <td>中</td>
          <td>穩定背景負載，短暫延遲不影響決策品質</td>
      </tr>
      <tr>
          <td>鑑識回溯</td>
          <td>低</td>
          <td>延遲容忍高，可排程到低負載時段執行</td>
      </tr>
      <tr>
          <td>Ad-hoc 探索</td>
          <td>最低</td>
          <td>非事故的探索性查詢，可被其他類型搶佔</td>
      </tr>
  </tbody>
</table>
<h3 id="query-timeout-差異化">Query timeout 差異化</h3>
<p>不同查詢類型設不同的 timeout：alert evaluation 設短 timeout（30 秒到 1 分鐘，跑不完說明 query 有問題）、即席診斷設中等 timeout（1 到 5 分鐘）、鑑識回溯允許較長 timeout（10 到 30 分鐘）。統一 timeout 會讓鑑識查詢被過早截斷、或讓 alert evaluation 等太久。</p>
<h3 id="query-cost-estimation">Query cost estimation</h3>
<p>在查詢執行前估算掃描量（掃描的 series 數、time range、shard 數），超過閾值的查詢被拒絕或降級。避免單一 heavy query（例：跨所有 service 的 90 天 full-resolution 聚合）拖垮 query engine。</p>
<p>Query cost estimation 對使用者的回饋要足夠清楚。拒絕查詢時要說明「這個查詢預計掃描 N 條 series × M 天，超過單次查詢上限；請縮小時間範圍或增加 filter 條件」，而不是只回 timeout 或 500 error。</p>
<h3 id="query-cache">Query cache</h3>
<p>聚合趨勢查詢的特徵是高頻重複 — 同一個 dashboard panel 每 30 秒查一次，查詢的時間範圍大部分重疊。Query cache 在 query-frontend 層快取最近的聯合結果，下一次刷新只需要增量計算新進的資料區間。</p>
<p>Thanos Query Frontend、Mimir Query Frontend、Grafana Cloud 的 query splitting + caching 都實作這個模式。Cache 的命中率直接影響 query engine 負載 — 高命中率讓 query engine 的常態負載下降、留更多資源給即席查詢。</p>
<h2 id="觀測領域的讀寫分離cqrs-的特化應用">觀測領域的讀寫分離：CQRS 的特化應用</h2>
<p>觀測查詢設計的底層問題是讀寫不對稱 — 寫入跟讀取的形狀、頻率、SLA 都不同，單一模型無法同時服務。這個問題在 application data 層有成熟的設計框架：<a href="/blog/backend/knowledge-cards/cqrs/" data-link-title="CQRS" data-link-desc="說明讀寫不對稱時為何需要分離查詢與寫入責任、分離的判準與代價">CQRS</a>。觀測領域面對的是同一類不對稱，但不對稱的程度更極端，實作層級也不同。</p>
<h3 id="觀測場景的不對稱比-application-更極端">觀測場景的不對稱比 application 更極端</h3>
<p><a href="/blog/backend/knowledge-cards/cqrs/" data-link-title="CQRS" data-link-desc="說明讀寫不對稱時為何需要分離查詢與寫入責任、分離的判準與代價">CQRS 知識卡</a>描述了讀寫不對稱的三個維度（形狀、頻率、SLA）。觀測場景在這三個維度上都比典型 application 更極端：</p>
<p><strong>形狀不對稱</strong>：application 的 <a href="/blog/backend/knowledge-cards/read-model/" data-link-title="Read Model" data-link-desc="說明為查詢場景建立的讀取模型，與正式狀態的責任分離">read model</a> 通常是一到兩種（列表頁、報表）。觀測的讀取面至少三種：即席診斷要精確 filter + 完整精度、聚合趨勢要 group by + pre-aggregated、鑑識回溯要寬範圍 + 完整性優先。三種形狀對索引、精度、儲存層的需求互斥。</p>
<p><strong>頻率不對稱</strong>：application 的讀寫比通常在 10:1 到 100:1 之間。觀測的 dashboard 每 30 秒刷新一次、alert 每分鐘 evaluate、十個團隊各自有 dashboard — 讀取頻率可以到寫入的千倍以上，而且是持續穩定的背景負載而非突發。</p>
<p><strong>SLA 不對稱</strong>：application CQRS 的讀寫 SLA 差距通常在同一個數量級（毫秒 vs 數百毫秒）。觀測的三種讀取模式 SLA 跨三個數量級 — 即席診斷要求毫秒到秒級、聚合趨勢容忍秒到十秒級、鑑識回溯容忍分鐘級。</p>
<h3 id="觀測領域怎麼實作讀寫分離">觀測領域怎麼實作讀寫分離</h3>
<p>CQRS 在 application 層透過 event handler、projector、read store 實作。觀測領域用自己的 first-class 機制做同樣的事：</p>
<table>
  <thead>
      <tr>
          <th>CQRS 概念</th>
          <th>觀測領域的對應</th>
          <th>設計責任</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Write model</td>
          <td>Raw series / log / span — append-only 寫入</td>
          <td>Schema 穩定、吞吐</td>
      </tr>
      <tr>
          <td>Read model</td>
          <td><a href="/blog/backend/knowledge-cards/recording-rule/" data-link-title="Recording Rule" data-link-desc="說明把 query-time 聚合計算推到寫入時的 pre-aggregation 機制">Recording rule</a>、<a href="/blog/backend/knowledge-cards/rollup/" data-link-title="Rollup / Downsampling" data-link-desc="說明時間序列資料隨時間降低精度以控制儲存成本與查詢效能的機制">rollup</a>、log-to-metric 轉換</td>
          <td>讀取最佳化</td>
      </tr>
      <tr>
          <td>Projection</td>
          <td>Collector 端的 aggregation / enrichment / routing</td>
          <td>寫入到讀取模型的轉換</td>
      </tr>
      <tr>
          <td>Event 同步延遲</td>
          <td>Recording rule evaluation lag、rollup delay、buffer freshness lag</td>
          <td>最終一致性的延遲窗口</td>
      </tr>
      <tr>
          <td>多 read store</td>
          <td><a href="/blog/backend/knowledge-cards/storage-tiering/" data-link-title="Storage Tiering" data-link-desc="說明按資料熱度分層儲存以平衡查詢速度、儲存成本與保留完整性的機制">Storage tiering</a>（hot / warm / cold 各自支援不同查詢模式）</td>
          <td>不同 SLA 的讀取走不同儲存層</td>
      </tr>
  </tbody>
</table>
<h3 id="cqrs-的代價在觀測領域同樣存在">CQRS 的代價在觀測領域同樣存在</h3>
<p><a href="/blog/backend/knowledge-cards/cqrs/" data-link-title="CQRS" data-link-desc="說明讀寫不對稱時為何需要分離查詢與寫入責任、分離的判準與代價">CQRS 知識卡</a>列出的三項代價（最終一致性、同步可靠性、多模型維護）在觀測場景都找得到對應：</p>
<p><strong>最終一致性</strong>：Recording rule 每 N 秒 evaluate 一次，dashboard 看到的聚合結果落後 raw data。Rollup 的延遲更長。事故中 incident commander 看 dashboard 做決策時，需要知道資料的 freshness — 這就是 CQRS 的 read model 延遲在觀測領域的具體表現。</p>
<p><strong>同步可靠性</strong>：Recording rule evaluation 本身可能失敗（expression 太重跑不完、TSDB 暫時不可用）。Log-to-metric 轉換可能因 schema 漂移而靜默歸零。這些同步失敗跟 application CQRS 的 projector 失敗是同一類問題 — read model 看起來有資料但其實是過期的。</p>
<p><strong>多模型維護</strong>：Metric schema 變更後，raw series、recording rule、rollup、dashboard query 都需要同步更新。Recording rule 引用的 label name 改了沒跟著改，aggregation 結果會靜默錯誤。這跟 application 的「schema migration 要同時更新 write model 跟所有 read model」是同一個維護負擔。</p>
<h3 id="術語邊界">術語邊界</h3>
<p>觀測領域的讀寫分離跟 CQRS 概念對應，但在業界溝通中直接說「log 的 CQRS」或「metrics 的 CQRS」會造成混淆。觀測領域有自己的 first-class 術語（recording rule、rollup、tiering、query routing），跟 application CQRS 的術語（command、query、projection、read model）平行但不互通。</p>
<p>理解 CQRS 的讀者可以把觀測查詢設計視為「infrastructure-level 的讀寫分離」，同樣的設計原則（分離的動機、最終一致性的代價、多模型維護的負擔）在不同層級重複出現。但設計決策時要用觀測領域的術語，把 recording rule 跟 rollup 當第一等公民，而非 CQRS 的衍生品。</p>
<h2 id="核心判讀">核心判讀</h2>
<p>判讀觀測查詢設計時，先看三種查詢模式是否有對應的資源與資料形狀，再看 pre-aggregation 跟 tiering 是否對齊實際查詢負載。</p>
<p>重點訊號包括：</p>
<ul>
<li>即席查詢在事故中的延遲是否在秒級以內</li>
<li>Dashboard 刷新是否佔用過多 query engine 資源</li>
<li>長時間範圍查詢是否有 rollup / recording rule 支撐</li>
<li>Storage tiering 的查詢路由是否對使用者透明</li>
<li>Alert evaluation 是否有最高 query priority</li>
<li>Pre-aggregation 規則是否跟 schema 保持同步</li>
</ul>
<h2 id="判讀訊號">判讀訊號</h2>
<ul>
<li>Dashboard 載入時間持續退化、panel timeout 增加</li>
<li>Alert rule evaluation duration 成長、偶發 missed evaluation</li>
<li>事故中即席查詢被 dashboard 背景負載擠壓</li>
<li>長時間範圍的查詢精度突變但使用者不知道</li>
<li>Recording rule 輸出跟 raw query 結果不一致</li>
<li>Rehydrate 需求頻繁但沒有預設流程</li>
<li>Query engine CPU 被少數 heavy query 佔滿</li>
</ul>
<h2 id="反模式">反模式</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>表面現象</th>
          <th>修正方向</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Raw log 當 OLAP 查</td>
          <td>聚合查詢掃 TB 級 log、timeout</td>
          <td>用 log-to-metric 轉換把常用聚合推到 metric 層</td>
      </tr>
      <tr>
          <td>Dashboard 直打 raw storage</td>
          <td>Panel 載入慢、query engine 過載</td>
          <td>用 recording rule / rollup 支撐高頻 panel</td>
      </tr>
      <tr>
          <td>Recording rule 跟 raw query 重複</td>
          <td>同一個指標有兩條查詢路徑、數值不一致</td>
          <td>統一入口：dashboard 讀 recording rule、ad-hoc 讀 raw</td>
      </tr>
      <tr>
          <td>所有查詢同一個 priority</td>
          <td>Alert 被 dashboard 查詢排隊延遲</td>
          <td>Query priority 分級、alert evaluation 最高</td>
      </tr>
      <tr>
          <td>Tier 邊界對使用者不透明</td>
          <td>拉長時間範圍時數值突變但不知為何</td>
          <td>Dashboard 標示 tier 邊界跟精度切換</td>
      </tr>
      <tr>
          <td>Rollup 聚合函數混用</td>
          <td>Histogram percentile 在長時間視圖被壓平</td>
          <td>按 metric type 指定聚合函數、histogram 保留 bucket</td>
      </tr>
      <tr>
          <td>所有訊號同一個 tier 邊界</td>
          <td>高價值訊號過早退化、低價值訊號佔 hot</td>
          <td>依訊號優先級設差異化 tier 邊界</td>
      </tr>
  </tbody>
</table>
<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>：log 的即席 / 聚合 / 鑑識三種查詢模式細節</li>
<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</a>：metrics 的 recording rule 與 rollup 設計</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 / cost</a>：storage 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>：讀取路徑作為 pipeline 的延伸</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>：query 資源的成本歸屬</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>：pre-aggregation 與 raw data 的一致性驗證</li>
<li><a href="/blog/backend/04-observability/observability-operating-model/" data-link-title="4.18 Observability Operating Model" data-link-desc="定義 platform / service team / on-call 對訊號、dashboard、alert 與成本的 ownership">4.18 operating model</a>：query 資源治理的 ownership</li>
<li><a href="/blog/monitoring/04-collector/read-write-separation/" data-link-title="讀寫分離與查詢擴展" data-link-desc="Monitor 在 PostgreSQL 層之後的讀寫競爭問題、Read Replica 分離策略、CQRS 判讀訊號">Monitoring 讀寫分離</a>：Monitor 專案的讀寫分離具體應用</li>
</ul>
]]></content:encoded></item><item><title>4.24 Client-to-Server 端到端觀測串接</title><link>https://tarrragon.github.io/blog/backend/04-observability/client-server-trace-integration/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/04-observability/client-server-trace-integration/</guid><description>&lt;p>Client-to-server 端到端觀測串接的核心責任是讓一次使用者操作的完整路徑 — 從 browser click 到 server 處理到 response rendering — 可以用同一個 trace ID 串起來。&lt;a href="https://tarrragon.github.io/blog/backend/04-observability/client-side-monitoring/" data-link-title="4.10 Client-side / Synthetic / RUM" data-link-desc="補 server-side 看不到的 user perceived 訊號">4.10 Client-side / Synthetic / RUM&lt;/a> 講的是概念和 vendor 定位；本篇走完一個具體場景的實作鏈路。&lt;a href="https://tarrragon.github.io/blog/monitoring/03-sdk-design/" data-link-title="模組三：SDK 設計模式" data-link-desc="跨平台 SDK 的自動攔截、手動上報、攢批送出、離線 buffer 設計">Monitoring 模組 03 SDK 設計&lt;/a> 講的是 client 端怎麼埋點；本篇講 server 端怎麼接收和整合。&lt;/p>
&lt;h2 id="完整鏈路">完整鏈路&lt;/h2>
&lt;p>以使用者在 web app 點擊「結帳」為例，一次操作產生的觀測鏈路：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">Browser: user clicks &amp;#34;checkout&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> → RUM SDK 建立 client span（type: resource / xhr）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> → HTTP POST /api/checkout + W3C traceparent header
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> → Server middleware 提取 trace context
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> → Server 建立 child span（checkout-handler）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> → DB query span（order insert）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> → Cache span（inventory check）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> → Queue span（event publish）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> → Server 回 200 + response body
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> → Browser 收到 response → resource timing 結束
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> → RUM SDK 關閉 client span（記錄 duration + status）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> → 統一 trace waterfall：client span 是 root、server spans 是 children&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>鏈路的每一段都需要 trace context 正確傳遞。任何一段斷掉，trace waterfall 就會出現孤立的 span — server 端看到的 trace 跟 client 端看到的 trace 是兩條不相關的紀錄。&lt;/p>
&lt;h2 id="trace-context-propagation">Trace context propagation&lt;/h2>
&lt;h3 id="w3c-traceparent-header">W3C traceparent header&lt;/h3>
&lt;p>W3C Trace Context 是跨 vendor 的標準 propagation 格式。Header 長這樣：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> │ trace-id (32 hex) parent-id (16 hex) flags
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> version&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>RUM SDK 在發起 XHR / fetch 時把 &lt;code>traceparent&lt;/code> 注入 request header。Server 的 trace SDK 從 header 提取 trace-id 和 parent-id，建立 child span。&lt;/p></description><content:encoded><![CDATA[<p>Client-to-server 端到端觀測串接的核心責任是讓一次使用者操作的完整路徑 — 從 browser click 到 server 處理到 response rendering — 可以用同一個 trace ID 串起來。<a href="/blog/backend/04-observability/client-side-monitoring/" data-link-title="4.10 Client-side / Synthetic / RUM" data-link-desc="補 server-side 看不到的 user perceived 訊號">4.10 Client-side / Synthetic / RUM</a> 講的是概念和 vendor 定位；本篇走完一個具體場景的實作鏈路。<a href="/blog/monitoring/03-sdk-design/" data-link-title="模組三：SDK 設計模式" data-link-desc="跨平台 SDK 的自動攔截、手動上報、攢批送出、離線 buffer 設計">Monitoring 模組 03 SDK 設計</a> 講的是 client 端怎麼埋點；本篇講 server 端怎麼接收和整合。</p>
<h2 id="完整鏈路">完整鏈路</h2>
<p>以使用者在 web app 點擊「結帳」為例，一次操作產生的觀測鏈路：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">Browser: user clicks &#34;checkout&#34;
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  → RUM SDK 建立 client span（type: resource / xhr）
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  → HTTP POST /api/checkout + W3C traceparent header
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    → Server middleware 提取 trace context
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    → Server 建立 child span（checkout-handler）
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      → DB query span（order insert）
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">      → Cache span（inventory check）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">      → Queue span（event publish）
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    → Server 回 200 + response body
</span></span><span class="line"><span class="ln">10</span><span class="cl">  → Browser 收到 response → resource timing 結束
</span></span><span class="line"><span class="ln">11</span><span class="cl">  → RUM SDK 關閉 client span（記錄 duration + status）
</span></span><span class="line"><span class="ln">12</span><span class="cl">  → 統一 trace waterfall：client span 是 root、server spans 是 children</span></span></code></pre></div><p>鏈路的每一段都需要 trace context 正確傳遞。任何一段斷掉，trace waterfall 就會出現孤立的 span — server 端看到的 trace 跟 client 端看到的 trace 是兩條不相關的紀錄。</p>
<h2 id="trace-context-propagation">Trace context propagation</h2>
<h3 id="w3c-traceparent-header">W3C traceparent header</h3>
<p>W3C Trace Context 是跨 vendor 的標準 propagation 格式。Header 長這樣：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
</span></span><span class="line"><span class="ln">2</span><span class="cl">              │  │                                │                  │
</span></span><span class="line"><span class="ln">3</span><span class="cl">              │  trace-id (32 hex)                 parent-id (16 hex) flags
</span></span><span class="line"><span class="ln">4</span><span class="cl">              version</span></span></code></pre></div><p>RUM SDK 在發起 XHR / fetch 時把 <code>traceparent</code> 注入 request header。Server 的 trace SDK 從 header 提取 trace-id 和 parent-id，建立 child span。</p>
<h3 id="client-端注入">Client 端注入</h3>
<p>各 RUM SDK 的注入方式：</p>
<table>
  <thead>
      <tr>
          <th>SDK</th>
          <th>注入機制</th>
          <th>配置</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Datadog RUM</td>
          <td>自動 patch XHR / fetch，注入 <code>x-datadog-*</code> + 可選 <code>traceparent</code></td>
          <td><code>allowedTracingUrls</code> 設定允許注入的 domain</td>
      </tr>
      <tr>
          <td>Sentry browser</td>
          <td>自動 patch fetch / XHR，注入 <code>sentry-trace</code> + <code>baggage</code> + 可選 <code>traceparent</code></td>
          <td><code>tracePropagationTargets</code> 設定目標 URL</td>
      </tr>
      <tr>
          <td>OTel browser SDK</td>
          <td>透過 <code>XMLHttpRequestInstrumentation</code> / <code>FetchInstrumentation</code> 注入 <code>traceparent</code></td>
          <td><code>propagateTraceHeaderCorsUrls</code> 設定 CORS 允許的 URL</td>
      </tr>
  </tbody>
</table>
<p>三者的共同模式：只對設定的 domain 注入 trace header。不設定白名單時，header 不會被注入到第三方 API（避免 information leakage）。</p>
<h3 id="server-端提取">Server 端提取</h3>
<p>Server 端的 trace SDK（OTel auto-instrumentation 或 vendor agent）從 incoming request 的 header 提取 trace context：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># OTel Python 範例 — auto-instrumentation 自動處理</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># 不需要手動提取，middleware 自動讀 traceparent header</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 建立的 span 會繼承 client 傳來的 trace-id 和 parent-id</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 手動提取（不用 auto-instrumentation 時）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry.propagate</span> <span class="kn">import</span> <span class="n">extract</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">ctx</span> <span class="o">=</span> <span class="n">extract</span><span class="p">(</span><span class="n">carrier</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;checkout-handler&#34;</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># server logic</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="cors-限制">CORS 限制</h3>
<p>跨域請求時，browser 的 CORS preflight 會阻止非標準 header。Server 需要明確允許 trace header：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Access-Control-Allow-Headers: traceparent, tracestate, sentry-trace, baggage</span></span></code></pre></div><p>CORS 是 client-server trace 串接最常見的斷裂原因。Server 沒有回 <code>Access-Control-Allow-Headers: traceparent</code> 時，browser 會 strip 掉 trace header，server 端收到的 request 沒有 trace context，建立的 span 成為新的 root — 跟 client span 斷裂。</p>
<h2 id="跨層-correlation-設計">跨層 correlation 設計</h2>
<h3 id="trace-id-串接">Trace ID 串接</h3>
<p>統一 trace-id 是最基本的 correlation。同一個 trace-id 下的所有 span（client + server）可以在 trace backend 的 waterfall view 裡按時間排列，看到完整的 request 路徑。</p>
<h3 id="session-跟-transaction-的-mapping">Session 跟 transaction 的 mapping</h3>
<p>RUM SDK 的 session（使用者的一次造訪）包含多個 user action，每個 action 可能觸發多個 HTTP request。Mapping 關係：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">RUM session
</span></span><span class="line"><span class="ln">2</span><span class="cl">  └── user action (click &#34;checkout&#34;)
</span></span><span class="line"><span class="ln">3</span><span class="cl">        ├── HTTP request /api/checkout  →  server transaction (trace)
</span></span><span class="line"><span class="ln">4</span><span class="cl">        ├── HTTP request /api/inventory →  server transaction (trace)
</span></span><span class="line"><span class="ln">5</span><span class="cl">        └── client-side rendering time</span></span></code></pre></div><p>Datadog RUM 和 Sentry 都支援從 session replay 點進去看對應的 server trace。這個 mapping 靠的是 RUM event 裡記錄的 trace-id，跟 server trace backend 裡的同一個 trace-id 做 join。</p>
<h3 id="breadcrumbs-跟-server-log-的時間對齊">Breadcrumbs 跟 server log 的時間對齊</h3>
<p>RUM SDK 收集的 breadcrumbs（使用者操作序列：page view → button click → form submit）跟 server-side log 的 timestamp 需要可比對。時間對齊的前提是 client 和 server 的 clock 差距在可接受範圍（通常 &lt; 1s）。</p>
<p>NTP 同步的 server 端 clock 通常精準。Client 端（browser）依賴使用者裝置的系統時間，可能偏差數秒到數分鐘。RUM SDK 通常會記錄 relative timing（相對於 session 開始的 offset），而非絕對 timestamp，來降低 clock skew 的影響。</p>
<h3 id="error-correlation">Error correlation</h3>
<p>Client-side JS error 跟 server-side 5xx 可能是同一個問題的兩面。Correlation 方式：</p>
<ul>
<li><strong>同一 trace-id</strong>：client error 發生在某個 HTTP request 的 response 處理中，該 request 的 trace-id 跟 server-side 500 的 trace-id 相同 — 直接 correlation</li>
<li><strong>時間窗 + endpoint</strong>：client error 沒有 trace-id（例如 CORS block 導致 request 沒發出），用時間窗 + endpoint 模式做 fuzzy correlation</li>
<li><strong>Server 無異常但 client 報錯</strong>：client-side rendering error（JSON parse failure、type error），server 端看不到 — 需要 RUM 獨立分析</li>
</ul>
<h2 id="evidence-package-整合">Evidence package 整合</h2>
<p>把 client-side 訊號納入 <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> 時，需要額外記錄：</p>
<table>
  <thead>
      <tr>
          <th>欄位</th>
          <th>Client-side 補充</th>
          <th>為什麼需要</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Source</td>
          <td>標註 &ldquo;RUM&rdquo; 或 &ldquo;Synthetic&rdquo;</td>
          <td>區分 server-side metrics 和 client-side metrics</td>
      </tr>
      <tr>
          <td>Latency</td>
          <td>Client perceived latency（含 DNS + network + server + rendering）</td>
          <td>跟 server-side latency 差異是 network + rendering 時間</td>
      </tr>
      <tr>
          <td>Known gap</td>
          <td>Trace sampling 不一致</td>
          <td>Client 和 server 可能各自取樣，同一個 request 不一定兩邊都有</td>
      </tr>
      <tr>
          <td>Confidence</td>
          <td>Client clock skew 可能影響 timestamp precision</td>
          <td>標注 client timestamp 的精確度限制</td>
      </tr>
  </tbody>
</table>
<p>Client perceived latency 跟 server-side latency 的差異本身就是一個觀測訊號。差異穩定在 50ms 是正常的 network overhead；差異突然從 50ms 跳到 500ms 代表網路或 CDN 出了問題 — 而這個問題 server-side dashboard 完全看不到。</p>
<h2 id="失敗場景判讀">失敗場景判讀</h2>
<table>
  <thead>
      <tr>
          <th>失敗訊號</th>
          <th>判讀</th>
          <th>下一步</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Client span 存在但 server span 缺失</td>
          <td>Trace context header 沒被 propagate — 最常見原因是 CORS block</td>
          <td>檢查 <code>Access-Control-Allow-Headers</code> 是否包含 <code>traceparent</code>；檢查 RUM SDK 的 <code>allowedTracingUrls</code> 設定</td>
      </tr>
      <tr>
          <td>Server 正常但 client perceived latency 高</td>
          <td>網路延遲或 client rendering 慢</td>
          <td>看 RUM 的 resource timing breakdown（DNS / TCP / TLS / TTFB / download / render）</td>
      </tr>
      <tr>
          <td>Client error 但 server 無對應 request</td>
          <td>Request 沒發出 — client-side validation 擋掉或 network offline</td>
          <td>看 RUM breadcrumbs 確認 request 是否有送出；檢查 navigator.onLine 狀態</td>
      </tr>
      <tr>
          <td>Trace sampling 不一致</td>
          <td>Client 取樣到但 server 沒取樣到同一個 request</td>
          <td>統一 sampling decision — 用 head-based sampling（decision 在 trace 起點做、propagate 到下游）</td>
      </tr>
      <tr>
          <td>Client 和 server 的 error count 對不上</td>
          <td>Client 包含 JS rendering error（server 看不到）；server 包含非 user-facing 的背景 job error</td>
          <td>分開看：API error 用 trace correlation 比對、non-API error 各自歸類</td>
      </tr>
  </tbody>
</table>
<h2 id="vendor-整合模式">Vendor 整合模式</h2>
<table>
  <thead>
      <tr>
          <th>組合</th>
          <th>串接方式</th>
          <th>限制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Datadog RUM + Datadog APM</td>
          <td>原生 — 同一個 Datadog org 裡 client 跟 server trace 自動關聯</td>
          <td>兩邊都要 Datadog plan</td>
      </tr>
      <tr>
          <td>Sentry browser + Sentry server</td>
          <td>原生 — <code>sentry-trace</code> header propagation</td>
          <td>Performance monitoring 需要 Sentry paid plan</td>
      </tr>
      <tr>
          <td>OTel browser SDK + OTel server SDK</td>
          <td>W3C <code>traceparent</code> — vendor-neutral 標準</td>
          <td>Browser SDK 較新、instrumentation 覆蓋度不如 server 端成熟</td>
      </tr>
      <tr>
          <td>混合（Sentry browser + Datadog server）</td>
          <td>手動橋接 — 確保雙方都支援 W3C <code>traceparent</code></td>
          <td>Trace context format 要一致；session-level correlation 需自建</td>
      </tr>
  </tbody>
</table>
<p>同 vendor 組合的串接最自然。跨 vendor 組合只要雙方都支援 W3C Trace Context，trace-level correlation 可以通；但 session-level 的功能（session replay → server trace）需要同 vendor 才有。</p>
<h2 id="交接路由">交接路由</h2>
<ul>
<li><a href="/blog/backend/04-observability/client-side-monitoring/" data-link-title="4.10 Client-side / Synthetic / RUM" data-link-desc="補 server-side 看不到的 user perceived 訊號">4.10 Client-side / Synthetic / RUM</a>：概念定位和 vendor 選型</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>：server-side trace context 設計</li>
<li><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。">4.22 Checkout API Evidence Package</a>：evidence 整合到 release gate</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>：evidence 欄位標準</li>
<li><a href="/blog/monitoring/03-sdk-design/" data-link-title="模組三：SDK 設計模式" data-link-desc="跨平台 SDK 的自動攔截、手動上報、攢批送出、離線 buffer 設計">Monitoring 03 SDK 設計</a>：client-side SDK 埋點設計</li>
<li><a href="/blog/monitoring/06-commercial-comparison/" data-link-title="模組六：商業方案對照" data-link-desc="Sentry / Crashlytics / Datadog RUM / Mixpanel — 自架 vs 商業的功能和成本取捨">Monitoring 06 商業方案</a>：Sentry / Datadog RUM 的 client-side 能力比較</li>
<li><a href="/blog/monitoring/telemetry-data-dual-use/" data-link-title="監控資料的雙重用途：行為分析與訊號治理" data-link-desc="同一份 event data 如何同時服務行為分析（funnel / cohort / attribution）和訊號治理（cardinality / cost / signal governance）— 格式交叉、治理衝突與分流架構">監控資料的雙重用途</a>：同一份 event data 如何同時服務行為分析與訊號治理</li>
</ul>
]]></content:encoded></item></channel></rss>